diff --git a/package-lock.json b/package-lock.json index 191d4b031da3959065982344f2f3a8fb139edd58..a0fff130c79012874b23e6edc912cdf52b49da48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "@compodoc/compodoc": "^1.1.25", "@ghsc/disagg-d3": "^0.9.0", "@ghsc/nshmp-lib-ng": "^18.1.3", + "@ghsc/nshmp-lib-no-ngrx": "^18.2.0", "@ghsc/nshmp-template": "^18.0.3", "@ghsc/nshmp-utils-ts": "^3.5.1", "@ngrx/effects": "^18.0.0", @@ -3966,6 +3967,18 @@ "@angular/core": "^18.0.2" } }, + "node_modules/@ghsc/nshmp-lib-no-ngrx": { + "version": "18.2.0", + "resolved": "https://code.usgs.gov/api/v4/projects/12416/packages/npm/@ghsc/nshmp-lib-no-ngrx/-/@ghsc/nshmp-lib-no-ngrx-18.2.0.tgz", + "integrity": "sha1-aJb/VPsi2fU3fsJn0T1zicsyY1c=", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^18.0.2", + "@angular/core": "^18.0.2" + } + }, "node_modules/@ghsc/nshmp-template": { "version": "18.0.3", "resolved": "https://code.usgs.gov/api/v4/projects/1416/packages/npm/@ghsc/nshmp-template/-/@ghsc/nshmp-template-18.0.3.tgz", diff --git a/package.json b/package.json index 970fab9170f22170830e85958b2cbb4c6dcbcb7f..c5c80e4ad21adc1a599da3b16952fdfa6d20ec64 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@compodoc/compodoc": "^1.1.25", "@ghsc/disagg-d3": "^0.9.0", "@ghsc/nshmp-lib-ng": "^18.1.3", + "@ghsc/nshmp-lib-no-ngrx": "^18.2.0", "@ghsc/nshmp-template": "^18.0.3", "@ghsc/nshmp-utils-ts": "^3.5.1", "@ngrx/effects": "^18.0.0", diff --git a/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts b/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts index 55592eb37e4b6a94923c322a4f7270eabc8b5d68..2535e4b485a8ab2fc24ade03ba70dde0efa02051 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/gmm.routes.ts @@ -1,9 +1,4 @@ import {Routes} from '@angular/router'; -import {provideEffects} from '@ngrx/effects'; -import {provideState} from '@ngrx/store'; - -import {HangingWallEffectsAppEffects} from './hanging-wall-effects/state/app.effects'; -import {hangingWallEffectsAppFeature} from './hanging-wall-effects/state/app.reducer'; /** Routes for dev GMM applications */ const routes: Routes = [ @@ -13,10 +8,6 @@ const routes: Routes = [ com => com.AppComponent ), path: 'hanging-wall-effects', - providers: [ - provideState(hangingWallEffectsAppFeature), - provideEffects(HangingWallEffectsAppEffects), - ], }, ]; diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html index 5bde83f8e8c36af493e24c8044ed0abbb9fb99f5..f9bffe28365fcdd73308f52bbb7fab74421c48c9 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.html @@ -1,4 +1,4 @@ -<nshmp-lib-ng-template [navigationList]="navigationList" [title]="title"> +<nshmp-lib-no-ngrx-template [navigationList]="navigationList" [title]="title"> <nshmp-template-content-container> <!-- Control panel --> <nshmp-template-control-panel> @@ -17,7 +17,7 @@ </nshmp-template-content-container> <!-- About page --> - <nshmp-lib-ng-about-page> + <nshmp-lib-no-ngrx-about-page> <app-about /> - </nshmp-lib-ng-about-page> -</nshmp-lib-ng-template> + </nshmp-lib-no-ngrx-about-page> +</nshmp-lib-no-ngrx-template> diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts index 55df0c74739b789e76aa7313c3de3ec6a6fccf24..464dcbb4a76acd8047df2ebeb266fb6227e49255 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/app.component.ts @@ -1,6 +1,6 @@ import {Component, OnInit} from '@angular/core'; -import {NshmpLibNgAboutPageComponent} from '@ghsc/nshmp-lib-ng/about'; -import {NshmpLibNgTemplateComponent} from '@ghsc/nshmp-lib-ng/nshmp'; +import {NshmpLibNgAboutPageComponent} from '@ghsc/nshmp-lib-no-ngrx/about'; +import {NshmpLibNgTemplateComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; import { NshmpTemplateContentContainerComponent, NshmpTemplateControlPanelComponent, diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.html index 24cd5fc763344f824880aa97d2a3c97872750478..3fad4144d7854c4df02e4c5fd74c9f03db3d4b79 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.html +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.html @@ -10,15 +10,15 @@ <mat-tab labelClass="medians-tab" label="Medians" - [disabled]="(hasData$ | async) === false" + [disabled]="hasData() === false" > <ng-template matTabContent> - <nshmp-lib-ng-export-data-table - [table]="table$ | async" + <nshmp-lib-no-ngrx-export-data-table + [table]="table()" filename="hanging-wall-effects.csv" buttonText="Export to CSV" /> - <nshmp-lib-ng-data-table [table]="table$ | async" /> + <nshmp-lib-no-ngrx-data-table [table]="table()" /> </ng-template> </mat-tab> </mat-tab-group> diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts index 3c7ea105abe149d229ee4159046bfe9eae280029..76112a27aad690bbd895c65199bdef3f8c94ae89 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.spec.ts @@ -1,6 +1,6 @@ +import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {provideNoopAnimations} from '@angular/platform-browser/animations'; -import {provideMockStore} from '@ngrx/store/testing'; import {ContentComponent} from './content.component'; @@ -11,10 +11,7 @@ describe('ContentComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ContentComponent], - providers: [ - provideMockStore({initialState: {}}), - provideNoopAnimations(), - ], + providers: [provideNoopAnimations(), provideHttpClient()], }).compileComponents(); fixture = TestBed.createComponent(ContentComponent); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.ts index 6178e6285ca3a0f12ebc78dd2d62d1df9aa54e94..8d7f1307434cdf356dff7a8ba441aedac4ab0aaf 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/content/content.component.ts @@ -1,12 +1,11 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed} from '@angular/core'; import {MatTab, MatTabContent, MatTabGroup} from '@angular/material/tabs'; -import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm'; +import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm'; import { NshmpLibNgDataTableComponent, NshmpLibNgExportDataTableComponent, -} from '@ghsc/nshmp-lib-ng/nshmp'; -import {map} from 'rxjs'; +} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; import {AppFacade} from '../../state/app.facade'; import {Plots} from '../../state/app.state'; @@ -34,18 +33,14 @@ export class ContentComponent { exp = true; /** Whether service has been called and data exists */ - hasData$ = this.facade.serviceResponses$.pipe( - map(responses => responses?.length > 0) - ); + hasData = computed(() => this.facade.state().serviceResponses?.length > 0); /** Table data for table */ - table$ = this.facade.plots$.pipe( - map(plots => - gmmUtils.plotToTable( - plots.get(Plots.GROUND_MOTION).plotData, - this.exp, - this.commonXValues - ) + table = computed(() => + gmmUtils.plotToTable( + this.facade.state().plots.get(Plots.GROUND_MOTION).plotData, + this.exp, + this.commonXValues ) ); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html index ec65e81599d89b08d483c2ced2af47c4e22e6712..692ceb089a66950fdee4d0475e591f041ce0500c 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.html @@ -1,155 +1,150 @@ <!-- Hanging wall effects control panel --> -@if (formState$ | async; as formState) { - <form - class="settings-section control-panel height-full overflow-auto" - [ngrxFormState]="formState" - (submit)="onSubmit()" - > - <mat-form-field - class="grid-col-12 multi-select-parameter-menu padding-top-1" - > - <mat-label>Multi-Selectable Parameter</mat-label> - <mat-select - [ngrxFormControlState]="formState?.controls?.multiSelectableParam" - > - <mat-option value="gmm">Ground Motion Models</mat-option> - <mat-option value="Mw">M<sub>W</sub></mat-option> - <mat-option value="vs30">V<sub>S30</sub></mat-option> - </mat-select> - </mat-form-field> +<form + class="settings-section control-panel height-full overflow-auto" + [formGroup]="formState" + (submit)="onSubmit()" +> + <mat-form-field class="grid-col-12 multi-select-parameter-menu padding-top-1"> + <mat-label>Multi-Selectable Parameter</mat-label> + <mat-select [formControl]="formState.controls.multiSelectableParam"> + <mat-option value="gmm">Ground Motion Models</mat-option> + <mat-option value="Mw">M<sub>W</sub></mat-option> + <mat-option value="vs30">V<sub>S30</sub></mat-option> + </mat-select> + </mat-form-field> - <!-- GMM menu --> - <nshmp-lib-ng-gmm-menu - [gmmControlState]="(controls$ | async)?.gmmSource" - [gmmGroupTypeState]="(controls$ | async)?.gmmGroupType" - [multiple]="(controls$ | async)?.multiSelectableParam.value === 'gmm'" - [parameters]="parameters$ | async" + <!-- GMM menu --> + @if (parameters()) { + <nshmp-lib-no-ngrx-gmm-menu + [gmmControl]="controls.gmmSource" + [gmmGroupTypeControl]="controls.gmmGroupType" + [multiple]="controls.multiSelectableParam.value === 'gmm'" + [parameters]="parameters()" + [imtControl]="controls.imt" + [supportedImts]="supportedImts()" /> + } - <!-- IMT select menu --> - <mat-form-field class="grid-col-12 imt-select"> - <mat-label>Intensity Measure Type</mat-label> - <mat-select - #imtSelectEl - [ngrxFormControlState]="formState?.controls?.imt" - > - @if ((gmmSelected$ | async) === false) { - <mat-option selected="true" value="default"> - --- Choose a GMM --- - </mat-option> - } - @for (imt of supportedImts$ | async; track imt) { - <mat-option [value]="imt.value"> - {{ imt.display }} - </mat-option> - } - </mat-select> - </mat-form-field> + <!-- IMT select menu --> + <mat-form-field class="grid-col-12 imt-select"> + <mat-label>Intensity Measure Type</mat-label> + <mat-select [formControl]="formState.controls.imt"> + @if ((gmmSelected$ | async) === false) { + <mat-option selected="true" [value]="imtPlaceHolder.value"> + {{ imtPlaceHolder.display }} + </mat-option> + } + @for (imt of supportedImts(); track imt) { + <mat-option [value]="imt.value"> + {{ imt.display }} + </mat-option> + } + </mat-select> + </mat-form-field> - <!-- Event parameters --> - <div class="settings-subsection event-parameters"> - <mat-label class="settings-subsection--label">Event Parameters</mat-label> - <!-- Event parameters: Mw input --> - <div class="settings-subsection--section"> - <nshmp-lib-ng-gmm-multi-select - class="grid-col-6 mw-input" - label="Mw" - [multiple]="MwMultiple$ | async" - [numberControl]="formState?.controls?.Mw" - [parameter]="(parameters$ | async)?.Mw" - placeholder="Select Magnitudes" - [selectControl]="formState?.controls?.MwMulti" - [selectOptions]="MwCommonValues" - /> - </div> + <!-- Event parameters --> + <div class="settings-subsection event-parameters"> + <mat-label class="settings-subsection--label">Event Parameters</mat-label> + <!-- Event parameters: Mw input --> + <div class="settings-subsection--section"> + <nshmp-lib-no-ngrx-gmm-multi-select + class="grid-col-6 mw-input" + label="Mw" + [multiple]="MwMultiple$ | async" + [numberControl]="formState.controls.Mw" + [parameter]="parameters()?.Mw" + placeholder="Select Magnitudes" + [selectControl]="formState.controls.MwMulti" + [selectOptions]="MwCommonValues" + /> </div> + </div> - <!-- Site & Basin--> - <div class="settings-subsection site-parameters"> - <mat-label class="settings-subsection--label">Site & Basin</mat-label> - <div class="settings-subsection--section grid-row grid-gap-sm"> - <!-- Site & Basin: Vs30 input --> - <nshmp-lib-ng-gmm-multi-select - class="grid-col-12 vs30-input" - label="V<sub>s30</sub> (<sup>m</sup>/<sub>s</sub>)" - [multiple]="vs30Multiple$ | async" - [numberControl]="formState?.controls?.vs30" - [parameter]="(parameters$ | async)?.vs30" - placeholder="Select values..." - [selectControl]="formState?.controls?.vs30Multi" - [selectOptions]="vs30CommonValues" - /> + <!-- Site & Basin--> + <div class="settings-subsection site-parameters"> + <mat-label class="settings-subsection--label">Site & Basin</mat-label> + <div class="settings-subsection--section grid-row grid-gap-sm"> + <!-- Site & Basin: Vs30 input --> + <nshmp-lib-no-ngrx-gmm-multi-select + class="grid-col-12 vs30-input" + label="V<sub>s30</sub> (<sup>m</sup>/<sub>s</sub>)" + [multiple]="vs30Multiple$ | async" + [numberControl]="formState.controls.vs30" + [parameter]="parameters()?.vs30" + placeholder="Select values..." + [selectControl]="formState.controls.vs30Multi" + [selectOptions]="vs30CommonValues" + /> - <!-- Site & Basin: Z 1.0 input --> - <mat-form-field class="grid-col-4 z1p0-input"> - <mat-label>Z<sub>1.0</sub> (km)</mat-label> - <input - matInput - [max]="(parameters$ | async)?.z1p0.max" - [min]="(parameters$ | async)?.z1p0.min" - [ngrxFormControlState]="formState?.controls?.z1p0" - step="1.0" - type="number" - /> - <mat-error> - [ - {{ (parameters$ | async)?.z1p0.min }}, - {{ (parameters$ | async)?.z1p0.max }} - ] - </mat-error> - </mat-form-field> + <!-- Site & Basin: Z 1.0 input --> + <mat-form-field class="grid-col-4 z1p0-input"> + <mat-label>Z<sub>1.0</sub> (km)</mat-label> + <input + matInput + [max]="parameters()?.z1p0.max" + [min]="parameters()?.z1p0.min" + [formControl]="formState.controls.z1p0" + step="1.0" + type="number" + /> + <mat-error> + [ + {{ parameters()?.z1p0.min }}, + {{ parameters()?.z1p0.max }} + ] + </mat-error> + </mat-form-field> - <!-- Site & Basin: Z 2.5 input --> - <mat-form-field class="grid-col-4 z2p5-input"> - <mat-label>Z<sub>2.5</sub> (km)</mat-label> - <input - matInput - [max]="(parameters$ | async)?.z2p5.max" - [min]="(parameters$ | async)?.z2p5.min" - [ngrxFormControlState]="formState?.controls?.z2p5" - step="1.0" - type="number" - /> - <mat-error> - [ - {{ (parameters$ | async)?.z2p5.min }}, - {{ (parameters$ | async)?.z2p5.max }} - ] - </mat-error> - </mat-form-field> + <!-- Site & Basin: Z 2.5 input --> + <mat-form-field class="grid-col-4 z2p5-input"> + <mat-label>Z<sub>2.5</sub> (km)</mat-label> + <input + matInput + [max]="parameters()?.z2p5.max" + [min]="parameters()?.z2p5.min" + [formControl]="formState.controls.z2p5" + step="1.0" + type="number" + /> + <mat-error> + [ + {{ parameters()?.z2p5.min }}, + {{ parameters()?.z2p5.max }} + ] + </mat-error> + </mat-form-field> - <!-- Site & Basin: zSed input --> - <mat-form-field class="grid-col-4 zsed-input"> - <mat-label>Z<sub>SED</sub> (km)</mat-label> - <input - matInput - [max]="(parameters$ | async)?.zSed?.max" - [min]="(parameters$ | async)?.zSed?.min" - [ngrxFormControlState]="formState?.controls?.zSed" - step="1.0" - type="number" - /> - <mat-error> - [ - {{ (parameters$ | async)?.zSed?.min }}, - {{ (parameters$ | async)?.zSed?.max }} - ] - </mat-error> - </mat-form-field> - </div> + <!-- Site & Basin: zSed input --> + <mat-form-field class="grid-col-4 zsed-input"> + <mat-label>Z<sub>SED</sub> (km)</mat-label> + <input + matInput + [max]="parameters()?.zSed?.max" + [min]="parameters()?.zSed?.min" + [formControl]="formState.controls.zSed" + step="1.0" + type="number" + /> + <mat-error> + [ + {{ parameters()?.zSed?.min }}, + {{ parameters()?.zSed?.max }} + ] + </mat-error> + </mat-form-field> </div> + </div> - <nshmp-lib-ng-gmm-plot-options-control-panel - [showEpistemicFormControl]="(controls$ | async)?.showEpistemicUncertainty" - /> + <nshmp-lib-no-ngrx-gmm-plot-options-control-panel + [showEpistemicFormControl]="controls.showEpistemicUncertainty" + /> - <div class="padding-y-3"></div> + <div class="padding-y-3"></div> - <nshmp-lib-ng-control-panel-buttons - [plotDisabled]="formState?.isInvalid" - [serviceCallInfo]="serviceCallInfo$ | async" - [resetDisabled]="formState?.isPristine" - (resetClick)="facade.resetControlPanel()" - /> - </form> -} + <nshmp-lib-no-ngrx-control-panel-buttons + [plotDisabled]="formState.invalid" + [serviceCallInfo]="serviceCallInfo()" + [resetDisabled]="formState.pristine" + (resetClick)="facade.resetControlPanel()" + /> +</form> diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts index 17b38b87c795f14adb6a7164459f488b9a119b06..e5be2cede3fcac4c81c4fcb544275e7e13f1c974 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.spec.ts @@ -1,6 +1,6 @@ import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {provideMockStore} from '@ngrx/store/testing'; +import {provideNoopAnimations} from '@angular/platform-browser/animations'; import {ControlPanelComponent} from './control-panel.component'; @@ -11,7 +11,7 @@ describe('ControlPanelComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ControlPanelComponent], - providers: [provideMockStore({initialState: {}}), provideHttpClient()], + providers: [provideHttpClient(), provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(ControlPanelComponent); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts index c6ed1d5e3c47c9ba2101705cc9ebe88ee8b09fa0..5c814a7cbfdd14e42d580b8025710dd03f11ebc6 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/control-panel/control-panel.component.ts @@ -1,23 +1,24 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed, OnDestroy, OnInit} from '@angular/core'; +import {ReactiveFormsModule} from '@angular/forms'; import {MatOption} from '@angular/material/core'; import {MatError, MatFormField, MatLabel} from '@angular/material/form-field'; import {MatInput} from '@angular/material/input'; import {MatSelect} from '@angular/material/select'; import { GmmFormControlIds, + gmmUtils, MW_COMMON_VALUES, NshmpLibNgGmmMenuComponent, NshmpLibNgGmmMultiSelectComponent, NshmpLibNgGmmPlotOptionsControlPanelComponent, VS30_COMMON_VALUES, -} from '@ghsc/nshmp-lib-ng/gmm'; +} from '@ghsc/nshmp-lib-no-ngrx/gmm'; import { NshmpLibNgControlPanelButtonsComponent, - NshmpNgrxFormsModule, NshmpService, -} from '@ghsc/nshmp-lib-ng/nshmp'; -import {unbox} from 'ngrx-forms'; +} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {combineLatest, Subscription} from 'rxjs'; import {map} from 'rxjs/operators'; import {AppFacade} from '../../state/app.facade'; @@ -38,63 +39,111 @@ import {AppFacade} from '../../state/app.facade'; NshmpLibNgGmmMultiSelectComponent, NshmpLibNgGmmPlotOptionsControlPanelComponent, NshmpLibNgControlPanelButtonsComponent, - NshmpNgrxFormsModule, + ReactiveFormsModule, ], selector: 'app-control-panel', standalone: true, styleUrl: './control-panel.component.scss', templateUrl: './control-panel.component.html', }) -export class ControlPanelComponent { +export class ControlPanelComponent implements OnInit, OnDestroy { + imtPlaceHolder = gmmUtils.imtPlaceHolder(); + /** List of common magnitudes */ MwCommonValues = MW_COMMON_VALUES; + /** List of common vs30 values */ vs30CommonValues = VS30_COMMON_VALUES; /** Form controls state */ - controls$ = this.facade.controlPanelForm$.pipe(map(form => form.controls)); + controls = this.facade.controlPanelForm.controls; /** Control panel form field state */ - formState$ = this.facade.controlPanelForm$; + formState = this.facade.controlPanelForm; /** Whether a GMM has been selected */ - gmmSelected$ = this.controls$.pipe( - map(controls => unbox(controls.gmmSource.value).length > 0) + gmmSelected$ = this.controls.gmmSource.valueChanges.pipe( + map(gmmSource => gmmSource?.length > 0) ); /** Usage parameters */ - parameters$ = this.facade.usage$.pipe( - map(usage => usage?.response?.parameters) + parameters = computed( + () => this.facade.state().usageResponse?.response?.parameters ); /** Service call info state */ - serviceCallInfo$ = this.facade.serviceCallInfo$; + serviceCallInfo = computed(() => this.facade.state().serviceCallInfo); /** Supported IMTs based on GMMs selected */ - supportedImts$ = this.facade.supportedImts$; + supportedImts = computed(() => this.facade.state().supportedImts); /** Whether magnitudes are multi-selectable */ - MwMultiple$ = this.controls$.pipe( + MwMultiple$ = this.controls.multiSelectableParam.valueChanges.pipe( map( - controls => - controls.multiSelectableParam.value === GmmFormControlIds.MW.toString() + multiSelectableParam => + multiSelectableParam.toString() === GmmFormControlIds.MW.toString() ) ); /** Whether Vs30 is multi-selectable */ - vs30Multiple$ = this.controls$.pipe( + vs30Multiple$ = this.controls.multiSelectableParam.valueChanges.pipe( map( - controls => - controls.multiSelectableParam.value === - GmmFormControlIds.VS30.toString() + multiSelectableParam => + multiSelectableParam.toString() === GmmFormControlIds.VS30.toString() ) ); + subs: Subscription[] = []; + constructor( public facade: AppFacade, private nshmpService: NshmpService ) {} + ngOnInit(): void { + this.subs.push( + this.formState.controls.gmmSource.valueChanges.subscribe(() => { + this.onGmmSource(); + }) + ); + + this.subs.push( + this.formState.controls.multiSelectableParam.valueChanges.subscribe(() => + this.onMultiSelectableParam() + ) + ); + + this.subs.push( + this.formState.controls.showEpistemicUncertainty.valueChanges.subscribe( + () => this.onShowEpistemicUncertainty() + ) + ); + + this.subs.push( + combineLatest([ + this.formState.controls.dip.valueChanges, + this.formState.controls.width.valueChanges, + ]).subscribe(() => this.updatePlots()) + ); + + this.subs.push( + combineLatest([ + this.formState.controls.imt.valueChanges, + this.formState.controls.Mw.valueChanges, + this.formState.controls.MwMulti.valueChanges, + this.formState.controls.vs30Multi.valueChanges, + this.formState.controls.vs30.valueChanges, + this.formState.controls.z1p0.valueChanges, + this.formState.controls.z2p5.valueChanges, + this.formState.controls.zSed.valueChanges, + ]).subscribe(() => this.facade.resetState()) + ); + } + + ngOnDestroy(): void { + this.subs.forEach(sub => sub?.unsubscribe()); + } + /** * On form submit. */ @@ -102,4 +151,62 @@ export class ControlPanelComponent { this.facade.callServices(); this.nshmpService.selectPlotControl(); } + + private onGmmSource() { + const formValues = this.formState.getRawValue(); + + if ( + !this.facade.state().usageResponse || + formValues.gmmSource.length === 0 + ) { + this.formState.controls.imt.patchValue(gmmUtils.imtPlaceHolder().value); + this.facade.updateState({ + supportedImts: [], + }); + return; + } + + const supportedImts = gmmUtils.getSupportedImts( + formValues.gmmSource, + this.facade.state().usageResponse + ); + + this.facade.updateState({ + supportedImts, + }); + + const imt = [...supportedImts].shift(); + + this.formState.controls.imt.patchValue(imt?.value); + this.facade.resetState(); + } + + private onMultiSelectableParam(): void { + if (!this.facade.state().usageResponse) { + return; + } + + const {multiSelectableParam} = this.formState.getRawValue(); + const parameters = this.facade.state().usageResponse.response.parameters; + + if (multiSelectableParam === GmmFormControlIds.MW) { + this.formState.controls.gmmSource.setValue([]); + this.formState.controls.Mw.setValue(parameters.Mw.value as number); + this.formState.controls.MwMulti.setValue([]); + } else if (multiSelectableParam === GmmFormControlIds.VS30) { + this.formState.controls.gmmSource.setValue([]); + this.formState.controls.vs30.setValue(parameters.vs30.value as number); + this.formState.controls.vs30Multi.setValue([]); + } + + this.facade.resetState(); + } + + private onShowEpistemicUncertainty(): void { + this.facade.createPlots(); + } + + private updatePlots(): void { + this.facade.createPlots(); + } } diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html index 57fcd8745ebc9a8e0118f02fbe06467553747c8c..2bad50664e43ca51084bd8612cb467f9ea6ca073 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.html @@ -1,40 +1,38 @@ <mat-label>{{ label }}</mat-label> -@if (formControlState) { - <div class="grid-row grid-gap-lg"> - <div class="grid-col-7"> - <mat-slider - discrete - [min]="parameter?.min" - [max]="parameter?.max" - matTooltip="Slide to change fault" - (change)="faultControlChange.emit()" - > - <input matSliderThumb [ngrxFormControlState]="formControlState" /> - </mat-slider> - </div> +<div class="grid-row grid-gap-lg"> + <div class="grid-col-7"> + <mat-slider + discrete + [min]="parameter?.min" + [max]="parameter?.max" + matTooltip="Slide to change fault" + (change)="facade.callServices()" + > + <input matSliderThumb [formControl]="control" /> + </mat-slider> + </div> - <mat-form-field class="grid-col-5"> - <mat-label>{{ label }}</mat-label> - <input - matInput - [ngrxFormControlState]="formControlState" - type="number" - [min]="parameter?.min" - [max]="parameter?.max" - (input)="faultControlChange.emit()" - /> + <mat-form-field class="grid-col-5"> + <mat-label>{{ label }}</mat-label> + <input + matInput + [formControl]="control" + type="number" + [min]="parameter?.min" + [max]="parameter?.max" + (input)="facade.callServices()" + /> - @if (textSuffix) { - <span matTextSuffix>{{ textSuffix }}</span> - } + @if (textSuffix) { + <span matTextSuffix>{{ textSuffix }}</span> + } - <mat-error> - [ - {{ parameter?.min }}, - {{ parameter?.max }} - ] - </mat-error> - </mat-form-field> - </div> -} + <mat-error> + [ + {{ parameter?.min }}, + {{ parameter?.max }} + ] + </mat-error> + </mat-form-field> +</div> diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts index ddb7e896113b6f064aebf10aeac4c57adced8b6d..1446776dc2db11d770a0637e94641b3aca5ac2d4 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.spec.ts @@ -1,5 +1,7 @@ +import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {provideMockStore} from '@ngrx/store/testing'; +import {FormControl} from '@angular/forms'; +import {provideNoopAnimations} from '@angular/platform-browser/animations'; import {FaultControlComponent} from './fault-control.component'; @@ -10,11 +12,14 @@ describe('FaultControlComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [FaultControlComponent], - providers: [provideMockStore({initialState: {}})], + providers: [provideHttpClient(), provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(FaultControlComponent); component = fixture.componentInstance; + component.control = new FormControl(0, { + nonNullable: true, + }); fixture.detectChanges(); }); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts index 442fa61f9ba8cf4a3ac32b4f0cceae5f32d2d524..c89c55fa076804a810168656256d4a5b65842f6a 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/fault-control/fault-control.component.ts @@ -1,4 +1,5 @@ -import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {Component, Input} from '@angular/core'; +import {FormControl, ReactiveFormsModule} from '@angular/forms'; import { MatError, MatFormField, @@ -8,9 +9,9 @@ import { import {MatInput} from '@angular/material/input'; import {MatSlider, MatSliderThumb} from '@angular/material/slider'; import {MatTooltip} from '@angular/material/tooltip'; -import {NshmpNgrxFormsModule} from '@ghsc/nshmp-lib-ng/nshmp'; import {GmmUsageParameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services'; -import {FormControlState} from 'ngrx-forms'; + +import {AppFacade} from '../../state/app.facade'; /** * Fault control with slider and input box. @@ -25,7 +26,7 @@ import {FormControlState} from 'ngrx-forms'; MatInput, MatSuffix, MatError, - NshmpNgrxFormsModule, + ReactiveFormsModule, ], selector: 'app-fault-control', standalone: true, @@ -33,23 +34,21 @@ import {FormControlState} from 'ngrx-forms'; templateUrl: './fault-control.component.html', }) export class FaultControlComponent { - /** Event emiiter when the slider or input value changes */ - @Output() - faultControlChange = new EventEmitter(); - - /** NGRX form control state */ - @Input() - formControlState: FormControlState<number>; + /** Form control state */ + @Input({required: true}) + control: FormControl<number>; /** Label */ @Input() label: string; /** GMM usage parameter with min and max values */ - @Input() + @Input({required: true}) parameter: GmmUsageParameter; /** Input text suffix */ @Input() textSuffix: string; + + constructor(public facade: AppFacade) {} } diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html index 721416a257b65769b7b0bf16d694b7a5b9ec4242..7478650f282308cce72a66a5436821122a657d90 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.html @@ -1,104 +1,102 @@ -@if (form$ | async; as form) { - <div class="grid-row parameter-summary"> - <div class="grid-col-12 tablet-lg:grid-col-6"> - <mat-list class="parameter-list"> - <mat-list-item> - <span class="parameter">Ground Motion Models</span>: +<div class="grid-row parameter-summary"> + <div class="grid-col-12 tablet-lg:grid-col-6"> + <mat-list class="parameter-list"> + <mat-list-item> + <span class="parameter">Ground Motion Models</span>: + </mat-list-item> + @for (gmmSource of form?.value?.gmmSource; track gmmSource) { + <mat-list-item class="indent-list font-small"> + {{ gmmSource.gmm.display }} </mat-list-item> - @for (gmmSource of unbox(form?.value?.gmmSource); track gmmSource) { - <mat-list-item class="indent-list font-small"> - {{ gmmSource.gmm.display }} - </mat-list-item> - } - </mat-list> - </div> + } + </mat-list> + </div> - <div class="grid-col-12 tablet-lg:grid-col-3"> - <mat-list class="parameter-list"> + <div class="grid-col-12 tablet-lg:grid-col-3"> + <mat-list class="parameter-list"> + <mat-list-item> + <span class="parameter">IMT</span>: + {{ imtIdToDisplay(form?.value?.imt) }} + </mat-list-item> + + @if (form.value.multiSelectableParam === GmmFormControlIds.MW) { <mat-list-item> - <span class="parameter">IMT</span>: - {{ imtIdToDisplay(form?.value?.imt) }} + <span class="parameter">Magnitude</span>: </mat-list-item> - @if (form.value.multiSelectableParam === GmmFormControlIds.MW) { - <mat-list-item> - <span class="parameter">Magnitude</span>: - </mat-list-item> - - @for (Mw of unbox(form?.value?.MwMulti); track Mw) { - <mat-list-item class="indent-list"> - {{ Mw }} - </mat-list-item> - } - } @else { - <mat-list-item> - <span class="parameter">Magnitude</span>: - {{ form?.value?.Mw }} + @for (Mw of form?.value?.MwMulti; track Mw) { + <mat-list-item class="indent-list"> + {{ Mw }} </mat-list-item> } - + } @else { <mat-list-item> - <span class="parameter"> Z<sub>TOR</sub> </span>: - {{ form?.value?.zTor }} km + <span class="parameter">Magnitude</span>: + {{ form?.value?.Mw }} </mat-list-item> + } - <mat-list-item> - <span class="parameter">Dip</span>: {{ form?.value?.dip }}° - </mat-list-item> + <mat-list-item> + <span class="parameter"> Z<sub>TOR</sub> </span>: + {{ form?.value?.zTor }} km + </mat-list-item> - <mat-list-item> - <span class="parameter">Width</span>: {{ form?.value?.width }} km - </mat-list-item> - </mat-list> - </div> + <mat-list-item> + <span class="parameter">Dip</span>: {{ form?.value?.dip }}° + </mat-list-item> - <div class="grid-col-12 tablet-lg:grid-col-3"> - <mat-list class="parameter-list"> - @if (form.value.multiSelectableParam === GmmFormControlIds.VS30) { - <mat-list-item> <span class="parameter">Vs30</span>: </mat-list-item> + <mat-list-item> + <span class="parameter">Width</span>: {{ form?.value?.width }} km + </mat-list-item> + </mat-list> + </div> - @for (vs30 of unbox(form?.value?.vs30Multi); track vs30) { - <mat-list-item class="indent-list"> - {{ vs30 }} - <sup> m</sup>/<sub>s</sub> - </mat-list-item> - } - } @else { - <mat-list-item> - <span class="parameter">Vs30</span>: {{ form?.value?.vs30 }} + <div class="grid-col-12 tablet-lg:grid-col-3"> + <mat-list class="parameter-list"> + @if (form.value.multiSelectableParam === GmmFormControlIds.VS30) { + <mat-list-item> <span class="parameter">Vs30</span>: </mat-list-item> + + @for (vs30 of form?.value?.vs30Multi; track vs30) { + <mat-list-item class="indent-list"> + {{ vs30 }} <sup> m</sup>/<sub>s</sub> </mat-list-item> } - + } @else { <mat-list-item> - <span class="parameter"> Z<sub>1.0</sub> </span>: - {{ form?.value?.z1p0 }} - @if (form?.value?.z1p0) { - <span> km</span> - } + <span class="parameter">Vs30</span>: {{ form?.value?.vs30 }} + <sup> m</sup>/<sub>s</sub> </mat-list-item> + } - <mat-list-item> - <span class="parameter"> Z<sub>2.5</sub> </span>: - {{ form?.value?.z2p5 }} - @if (form?.value?.z2p5) { - <span> km</span> - } - </mat-list-item> + <mat-list-item> + <span class="parameter"> Z<sub>1.0</sub> </span>: + {{ form?.value?.z1p0 }} + @if (form?.value?.z1p0) { + <span> km</span> + } + </mat-list-item> - <mat-list-item> - <span class="parameter"> Z<sub>SED</sub> </span>: - {{ form?.value?.zSed }} - @if (form?.value?.zSed) { - <span> km</span> - } - </mat-list-item> + <mat-list-item> + <span class="parameter"> Z<sub>2.5</sub> </span>: + {{ form?.value?.z2p5 }} + @if (form?.value?.z2p5) { + <span> km</span> + } + </mat-list-item> - <mat-list-item> - <span class="parameters">Epistemic Uncertainty</span>: - {{ form?.value?.showEpistemicUncertainty ? 'On' : 'Off' }} - </mat-list-item> - </mat-list> - </div> + <mat-list-item> + <span class="parameter"> Z<sub>SED</sub> </span>: + {{ form?.value?.zSed }} + @if (form?.value?.zSed) { + <span> km</span> + } + </mat-list-item> + + <mat-list-item> + <span class="parameters">Epistemic Uncertainty</span>: + {{ form?.value?.showEpistemicUncertainty ? 'On' : 'Off' }} + </mat-list-item> + </mat-list> </div> -} +</div> diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.spec.ts index 10bfea4db08555fa96249b81d15dd82cba66a7b4..32fac014cc17324c50894465d7c80a661cc18395 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.spec.ts @@ -1,5 +1,5 @@ +import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; -import {provideMockStore} from '@ngrx/store/testing'; import {ParameterSummaryComponent} from './parameter-summary.component'; @@ -10,7 +10,7 @@ describe('ParameterSummaryComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ParameterSummaryComponent], - providers: [provideMockStore({initialState: {}})], + providers: [provideHttpClient()], }).compileComponents(); fixture = TestBed.createComponent(ParameterSummaryComponent); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts index 218ec9395eacf70e30a456f05ca8e5e706153feb..1621d574cd49d42c091bf8dcb1d883e93b947cfd 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/parameter-summary/parameter-summary.component.ts @@ -1,9 +1,7 @@ import {AsyncPipe} from '@angular/common'; import {Component} from '@angular/core'; import {MatList, MatListItem} from '@angular/material/list'; -import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm'; -import {unbox} from 'ngrx-forms'; -import {map} from 'rxjs'; +import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm'; import {AppFacade} from '../../state/app.facade'; @@ -23,16 +21,9 @@ export class ParameterSummaryComponent { /** Function to convert IMT id to display */ imtIdToDisplay = gmmUtils.imtIdToDisplay; - /** ngrx-forms unbox function */ - unbox = unbox; /** Control form field state */ - form$ = this.facade.controlPanelForm$; - - /** List of GMMs */ - gmms$ = this.facade.usage$.pipe( - map(usage => usage?.response?.parameters?.gmm.values) - ); + form = this.facade.controlPanelForm; constructor(private facade: AppFacade) {} } diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html index 38395ac8064f04fadef85f6d11ba7fcedd9b7524..a59c17095885b8f2ae5911def6788decdcd3eff1 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.html @@ -5,16 +5,16 @@ <mat-expansion-panel-header> <mat-panel-title>{{ panel.title }}</mat-panel-title> </mat-expansion-panel-header> - <nshmp-lib-ng-plot-settings [plot]="panel.plot$ | async" /> + <nshmp-lib-no-ngrx-plot-settings + [plot]="panel.plot()" + (updatedPlot)="updatePlot($event)" + /> </mat-expansion-panel> } </mat-accordion> - <nshmp-lib-ng-plot-reset-plot-settings + <nshmp-lib-no-ngrx-plot-reset-plot-settings (resetClick)="facade.resetPlotSettings()" - [resetDisabled]=" - (groundMotionPlotData$ | async)?.settingsForm?.isPristine && - (groundMotionPlotData$ | async)?.settingsForm?.isUntouched - " + [resetDisabled]="resetDisabled" /> </div> diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts index dc4ce1a2437efbfb89663a635f73e4402fa0b5f7..7e8e9ecfcb3122bb29ad6c109ae5e92d3b8c72c0 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.spec.ts @@ -1,7 +1,6 @@ import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {provideNoopAnimations} from '@angular/platform-browser/animations'; -import {provideMockStore} from '@ngrx/store/testing'; import {PlotSettingsComponent} from './plot-settings.component'; @@ -12,11 +11,7 @@ describe('PlotSettingsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [PlotSettingsComponent], - providers: [ - provideMockStore({initialState: {}}), - provideHttpClient(), - provideNoopAnimations(), - ], + providers: [provideHttpClient(), provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(PlotSettingsComponent); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts index 261f8b8ce8f8a72ca04d0a7490bd4dd1e7fd6969..8eccf40700b5fce6285dd6acce7f86d5e1c87412 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plot-settings/plot-settings.component.ts @@ -1,5 +1,4 @@ -import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed, OnDestroy} from '@angular/core'; import { MatAccordion, MatExpansionPanel, @@ -9,9 +8,10 @@ import { import { NshmpLibNgPlotResetPlotSettingsComponent, NshmpLibNgPlotSettingsComponent, + NshmpPlot, PlotSettingsPanel, -} from '@ghsc/nshmp-lib-ng/plot'; -import {map} from 'rxjs'; +} from '@ghsc/nshmp-lib-no-ngrx/plot'; +import {Subscription} from 'rxjs'; import {AppFacade} from '../../state/app.facade'; import {Plots} from '../../state/app.state'; @@ -24,25 +24,45 @@ import {Plots} from '../../state/app.state'; MatExpansionPanel, MatExpansionPanelHeader, MatExpansionPanelTitle, - AsyncPipe, ], selector: 'app-plot-settings', standalone: true, styleUrl: './plot-settings.component.scss', templateUrl: './plot-settings.component.html', }) -export class PlotSettingsComponent { +export class PlotSettingsComponent implements OnDestroy { /** Ground motion plot */ - groundMotionPlotData$ = this.facade.plots$.pipe( - map(plots => plots?.get(Plots.GROUND_MOTION)) - ); + groundMotionPlot = computed(() => { + this.settingsFormSubscription?.unsubscribe(); + const plot = this.facade.state().plots.get(Plots.GROUND_MOTION); + + this.settingsFormSubscription = plot.settingsForm.valueChanges.subscribe( + () => { + this.resetDisabled = + plot.settingsForm.pristine && plot.settingsForm.untouched; + } + ); + + return plot; + }); panels: PlotSettingsPanel[] = [ { - plot$: this.groundMotionPlotData$, + plot: this.groundMotionPlot, title: 'Ground Motion Plot Settings', }, ]; + resetDisabled = true; + private settingsFormSubscription = new Subscription(); + constructor(public facade: AppFacade) {} + + ngOnDestroy(): void { + this.settingsFormSubscription.unsubscribe(); + } + + updatePlot(plot: NshmpPlot): void { + this.facade.updatePlot(plot, Plots.GROUND_MOTION); + } } diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.html b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.html index 230e07f5420450bf0ecf87f48bb70573969132ba..6beaf05147eb73f519f3380c9651fe347c3143b8 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.html +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.html @@ -1,4 +1,4 @@ -<nshmp-lib-ng-plots-container> +<nshmp-lib-no-ngrx-plots-container> <mat-accordion multi> <!-- GMM vs. distance plot --> <mat-expansion-panel expanded> @@ -8,8 +8,8 @@ <mat-divider /> - @if (groundMotionPlotData$ | async; as groundMotionPlot) { - <nshmp-lib-ng-plot [plot]="groundMotionPlot" /> + @if (groundMotionPlotData()) { + <nshmp-lib-no-ngrx-plot [plot]="groundMotionPlotData()" /> } <mat-divider /> @@ -18,8 +18,8 @@ <div class="fault grid-row"> <!-- Fault plot --> <div class="fault-plot grid-col-12 tablet-lg:grid-col-7"> - @if (faultPlotData$ | async; as faultPlot) { - <nshmp-lib-ng-plot [plot]="faultPlot" /> + @if (faultPlotData()) { + <nshmp-lib-no-ngrx-plot [plot]="faultPlotData()" /> } </div> @@ -29,36 +29,31 @@ <div class="fault-controls grid-col-12 tablet-lg:grid-col"> <mat-label class="section-label">Source Geometry</mat-label> - @if (formState$ | async; as formState) { - <form [ngrxFormState]="formState"> - <!-- Dip --> - <app-fault-control - [formControlState]="formState?.controls?.dip" - label="Dip" - [parameter]="dipParameter$ | async" - textSuffix="°" - (faultControlChange)="onFaultControlChange()" - /> + <form [formGroup]="formState"> + <!-- Dip --> + <app-fault-control + [control]="formState.controls.dip" + label="Dip" + [parameter]="dipParameter()" + textSuffix="°" + /> - <!-- Width --> - <app-fault-control - [formControlState]="formState?.controls?.width" - label="Width" - [parameter]="widthParameter$ | async" - textSuffix="km" - (faultControlChange)="onFaultControlChange()" - /> + <!-- Width --> + <app-fault-control + [control]="formState.controls.width" + label="Width" + [parameter]="widthParameter()" + textSuffix="km" + /> - <!-- zTop --> - <app-fault-control - [formControlState]="formState?.controls?.zTor" - label="zTor" - [parameter]="zTorParameter$ | async" - textSuffix="km" - (faultControlChange)="onFaultControlChange()" - /> - </form> - } + <!-- zTop --> + <app-fault-control + [control]="formState.controls.zTor" + label="zTor" + [parameter]="zTorParameter()" + textSuffix="km" + /> + </form> </div> </div> </mat-expansion-panel> @@ -80,7 +75,7 @@ </mat-expansion-panel-header> <mat-divider /> - <nshmp-lib-ng-app-metadata [repositories]="repositories$ | async" /> + <nshmp-lib-no-ngrx-app-metadata [repositories]="repositories()" /> </mat-expansion-panel> </mat-accordion> -</nshmp-lib-ng-plots-container> +</nshmp-lib-no-ngrx-plots-container> diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts index e562e2d316dbc6e5c6968468fe2ecbe0b704f44f..dca4b3cde8d936b2ebf9434b673b175b51a007a9 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.spec.ts @@ -1,7 +1,6 @@ import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {provideNoopAnimations} from '@angular/platform-browser/animations'; -import {provideMockStore} from '@ngrx/store/testing'; import {PlotsComponent} from './plots.component'; @@ -12,11 +11,7 @@ describe('PlotsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [PlotsComponent], - providers: [ - provideMockStore({initialState: {}}), - provideHttpClient(), - provideNoopAnimations(), - ], + providers: [provideHttpClient(), provideNoopAnimations()], }).compileComponents(); fixture = TestBed.createComponent(PlotsComponent); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts index 2ce03e07c256605c121e7df133d65e31e44802de..154d539383dc568b9685413c4bd572e7aeeaffec 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/components/plots/plots.component.ts @@ -1,5 +1,6 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed, Signal} from '@angular/core'; +import {ReactiveFormsModule} from '@angular/forms'; import {MatDivider} from '@angular/material/divider'; import { MatAccordion, @@ -8,12 +9,12 @@ import { MatExpansionPanelTitle, } from '@angular/material/expansion'; import {MatLabel} from '@angular/material/form-field'; -import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-ng/nshmp'; +import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; import { NshmpLibNgPlotComponent, NshmpLibNgPlotsContainerComponent, -} from '@ghsc/nshmp-lib-ng/plot'; -import {map} from 'rxjs'; +} from '@ghsc/nshmp-lib-no-ngrx/plot'; +import {GmmUsageParameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services'; import {AppFacade} from '../../state/app.facade'; import {Plots} from '../../state/app.state'; @@ -41,6 +42,7 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary. FaultControlComponent, ParameterSummaryComponent, AsyncPipe, + ReactiveFormsModule, ], selector: 'app-plots', standalone: true, @@ -49,45 +51,41 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary. }) export class PlotsComponent { /** Usage parameters */ - private parameters$ = this.facade.usage$.pipe( - map(usage => usage?.response?.parameters) + private parameters = computed( + () => this.facade.state().usageResponse?.response?.parameters ); /** Ground motion plot */ - groundMotionPlotData$ = this.facade.plots$.pipe( - map(plots => plots?.get(Plots.GROUND_MOTION)?.plotData) + groundMotionPlotData = computed( + () => this.facade.state().plots?.get(Plots.GROUND_MOTION)?.plotData ); /** Fault plot */ - faultPlotData$ = this.facade.plots$.pipe( - map(plots => plots?.get(Plots.FAULT)?.plotData) + faultPlotData = computed( + () => this.facade.state().plots?.get(Plots.FAULT)?.plotData ); /** Control panel form field state */ - formState$ = this.facade.controlPanelForm$; + formState = this.facade.controlPanelForm; /** Dip gmm parameter */ - dipParameter$ = this.parameters$.pipe(map(paramerters => paramerters?.dip)); + dipParameter = computed(() => this.parameters()?.dip); /** Width gmm parameter with more restrictive max */ - widthParameter$ = this.parameters$.pipe( - map(paramerters => ({ - ...paramerters?.width, - max: 30, - })) - ); + widthParameter: Signal<GmmUsageParameter> = computed(() => ({ + ...this.parameters()?.width, + max: 30, + })); /** zTor gmm parameter with more restrictive max */ - zTorParameter$ = this.parameters$.pipe( - map(paramerters => ({ - ...paramerters?.zTor, - max: 10, - })) - ); + zTorParameter: Signal<GmmUsageParameter> = computed(() => ({ + ...this.parameters()?.zTor, + max: 10, + })); /** Repo metadata */ - repositories$ = this.facade.usage$.pipe( - map(usage => usage?.metadata.repositories) + repositories = computed( + () => this.facade.state().usageResponse?.metadata.repositories ); constructor(private facade: AppFacade) {} diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.actions.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.actions.ts deleted file mode 100644 index 38c38fde5fb301c62eaafbf8b4031ab47441f4b8..0000000000000000000000000000000000000000 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.actions.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - GmmDistanceResponse, - GmmDistanceUsage, -} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services'; -import {createActionGroup, emptyProps, props} from '@ngrx/store'; -import {sharedActions} from 'projects/nshmp-apps/src/shared/state/shared'; - -/** - * Application NGRX actions - */ -export const appActions = createActionGroup({ - events: { - ...sharedActions, - /** Action to call services from query is valid */ - 'Initial Call From Query': emptyProps(), - /** Action to set the initial form values from query or default */ - 'Initial Form Set': emptyProps(), - /** Set the service response action */ - 'Service Responses': props<{serviceResponses: GmmDistanceResponse[]}>(), - /** Set the usage response action */ - 'Usage Response': props<{ - usageResponse: GmmDistanceUsage; - }>(), - }, - source: 'Development Hanging Wall Effects', -}); diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.effects.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.effects.ts deleted file mode 100644 index 9b5aaca77b9290b4ad5591c4a0d55a1dd8aa2d6c..0000000000000000000000000000000000000000 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.effects.ts +++ /dev/null @@ -1,153 +0,0 @@ -import {Injectable} from '@angular/core'; -import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm'; -import { - NshmpService, - ServiceCallInfo, - SpinnerService, -} from '@ghsc/nshmp-lib-ng/nshmp'; -import { - GmmDistanceResponse, - GmmDistanceUsage, -} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; -import {concatLatestFrom} from '@ngrx/operators'; -import {Store} from '@ngrx/store'; -import {environment} from 'projects/nshmp-apps/src/environments/environment'; -import {catchError, exhaustMap, map, mergeMap} from 'rxjs/operators'; - -import {createPlots} from '../utils/response-handler.utils'; -import {appActions} from './app.actions'; -import {hangingWallEffectsAppFeature} from './app.reducer'; -import {defaultFormValues} from './app.state'; - -/** - * NGRX application effects. - */ -@Injectable() -export class HangingWallEffectsAppEffects { - /** nshmp-ws base URL */ - baseUrl = environment.webServices.data.url; - /** GMM service URL */ - serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmHwFw}`; - - /** - * Call the services. - */ - callService$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.callServices), - concatLatestFrom(() => - this.store.select( - hangingWallEffectsAppFeature.selectHanginWallEffectsAppState - ) - ), - exhaustMap(([, state]) => { - if (state.controlPanelForm.isInvalid) { - return []; - } - - this.spinnerService.show(SpinnerService.MESSAGE_SERVICE); - - const urls = gmmUtils.serviceEndpoints( - this.serviceUrl, - state.controlPanelForm.value, - state.controlPanelForm.value.multiSelectableParam - ); - - return this.nshmpService.callServices$(urls).pipe( - mergeMap((serviceResponses: GmmDistanceResponse[]) => { - return [appActions.serviceResponses({serviceResponses})]; - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ); - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Create the plot data from the service responses. - */ - createPlots$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.serviceResponses), - concatLatestFrom(() => - this.store.select( - hangingWallEffectsAppFeature.selectHanginWallEffectsAppState - ) - ), - map(([, state]) => { - const plots = createPlots(state); - this.spinnerService.remove(); - return appActions.plots({plots}); - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Initialize the application. - */ - init$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.init), - concatLatestFrom(() => - this.store.select( - hangingWallEffectsAppFeature.selectHanginWallEffectsAppState - ) - ), - exhaustMap(([, state]) => { - this.spinnerService.show(SpinnerService.MESSAGE_METADATA); - - return this.nshmpService - .callService$<GmmDistanceUsage>(this.serviceUrl) - .pipe( - mergeMap(usageResponse => { - this.spinnerService.remove(); - const parameters = usageResponse.response.parameters; - const values = defaultFormValues(parameters); - const controls = state.controlPanelForm.controls; - - const typedActions = gmmUtils.initialFormSetActions( - state.controlPanelForm, - values, - parameters - ); - typedActions.push(appActions.usageResponse({usageResponse})); - gmmUtils.addFormAction( - parameters.rMax, - controls.rMax.id, - values.rMax, - typedActions - ); - gmmUtils.addFormAction( - parameters.rMin, - controls.rMin.id, - values.rMin, - typedActions - ); - - const serviceCallInfo: ServiceCallInfo = { - ...state.serviceCallInfo, - usage: [this.serviceUrl], - }; - - return [ - appActions.serviceCallInfo({serviceCallInfo}), - ...typedActions, - ]; - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ); - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - constructor( - private actions$: Actions, - private nshmpService: NshmpService, - private spinnerService: SpinnerService, - private store: Store - ) {} -} diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts index 34a40e6e0fceb67ea7c10614e2bb82f18c2c5666..e1788aeb6a5c9909c2c232a33b9659204ca4be67 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts @@ -1,105 +1,252 @@ -import {Injectable} from '@angular/core'; -import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp'; -import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot'; +import {Injectable, signal} from '@angular/core'; +import {AbstractControl, FormBuilder, Validators} from '@angular/forms'; +import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm'; +import { + FormGroupControls, + NshmpService, + ServiceCallInfo, + SpinnerService, +} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; import { GmmDistanceResponse, GmmDistanceUsage, } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services'; -import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {select, Store} from '@ngrx/store'; -import {FormGroupState} from 'ngrx-forms'; -import {Observable} from 'rxjs'; - -import {appActions} from './app.actions'; -import {hangingWallEffectsAppFeature} from './app.reducer'; -import {ControlPanelForm} from './app.state'; - -/** - * Entrypoint to NGRX store for hanging wall effects application. - */ -@Injectable({ - providedIn: 'root', -}) +import {environment} from 'projects/nshmp-apps/src/environments/environment'; +import {catchError} from 'rxjs'; + +import {createPlots} from '../utils/response-handler.utils'; +import { + AppState, + ControlPanelForm, + defaultFormValues, + defaultPlots, + initialState, + usageFormValues, +} from './app.state'; + +@Injectable({providedIn: 'root'}) export class AppFacade { - constructor(private store: Store) {} + /** Application state */ + readonly state = signal<AppState>({ + ...initialState(), + }); + + /** Control panel form group */ + private formGroup = this.formBuilder.group({ + ...defaultFormValues(), + gmmSource: [], + MwMulti: [], + vs30Multi: [], + }) as FormGroupControls<ControlPanelForm>; + + /** nshmp-ws base URL */ + private baseUrl = environment.webServices.data.url; + /** GMM service URL */ + private serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmHwFw}`; + + constructor( + private spinnerService: SpinnerService, + private nshmpService: NshmpService, + private formBuilder: FormBuilder + ) { + this.validators(this.formGroup); + this.formGroup.controls.gmmSource.setValue([]); + this.formGroup.controls.showEpistemicUncertainty.disable(); + } /** - * Returns the control panel form state. + * Call the GMM distance service. */ - get controlPanelForm$(): Observable<FormGroupState<ControlPanelForm>> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectControlPanelForm) + callServices(): void { + if (this.formGroup.invalid) { + return; + } + + this.spinnerService.show(SpinnerService.MESSAGE_SERVICE); + + const urls = gmmUtils.serviceEndpoints( + this.serviceUrl, + this.formGroup.getRawValue(), + this.formGroup.getRawValue().multiSelectableParam ); + + this.nshmpService + .callServices$<GmmDistanceResponse>(urls) + .pipe(catchError((error: Error) => this.nshmpService.throwError$(error))) + .subscribe(serviceResponses => + this.handleServiceResponses(serviceResponses) + ); } /** - * Returns the plots. + * Returns the control panel form state. */ - get plots$(): Observable<Map<string, NshmpPlot>> { - return this.store.pipe(select(hangingWallEffectsAppFeature.selectPlots)); + get controlPanelForm(): FormGroupControls<ControlPanelForm> { + return this.formGroup; } /** - * Returns the service call info. + * Create plots based on current state and form group. */ - get serviceCallInfo$(): Observable<ServiceCallInfo> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectServiceCallInfo) - ); + createPlots(): void { + this.updateState({ + plots: createPlots(this.state(), this.formGroup), + }); } /** - * Returns the service responses. + * Initialize application. */ - get serviceResponses$(): Observable<GmmDistanceResponse[]> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectServiceResponses) - ); + init(): void { + this.spinnerService.show(SpinnerService.MESSAGE_METADATA); + + this.nshmpService + .callService$<GmmDistanceUsage>(this.serviceUrl) + .pipe(catchError((error: Error) => this.nshmpService.throwError$(error))) + .subscribe(usageResponse => this.handleUsageResponse(usageResponse)); } /** - * Returns the supported IMTs. + * Reset the control panel. */ - get supportedImts$(): Observable<EnumParameterValues[]> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectSupportedImts) + resetControlPanel(): void { + this.formGroup.reset( + usageFormValues(this.state().usageResponse.response.parameters) ); } /** - * Return the usage for the selected model. + * Reset the plot settings. */ - get usage$(): Observable<GmmDistanceUsage> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectUsageResponse) - ); + resetPlotSettings(): void { + this.state().plots.forEach((plot, id) => { + const defaultSettings = defaultPlots().get(id).settingsForm; + plot.settingsForm.patchValue(defaultSettings.getRawValue()); + }); } /** - * Call the GMM distance service. + * Reset the state. */ - callServices(): void { - this.store.dispatch(appActions.callServices()); + resetState(): void { + const serviceCallInfo = gmmUtils.serviceCallInfo({ + multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam, + serviceName: this.state().serviceCallInfo.serviceName, + serviceResponses: this.state().serviceResponses, + serviceUrl: this.serviceUrl, + values: this.formGroup.getRawValue(), + }); + + this.updateState({ + serviceCallInfo, + serviceResponses: null, + }); + + this.createPlots(); } /** - * Initialize the application. + * Update a single plot in the state. + * + * @param plot Updated plot + * @param id Id of plot to update */ - init(): void { - this.store.dispatch(appActions.init()); + updatePlot(plot: NshmpPlot, id: string): void { + const plots = this.state().plots; + plots.set(id, plot); + this.updateState({ + plots, + }); } /** - * Reset the control panel. + * Update state. + * + * @param state Partial new state to update */ - resetControlPanel(): void { - this.store.dispatch(appActions.resetControlPanel()); + updateState(state: Partial<AppState>): void { + this.state.set({ + ...this.state(), + ...state, + }); } /** - * Reset the control panel. + * Handle service response result. + * + * @param serviceResponses The service responses */ - resetPlotSettings(): void { - this.store.dispatch(appActions.resetSettings()); + private handleServiceResponses( + serviceResponses: GmmDistanceResponse[] + ): void { + const means = serviceResponses.map(s => s.response.means); + const hasLogicTree = gmmUtils.hasTree(means); + + if (hasLogicTree) { + this.formGroup.controls.showEpistemicUncertainty.enable(); + } else { + this.formGroup.controls.showEpistemicUncertainty.disable(); + } + + this.updateState({ + serviceCallInfo: gmmUtils.serviceCallInfo({ + multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam, + serviceName: this.state().serviceCallInfo.serviceName, + serviceResponses, + serviceUrl: this.serviceUrl, + values: this.formGroup.getRawValue(), + }), + serviceResponses, + }); + + this.createPlots(); + + this.spinnerService.remove(); + } + + /** + * Handle usage response. + * + * @param usageResponse Usage response + */ + private handleUsageResponse(usageResponse: GmmDistanceUsage): void { + this.spinnerService.remove(); + const parameters = usageResponse.response.parameters; + const values = usageFormValues(parameters); + + this.formGroup.patchValue({ + ...values, + }); + + const serviceCallInfo: ServiceCallInfo = { + ...this.state().serviceCallInfo, + usage: [this.serviceUrl], + }; + + this.updateState({ + serviceCallInfo, + usageResponse, + }); + + this.spinnerService.remove(); + } + + /** + * Add validators to form controls. + * + * @param form The form group + */ + private validators(form: FormGroupControls<ControlPanelForm>): void { + const required = (control: AbstractControl) => Validators.required(control); + + form.controls.Mw.addValidators(required); + form.controls.dip.addValidators(required); + form.controls.gmmSource.addValidators(required); + form.controls.imt.addValidators(required); + form.controls.multiSelectableParam.addValidators(required); + form.controls.vs30.addValidators(required); + form.controls.width.addValidators(required); + form.controls.zTor.addValidators(required); } } diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.reducer.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.reducer.ts deleted file mode 100644 index 386e34c782dddfc91e902d02ed7d44c29e45b1d8..0000000000000000000000000000000000000000 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.reducer.ts +++ /dev/null @@ -1,282 +0,0 @@ -import {GmmFormControlIds, gmmUtils} from '@ghsc/nshmp-lib-ng/gmm'; -import {nshmpUtils} from '@ghsc/nshmp-lib-ng/nshmp'; -import {createFeature, createReducer, on} from '@ngrx/store'; -import { - box, - createFormGroupState, - disable, - enable, - onNgrxForms, - onNgrxFormsAction, - setValue, - SetValueAction, - unbox, - updateGroup, - validate, - wrapReducerWithFormStateUpdate, -} from 'ngrx-forms'; -import {required} from 'ngrx-forms/validation'; -import {environment} from 'projects/nshmp-apps/src/environments/environment'; -import { - onPlotRedraw, - onPlots, - onPlotSettingsForm, - onResetSetting, -} from 'projects/nshmp-apps/src/shared/state/shared'; - -import {createPlots} from '../utils/response-handler.utils'; -import {appActions} from './app.actions'; -import { - AppState, - CONTROL_PANEL_FORM_ID, - ControlPanelForm, - defaultFormValues, - defaultPlots, - initialState, - PlotSettingsId, -} from './app.state'; - -/** Form state key ids */ -const formKeys = { - controlPanel: { - dip: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.DIP}`, - gmmSource: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.GMM_SOURCE}`, - imt: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.IMT}`, - multiSelectable: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.MULTI_SELECTABLE_PARAM}`, - showEpistemicUncertainty: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.SHOW_EPISTEMIC_UNCERTAINTY}`, - width: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.WIDTH}`, - zTop: `${CONTROL_PANEL_FORM_ID}.${GmmFormControlIds.ZTOR}`, - }, -}; - -const baseUrl = environment.webServices.data.url; -const endpoint = environment.webServices.data.services.gmmHwFw; -const serviceUrl = `${baseUrl}${endpoint}`; - -export const hangingWallEffectsAppFeature = createFeature({ - name: 'hanginWallEffectsApp', - reducer: createReducer( - // Initial state - initialState(), - // On NGRX forms - onNgrxForms(), - // Handle NGRX form changes - onNgrxFormsAction(SetValueAction, (state, action) => - onFormAction(state, action) - ), - // Handle initialization - on(appActions.init, state => ({ - ...state, - plots: createPlots(state), - })), - // On plots action - on(appActions.plots, (state, {plots}) => onPlots(state, plots)), - // On plot redraw action - on(appActions.plotRedraw, state => onPlotRedraw(state)), - // Reset the control panel - on(appActions.resetControlPanel, state => { - state = { - ...state, - controlPanelForm: createFormGroupState<ControlPanelForm>( - CONTROL_PANEL_FORM_ID, - defaultFormValues(state.usageResponse.response.parameters) - ), - plots: initialState().plots, - serviceCallInfo: initialState().serviceCallInfo, - serviceResponses: initialState().serviceResponses, - }; - - return { - ...state, - serviceCallInfo: gmmUtils.serviceCallInfo({ - multiSelectableParam: - state.controlPanelForm.value.multiSelectableParam, - serviceName: state.serviceCallInfo.serviceName, - serviceResponses: state.serviceResponses, - serviceUrl, - values: state.controlPanelForm.value, - }), - }; - }), - /* On reset plot settings action */ - on(appActions.resetSettings, state => { - return onResetSetting(state, defaultPlots()); - }), - // On service call info action - on(appActions.serviceCallInfo, (state, {serviceCallInfo}) => ({ - ...state, - serviceCallInfo: { - ...state.serviceCallInfo, - ...serviceCallInfo, - }, - })), - // Set the service responses - on(appActions.serviceResponses, (state, {serviceResponses}) => { - const means = serviceResponses.map(s => s.response.means); - const hasLogicTree = gmmUtils.hasTree(means); - - state = { - ...state, - controlPanelForm: updateGroup<ControlPanelForm>({ - showEpistemicUncertainty: hasLogicTree ? enable : disable, - })(state.controlPanelForm), - serviceResponses, - }; - - return { - ...state, - serviceCallInfo: gmmUtils.serviceCallInfo({ - multiSelectableParam: - state.controlPanelForm.value.multiSelectableParam, - serviceName: state.serviceCallInfo.serviceName, - serviceResponses: state.serviceResponses, - serviceUrl, - values: state.controlPanelForm.value, - }), - }; - }), - // On usage response action - on(appActions.usageResponse, (state, {usageResponse}) => ({ - ...state, - usageResponse, - })) - ), -}); - -// Add validators -hangingWallEffectsAppFeature.reducer = wrapReducerWithFormStateUpdate( - hangingWallEffectsAppFeature.reducer, - state => state.controlPanelForm, - updateGroup<ControlPanelForm>({ - dip: nshmpUtils.validateBounds, - gmmSource: validate([required]), - imt: validate([required]), - multiSelectableParam: validate([required]), - Mw: nshmpUtils.validateBounds, - vs30: nshmpUtils.validateBounds, - width: nshmpUtils.validateBounds, - z1p0: nshmpUtils.validateBoundsOnly, - z2p5: nshmpUtils.validateBoundsOnly, - zSed: nshmpUtils.validateBoundsOnly, - zTor: nshmpUtils.validateBounds, - }) -); - -/** - * Handle form actions. - * - * @param state The current state - * @param action The action - */ -function onFormAction( - state: AppState, - action: SetValueAction<unknown> -): AppState { - const clearState: AppState = { - ...state, - controlPanelForm: updateGroup<ControlPanelForm>({ - showEpistemicUncertainty: control => disable(setValue(control, false)), - })(state.controlPanelForm), - serviceResponses: null, - }; - clearState.plots = createPlots(clearState); - - const serviceCallInfo = gmmUtils.serviceCallInfo({ - multiSelectableParam: - clearState.controlPanelForm.value.multiSelectableParam, - serviceName: clearState.serviceCallInfo.serviceName, - serviceResponses: clearState.serviceResponses, - serviceUrl, - values: clearState.controlPanelForm.value, - }); - - switch (action.controlId) { - case formKeys.controlPanel.gmmSource: { - const gmm = unbox(state.controlPanelForm.value.gmmSource); - if ( - state.usageResponse && - state.controlPanelForm.controls.gmmSource && - gmm.length > 0 - ) { - const supportedImts = gmmUtils.getSupportedImts( - gmm, - state.usageResponse - ); - - const controlPanelForm = gmmUtils.onGmmChange( - clearState.controlPanelForm, - supportedImts - ); - - return { - ...clearState, - controlPanelForm, - serviceCallInfo, - supportedImts, - }; - } - return { - ...clearState, - serviceCallInfo, - }; - } - case formKeys.controlPanel.multiSelectable: { - let form = {...clearState.controlPanelForm}; - const parameters = clearState.usageResponse?.response?.parameters || null; - - if (form.value.multiSelectableParam === GmmFormControlIds.MW) { - form = updateGroup<ControlPanelForm>({ - gmmSource: gmm => setValue(gmm, box([])), - Mw: Mw => setValue(Mw, (parameters.Mw.value as number) || null), - MwMulti: Mw => setValue(Mw, box([])), - })(state.controlPanelForm); - } else if (form.value.multiSelectableParam === GmmFormControlIds.VS30) { - form = updateGroup<ControlPanelForm>({ - gmmSource: gmm => setValue(gmm, box([])), - vs30: vs30 => - setValue(vs30, (parameters.vs30.value as number) || null), - vs30Multi: vs30 => setValue(vs30, box([])), - })(state.controlPanelForm); - } - - return { - ...clearState, - controlPanelForm: form, - serviceCallInfo, - }; - } - case formKeys.controlPanel.showEpistemicUncertainty: { - return { - ...state, - plots: createPlots(state), - }; - } - case formKeys.controlPanel.dip: - case formKeys.controlPanel.width: - case formKeys.controlPanel.zTop: { - return { - ...state, - plots: createPlots(state), - serviceCallInfo, - }; - } - case formKeys.controlPanel.imt: { - return { - ...clearState, - serviceCallInfo, - }; - } - default: { - if ( - Object.values(PlotSettingsId).some(id => action.controlId.includes(id)) - ) { - return onPlotSettingsForm(state, action); - } else { - return { - ...clearState, - serviceCallInfo, - }; - } - } - } -} diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts index bad7120487b405ebff6cdd1b0f741dfc1e638362..b34d5acb272ee646ce4601ab29d98d28a8507ae1 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.state.ts @@ -1,15 +1,15 @@ import { GmmFormControlIds, GmmImtFormControls, - GmmSource, -} from '@ghsc/nshmp-lib-ng/gmm'; -import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp'; + gmmUtils, +} from '@ghsc/nshmp-lib-no-ngrx/gmm'; +import {ServiceCallInfo} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; import { NshmpPlot, NshmpPlotSettings, PlotOptions, plotUtils, -} from '@ghsc/nshmp-lib-ng/plot'; +} from '@ghsc/nshmp-lib-no-ngrx/plot'; import { GmmDistanceResponse, GmmDistanceUsage, @@ -17,18 +17,6 @@ import { GmmGroupType, } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services'; import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import { - box, - createFormGroupState, - disable, - FormGroupState, - updateGroup, -} from 'ngrx-forms'; -import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared'; - -/** Control panel form id */ -export const CONTROL_PANEL_FORM_ID = - 'ngrx-forms [Hanging Wall Effects Control Form]'; /** * Plot ids. @@ -38,20 +26,11 @@ export enum Plots { GROUND_MOTION = 'GROUND_MOTIONS', } -/** - * Plot settings ids. - */ -export enum PlotSettingsId { - FAULT = 'ngrx-forms [Fault Settings]', - GROUND_MOTION = 'ngrx-forms [Ground Motion Settings]', -} - /** * GMM Hanging Wall Effects NGRX state. */ -export interface AppState extends SharedAppState { - /** Control panel form field state */ - controlPanelForm: FormGroupState<ControlPanelForm>; +export interface AppState { + plots: Map<string, NshmpPlot>; /** Service call info */ serviceCallInfo: ServiceCallInfo; /** GMM service response */ @@ -72,45 +51,16 @@ export interface ControlPanelForm extends GmmImtFormControls { rMin: number; } -/** - * Initial state for the control panel, - */ -export function controlPanelFormInitialState(): FormGroupState<ControlPanelForm> { - const state = createFormGroupState<ControlPanelForm>(CONTROL_PANEL_FORM_ID, { - dip: null, - gmmGroupType: GmmGroupType.ACTIVE_CRUST, - gmmSource: box([] as GmmSource[]), - imt: 'default', - multiSelectableParam: GmmFormControlIds.GMM, - Mw: null, - MwMulti: box([] as number[]), - rMax: 100, - rMin: -20, - showEpistemicUncertainty: false, - vs30: null, - vs30Multi: box([] as number[]), - width: null, - z1p0: null, - z2p5: null, - zSed: null, - zTor: null, - }); - - return updateGroup<ControlPanelForm>({ - showEpistemicUncertainty: disable, - })(state); -} - /** * Returns the default form values based on the usage. * * @param parameters The response parameters */ -export function defaultFormValues( +export function usageFormValues( parameters: GmmDistanceUsageParameters ): ControlPanelForm { return { - ...controlPanelFormInitialState().value, + ...defaultFormValues(), dip: parameters.dip.value as number, Mw: parameters.Mw.value as number, vs30: parameters.vs30.value as number, @@ -121,65 +71,47 @@ export function defaultFormValues( }; } +/** + * Returns the default form values. + */ +export function defaultFormValues(): ControlPanelForm { + return { + dip: null, + gmmGroupType: GmmGroupType.ACTIVE_CRUST, + gmmSource: [], + imt: gmmUtils.imtPlaceHolder().value, + multiSelectableParam: GmmFormControlIds.GMM, + Mw: null, + MwMulti: [], + rMax: 100, + rMin: -20, + showEpistemicUncertainty: false, + vs30: null, + vs30Multi: [], + width: null, + z1p0: null, + z2p5: null, + zSed: null, + zTor: null, + }; +} + /** * Returns the default plots. */ export function defaultPlots(): Map<string, NshmpPlot> { const plots = new Map<string, NshmpPlot>(); - const groundMotionPlotData = plotUtils.defaultPlot({ - id: 'ground-motion-curves', - mobileOptions: { - ...groundMotionPlotOptions, - layout: { - ...groundMotionPlotOptions.layout, - aspectRatio: '4:3', - }, - }, - options: groundMotionPlotOptions, - title: 'Hanging Wall Effects', - xLabel: 'Distance (km)', - yLabel: 'Median ground motion (g)', - }); - plots.set(Plots.GROUND_MOTION, { label: 'Hanging Wall Effects', plotData: groundMotionPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.GROUND_MOTION, - { - config: groundMotionPlotData.config, - layout: plotUtils.plotlyLayoutToSettings(groundMotionPlotData.layout), - } - ), - }); - - const faultPlotData = plotUtils.defaultPlot({ - id: 'fault', - mobileOptions: { - ...faultPlotOptions, - layout: { - ...faultPlotOptions.layout, - aspectRatio: '4:3', - }, - }, - options: faultPlotOptions, - panelBreakpoint: 660 * 0.6, - title: '', - xLabel: 'Distance (km)', - yLabel: 'Depth (km)', + settingsForm: plotUtils.plotSettingsToFormGroup(groundMotionSettingsForm), }); plots.set(Plots.FAULT, { - label: 'Fault', + label: 'Faults', plotData: faultPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.FAULT, - { - config: faultPlotData.config, - layout: plotUtils.plotlyLayoutToSettings(faultPlotData.layout), - } - ), + settingsForm: plotUtils.plotSettingsToFormGroup(faultSettingsForm), }); return plots; @@ -190,14 +122,13 @@ export function defaultPlots(): Map<string, NshmpPlot> { */ export function initialState(): AppState { return { - controlPanelForm: controlPanelFormInitialState(), plots: defaultPlots(), serviceCallInfo: { serviceCalls: [], serviceName: 'Ground Motion vs. Distance', - usage: null, + usage: [], }, - serviceResponses: null, + serviceResponses: [], supportedImts: [], usageResponse: null, }; @@ -245,3 +176,44 @@ const faultPlotOptions: PlotOptions = { }, }, }; + +const groundMotionPlotData = plotUtils.defaultPlot({ + id: 'ground-motions', + mobileOptions: { + ...groundMotionPlotOptions, + layout: { + ...groundMotionPlotOptions.layout, + aspectRatio: '4:3', + }, + }, + options: groundMotionPlotOptions, + title: 'Hanging Wall Effects', + xLabel: 'Distance (km)', + yLabel: 'Median ground motion (g)', +}); + +const groundMotionSettingsForm: NshmpPlotSettings = { + config: groundMotionPlotData.config, + layout: plotUtils.plotlyLayoutToSettings(groundMotionPlotData.layout), +}; + +const faultPlotData = plotUtils.defaultPlot({ + id: 'fault-id', + mobileOptions: { + ...faultPlotOptions, + layout: { + ...faultPlotOptions.layout, + aspectRatio: '4:3', + }, + }, + options: faultPlotOptions, + panelBreakpoint: 660 * 0.6, + title: '', + xLabel: 'Distance (km)', + yLabel: 'Depth (km)', +}); + +const faultSettingsForm: NshmpPlotSettings = { + config: faultPlotData.config, + layout: plotUtils.plotlyLayoutToSettings(faultPlotData.layout), +}; diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts index 3ec535ff06c73218a39b9f50579ebd8b597498d3..03912b07fd7ba8249f146eef144db570ef004d52 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/utils/response-handler.utils.ts @@ -1,5 +1,6 @@ -import {gmmUtils} from '@ghsc/nshmp-lib-ng/gmm'; -import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot'; +import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm'; +import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; import {XySequence} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/data'; import {Imt, imtToString} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm'; import {PlotlyPlot} from '@ghsc/nshmp-utils-ts/libs/plotly'; @@ -17,16 +18,27 @@ import { * * @param state The application state */ -export function createPlots(state: AppState): Map<string, NshmpPlot> { +export function createPlots( + state: AppState, + form: FormGroupControls<ControlPanelForm> +): Map<string, NshmpPlot> { const plots = new Map<string, NshmpPlot>(); const groundMotionPlot = state.plots.get(Plots.GROUND_MOTION); const faultPlot = state.plots.get(Plots.FAULT); - const formValues = state.controlPanelForm.value; + const formValues = form.getRawValue(); if (state.serviceResponses !== null && state.serviceResponses?.length !== 0) { const hoverTemplate = '%{x} km, %{y} AFE'; const title = `Hanging Wall Effects: ${imtToString(formValues.imt as Imt)}`; + groundMotionPlot.settingsForm.patchValue({ + layout: { + title: { + text: title, + }, + }, + }); + const responses: gmmUtils.GmmResponse<XySequence, number[]>[] = state.serviceResponses.map(serviceResponse => ({ input: serviceResponse.request.input,