diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.html index cc96faf4863cc775939491acf27205571d0e7dd1..bc7ed586bdd1d2a93cfd116180b1f526570d66d5 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.html @@ -18,12 +18,12 @@ <!-- Provisional model warning --> <nshmp-lib-ng-hazard-provisional-model - [name]="(facade.usageModelA$ | async)?.response?.model?.name" + [name]="facade.usageModelA()?.response?.model?.name" /> <!-- Provisional model warning --> <nshmp-lib-ng-hazard-provisional-model - [name]="(facade.usageModelB$ | async)?.response?.model?.name" + [name]="facade.usageModelB()?.response?.model?.name" /> <!-- About page --> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.spec.ts index ed7e68cdb2a13134d56a4082a7956654aa156e18..9809620ec6eab3096234083cb5557222b12b1745 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/app.component.spec.ts @@ -2,7 +2,6 @@ import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {provideNoopAnimations} from '@angular/platform-browser/animations'; import {provideRouter} from '@angular/router'; -import {provideMockStore} from '@ngrx/store/testing'; import {AppComponent} from './app.component'; import {AboutComponent} from './components/about/about.component'; @@ -34,7 +33,6 @@ describe('AppComponent', () => { ParameterSummaryComponent, ], providers: [ - provideMockStore({initialState: {}}), provideHttpClient(), provideNoopAnimations(), provideRouter([]), diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.html index 66789e92d33da64912291468d6bb9eccfcbc8468..cc0344672b48a9bc32f16ed24f197f5583b75f03 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.html @@ -7,17 +7,14 @@ </mat-tab> <!-- Data tab --> - <mat-tab label="Hazard Curve Data" [disabled]="(hasData$ | async) === false"> + <mat-tab label="Hazard Curve Data" [disabled]="hasData() === false"> <ng-template matTabContent> <app-hazard-data /> </ng-template> </mat-tab> <!-- Spectra tab --> - <mat-tab - label="Response Spectrum Data" - [disabled]="(hasData$ | async) === false" - > + <mat-tab label="Response Spectrum Data" [disabled]="hasData() === false"> <ng-template matTabContent> <app-spectra-data /> </ng-template> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.spec.ts index 3c7ea105abe149d229ee4159046bfe9eae280029..a02bce78bf9b9f4a786db9b0bb7913fb4daadfda 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.spec.ts @@ -1,6 +1,7 @@ +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 {provideRouter} from '@angular/router'; import {ContentComponent} from './content.component'; @@ -12,8 +13,9 @@ describe('ContentComponent', () => { await TestBed.configureTestingModule({ imports: [ContentComponent], providers: [ - provideMockStore({initialState: {}}), provideNoopAnimations(), + provideHttpClient(), + provideRouter([]), ], }).compileComponents(); diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.ts index 895cf79bb35adcb91fcf64d540b15d810474582d..d66be9f4bbe2c02e7ad7bb2621c82e7e0afd7161 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/content/content.component.ts @@ -1,7 +1,6 @@ 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 {map} from 'rxjs'; import {AppFacade} from '../../state/app.facade'; import {HazardDataComponent} from '../hazard-data/hazard-data.component'; @@ -31,13 +30,14 @@ import {SpectraDataComponent} from '../spectra-data/spectra-data.component'; }) export class ContentComponent { /** Whether there is data */ - hasData$ = this.facade.serviceResponses$.pipe( - map( - models => - models.modelA?.hazardResponse !== undefined && - models.modelB?.hazardResponse !== undefined - ) - ); + hasData = computed(() => { + const {modelA, modelB} = this.facade.serviceResponses(); + + return ( + modelA?.hazardResponse !== undefined && + modelB?.hazardResponse !== undefined + ); + }); constructor(private facade: AppFacade) {} } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.html index 3bf9b85de0912e3c6704cfd4307b8cf381e37220..a1a3b1e134375a2639fbd258fc3d0b4f027bef89 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.html @@ -1,100 +1,95 @@ <!-- Control Panel --> -@if (formState$ | async; as formState) { - <form - class="height-full overflow-auto" - [ngrxFormState]="formState" - (submit)="onSubmit()" - > - <!-- Model --> - <nshmp-lib-ng-hazard-model-form - [controlState]="formState?.controls?.model" - [models]="facade.availableModels$ | async" - /> +<form + class="height-full overflow-auto" + [formGroup]="formGroup" + (submit)="onSubmit()" +> + <!-- Model --> + <nshmp-lib-no-ngrx-hazard-model-form + [modelControl]="formGroup.controls.model" + [models]="facade.availableModels()" + /> - <!-- Model Compare --> - <nshmp-lib-ng-hazard-model-form - [controlState]="formState?.controls?.modelCompare" - [models]="facade.comparableModels$ | async" - label="Model Compare" - placeHolderText="--- Select a Different Model ---" - /> + <!-- Model Compare --> + <nshmp-lib-no-ngrx-hazard-model-form + [modelControl]="formGroup.controls.modelCompare" + [models]="facade.comparableModels()" + label="Model Compare" + placeHolderText="--- Select a Different Model ---" + /> + @if (facade.usage()) { <!-- Latitude --> - <nshmp-lib-ng-hazard-location-form + <nshmp-lib-no-ngrx-hazard-location-form class="latitude-input" - [controlState]="formState?.controls?.latitude" + [locationControl]="formGroup.controls.latitude" + [modelControl]="formGroup.controls.model" label="Latitude" - [bounds]="(facade.usage$ | async)?.response?.latitude" + [bounds]="facade.usage().response.latitude" /> <!-- Longitude --> - <nshmp-lib-ng-hazard-location-form + <nshmp-lib-no-ngrx-hazard-location-form class="longitude-input" - [controlState]="formState?.controls?.longitude" + [locationControl]="formGroup.controls.longitude" + [modelControl]="formGroup.controls.model" label="Longitude" - [bounds]="(facade.usage$ | async)?.response?.longitude" + [bounds]="facade.usage().response.longitude" /> + } - <!-- Select site --> - <nshmp-lib-ng-map-select-site - [data]="selectSiteData$ | async" - (siteSelect)="facade.setLocation($event)" - /> + <!-- Select site --> + <nshmp-lib-no-ngrx-map-select-site + [data]="selectSiteData()" + (siteSelect)="facade.setLocation($event)" + /> - <!-- Imt --> - <mat-form-field class="grid-col-12"> - <mat-label>Intensity Measure Type</mat-label> - <mat-select [ngrxFormControlState]="formState?.controls?.imt"> - @for (imt of imts$ | async; track imt) { - <mat-option [value]="imt.value"> - {{ imt.display }} - </mat-option> - } - </mat-select> - </mat-form-field> + <!-- Imt --> + <mat-form-field class="grid-col-12"> + <mat-label>Intensity Measure Type</mat-label> + <mat-select [formControl]="formGroup.controls.imt"> + @for (imt of imts(); track imt) { + <mat-option [value]="imt.value"> + {{ imt.display }} + </mat-option> + } + </mat-select> + </mat-form-field> - <!-- Site Class --> - <div class="grid-row grid-gap-sm"> - <!-- Site classes --> - <nshmp-lib-ng-hazard-site-class-form - class="grid-col-7" - [controlState]="formState?.controls?.siteClass" - [siteClasses]="siteClasses$ | async" - formFieldClass="grid-col-12" + <!-- Site Class --> + <div class="grid-row grid-gap-sm"> + <!-- Site classes --> + @if (siteClasses()) { + <nshmp-lib-no-ngrx-hazard-site-class-form + [siteClassControl]="formGroup.controls.siteClass" + [siteClasses]="siteClasses()" + [modelControl]="formGroup.controls.model" + [vs30Control]="formGroup.controls.vs30" + [vs30Bounds]="facade.usage()?.response.vs30" /> + } + </div> - <!-- Vs30--> - <nshmp-lib-ng-hazard-vs30-form - class="grid-col-5" - [controlState]="formState?.controls?.vs30" - [bounds]="(facade.usage$ | async)?.vs30" - formFieldClass="grid-col-12" - /> - </div> + <!-- Return Period Controls --> + <nshmp-lib-no-ngrx-hazard-return-period-form + [returnPeriodControl]="formGroup.controls.returnPeriod" + [commonReturnPeriodControl]="formGroup.controls.commonReturnPeriods" + /> - <!-- Return Period Controls --> - <nshmp-lib-ng-hazard-return-period-form - [returnPeriodControlState]="formState?.controls?.returnPeriod" - [commonReturnPeriodControlState]=" - formState?.controls?.commonReturnPeriods - " - /> + <!-- Truncation --> + <nshmp-lib-no-ngrx-hazard-truncation-form + [truncationControl]="formGroup.controls.truncate" + /> - <!-- Truncation --> - <nshmp-lib-ng-hazard-truncation-form - [controlState]="formState?.controls?.truncate" - /> - - <!-- Max Direction --> - <nshmp-lib-ng-hazard-max-direction-form - [controlState]="formState?.controls?.maxDirection" - /> + <!-- Max Direction --> + <nshmp-lib-no-ngrx-hazard-max-direction-form + [maxDirectionControl]="formGroup.controls.maxDirection" + /> - <nshmp-lib-ng-control-panel-buttons - [plotDisabled]="formState?.isInvalid" - [serviceCallInfo]="facade.serviceCallInfo$ | async" - [resetDisabled]="formState?.isPristine" - (resetClick)="facade.resetControlPanel()" - /> - </form> -} + <nshmp-lib-no-ngrx-control-panel-buttons + [plotDisabled]="formGroup.invalid" + [serviceCallInfo]="facade.serviceCallInfo()" + [resetDisabled]="formGroup.pristine" + (resetClick)="facade.resetControlPanel()" + /> +</form> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.spec.ts index 17b38b87c795f14adb6a7164459f488b9a119b06..dcbdc85c343b5a6ab603b0bae813fc69662deeb7 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.spec.ts @@ -1,6 +1,7 @@ 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 {provideRouter} from '@angular/router'; import {ControlPanelComponent} from './control-panel.component'; @@ -11,7 +12,11 @@ describe('ControlPanelComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ControlPanelComponent], - providers: [provideMockStore({initialState: {}}), provideHttpClient()], + providers: [ + provideHttpClient(), + provideRouter([]), + provideNoopAnimations(), + ], }).compileComponents(); fixture = TestBed.createComponent(ControlPanelComponent); diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.ts index 1af22eebf94b378d005286ed8aa6ea3e60343e67..ce26f18abe6a1aec150d12cb8cbb0844ca7e6f4b 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/control-panel/control-panel.component.ts @@ -1,34 +1,35 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed, OnDestroy, OnInit, Signal} from '@angular/core'; +import {ReactiveFormsModule} from '@angular/forms'; import {MatOption} from '@angular/material/core'; -import {MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatError, MatFormField, MatLabel} from '@angular/material/form-field'; +import {MatInput} from '@angular/material/input'; import {MatSelect} from '@angular/material/select'; import { - hazardUtils, NshmpLibNgHazardLocationFormComponent, NshmpLibNgHazardMaxDirectionFormComponent, NshmpLibNgHazardModelFormComponent, NshmpLibNgHazardReturnPeriodFormComponent, NshmpLibNgHazardSiteClassFormComponent, NshmpLibNgHazardTruncationFormComponent, - NshmpLibNgHazardVs30FormComponent, -} from '@ghsc/nshmp-lib-ng/hazard'; +} from '@ghsc/nshmp-lib-no-ngrx/hazard'; import { NshmpLibNgMapSelectSiteComponent, SelectSiteDialogData, -} from '@ghsc/nshmp-lib-ng/map'; +} from '@ghsc/nshmp-lib-no-ngrx/map'; import { NshmpLibNgControlPanelButtonsComponent, - NshmpNgrxFormsModule, NshmpService, nshmpUtils, RETURN_PERIOD_BOUNDS, RETURN_PERIODS, -} from '@ghsc/nshmp-lib-ng/nshmp'; +} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm'; import {environment} from 'projects/nshmp-apps/src/environments/environment'; -import {combineLatest, map, Observable} from 'rxjs'; +import {Subscription} from 'rxjs'; import {AppFacade} from '../../state/app.facade'; +import {combineUsages} from '../../utils/response-handler.utils'; /** * Control panel with form fields to call dynamic hazard services. @@ -40,23 +41,24 @@ import {AppFacade} from '../../state/app.facade'; MatSelect, MatOption, AsyncPipe, + MatInput, NshmpLibNgHazardModelFormComponent, NshmpLibNgHazardLocationFormComponent, NshmpLibNgMapSelectSiteComponent, NshmpLibNgHazardSiteClassFormComponent, - NshmpLibNgHazardVs30FormComponent, NshmpLibNgHazardReturnPeriodFormComponent, NshmpLibNgHazardTruncationFormComponent, NshmpLibNgHazardMaxDirectionFormComponent, NshmpLibNgControlPanelButtonsComponent, - NshmpNgrxFormsModule, + ReactiveFormsModule, + MatError, ], 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 { /** Max and min bounds for return periods */ returnPeriodBounds = RETURN_PERIOD_BOUNDS; /** List of return periods */ @@ -65,45 +67,92 @@ export class ControlPanelComponent { siteClassPlaceHolder = nshmpUtils.selectPlaceHolder(); /** Form field state */ - formState$ = this.facade.controlPanelForm$; + formGroup = this.facade.formGroup; /** List of available IMTs */ - imts$ = this.facade.usage$.pipe(map(usage => usage?.response?.model?.imts)); + imts = computed(() => this.facade.usage()?.response?.model?.imts); /** Data for select site component */ - selectSiteData$: Observable<SelectSiteDialogData> = combineLatest([ - this.facade.usage$, - this.facade.controlPanelForm$, - this.facade.nshmService$, - ]).pipe( - map(([usage, form, nshmService]) => { - if (usage === null || nshmService === undefined) { - return; - } - - const services = - environment.webServices.nshmpHazWs.services.curveServices; - - return { - mapUrl: `${nshmService.url}${services.map}?raw=true`, - nshm: form.value.model, - sitesUrl: `${nshmService.url}${services.sites}?raw=true`, - }; - }) - ); + selectSiteData: Signal<SelectSiteDialogData> = computed(() => { + const usage = this.facade.usage(); + const nshmService = this.facade.nshmService(); + + if (usage === null || nshmService === undefined) { + return; + } + + const services = environment.webServices.nshmpHazWs.services.curveServices; + + return { + mapUrl: `${nshmService.url}${services.map}?raw=true`, + nshm: this.facade.formGroup.getRawValue().model, + sitesUrl: `${nshmService.url}${services.sites}?raw=true`, + }; + }); /** List of site class `Parameter`s */ - siteClasses$ = this.facade.usage$.pipe( - map(usage => - hazardUtils.siteClassesToParameters(usage?.response?.model?.siteClasses) - ) + siteClasses = computed( + () => this.facade.usage()?.response?.model.siteClasses ); + private imtSubscription = new Subscription(); + private maxDirectionSubscription = new Subscription(); + private modelCompareSubscription = new Subscription(); + private modelSubscription = new Subscription(); + private returnPeriodSubscription = new Subscription(); + private truncateSubscription = new Subscription(); + private vs30Subscription = new Subscription(); + constructor( public facade: AppFacade, private nshmpService: NshmpService ) {} + ngOnInit(): void { + this.modelSubscription = + this.formGroup.controls.model.valueChanges.subscribe(() => { + this.facade.onModelChange(); + }); + + this.modelCompareSubscription = + this.formGroup.controls.modelCompare.valueChanges.subscribe( + modelCompare => this.onModelCompare(modelCompare) + ); + + this.imtSubscription = this.formGroup.controls.imt.valueChanges.subscribe( + () => this.facade.createPlots() + ); + + this.maxDirectionSubscription = + this.formGroup.controls.maxDirection.valueChanges.subscribe(() => + this.facade.createPlots() + ); + + this.returnPeriodSubscription = + this.formGroup.controls.returnPeriod.valueChanges.subscribe(() => + this.facade.createPlots() + ); + + this.truncateSubscription = + this.formGroup.controls.truncate.valueChanges.subscribe(() => + this.facade.createPlots() + ); + + this.vs30Subscription = this.formGroup.controls.vs30.valueChanges.subscribe( + () => this.facade.resetState() + ); + } + + ngOnDestroy(): void { + this.imtSubscription.unsubscribe(); + this.maxDirectionSubscription.unsubscribe(); + this.modelCompareSubscription.unsubscribe(); + this.modelSubscription.unsubscribe(); + this.returnPeriodSubscription.unsubscribe(); + this.truncateSubscription.unsubscribe(); + this.vs30Subscription.unsubscribe(); + } + /** * On form submit. */ @@ -111,4 +160,16 @@ export class ControlPanelComponent { this.facade.callServices(); this.nshmpService.selectPlotControl(); } + + private onModelCompare(modelCompare: NshmId): void { + if (modelCompare === null) { + this.facade.onModelChange(); + } else { + const usages = [this.facade.usageModelA(), this.facade.usageModelB()]; + + this.facade.updateState({ + combinedUsage: combineUsages(usages), + }); + } + } } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.html index 16a791208cc5f59fc2c18df32a23339e39ed7e56..786dd8646e3c858c78d961d5fa37a3d4b7e3c287 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.html @@ -1,20 +1,20 @@ <div class="grid-container-widescreen-xl margin-y-3"> <mat-accordion multi> <app-table-data-panel - [filename]="hazardFilename$ | async" - [tableData]="hazardTableData$ | async" + [filename]="hazardFilename" + [tableData]="hazardTableData()" panelTitle="Hazard Curves: Total" /> <app-table-data-panel - [filename]="hazardDiffFilename$ | async" - [tableData]="hazardDiffTableData$ | async" + [filename]="hazardDiffFilename" + [tableData]="hazardDiffTableData()" panelTitle="Hazard Curves: Percent Difference" /> <app-table-data-panel - [filename]="hazardComponentsFilename$ | async" - [tableData]="hazardComponentsTableData$ | async" + [filename]="hazardComponentsFilename" + [tableData]="hazardComponentsTableData()" panelTitle="Hazard Curves: Components" /> </mat-accordion> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.spec.ts index b82e6f9c6da2cad266cbc141500beec917c4eb13..b59cd9f4b8400fbd7d3cd9694af7c77edc83425d 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.spec.ts @@ -1,7 +1,7 @@ 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 {provideRouter} from '@angular/router'; import {HazardDataComponent} from './hazard-data.component'; @@ -13,9 +13,9 @@ describe('HazardDataComponent', () => { await TestBed.configureTestingModule({ imports: [HazardDataComponent], providers: [ - provideMockStore({initialState: {}}), provideHttpClient(), provideNoopAnimations(), + provideRouter([]), ], }).compileComponents(); diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.ts index 7f1bc6575be0f83b2d0436d23805faddd0b6746d..623e587be6e87934a50c515428569d63fc3854ac 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/hazard-data/hazard-data.component.ts @@ -1,10 +1,9 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed} from '@angular/core'; import {MatAccordion} from '@angular/material/expansion'; -import {PlotTableDataParams, plotUtils} from '@ghsc/nshmp-lib-ng/plot'; +import {PlotTableDataParams, plotUtils} from '@ghsc/nshmp-lib-no-ngrx/plot'; import {Imt, imtToString} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm'; import {PlotData} from 'plotly.js'; -import {map} from 'rxjs'; import {AppFacade} from '../../state/app.facade'; import {Plots} from '../../state/app.state'; @@ -22,19 +21,15 @@ import {TableDataPanelComponent} from '../table-data-panel/table-data-panel.comp }) export class HazardDataComponent { /** Hazard components plot data */ - private hazardComponentsPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD_COMPONENTS)) - ); + private hazardComponentsPlot = this.facade + .plots() + .get(Plots.HAZARD_COMPONENTS); /** Hazard % diff plot data */ - private hazardDiffPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD_DIFFERENCES)) - ); + private hazardDiffPlot = this.facade.plots().get(Plots.HAZARD_DIFFERENCES); /** Hazard plot data */ - private hazardPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD)) - ); + private hazardPlot = this.facade.plots().get(Plots.HAZARD); /** Table data parameters */ private tableParams: PlotTableDataParams = { @@ -49,71 +44,57 @@ export class HazardDataComponent { }; /** Model values */ - private models$ = this.facade.controlPanelForm$.pipe( - map(form => `${form.value.model}-${form.value.modelCompare}`) - ); + private models = `${this.facade.formGroup.getRawValue().model}-${this.facade.formGroup.getRawValue().modelCompare}`; /** Filename for hazard components export CSV */ - hazardComponentsFilename$ = this.models$.pipe( - map(models => `hazard-components-${models}.csv`) - ); + hazardComponentsFilename = `hazard-components-${this.models}.csv`; /** Filename for hazard export CSV */ - hazardFilename$ = this.models$.pipe( - map(models => `hazard-compare-${models}.csv`) - ); + hazardFilename = `hazard-compare-${this.models}.csv`; /** Filename for hazard difference export CSV */ - hazardDiffFilename$ = this.models$.pipe( - map(models => `hazard-%-diff-${models}.csv`) - ); + hazardDiffFilename = `hazard-%-diff-${this.models}.csv`; /** Hazard table data */ - hazardTableData$ = this.hazardPlot$.pipe( - map(plot => { - const data = [...plot.plotData.data]; - // Remove return period from data - data.shift(); + hazardTableData = computed(() => { + const data = [...this.hazardPlot.plotData.data]; + // Remove return period from data + data.shift(); - return plotUtils.plotDataToTableData( - { - ...plot, - plotData: { - ...plot.plotData, - data, - }, + return plotUtils.plotDataToTableData( + { + ...this.hazardPlot, + plotData: { + ...this.hazardPlot.plotData, + data, }, - this.tableParams - ); - }) - ); + }, + this.tableParams + ); + }); /** Hazard difference table data */ - hazardDiffTableData$ = this.hazardDiffPlot$.pipe( - map(plot => { - return plotUtils.plotDataToTableData(plot, this.tableParams); - }) + hazardDiffTableData = computed(() => + plotUtils.plotDataToTableData(this.hazardDiffPlot, this.tableParams) ); /** Hazard components table data */ - hazardComponentsTableData$ = this.hazardComponentsPlot$.pipe( - map(plot => { - const data = [...plot.plotData.data]; - // Remove return period from data - data.shift(); + hazardComponentsTableData = computed(() => { + const data = [...this.hazardComponentsPlot.plotData.data]; + // Remove return period from data + data.shift(); - return plotUtils.plotDataToTableData( - { - ...plot, - plotData: { - ...plot.plotData, - data, - }, + return plotUtils.plotDataToTableData( + { + ...this.hazardComponentsPlot, + plotData: { + ...this.hazardComponentsPlot.plotData, + data, }, - this.tableParams - ); - }) - ); + }, + this.tableParams + ); + }); constructor(private facade: AppFacade) {} } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.html index 2b1c1476e38f3faa9f6d22a094303d7ba01c8565..4689f8b4e662c99e5375b0b8d8d6913720f0e80c 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.html @@ -1,69 +1,65 @@ -@if (form$ | async; as form) { - <div class="grid-row parameter-summary"> - <div class="grid-col-12 tablet:grid-col-6"> - <mat-list class="parameter-list"> - <mat-list-item> - <span class="parameter">Model</span>: - {{ toDisplay(form.value.model, facade.availableModels$ | async) }} - </mat-list-item> +<div class="grid-row parameter-summary"> + <div class="grid-col-12 tablet:grid-col-6"> + <mat-list class="parameter-list"> + <mat-list-item> + <span class="parameter">Model</span>: + {{ toDisplay(form.value.model, facade.availableModels()) }} + </mat-list-item> - <mat-list-item> - <span class="parameter">Model Compare</span>: - {{ - toDisplay(form.value.modelCompare, facade.availableModels$ | async) - }} - </mat-list-item> + <mat-list-item> + <span class="parameter">Model Compare</span>: + {{ toDisplay(form.value.modelCompare, facade.availableModels()) }} + </mat-list-item> - <mat-list-item> - <span class="parameter">Latitude</span>: {{ form.value.latitude }}° - </mat-list-item> + <mat-list-item> + <span class="parameter">Latitude</span>: {{ form.value.latitude }}° + </mat-list-item> - <mat-list-item> - <span class="parameter">Longitude</span>: {{ form.value.longitude }}° - </mat-list-item> + <mat-list-item> + <span class="parameter">Longitude</span>: {{ form.value.longitude }}° + </mat-list-item> - <mat-list-item> - @if (form.value.siteClass !== selectPlaceHolder.value) { - <span class="parameter">Site Class</span>: - {{ toDisplay(form.value.siteClass, siteClasses$ | async) }} - } @else { - <span class="parameter">Vs30</span>: {{ form.value.vs30 }} m/s - } - </mat-list-item> - </mat-list> - </div> + <mat-list-item> + @if (form.value.siteClass !== selectPlaceHolder.value) { + <span class="parameter">Site Class</span>: + {{ toDisplay(form.value.siteClass, siteClasses$ | async) }} + } @else { + <span class="parameter">Vs30</span>: {{ form.value.vs30 }} m/s + } + </mat-list-item> + </mat-list> + </div> - <div class="grid-col-12 tablet:grid-col-6 print-flex-basis-half"> - <mat-list class="parameter-list"> - <mat-list-item> - <span class="parameter">IMT</span>: - {{ form.value.imt }} - </mat-list-item> + <div class="grid-col-12 tablet:grid-col-6 print-flex-basis-half"> + <mat-list class="parameter-list"> + <mat-list-item> + <span class="parameter">IMT</span>: + {{ form.value.imt }} + </mat-list-item> - <mat-list-item> - <span class="parameter">Return Period</span>: - @if (form.value.commonReturnPeriod !== selectPlaceHolder.value) { - {{ returnPeriodName[form.value.returnPeriod] }} - } @else { - {{ form.value.returnPeriod }} yr - } - </mat-list-item> + <mat-list-item> + <span class="parameter">Return Period</span>: + <!-- @if (form.getRawValue().returnPeriod !== selectPlaceHolder.value) { + {{ returnPeriodName[form.value.returnPeriod] }} + } @else { + {{ form.value.returnPeriod }} yr + } --> + </mat-list-item> - <mat-list-item> - <span class="parameter">Source Type</span>: - {{ form.value.sourceType }} - </mat-list-item> + <mat-list-item> + <span class="parameter">Source Type</span>: + {{ form.value.sourceType }} + </mat-list-item> - <mat-list-item> - <span class="parameter">Truncate</span>: - {{ form.value.truncate === true ? 'On' : 'Off' }} - </mat-list-item> + <mat-list-item> + <span class="parameter">Truncate</span>: + {{ form.value.truncate === true ? 'On' : 'Off' }} + </mat-list-item> - <mat-list-item> - <span class="parameter">Max Direction</span>: - {{ form.value.maxDirection === true ? 'On' : 'Off' }} - </mat-list-item> - </mat-list> - </div> + <mat-list-item> + <span class="parameter">Max Direction</span>: + {{ form.value.maxDirection === true ? 'On' : 'Off' }} + </mat-list-item> + </mat-list> </div> -} +</div> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.spec.ts index 10bfea4db08555fa96249b81d15dd82cba66a7b4..3642d0d1e9b31b6f64d0c505b3ad5a45af58edad 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.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 {provideNoopAnimations} from '@angular/platform-browser/animations'; +import {provideRouter} from '@angular/router'; import {ParameterSummaryComponent} from './parameter-summary.component'; @@ -10,7 +12,11 @@ describe('ParameterSummaryComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ParameterSummaryComponent], - providers: [provideMockStore({initialState: {}})], + providers: [ + provideHttpClient(), + provideNoopAnimations(), + provideRouter([]), + ], }).compileComponents(); fixture = TestBed.createComponent(ParameterSummaryComponent); diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.ts index 9ed74b4711ccf5a74a39e008f1d0f86562a04261..17c3004aeefc4ca6c4a27250e027e4e89ed91e68 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/parameter-summary/parameter-summary.component.ts @@ -1,9 +1,8 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed} from '@angular/core'; import {MatList, MatListItem} from '@angular/material/list'; -import {hazardUtils} from '@ghsc/nshmp-lib-ng/hazard'; -import {nshmpUtils, returnPeriodAltName} from '@ghsc/nshmp-lib-ng/nshmp'; -import {map} from 'rxjs'; +import {hazardUtils} from '@ghsc/nshmp-lib-no-ngrx/hazard'; +import {nshmpUtils, returnPeriodAltName} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; import {AppFacade} from '../../state/app.facade'; @@ -25,14 +24,20 @@ export class ParameterSummaryComponent { toDisplay = nshmpUtils.parameterToDisplay; /** Form field state */ - form$ = this.facade.controlPanelForm$; + form = this.facade.formGroup; /** Site classes from usage as `Parameter`s */ - siteClasses$ = this.facade.usage$.pipe( - map(usage => - hazardUtils.siteClassesToParameters(usage?.response?.model?.siteClasses) - ) - ); + siteClasses = computed(() => { + const usage = this.facade.usage(); - constructor(private facade: AppFacade) {} + if (usage === undefined || usage === null) { + return []; + } + + return hazardUtils.siteClassesToParameters( + usage.response.model.siteClasses + ); + }); + + constructor(public facade: AppFacade) {} } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.html index 31daed70807f3d906515b047eb2163bae00dd805..8dfad0ad328f5207a5aa9ecface99524146296a5 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.html @@ -5,7 +5,10 @@ <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(panel.id, $event)" + /> </mat-expansion-panel> } </mat-accordion> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.spec.ts index 444a23c17df0dbd51a4a3bd80357501eb395a58c..29446243669399ceb07960abe2eede6a9c3289c2 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.spec.ts @@ -1,6 +1,7 @@ +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 {provideRouter} from '@angular/router'; import {PlotSettingsComponent} from './plot-settings.component'; @@ -12,8 +13,9 @@ describe('PlotSettingsComponent', () => { await TestBed.configureTestingModule({ imports: [PlotSettingsComponent], providers: [ - provideMockStore({initialState: {}}), + provideHttpClient(), provideNoopAnimations(), + provideRouter([]), ], }).compileComponents(); diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.ts index 6156988a8fd66e4d091b81b5be994a4ac53a6d74..4b262db0941e5e8d02d85422f22fed00cc5c2dda 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plot-settings/plot-settings.component.ts @@ -1,5 +1,5 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed} from '@angular/core'; import { MatAccordion, MatExpansionPanel, @@ -8,9 +8,9 @@ import { } from '@angular/material/expansion'; import { NshmpLibNgPlotSettingsComponent, - PlotSettingsPanel, -} from '@ghsc/nshmp-lib-ng/plot'; -import {map} from 'rxjs'; + NshmpPlot, +} from '@ghsc/nshmp-lib-no-ngrx/plot'; +import {PlotSettingsPanelId} from 'projects/nshmp-apps/src/shared/models/plot-settings-panel.model'; import {AppFacade} from '../../state/app.facade'; import {Plots} from '../../state/app.state'; @@ -34,62 +34,73 @@ import {Plots} from '../../state/app.state'; }) export class PlotSettingsComponent { /** Hazard plot data */ - private hazardPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD)) - ); + private hazardPlot = computed(() => this.facade.plots().get(Plots.HAZARD)); /** Hazard components plot data */ - private hazardComponentsPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD_COMPONENTS)) + private hazardComponentsPlot = computed(() => + this.facade.plots().get(Plots.HAZARD_COMPONENTS) ); /** Hazard % diff plot data */ - private hazardDiffPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD_DIFFERENCES)) + private hazardDiffPlot = computed(() => + this.facade.plots().get(Plots.HAZARD_DIFFERENCES) ); /** Spectra components plot data */ - private spectraComponentsPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.SPECTRUM_COMPONENTS)) + private spectraComponentsPlot = computed(() => + this.facade.plots().get(Plots.SPECTRUM_COMPONENTS) ); /** Spectra % diff plot data */ - private spectraDiffPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.SPECTRUM_DIFFERENCES)) + private spectraDiffPlot = computed(() => + this.facade.plots().get(Plots.SPECTRUM_DIFFERENCES) ); /** Spectra plot data */ - private spectraPlot$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.SPECTRUM)) - ); + private spectraPlot = computed(() => this.facade.plots().get(Plots.SPECTRUM)); /** The panels with plot settings */ - panels: PlotSettingsPanel[] = [ + panels: PlotSettingsPanelId[] = [ { - plot$: this.hazardPlot$, + id: Plots.HAZARD, + plot: this.hazardPlot, title: 'Hazard Curves Plot Settings', }, { - plot$: this.hazardDiffPlot$, + id: Plots.HAZARD_DIFFERENCES, + plot: this.hazardDiffPlot, title: 'Hazard % Difference Plot Settings', }, { - plot$: this.spectraPlot$, + id: Plots.SPECTRUM, + plot: this.spectraPlot, title: 'Spectra Plot Settings', }, { - plot$: this.spectraDiffPlot$, + id: Plots.SPECTRUM_DIFFERENCES, + plot: this.spectraDiffPlot, title: 'Spectra % Difference Plot Settings', }, { - plot$: this.hazardComponentsPlot$, + id: Plots.HAZARD_COMPONENTS, + plot: this.hazardComponentsPlot, title: 'Hazard Component Curves Plot Settings', }, { - plot$: this.spectraComponentsPlot$, + id: Plots.SPECTRUM_COMPONENTS, + plot: this.spectraComponentsPlot, title: 'Spectra Component Curves Plot Settings', }, ]; constructor(private facade: AppFacade) {} + + updatePlot(id: string, plot: NshmpPlot): void { + console.log(id, plot); + const plots = new Map(this.facade.state().plots); + plots.set(id, plot); + this.facade.updateState({ + plots, + }); + } } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.html index 184bd1f0660fb3885f5ec700efe7616fe15b1f06..256fa41fc70374bc3503878e0a0ebb16a660da5b 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.html @@ -1,5 +1,7 @@ -<nshmp-lib-ng-plots-container containerClass="grid-container-widescreen-xl"> - <nshmp-lib-ng-hazard-design-code-warning /> +<nshmp-lib-no-ngrx-plots-container + containerClass="grid-container-widescreen-xl" +> + <nshmp-lib-no-ngrx-hazard-design-code-warning /> <div class="grid-row"> @for (plot of plots; track plot) { @@ -16,13 +18,13 @@ <mat-panel-title>{{ plot.title }}</mat-panel-title> </mat-expansion-panel-header> <mat-divider /> - @if (plot.plotData$ | async; as plotData) { - <nshmp-lib-ng-plot [plot]="plotData" /> - } - @if (plot.diffPlotData$ | async; as diffPlotData) { + + <nshmp-lib-no-ngrx-plot [plot]="plot.plotData()" /> + + @if (plot.diffPlotData) { <div class="padding-top-4"> <mat-divider /> - <nshmp-lib-ng-plot [plot]="diffPlotData" /> + <nshmp-lib-no-ngrx-plot [plot]="plot.diffPlotData()" /> </div> } </mat-expansion-panel> @@ -49,9 +51,9 @@ </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> </div> </div> -</nshmp-lib-ng-plots-container> +</nshmp-lib-no-ngrx-plots-container> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.spec.ts index e562e2d316dbc6e5c6968468fe2ecbe0b704f44f..df5874f64cde103957e857959fb56fc80181bef3 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.spec.ts @@ -1,7 +1,7 @@ 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 {provideRouter} from '@angular/router'; import {PlotsComponent} from './plots.component'; @@ -13,7 +13,7 @@ describe('PlotsComponent', () => { await TestBed.configureTestingModule({ imports: [PlotsComponent], providers: [ - provideMockStore({initialState: {}}), + provideRouter([]), provideHttpClient(), provideNoopAnimations(), ], diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.ts index 206fc18f6edc1a9ee46040af196ffea67a1bf748..89210f5d8fdaab491f0a0c290b29197e9664897d 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/plots/plots.component.ts @@ -1,5 +1,5 @@ import {AsyncPipe, NgClass} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed, Signal} from '@angular/core'; import {MatDivider} from '@angular/material/divider'; import { MatAccordion, @@ -7,15 +7,15 @@ import { MatExpansionPanelHeader, MatExpansionPanelTitle, } from '@angular/material/expansion'; -import {NshmpLibNgHazardDesignCodeWarningComponent} from '@ghsc/nshmp-lib-ng/hazard'; -import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-ng/nshmp'; +import {NshmpLibNgHazardDesignCodeWarningComponent} from '@ghsc/nshmp-lib-no-ngrx/hazard'; +import {NshmpLibNgAppMetadataComponent} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; import { NshmpLibNgPlotComponent, NshmpLibNgPlotsContainerComponent, -} from '@ghsc/nshmp-lib-ng/plot'; +} from '@ghsc/nshmp-lib-no-ngrx/plot'; import {AppControlsService} from '@ghsc/nshmp-template'; import {PlotlyPlot} from '@ghsc/nshmp-utils-ts/libs/plotly'; -import {map, Observable} from 'rxjs'; +import {map} from 'rxjs'; import {AppFacade} from '../../state/app.facade'; import {Plots} from '../../state/app.state'; @@ -26,12 +26,12 @@ import {ParameterSummaryComponent} from '../parameter-summary/parameter-summary. */ interface PlotInfo { /** Main plot data */ - plotData$: Observable<PlotlyPlot>; + plotData: Signal<PlotlyPlot>; /** Expansion panel title */ title: string; /** % diff plot data */ - diffPlotData$?: Observable<PlotlyPlot>; + diffPlotData?: Signal<PlotlyPlot>; } /** @@ -58,34 +58,34 @@ interface PlotInfo { templateUrl: './plots.component.html', }) export class PlotsComponent { - /** Hazard components plot data */ - private hazardComponentsPlotData$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD_COMPONENTS)?.plotData) + /** Hazard plot data */ + private hazardPlotData = computed( + () => this.facade.plots().get(Plots.HAZARD).plotData ); - /** Hazard % diff plot data */ - private hazardDiffPlotData$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD_DIFFERENCES)?.plotData) + /** Hazard components plot data */ + private hazardComponentsPlotData = computed( + () => this.facade.plots().get(Plots.HAZARD_COMPONENTS).plotData ); - /** Hazard plot data */ - private hazardPlotData$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.HAZARD)?.plotData) + /** Hazard % diff plot data */ + private hazardDiffPlotData = computed( + () => this.facade.plots().get(Plots.HAZARD_DIFFERENCES).plotData ); /** Spectra components plot data */ - private spectraComponentsPlotData$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.SPECTRUM_COMPONENTS)?.plotData) + private spectraComponentsPlotData = computed( + () => this.facade.plots().get(Plots.SPECTRUM_COMPONENTS).plotData ); /** Spectra % diff plot data */ - private spectraDiffPlotData$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.SPECTRUM_DIFFERENCES)?.plotData) + private spectraDiffPlotData = computed( + () => this.facade.plots().get(Plots.SPECTRUM_DIFFERENCES).plotData ); /** Spectra plot data */ - private spectraPlotData$ = this.facade.plots$.pipe( - map(plots => plots.get(Plots.SPECTRUM)?.plotData) + private spectraPlotData = computed( + () => this.facade.plots().get(Plots.SPECTRUM).plotData ); /** Wheather both the control and settings panels are closed */ @@ -109,29 +109,27 @@ export class PlotsComponent { /** The plots */ plots: PlotInfo[] = [ { - diffPlotData$: this.hazardDiffPlotData$, - plotData$: this.hazardPlotData$, + diffPlotData: this.hazardDiffPlotData, + plotData: this.hazardPlotData, title: 'Hazard Curves', }, { - diffPlotData$: this.spectraDiffPlotData$, - plotData$: this.spectraPlotData$, + diffPlotData: this.spectraDiffPlotData, + plotData: this.spectraPlotData, title: 'Spectra', }, { - plotData$: this.hazardComponentsPlotData$, + plotData: this.hazardComponentsPlotData, title: 'Hazard Components', }, { - plotData$: this.spectraComponentsPlotData$, + plotData: this.spectraComponentsPlotData, title: 'Spectra Components', }, ]; /** Repo metadata */ - repositories$ = this.facade.usage$.pipe( - map(usage => usage?.metadata.repositories) - ); + repositories = computed(() => this.facade.usage()?.metadata.repositories); constructor( private facade: AppFacade, diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.html index dad4df7af0054e45762c5aac955114a5f4dbfadd..4926ad259f303a5802d370cf6228d361e9e4f444 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.html @@ -1,20 +1,20 @@ <div class="grid-container-widescreen-xl margin-y-3"> <mat-accordion multi> <app-table-data-panel - [filename]="spectraFilename$ | async" - [tableData]="spectraTotalTableData$ | async" + [filename]="spectraFilename" + [tableData]="spectraTotalTableData()" panelTitle="Response Spectra: Total" /> <app-table-data-panel - [filename]="spectraDiffFilename$ | async" - [tableData]="spectraDiffTableData$ | async" + [filename]="spectraDiffFilename" + [tableData]="spectraDiffTableData()" panelTitle="Response Spectra: Percent Difference" /> <app-table-data-panel - [filename]="spectraComponentsFilename$ | async" - [tableData]="spectraComponentsTableData$ | async" + [filename]="spectraComponentsFilename" + [tableData]="spectraComponentsTableData()" panelTitle="Response Spectra: Components" /> </mat-accordion> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.spec.ts index 03edf5f3627fa03199db0bec9e2b59b424959f42..5206c02f20730b21c94e1a5c77da2ba58785472d 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.spec.ts @@ -1,7 +1,7 @@ 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 {provideRouter} from '@angular/router'; import {SpectraDataComponent} from './spectra-data.component'; @@ -13,7 +13,7 @@ describe('SpectraDataComponent', () => { await TestBed.configureTestingModule({ imports: [SpectraDataComponent], providers: [ - provideMockStore({initialState: {}}), + provideRouter([]), provideHttpClient(), provideNoopAnimations(), ], diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.ts index c9192d6e93940b686dccf4468cd163b65989483e..12bb257303f592946db97e4ecd85662c51f6d026 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/spectra-data/spectra-data.component.ts @@ -1,14 +1,12 @@ import {AsyncPipe} from '@angular/common'; -import {Component} from '@angular/core'; +import {Component, computed, Signal} from '@angular/core'; import {MatAccordion} from '@angular/material/expansion'; -import {TableData} from '@ghsc/nshmp-lib-ng/nshmp'; +import {TableData} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; import { SourceType, sourceTypeToCapitalCase, } from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/model'; import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import equal from 'fast-deep-equal'; -import {combineLatest, map, Observable} from 'rxjs'; import {AppFacade} from '../../state/app.facade'; import { @@ -62,98 +60,91 @@ interface ToTableDataOptions { }) export class SpectraDataComponent { /** Model values */ - private models$ = this.facade.controlPanelForm$.pipe( - map(form => `${form.value.model}-${form.value.modelCompare}`) - ); + private models = `${this.facade.formGroup.getRawValue().model}-${this.facade.formGroup.getRawValue().modelCompare}`; /** Filename for hazard components export CSV */ - spectraComponentsFilename$ = this.models$.pipe( - map(models => `spectra-components-${models}.csv`) - ); + spectraComponentsFilename = `spectra-components-${this.models}.csv`; /** Filename for hazard difference export CSV */ - spectraDiffFilename$ = this.models$.pipe( - map(models => `spectra-%-diff-${models}.csv`) - ); + spectraDiffFilename = `spectra-%-diff-${this.models}.csv`; /** Filename for hazard export CSV */ - spectraFilename$ = this.models$.pipe( - map(models => `spectra-compare-${models}.csv`) - ); + spectraFilename = `spectra-compare-${this.models}.csv`; /** Table data for response spectra components */ - spectraComponentsTableData$ = combineLatest([ - this.facade.serviceResponses$, - this.facade.controlPanelForm$, - this.facade.availableModels$, - ]).pipe( - map(([serviceResponses, form, availableModels]) => { - const imts = this.spectraImtRow(serviceResponses); - - const spectraFilter = (spectra: Spectra) => - spectra.sourceType !== SourceType.TOTAL; - - return [ - imts, - ...this.serviceResponsesToTableData({ - availableModels, - name: (modelInfo: Parameter, spectra: Spectra) => - `${modelInfo.display} - ${sourceTypeToCapitalCase( - spectra.sourceType - )}`, - returnPeriod: form.value.returnPeriod, - serviceResponses, - spectraFilter, - }), - ]; - }) - ); + spectraComponentsTableData = computed(() => { + const serviceResponses = this.facade.serviceResponses(); + + if (serviceResponses.modelA === null || serviceResponses.modelB === null) { + return []; + } + + const imts = this.spectraImtRow(serviceResponses); + + const spectraFilter = (spectra: Spectra) => + spectra.sourceType !== SourceType.TOTAL; + + return [ + imts, + ...this.serviceResponsesToTableData({ + availableModels: this.facade.availableModels(), + name: (modelInfo: Parameter, spectra: Spectra) => + `${modelInfo.display} - ${sourceTypeToCapitalCase( + spectra.sourceType + )}`, + returnPeriod: this.facade.formGroup.getRawValue().returnPeriod, + serviceResponses: this.facade.serviceResponses(), + spectraFilter, + }), + ]; + }); /** Table data for response spectra difference */ - spectraDiffTableData$: Observable<TableData[]> = combineLatest([ - this.facade.serviceResponses$, - this.facade.controlPanelForm$, - ]).pipe( - map(([serviceResponses, form]) => { - const spectraDiff = spectraPercentDifference( - serviceResponses, - form.value.returnPeriod - ); - - return [ - this.spectraImtRow(serviceResponses), - { - td: spectraDiff.percentDifference, - th: 'Percent Difference', - }, - ]; - }) - ); + spectraDiffTableData: Signal<TableData[]> = computed(() => { + const serviceResponses = this.facade.serviceResponses(); + + if (serviceResponses.modelA === null || serviceResponses.modelB === null) { + return []; + } + + const spectraDiff = spectraPercentDifference( + serviceResponses, + this.facade.formGroup.getRawValue().returnPeriod + ); + + return [ + this.spectraImtRow(serviceResponses), + { + td: spectraDiff.percentDifference, + th: 'Percent Difference', + }, + ]; + }); /** Table data for response spectra */ - spectraTotalTableData$ = combineLatest([ - this.facade.serviceResponses$, - this.facade.controlPanelForm$, - this.facade.availableModels$, - ]).pipe( - map(([serviceResponses, form, availableModels]) => { - const imts = this.spectraImtRow(serviceResponses); - - const spectraFilter = (spectra: Spectra) => - spectra.sourceType === SourceType.TOTAL; - - return [ - imts, - ...this.serviceResponsesToTableData({ - availableModels, - name: (modelInfo: Parameter) => modelInfo.display, - returnPeriod: form.value.returnPeriod, - serviceResponses, - spectraFilter, - }), - ]; - }) - ); + spectraTotalTableData = computed(() => { + const serviceResponses = this.facade.serviceResponses(); + + if (serviceResponses.modelA === null || serviceResponses.modelB === null) { + return []; + } + + const imts = this.spectraImtRow(serviceResponses); + + const spectraFilter = (spectra: Spectra) => + spectra.sourceType === SourceType.TOTAL; + + return [ + imts, + ...this.serviceResponsesToTableData({ + availableModels: this.facade.availableModels(), + name: (modelInfo: Parameter) => modelInfo.display, + returnPeriod: this.facade.formGroup.getRawValue().returnPeriod, + serviceResponses, + spectraFilter, + }), + ]; + }); constructor(private facade: AppFacade) {} @@ -170,12 +161,11 @@ export class SpectraDataComponent { spectra => spectra.sourceType === SourceType.TOTAL ); - if (!equal(spectraA.responseSpectra.imts, spectraB.responseSpectra.imts)) { - throw new Error('Response spectra IMTs do not match'); - } + const imtsA = spectraA.responseSpectra.imts; + const imtsB = spectraB.responseSpectra.imts; return { - td: spectraA.responseSpectra.imts, + td: imtsA.length >= imtsB.length ? imtsA : imtsB, th: 'IMT', }; } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.html b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.html index b0b49ca7b35221d3a701444f50dcc4ce4e9a6846..d83e9cb1bac3cec10df3c384b1a32e174e51bc8f 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.html +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.html @@ -3,11 +3,11 @@ <mat-panel-title>{{ panelTitle }}</mat-panel-title> </mat-expansion-panel-header> - <nshmp-lib-ng-export-data-table + <nshmp-lib-no-ngrx-export-data-table [table]="tableData" [filename]="filename | lowercase" [buttonText]="buttonText" /> - <nshmp-lib-ng-data-table [table]="tableData" /> + <nshmp-lib-no-ngrx-data-table [table]="tableData" /> </mat-expansion-panel> diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.spec.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.spec.ts index 9f3bb55fb1641af9fca5736f4a189e397681f4c7..e55d98b55c4d156431cf20c42e74fb3e452327dc 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.spec.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.spec.ts @@ -1,6 +1,7 @@ import {provideHttpClient} from '@angular/common/http'; import {ComponentFixture, TestBed} from '@angular/core/testing'; import {provideNoopAnimations} from '@angular/platform-browser/animations'; +import {provideRouter} from '@angular/router'; import {TableDataPanelComponent} from './table-data-panel.component'; @@ -11,7 +12,11 @@ describe('TableDataPanelComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [TableDataPanelComponent], - providers: [provideHttpClient(), provideNoopAnimations()], + providers: [ + provideHttpClient(), + provideNoopAnimations(), + provideRouter([]), + ], }).compileComponents(); fixture = TestBed.createComponent(TableDataPanelComponent); diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.ts index 5a61dc77338d6e12ed34196a9a063ca33c9dd63e..43861892039a1f488c4c277018d0eb3a59611240 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/components/table-data-panel/table-data-panel.component.ts @@ -9,7 +9,7 @@ import { NshmpLibNgDataTableComponent, NshmpLibNgExportDataTableComponent, TableData, -} from '@ghsc/nshmp-lib-ng/nshmp'; +} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; /** * Expansion panel with data table and export. diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.actions.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.actions.ts deleted file mode 100644 index 5a060e853227a678d96131ff6800f7aef2685b73..0000000000000000000000000000000000000000 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.actions.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {ResponseSpectra} from '@ghsc/nshmp-lib-ng/hazard'; -import {HazardUsageResponse} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/hazard-service'; -import {NshmMetadata} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/nshm-service'; -import {Location} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/geo'; -import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {createActionGroup, emptyProps, props} from '@ngrx/store'; -import {sharedActions} from 'projects/nshmp-apps/src/shared/state/shared'; - -import {ServiceResponses} from './app.state'; - -/** - * Application NGRX actions - */ -export const appActions = createActionGroup({ - events: { - ...sharedActions, - /** Action for available NSHMs */ - 'Available Models': props<{models: Parameter[]}>(), - /** 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(), - /** Action for NSHM metadata */ - 'nshm Services': props<{nshmServices: NshmMetadata[]}>(), - /** Set the response spectra data action */ - 'Response Spectra': props<{responseSpectra: ResponseSpectra[]}>(), - /** Set the service response action */ - 'Service Responses': props<{serviceResponses: ServiceResponses}>(), - /** Set the site location action */ - 'Set Location': props<{location: Location}>(), - /** Set the usage responses action */ - 'Usage Responses': props<{ - usageResponses: Map<string, HazardUsageResponse>; - }>(), - }, - source: 'Development Dynamic Hazard Compare', -}); diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.effects.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.effects.ts deleted file mode 100644 index 8f4c7f8f60759a76a17a8c3fcaaa9783f6afba6b..0000000000000000000000000000000000000000 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.effects.ts +++ /dev/null @@ -1,474 +0,0 @@ -import {Injectable} from '@angular/core'; -import {ActivatedRoute, Router} from '@angular/router'; -import { - HazardFormControlIds, - HazardService, - hazardUtils, -} from '@ghsc/nshmp-lib-ng/hazard'; -import { - NshmpService, - ServiceCallInfo, - SpinnerService, -} from '@ghsc/nshmp-lib-ng/nshmp'; -import { - HazardCalcResponse, - HazardRequestMetadata, - HazardResponse, -} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/hazard-service'; -import {NshmMetadata} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/nshm-service'; -import {Imt, NehrpSiteClass} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm'; -import {sourceTypeFromPascalCase} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/model'; -import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm'; -import {Actions, createEffect, ofType} from '@ngrx/effects'; -import {concatLatestFrom} from '@ngrx/operators'; -import {Store} from '@ngrx/store'; -import {MarkAsDirtyAction, SetValueAction} from 'ngrx-forms'; -import {environment} from 'projects/nshmp-apps/src/environments/environment'; -import {devApps} from 'projects/nshmp-apps/src/shared/utils/applications.utils'; -import {catchError, exhaustMap, forkJoin, map, mergeMap, tap} from 'rxjs'; - -import {createPlots} from '../utils/response-handler.utils'; -import {appActions} from './app.actions'; -import {dynamicCompareAppFeature} from './app.reducer'; -import { - ControlForm, - controlPanelFormInitialState, - FORM_ID, - ServiceResponses, - Spectra, -} from './app.state'; - -/** - * URL query. - */ -interface Query { - imt: string; - latitude: string; - longitude: string; - maxDirection: string; - model: string; - modelCompare: string; - returnPeriod: string; - siteClass: string; - truncate: string; - vs30: string; -} - -/** - * NGRX effects for dynamic hazard compare application. - */ -@Injectable() -export class DynamicCompareAppEffects { - /** nshmp-haz-ws web config */ - nshmpHazWs = environment.webServices.nshmpHazWs; - /** Hazard endpoint */ - serviceEndpoint = this.nshmpHazWs.services.curveServices.hazard; - - /** - * Create the plot data from the service response. - */ - createPlots$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.serviceResponses), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectDynamicCompareAppState) - ), - map(([, state]) => { - const plots = createPlots(state); - this.spinnerService.remove(); - return appActions.plots({plots}); - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Call dynamic hazard services. - */ - callServices$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.callServices), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectDynamicCompareAppState) - ), - exhaustMap(([, state]) => { - this.spinnerService.show(SpinnerService.MESSAGE_SERVICE); - const form = state.controlPanelForm.value; - - const modelUrl = this.serviceCallUrl( - form.model, - form, - state.nshmServices - ); - - const modelCompareUrl = this.serviceCallUrl( - form.modelCompare, - form, - state.nshmServices - ); - - const serviceCallInfo: ServiceCallInfo = { - ...state.serviceCallInfo, - serviceCalls: [modelUrl, modelCompareUrl], - }; - - const calls = [modelUrl, modelCompareUrl].map(url => - this.nshmpService.callService$(url) - ); - - return forkJoin(calls).pipe( - mergeMap((hazardResponses: HazardCalcResponse[]) => { - if (hazardResponses.length !== 2) { - throw new Error('Does not contain two responses'); - } - - const modelA = hazardResponses[0]; - const modelB = hazardResponses[1]; - - const serviceResponses: ServiceResponses = { - modelA: { - hazardResponse: modelA, - model: form.model, - spectra: this.responseSpectra( - modelA.response.hazardCurves, - form - ), - }, - modelB: { - hazardResponse: modelB, - model: form.modelCompare, - spectra: this.responseSpectra( - modelB.response.hazardCurves, - form - ), - }, - }; - - return [ - appActions.serviceResponses({serviceResponses}), - appActions.serviceCallInfo({serviceCallInfo}), - ]; - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ); - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Initialize the app by getting all the usages from the available dynamic services. - */ - init$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.init), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectDynamicCompareAppState) - ), - exhaustMap(([, state]) => { - this.spinnerService.show(SpinnerService.MESSAGE_METADATA); - - return this.hazardService - .dynamicNshms$<HazardRequestMetadata>( - `${this.nshmpHazWs.url}${this.nshmpHazWs.services.nshms}`, - this.serviceEndpoint - ) - .pipe( - mergeMap(({models, nshmServices, usageResponses}) => { - this.spinnerService.remove(); - const serviceCallInfo: ServiceCallInfo = { - ...state.serviceCallInfo, - usage: nshmServices.map( - service => `${service.url}${this.serviceEndpoint}` - ), - }; - - return [ - appActions.nshmServices({nshmServices}), - appActions.usageResponses({usageResponses}), - appActions.serviceCallInfo({serviceCallInfo}), - appActions.availableModels({models}), - appActions.initialFormSet(), - ]; - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ); - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Throw error if no comparable models are found. - */ - noComparableModel$ = createEffect( - () => - this.actions$.pipe( - ofType(SetValueAction.TYPE), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectControlPanelForm) - ), - tap(([action, {value}]) => { - const setValueAction = action as SetValueAction<unknown>; - - if ( - setValueAction.controlId === - `${FORM_ID}.${HazardFormControlIds.MODEL}` && - value.modelCompare === null - ) { - this.nshmpService.throwError$( - new Error( - 'No available models to compare.<br>Select a different starting model.' - ) - ); - } - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ), - { - dispatch: false, - } - ); - - /** - * Check if form is valid from the url query parameters and call service. - */ - initialCallFromUrlQuery$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.initialCallFromQuery), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectControlPanelForm) - ), - mergeMap(([, form]) => { - if (form.isValid) { - this.nshmpService.selectPlotControl(); - return [new MarkAsDirtyAction(form.id), appActions.callServices()]; - } else if (form.value !== controlPanelFormInitialState().value) { - return [new MarkAsDirtyAction(form.id)]; - } else { - return []; - } - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Set the form values. - */ - initialFormSet$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.initialFormSet), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectControlPanelForm) - ), - mergeMap(([, form]) => { - const query = this.route.snapshot.queryParams as Query; - const defaultValues = controlPanelFormInitialState().value; - - const formValues: ControlForm = { - commonReturnPeriods: defaultValues.commonReturnPeriods, - imt: this.getImt(query.imt, defaultValues.imt), - latitude: query.latitude - ? Number.parseFloat(query.latitude) - : defaultValues.latitude, - longitude: query.longitude - ? Number.parseFloat(query.longitude) - : defaultValues.longitude, - maxDirection: - query.maxDirection !== undefined - ? (JSON.parse(query.maxDirection) as boolean) - : defaultValues.maxDirection, - model: this.getModel(query.model, defaultValues.model), - modelCompare: this.getModel( - query.modelCompare, - defaultValues.modelCompare - ), - returnPeriod: query.returnPeriod - ? Number.parseInt(query.returnPeriod, 10) - : defaultValues.returnPeriod, - siteClass: this.getSiteClass( - query.siteClass, - defaultValues.siteClass as NehrpSiteClass - ), - sourceType: null, - truncate: - query.truncate !== undefined - ? (JSON.parse(query.truncate) as boolean) - : defaultValues.truncate, - vs30: query.vs30 ? Number.parseFloat(query.vs30) : defaultValues.vs30, - }; - - const actions = hazardUtils.initialFormSetActions(form, formValues); - actions.push( - new SetValueAction( - form.controls.modelCompare.id, - formValues.modelCompare - ) - ); - actions.push(new SetValueAction(form.controls.imt.id, formValues.imt)); - actions.push( - new SetValueAction(form.controls.vs30.id, formValues.vs30) - ); - actions.push(appActions.initialCallFromQuery()); - return actions; - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Effect to set location form fields. - */ - setLocation$ = createEffect(() => - this.actions$.pipe( - ofType(appActions.setLocation), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectControlPanelForm) - ), - exhaustMap(([{location}, form]) => { - return [ - new SetValueAction(form.controls.latitude.id, location.latitude), - new MarkAsDirtyAction(form.controls.latitude.id), - new SetValueAction(form.controls.longitude.id, location.longitude), - new MarkAsDirtyAction(form.controls.longitude.id), - ]; - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ) - ); - - /** - * Update the URL query for settings - */ - updateUrl$ = createEffect( - () => - this.actions$.pipe( - ofType(SetValueAction.TYPE), - concatLatestFrom(() => - this.store.select(dynamicCompareAppFeature.selectControlPanelForm) - ), - map(([action, {value}]) => { - if ((action as SetValueAction<unknown>).controlId.includes(FORM_ID)) { - const query: Query = { - imt: value.imt, - latitude: value.latitude?.toString(), - longitude: value.longitude?.toString(), - maxDirection: String(value.maxDirection), - model: value.model, - modelCompare: value.modelCompare, - returnPeriod: value.returnPeriod?.toString(), - siteClass: value.siteClass, - truncate: String(value.truncate), - vs30: value.vs30?.toString(), - }; - - this.router - .navigate([devApps().hazard.dynamicCompare.routerLink], { - queryParams: { - ...query, - }, - }) - .catch((error: Error) => this.nshmpService.throwError$(error)); - } - }), - catchError((error: Error) => this.nshmpService.throwError$(error)) - ), - {dispatch: false} - ); - - constructor( - private actions$: Actions, - private hazardService: HazardService, - private nshmpService: NshmpService, - private route: ActivatedRoute, - private router: Router, - private spinnerService: SpinnerService, - private store: Store - ) {} - - /** - * Returns the IMT to use. - * - * @param query The query value - * @param defaultImt The default IMT - */ - private getImt(query: string, defaultImt: Imt): Imt { - return ( - Object.values(Imt).find(imt => imt.toString() === query) ?? defaultImt - ); - } - - /** - * Returns the model to use. - * - * @param query The query model - * @param defaultModel The default model - */ - private getModel(query: string, defaultModel: NshmId): NshmId { - return ( - Object.values(NshmId).find(model => model.toString() === query) ?? - defaultModel - ); - } - - /** - * Returns the site class to use. - * - * @param query The query value - * @param defaultSiteClass The default site class - */ - private getSiteClass( - query: string, - defaultSiteClass: NehrpSiteClass - ): NehrpSiteClass { - return ( - Object.values(NehrpSiteClass).find( - siteClass => siteClass.toString() === query - ) ?? defaultSiteClass - ); - } - - /** - * Create the response spectra for a specific model and for each source type available. - * - * @param hazardResponses The hazard responses - * @param form The control form values - */ - private responseSpectra( - hazardResponses: HazardResponse[], - form: ControlForm - ): Spectra[] { - const spectra: Spectra[] = []; - const sourceTypes = [...hazardResponses] - .shift() - .data.map(data => data.component); - - sourceTypes.forEach(sourceType => { - form.sourceType = sourceType; - spectra.push({ - responseSpectra: hazardUtils.responseSpectra(hazardResponses, form), - sourceType: sourceTypeFromPascalCase(sourceType), - }); - }); - - return spectra; - } - - /** - * Returns the URL to call for a specific model. - * - * @param model The NSHM to call - * @param formValues The control panel form values - * @param nshmServices The NSHM service metadata - */ - private serviceCallUrl( - model: NshmId, - formValues: ControlForm, - nshmServices: NshmMetadata[] - ): string { - const {latitude, longitude, vs30} = formValues; - const service = nshmServices.find(service => service.model === model); - return `${service.url}${this.serviceEndpoint}/${longitude}/${latitude}/${vs30}`; - } -} diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.facade.ts index 74e1ff942692b5d4c774ede23a40a78f4142f28e..f17c639334f0a75c5630a20e35cf23ee725c898e 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.facade.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.facade.ts @@ -1,18 +1,61 @@ -import {Injectable} from '@angular/core'; -import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp'; -import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot'; -import {HazardUsageResponse} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/hazard-service'; +import {computed, Injectable, Signal, signal} from '@angular/core'; +import {AbstractControl, FormBuilder, Validators} from '@angular/forms'; +import {ActivatedRoute, Router} from '@angular/router'; +import {HazardService, hazardUtils} from '@ghsc/nshmp-lib-no-ngrx/hazard'; +import { + NshmpService, + ServiceCallInfo, + SpinnerService, +} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; +import { + HazardCalcResponse, + HazardRequestMetadata, + HazardResponse, + HazardUsageResponse, +} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/hazard-service'; import {NshmMetadata} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/nshm-service'; import {Location} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/geo'; +import {Imt, NehrpSiteClass} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm'; +import {sourceTypeFromPascalCase} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/model'; +import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm'; import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {select, Store} from '@ngrx/store'; -import {FormGroupState} from 'ngrx-forms'; import {environment} from 'projects/nshmp-apps/src/environments/environment'; -import {Observable} from 'rxjs'; +import {devApps} from 'projects/nshmp-apps/src/shared/utils/applications.utils'; +import { + redrawPlots, + resetPlotSettings, + validateNan, +} from 'projects/nshmp-apps/src/shared/utils/facade.utils'; +import {catchError, forkJoin, Observable} from 'rxjs'; -import {appActions} from './app.actions'; -import {dynamicCompareAppFeature} from './app.reducer'; -import {ControlForm, ServiceResponses} from './app.state'; +import {defaultPlots} from '../utils/app.utils'; +import {createPlots} from '../utils/response-handler.utils'; +import * as responseHandler from '../utils/response-handler.utils'; +import { + AppState, + ControlForm, + controlPanelFormInitialState, + initialState, + ServiceResponses, + Spectra, +} from './app.state'; + +/** + * URL query. + */ +interface Query { + imt: string; + latitude: string; + longitude: string; + maxDirection: string; + model: string; + modelCompare: string; + returnPeriod: string; + siteClass: string; + truncate: string; + vs30: string; +} /** * Entrypoint to NGRX store for dynamic hazard compare application. @@ -21,128 +64,265 @@ import {ControlForm, ServiceResponses} from './app.state'; providedIn: 'root', }) export class AppFacade { + public readonly formGroup = this.formBuilder.group<ControlForm>( + controlPanelFormInitialState() + ); + public readonly state = signal<AppState>(initialState()); + /** nshmp-haz-ws web config */ - nshmpHazWs = environment.webServices.nshmpHazWs; + public nshmpHazWs = environment.webServices.nshmpHazWs; /** Hazard endpoint */ - serviceEndpoint = this.nshmpHazWs.services.curveServices.hazard; + public serviceEndpoint = this.nshmpHazWs.services.curveServices.hazard; - constructor(private store: Store) {} + constructor( + private formBuilder: FormBuilder, + private spinnerService: SpinnerService, + private nshmpService: NshmpService, + private hazardService: HazardService, + private route: ActivatedRoute, + private router: Router + ) { + this.addValidators(); + } /** * Returns the available dynamic models. */ - get availableModels$(): Observable<Parameter[]> { - return this.store.pipe( - select(dynamicCompareAppFeature.selectAvailableModels) - ); + public get availableModels(): Signal<Parameter[]> { + return computed(() => this.state().availableModels); } /** * Returns the models that can been compared with selected model. */ - get comparableModels$(): Observable<Parameter[]> { - return this.store.pipe( - select(dynamicCompareAppFeature.selectComparableModels) - ); + public get comparableModels(): Signal<Parameter[]> { + return computed(() => this.state().comparableModels); } /** - * Returns the control panel form state. + * Returns the metadata of the NSHM observable. */ - get controlPanelForm$(): Observable<FormGroupState<ControlForm>> { - return this.store.pipe( - select(dynamicCompareAppFeature.selectControlPanelForm) + public get nshmService(): Signal<NshmMetadata> { + return computed(() => + this.state().nshmServices.find( + nshmService => nshmService.model === this.formGroup.getRawValue().model + ) ); } /** * Returns the `Map` of the plots. */ - get plots$(): Observable<Map<string, NshmpPlot>> { - return this.store.pipe(select(dynamicCompareAppFeature.selectPlots)); - } - - /** - * Returns the metadata of the NSHM observable. - */ - get nshmService$(): Observable<NshmMetadata> { - return this.store.pipe(select(dynamicCompareAppFeature.selectNshmService)); + public get plots(): Signal<Map<string, NshmpPlot>> { + return computed(() => this.state().plots); } /** * Returns the service call info. */ - get serviceCallInfo$(): Observable<ServiceCallInfo> { - return this.store.pipe( - select(dynamicCompareAppFeature.selectServiceCallInfo) - ); + public get serviceCallInfo(): Signal<ServiceCallInfo> { + return computed(() => this.state().serviceCallInfo); } /** * Returns the service responses. */ - get serviceResponses$(): Observable<ServiceResponses> { - return this.store.pipe( - select(dynamicCompareAppFeature.selectServiceResponses) - ); + public get serviceResponses(): Signal<ServiceResponses> { + return computed(() => this.state().serviceResponses); } /** * Return the usage for the selected model. */ - get usage$(): Observable<HazardUsageResponse> { - return this.store.pipe( - select(dynamicCompareAppFeature.selectCombinedUsage) - ); + public get usage(): Signal<HazardUsageResponse> { + return computed(() => this.state().combinedUsage); } /** * Return the usage for the selected model. */ - get usageModelA$(): Observable<HazardUsageResponse> { - return this.store.pipe(select(dynamicCompareAppFeature.selectUsageModelA)); + public get usageModelA(): Signal<HazardUsageResponse> { + return computed(() => + this.state().usageResponses?.get(this.formGroup.getRawValue().model) + ); } /** * Return the usage for the selected model. */ - get usageModelB$(): Observable<HazardUsageResponse> { - return this.store.pipe(select(dynamicCompareAppFeature.selectUsageModelB)); + public get usageModelB(): Signal<HazardUsageResponse> { + return computed(() => + this.state().usageResponses?.get( + this.formGroup.getRawValue().modelCompare + ) + ); } /** * Call the hazard service. */ - callServices(): void { - this.store.dispatch(appActions.callServices()); + public callServices(): void { + this.spinnerService.show(SpinnerService.MESSAGE_SERVICE); + const form = this.formGroup.getRawValue(); + + const modelUrl = this.serviceCallUrl( + form.model, + form, + this.state().nshmServices + ); + + const modelCompareUrl = this.serviceCallUrl( + form.modelCompare, + form, + this.state().nshmServices + ); + + const serviceCallInfo: ServiceCallInfo = { + ...this.state().serviceCallInfo, + serviceCalls: [modelUrl, modelCompareUrl], + }; + + const calls: Observable<HazardCalcResponse>[] = [ + modelUrl, + modelCompareUrl, + ].map(url => this.nshmpService.callService$(url)); + + forkJoin(calls) + .pipe(catchError((error: Error) => this.nshmpService.throwError$(error))) + .subscribe(hazardResponses => { + if (hazardResponses.length !== 2) { + throw new Error('Does not contain two responses'); + } + + const modelA = hazardResponses[0]; + const modelB = hazardResponses[1]; + + const serviceResponses: ServiceResponses = { + modelA: { + hazardResponse: modelA, + model: form.model, + spectra: this.responseSpectra(modelA.response.hazardCurves, form), + }, + modelB: { + hazardResponse: modelB, + model: form.modelCompare, + spectra: this.responseSpectra(modelB.response.hazardCurves, form), + }, + }; + + this.updateState({serviceCallInfo, serviceResponses}); + this.createPlots(); + this.spinnerService.remove(); + }); + } + + public createPlots(): void { + const plots = createPlots(this.state(), this.formGroup); + this.updateState({plots}); } /** * Initialize applicaiton. */ - init(): void { - this.store.dispatch(appActions.init()); + public init(): void { + this.spinnerService.show(SpinnerService.MESSAGE_METADATA); + + this.hazardService + .dynamicNshms$<HazardRequestMetadata>( + `${this.nshmpHazWs.url}${this.nshmpHazWs.services.nshms}`, + this.serviceEndpoint + ) + .pipe(catchError((error: Error) => this.nshmpService.throwError$(error))) + .subscribe(({models, nshmServices, usageResponses}) => { + this.spinnerService.remove(); + const serviceCallInfo: ServiceCallInfo = { + ...this.state().serviceCallInfo, + usage: nshmServices.map( + service => `${service.url}${this.serviceEndpoint}` + ), + }; + + this.updateState({ + availableModels: models, + nshmServices, + serviceCallInfo, + usageResponses, + }); + + this.onModelChange(); + this.initialFormSet(); + }); + } + + /** + * Returns the updated state for a model change. + * + * @param state The current application state + */ + public onModelChange(): void { + const comparableModels = this.getComparableModels(); + const defaultComparableModel = [...comparableModels]?.pop()?.value ?? null; + const combinedUsage = this.getCombinedUsage( + this.state().usageResponses, + this.formGroup.getRawValue().model, + defaultComparableModel + ); + + if ( + this.formGroup.getRawValue().modelCompare !== + (defaultComparableModel as NshmId) + ) { + this.formGroup.patchValue({ + modelCompare: defaultComparableModel as NshmId, + }); + } + + this.updateState({ + combinedUsage, + comparableModels, + plots: initialState().plots, + serviceCallInfo: { + ...this.state().serviceCallInfo, + serviceCalls: [], + }, + serviceResponses: initialState().serviceResponses, + }); } /** * Redraw the plots. */ - plotRedraw(): void { - this.store.dispatch(appActions.plotRedraw()); + public plotRedraw(): void { + redrawPlots(this.state().plots); } /** * Reset the control panel. */ - resetControlPanel(): void { - this.store.dispatch(appActions.resetControlPanel()); + public resetControlPanel(): void { + this.formGroup.reset(controlPanelFormInitialState()); + this.resetState(); + this.onModelChange(); } /** * Reset the plot settings. */ - resetSettings(): void { - this.store.dispatch(appActions.resetSettings()); + public resetSettings(): void { + resetPlotSettings({ + currentPlots: this.state().plots, + defaultPlots: defaultPlots(), + }); + } + + public resetState(): void { + this.updateState({ + plots: initialState().plots, + serviceCallInfo: { + ...this.state().serviceCallInfo, + serviceCalls: [], + }, + }); } /** @@ -152,7 +332,7 @@ export class AppFacade { * @param nshmServices The NSHM service metadata * @param serviceEndpoint The service endpoint to call */ - serviceCallUrls( + public serviceCallUrls( formValues: ControlForm, nshmServices: NshmMetadata[] ): string[] { @@ -175,7 +355,248 @@ export class AppFacade { * * @param location The location */ - setLocation(location: Location): void { - this.store.dispatch(appActions.setLocation({location})); + public setLocation(location: Location): void { + this.formGroup.patchValue({ + latitude: location.latitude, + longitude: location.longitude, + }); + } + + public updateState(state: Partial<AppState>): void { + this.state.set({ + ...this.state(), + ...state, + }); + } + + private addRequiredValidator(control: AbstractControl): void { + control.addValidators(control => Validators.required(control)); + } + + private addValidators(): void { + const controls = this.formGroup.controls; + + this.addRequiredValidator(controls.imt); + this.addRequiredValidator(controls.imt); + this.addRequiredValidator(controls.latitude); + this.addRequiredValidator(controls.longitude); + this.addRequiredValidator(controls.model); + this.addRequiredValidator(controls.modelCompare); + this.addRequiredValidator(controls.vs30); + this.addRequiredValidator(controls.returnPeriod); + + controls.latitude.addValidators(validateNan()); + controls.longitude.addValidators(validateNan()); + } + + /** + * Returns the combined usage from model and comparable model. + * + * @param usageResponses The usages + * @param model The current selected model + * @param comparableModel The current comparable model + */ + private getCombinedUsage( + usageResponses: Map<string, HazardUsageResponse>, + model: NshmId, + comparableModel: string | null + ): HazardUsageResponse { + let combinedUsage = usageResponses.get(model); + + if (comparableModel !== null) { + combinedUsage = responseHandler.combineUsages([ + usageResponses.get(model), + usageResponses.get(comparableModel), + ]); + } + + return combinedUsage; + } + + /** + * Returns the available models that are comparable to the select model. + * + * @param state The application state + */ + private getComparableModels(): Parameter[] { + const formValues = this.formGroup.getRawValue(); + const state = this.state(); + + const currentNshmService = state.nshmServices.find( + nshmService => nshmService.model === formValues.model + ); + + const comparableServices = state.nshmServices.filter( + nshmService => + nshmService.model !== currentNshmService.model && + nshmService.project === currentNshmService.project + ); + + return state.availableModels.filter(model => + comparableServices.find( + service => service.model.toString() === model.value + ) + ); + } + + /** + * Returns the IMT to use. + * + * @param query The query value + * @param defaultImt The default IMT + */ + private getImt(query: string, defaultImt: Imt): Imt { + return ( + Object.values(Imt).find(imt => imt.toString() === query) ?? defaultImt + ); + } + + /** + * Returns the model to use. + * + * @param query The query model + * @param defaultModel The default model + */ + private getModel(query: string, defaultModel: NshmId): NshmId { + return ( + Object.values(NshmId).find(model => model.toString() === query) ?? + defaultModel + ); + } + + /** + * Returns the site class to use. + * + * @param query The query value + * @param defaultSiteClass The default site class + */ + private getSiteClass( + query: string, + defaultSiteClass: NehrpSiteClass + ): NehrpSiteClass { + return ( + Object.values(NehrpSiteClass).find( + siteClass => siteClass.toString() === query + ) ?? defaultSiteClass + ); + } + + private initialFormSet(): void { + const query = this.route.snapshot.queryParams as Query; + const defaultValues = controlPanelFormInitialState(); + + console.log(query); + + const formValues: ControlForm = { + commonReturnPeriods: defaultValues.commonReturnPeriods, + imt: this.getImt(query.imt, defaultValues.imt), + latitude: query.latitude + ? Number.parseFloat(query.latitude) + : defaultValues.latitude, + longitude: query.longitude + ? Number.parseFloat(query.longitude) + : defaultValues.longitude, + maxDirection: + query.maxDirection !== undefined + ? (JSON.parse(query.maxDirection) as boolean) + : defaultValues.maxDirection, + model: this.getModel(query.model, defaultValues.model), + modelCompare: this.getModel( + query.modelCompare, + defaultValues.modelCompare + ), + returnPeriod: query.returnPeriod + ? Number.parseInt(query.returnPeriod, 10) + : defaultValues.returnPeriod, + siteClass: this.getSiteClass( + query.siteClass, + defaultValues.siteClass as NehrpSiteClass + ), + sourceType: null, + truncate: + query.truncate !== undefined + ? (JSON.parse(query.truncate) as boolean) + : defaultValues.truncate, + vs30: query.vs30 ? Number.parseFloat(query.vs30) : defaultValues.vs30, + }; + + this.formGroup.patchValue(formValues); + + if (this.formGroup.valid) { + this.nshmpService.selectPlotControl(); + this.callServices(); + } else if (this.formGroup.getRawValue() !== defaultValues) { + this.formGroup.markAsDirty(); + } + + this.formGroup.valueChanges.subscribe(() => this.updateUrl()); + } + + /** + * Create the response spectra for a specific model and for each source type available. + * + * @param hazardResponses The hazard responses + * @param form The control form values + */ + private responseSpectra( + hazardResponses: HazardResponse[], + form: ControlForm + ): Spectra[] { + const spectra: Spectra[] = []; + const sourceTypes = [...hazardResponses] + .shift() + .data.map(data => data.component); + + sourceTypes.forEach(sourceType => { + form.sourceType = sourceType; + spectra.push({ + responseSpectra: hazardUtils.responseSpectra(hazardResponses, form), + sourceType: sourceTypeFromPascalCase(sourceType), + }); + }); + + return spectra; + } + + /** + * Returns the URL to call for a specific model. + * + * @param model The NSHM to call + * @param formValues The control panel form values + * @param nshmServices The NSHM service metadata + */ + private serviceCallUrl( + model: NshmId, + formValues: ControlForm, + nshmServices: NshmMetadata[] + ): string { + const {latitude, longitude, vs30} = formValues; + const service = nshmServices.find(service => service.model === model); + return `${service.url}${this.serviceEndpoint}/${longitude}/${latitude}/${vs30}`; + } + + private updateUrl(): void { + const value = this.formGroup.getRawValue(); + + const query: Query = { + imt: value.imt, + latitude: value.latitude?.toString(), + longitude: value.longitude?.toString(), + maxDirection: String(value.maxDirection), + model: value.model, + modelCompare: value.modelCompare, + returnPeriod: value.returnPeriod?.toString(), + siteClass: value.siteClass, + truncate: String(value.truncate), + vs30: value.vs30?.toString(), + }; + + this.router + .navigate([devApps().hazard.dynamicCompare.routerLink], { + queryParams: { + ...query, + }, + }) + .catch((error: Error) => this.nshmpService.throwError$(error)); } } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.reducer.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.reducer.ts deleted file mode 100644 index 407ad601086e6ed8fafa776c67b5687363ce8ce9..0000000000000000000000000000000000000000 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.reducer.ts +++ /dev/null @@ -1,352 +0,0 @@ -import {HazardFormControlIds, hazardUtils} from '@ghsc/nshmp-lib-ng/hazard'; -import {nshmpUtils} from '@ghsc/nshmp-lib-ng/nshmp'; -import {HazardUsageResponse} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/hazard-service'; -import {NehrpSiteClass} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm'; -import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm'; -import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {createFeature, createReducer, createSelector, on} from '@ngrx/store'; -import { - onNgrxForms, - onNgrxFormsAction, - setUserDefinedProperty, - setValue, - SetValueAction, - updateGroup, - validate, - wrapReducerWithFormStateUpdate, -} from 'ngrx-forms'; -import {required} from 'ngrx-forms/validation'; -import { - onPlotRedraw, - onPlots, - onPlotSettingsForm, -} from 'projects/nshmp-apps/src/shared/state/shared'; - -import {PlotSettingsId} from '../utils/app.utils'; -import * as responseHandler from '../utils/response-handler.utils'; -import {appActions} from './app.actions'; -import {AppState, ControlForm, FORM_ID, initialState} from './app.state'; - -/** - * NGRX form ids - */ -const formKeys = { - controlPanel: { - commonReturnPeriods: `${FORM_ID}.${HazardFormControlIds.COMMON_RETURN_PERIODS}`, - imt: `${FORM_ID}.${HazardFormControlIds.IMT}`, - maxDirection: `${FORM_ID}.${HazardFormControlIds.MAX_DIRECTION}`, - model: `${FORM_ID}.${HazardFormControlIds.MODEL}`, - modelCompare: `${FORM_ID}.modelCompare`, - returnPeriod: `${FORM_ID}.${HazardFormControlIds.RETURN_PERIOD}`, - siteClass: `${FORM_ID}.${HazardFormControlIds.SITE_CLASS}`, - truncate: `${FORM_ID}.${HazardFormControlIds.TRUNCATE}`, - vs30: `${FORM_ID}.${HazardFormControlIds.VS30}`, - }, -}; - -/** - * NGRX state feature with reducer and selectors. - */ -export const dynamicCompareAppFeature = createFeature({ - // Add additional selectors - extraSelectors: ({ - selectControlPanelForm, - selectNshmServices, - selectUsageResponses, - }) => ({ - /** Select `NshmService` of current model */ - selectNshmService: createSelector( - selectNshmServices, - selectControlPanelForm, - (nshmServices, controlPanelForm) => - nshmServices?.find( - nshmService => nshmService?.model === controlPanelForm.value.model - ) - ), - /** Select model A usage */ - selectUsageModelA: createSelector( - selectControlPanelForm, - selectUsageResponses, - (controlPanel, usages) => usages?.get(controlPanel.value.model) - ), - /** Select model B usage */ - selectUsageModelB: createSelector( - selectControlPanelForm, - selectUsageResponses, - (controlPanel, usages) => usages?.get(controlPanel.value.modelCompare) - ), - }), - // Applicaiton name - name: 'dynamicCompareApp', - // NGRX reducer - reducer: createReducer( - // Initial state - initialState(), - // On NGRX forms - onNgrxForms(), - // Handle NGRX form changes - onNgrxFormsAction(SetValueAction, (state, action) => - onFormAction(state, action) - ), - // On available models action - on(appActions.availableModels, (state, {models}) => ({ - ...state, - availableModels: [...models], - })), - // Handle initialization - on(appActions.init, state => ({ - ...state, - })), - // Handle NSHM services action - on(appActions.nshmServices, (state, {nshmServices}) => ({ - ...state, - nshmServices, - })), - // 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: initialState().controlPanelForm, - }; - - return { - ...state, - ...onModelChange(state), - plots: initialState().plots, - serviceCallInfo: initialState().serviceCallInfo, - serviceResponses: initialState().serviceResponses, - }; - }), - // On service call info action - on(appActions.serviceCallInfo, (state, {serviceCallInfo}) => ({ - ...state, - serviceCallInfo: { - ...state.serviceCallInfo, - ...serviceCallInfo, - }, - })), - // Set the service responses - on(appActions.serviceResponses, (state, {serviceResponses}) => ({ - ...state, - serviceResponses, - })), - // On usage response action - on(appActions.usageResponses, (state, {usageResponses}) => ({ - ...state, - usageResponses, - })) - ), -}); - -// Add validators -dynamicCompareAppFeature.reducer = wrapReducerWithFormStateUpdate( - dynamicCompareAppFeature.reducer, - state => state.controlPanelForm, - updateGroup<ControlForm>({ - ...hazardUtils.hazardValidateControlPanelForm(), - sourceType: validate([required]), - vs30: nshmpUtils.validateBounds, - }) -); - -/** - * Returns the available models that are comparable to the select model. - * - * @param state The application state - */ -function getComparableModels(state: AppState): Parameter[] { - const formValues = state.controlPanelForm.value; - - const currentNshmService = state.nshmServices.find( - nshmService => nshmService.model === formValues.model - ); - - const comparableServices = state.nshmServices.filter( - nshmService => - nshmService.model !== currentNshmService.model && - nshmService.project === currentNshmService.project - ); - - return state.availableModels.filter(model => - comparableServices.find(service => service.model.toString() === model.value) - ); -} - -/** - * Returns the combined usage from model and comparable model. - * - * @param usageResponses The usages - * @param model The current selected model - * @param comparableModel The current comparable model - */ -function getCombinedUsage( - usageResponses: Map<string, HazardUsageResponse>, - model: NshmId, - comparableModel: string | null -): HazardUsageResponse { - let combinedUsage = usageResponses.get(model); - - if (comparableModel !== null) { - combinedUsage = responseHandler.combineUsages([ - usageResponses.get(model), - usageResponses.get(comparableModel), - ]); - } - - return combinedUsage; -} - -/** - * Handle form actions. - * - * @param state The current state - * @param action The action - */ -function onFormAction( - state: AppState, - action: SetValueAction<unknown> -): AppState { - const formValues = state.controlPanelForm.value; - - switch (action.controlId) { - case formKeys.controlPanel.model: { - return onModelChange(state); - } - case formKeys.controlPanel.modelCompare: { - if (action.value === null) { - return onModelChange(state); - } - - const usages = [ - state.usageResponses.get(formValues.model), - state.usageResponses.get(formValues.modelCompare), - ]; - - return { - ...state, - combinedUsage: responseHandler.combineUsages(usages), - }; - } - case formKeys.controlPanel.commonReturnPeriods: { - const updatedState: AppState = { - ...state, - controlPanelForm: hazardUtils.onCommonReturnPeriodChange( - state.controlPanelForm - ), - }; - - return { - ...updatedState, - plots: responseHandler.createPlots(updatedState), - }; - } - case formKeys.controlPanel.returnPeriod: { - const updatedState: AppState = { - ...state, - controlPanelForm: hazardUtils.onReturnPeriodChange( - state.controlPanelForm - ), - }; - - return { - ...updatedState, - plots: responseHandler.createPlots(updatedState), - }; - } - case formKeys.controlPanel.imt: - case formKeys.controlPanel.maxDirection: - case formKeys.controlPanel.truncate: { - return { - ...state, - plots: responseHandler.createPlots(state), - }; - } - case formKeys.controlPanel.siteClass: { - return { - ...state, - controlPanelForm: hazardUtils.onSiteClassChange( - state.controlPanelForm, - state.combinedUsage.response.model.siteClasses - ), - plots: initialState().plots, - serviceResponses: initialState().serviceResponses, - }; - } - case formKeys.controlPanel.vs30: { - return { - ...state, - controlPanelForm: hazardUtils.onVs30Change( - state.controlPanelForm, - state.combinedUsage.response.model.siteClasses - ), - plots: initialState().plots, - }; - } - default: { - if ( - Object.values(PlotSettingsId).some(id => action.controlId.includes(id)) - ) { - return onPlotSettingsForm(state, action); - } else { - return { - ...state, - }; - } - } - } -} - -/** - * Returns the updated state for a model change. - * - * @param state The current application state - */ -function onModelChange(state: AppState): AppState { - const formValues = state.controlPanelForm.value; - - const comparableModels = getComparableModels(state); - const defaultComparableModel = [...comparableModels]?.pop()?.value ?? null; - const combinedUsage = getCombinedUsage( - state.usageResponses, - formValues.model, - defaultComparableModel - ); - - const siteClasses = combinedUsage.response.model.siteClasses; - - let controlPanelForm = updateGroup<ControlForm>({ - modelCompare: modelCompare => - setValue(modelCompare, defaultComparableModel), - vs30: vs30 => - setUserDefinedProperty( - vs30, - 'bounds', - hazardUtils.vs30Bounds(combinedUsage) - ), - })(state.controlPanelForm); - - controlPanelForm = hazardUtils.onModelChange( - controlPanelForm, - Object.keys(siteClasses) as NehrpSiteClass[], - hazardUtils.locationBounds(combinedUsage) - ); - - controlPanelForm = hazardUtils.onVs30Change(controlPanelForm, siteClasses); - - return { - ...state, - combinedUsage, - comparableModels, - controlPanelForm, - plots: initialState().plots, - serviceCallInfo: { - ...state.serviceCallInfo, - serviceCalls: [], - }, - serviceResponses: initialState().serviceResponses, - }; -} diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.state.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.state.ts index e493ca762039d97dc9ff076bb1d88da07bb94734..f96ab64ad0aa26c97fd95b3675579fc4670128ce 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.state.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/state/app.state.ts @@ -1,9 +1,10 @@ import { DynamicHazardControlForm, - hazardUtils, ResponseSpectra, } from '@ghsc/nshmp-lib-ng/hazard'; -import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp'; +import {hazardUtils} from '@ghsc/nshmp-lib-no-ngrx/hazard'; +import {ServiceCallInfo} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; import { HazardCalcResponse, HazardUsageResponse, @@ -16,8 +17,6 @@ import { } from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/model'; import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm'; import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {createFormGroupState, FormGroupState, FormState} from 'ngrx-forms'; -import {SharedAppState} from 'projects/nshmp-apps/src/shared/state/shared'; import {defaultPlots} from '../utils/app.utils'; @@ -39,17 +38,17 @@ export enum Plots { /** * Development dynamic hazard compare NGRX state. */ -export interface AppState extends SharedAppState { +export interface AppState { /** Available NSHMs */ availableModels: Parameter[]; /** Common usage for selected models */ combinedUsage: HazardUsageResponse; /** Models that can compare with a selected model */ comparableModels: Parameter[]; - /** Control panel form field state */ - controlPanelForm: FormState<ControlForm>; /** NSHM service metadata */ nshmServices: NshmMetadata[]; + /** The application's plots */ + plots: Map<string, NshmpPlot>; /** Service call info */ serviceCallInfo: ServiceCallInfo; /** Hazard service responses */ @@ -108,13 +107,12 @@ export function initialState(): AppState { availableModels: [], combinedUsage: null, comparableModels: [], - controlPanelForm: controlPanelFormInitialState(), nshmServices: [], plots: defaultPlots(), serviceCallInfo: { serviceCalls: [], serviceName: 'Dynamic Hazard Curves', - usage: null, + usage: [], }, serviceResponses: { modelA: null, @@ -127,12 +125,12 @@ export function initialState(): AppState { /** * Initial state for the control panel, */ -export function controlPanelFormInitialState(): FormGroupState<ControlForm> { - return createFormGroupState<ControlForm>(FORM_ID, { +export function controlPanelFormInitialState(): ControlForm { + return { ...hazardUtils.hazardDefaultFormValues(), imt: Imt.PGA, modelCompare: null, sourceType: sourceTypeToCapitalCase(SourceType.TOTAL), vs30: 760, - }); + }; } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/app.utils.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/app.utils.ts index e7f6b20a24512f9b0c6ddeeb07d30ed975d5b78b..fe276caedd5f5dd0c1de77f547c808d4c85e290f 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/app.utils.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/app.utils.ts @@ -1,38 +1,22 @@ -import {hazardUtils} from '@ghsc/nshmp-lib-ng/hazard'; -import { - NshmpPlot, - NshmpPlotSettings, - PlotOptions, - plotUtils, -} from '@ghsc/nshmp-lib-ng/plot'; +import {hazardUtils} from '@ghsc/nshmp-lib-no-ngrx/hazard'; +import {NshmpPlot, PlotOptions, plotUtils} from '@ghsc/nshmp-lib-no-ngrx/plot'; import {NshmId} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/nshm'; import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {createFormGroupState} from 'ngrx-forms'; import {Plots} from '../state/app.state'; -/** - * Plot ngrx-form ids. - */ -export enum PlotSettingsId { - HAZARD = 'ngrx-forms [Hazard Curves Settings]', - HAZARD_COMPONENTS = 'ngrx-forms [Hazard Component Curves Settings]', - HAZARD_DIFFERENCES = 'ngrx-forms [Hazard Difference Settings]', - SPECTRA = 'ngrx-forms [Spectra Settings]', - SPECTRA_COMPONENTS = 'ngrx-forms [Spectra Components Settings]', - SPECTRA_DIFFERENCES = 'ngrx-forms [Spectra Difference Settings]', -} - const differenceAspectRatio = '3:1'; const hazardXLabel = 'Ground Motion (g)'; const hazardYLabel = 'Annual Frequency of Exceedence'; const spectraXLabel = 'Spectral Period (s)'; const spectraYLabel = hazardXLabel; + const plotOptions: PlotOptions = { layout: { aspectRatio: hazardUtils.HAZARD_ASPECT_RATIO, }, }; + const diffPlotOptions: PlotOptions = { layout: { aspectRatio: differenceAspectRatio, @@ -113,13 +97,10 @@ function setHazardPlots(plots: Map<string, NshmpPlot>): void { plots.set(Plots.HAZARD, { label: 'Hazard Curves', plotData: hazardPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.HAZARD, - { - config: hazardPlotData.config, - layout: plotUtils.plotlyLayoutToSettings(hazardPlotData.layout), - } - ), + settingsForm: plotUtils.plotSettingsToFormGroup({ + config: hazardPlotData.config, + layout: plotUtils.plotlyLayoutToSettings(hazardPlotData.layout), + }), }); const hazardDiffPlotData = plotUtils.defaultPlot({ @@ -134,13 +115,10 @@ function setHazardPlots(plots: Map<string, NshmpPlot>): void { plots.set(Plots.HAZARD_DIFFERENCES, { label: 'Hazard Curve Differences', plotData: hazardDiffPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.HAZARD_DIFFERENCES, - { - config: hazardDiffPlotData.config, - layout: plotUtils.plotlyLayoutToSettings(hazardDiffPlotData.layout), - } - ), + settingsForm: plotUtils.plotSettingsToFormGroup({ + config: hazardDiffPlotData.config, + layout: plotUtils.plotlyLayoutToSettings(hazardDiffPlotData.layout), + }), }); const hazardComponentsPlotData = plotUtils.defaultPlot({ @@ -155,15 +133,10 @@ function setHazardPlots(plots: Map<string, NshmpPlot>): void { plots.set(Plots.HAZARD_COMPONENTS, { label: 'Hazard Component Curves', plotData: hazardComponentsPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.HAZARD_COMPONENTS, - { - config: hazardComponentsPlotData.config, - layout: plotUtils.plotlyLayoutToSettings( - hazardComponentsPlotData.layout - ), - } - ), + settingsForm: plotUtils.plotSettingsToFormGroup({ + config: hazardComponentsPlotData.config, + layout: plotUtils.plotlyLayoutToSettings(hazardComponentsPlotData.layout), + }), }); } @@ -185,13 +158,10 @@ function setSpectraPlots(plots: Map<string, NshmpPlot>): void { plots.set(Plots.SPECTRUM, { label: 'Spectrum', plotData: spectrumPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.SPECTRA, - { - config: spectrumPlotData.config, - layout: plotUtils.plotlyLayoutToSettings(spectrumPlotData.layout), - } - ), + settingsForm: plotUtils.plotSettingsToFormGroup({ + config: spectrumPlotData.config, + layout: plotUtils.plotlyLayoutToSettings(spectrumPlotData.layout), + }), }); const spectrumDiffPlotData = plotUtils.defaultPlot({ @@ -206,13 +176,10 @@ function setSpectraPlots(plots: Map<string, NshmpPlot>): void { plots.set(Plots.SPECTRUM_DIFFERENCES, { label: 'Spectrum Differences', plotData: spectrumDiffPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.SPECTRA_DIFFERENCES, - { - config: spectrumDiffPlotData.config, - layout: plotUtils.plotlyLayoutToSettings(spectrumDiffPlotData.layout), - } - ), + settingsForm: plotUtils.plotSettingsToFormGroup({ + config: spectrumDiffPlotData.config, + layout: plotUtils.plotlyLayoutToSettings(spectrumDiffPlotData.layout), + }), }); const spectraComponentsPlotData = plotUtils.defaultPlot({ @@ -227,14 +194,11 @@ function setSpectraPlots(plots: Map<string, NshmpPlot>): void { plots.set(Plots.SPECTRUM_COMPONENTS, { label: 'Spectra Component Curves', plotData: spectraComponentsPlotData, - settingsForm: createFormGroupState<NshmpPlotSettings>( - PlotSettingsId.SPECTRA_COMPONENTS, - { - config: spectraComponentsPlotData.config, - layout: plotUtils.plotlyLayoutToSettings( - spectraComponentsPlotData.layout - ), - } - ), + settingsForm: plotUtils.plotSettingsToFormGroup({ + config: spectraComponentsPlotData.config, + layout: plotUtils.plotlyLayoutToSettings( + spectraComponentsPlotData.layout + ), + }), }); } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/hazard-plots.utils.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/hazard-plots.utils.ts index 1182fbcecb119d2ffc3eeffcfb52f49c72bdb2ab..e480a7c02610a797c626ae30918a27d71c49a522 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/hazard-plots.utils.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/hazard-plots.utils.ts @@ -1,6 +1,6 @@ -import {hazardUtils} from '@ghsc/nshmp-lib-ng/hazard'; -import {NumberBounds} from '@ghsc/nshmp-lib-ng/nshmp'; -import {NshmpPlot, plotUtils} from '@ghsc/nshmp-lib-ng/plot'; +import {hazardUtils} from '@ghsc/nshmp-lib-no-ngrx/hazard'; +import {FormGroupControls, NumberBounds} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot, plotUtils} from '@ghsc/nshmp-lib-no-ngrx/plot'; import { HazardCurve, HazardResponse, @@ -82,7 +82,10 @@ interface PlotDash { * * @param state The app state */ -export function createHazardPlots(state: AppState): Map<string, NshmpPlot> { +export function createHazardPlots( + state: AppState, + formGroup: FormGroupControls<ControlForm> +): Map<string, NshmpPlot> { const plots = new Map<string, NshmpPlot>(); const hazardPlot = state.plots.get(Plots.HAZARD); @@ -93,7 +96,7 @@ export function createHazardPlots(state: AppState): Map<string, NshmpPlot> { state.serviceResponses, state.availableModels, hazardPlot, - state.controlPanelForm.value + formGroup.getRawValue() ); plots.set(Plots.HAZARD, { @@ -103,7 +106,7 @@ export function createHazardPlots(state: AppState): Map<string, NshmpPlot> { const hazardCurveDifference = createHazardDiffPlot( state.serviceResponses, - state.controlPanelForm.value.imt, + formGroup.getRawValue().imt, hazardDiffPlot ); @@ -116,7 +119,7 @@ export function createHazardPlots(state: AppState): Map<string, NshmpPlot> { state.serviceResponses, state.availableModels, hazardComponentsPlot, - state.controlPanelForm.value + formGroup.getRawValue() ); plots.set(Plots.HAZARD_COMPONENTS, { diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/response-handler.utils.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/response-handler.utils.ts index 5de1076c701ac81a87bf7eee32de7dd0c3f5f92d..c393e7ad8cf154efc1593a5416255b1bb6253cb1 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/response-handler.utils.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/response-handler.utils.ts @@ -1,8 +1,9 @@ -import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot'; +import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; import {HazardUsageResponse} from '@ghsc/nshmp-utils-ts/libs/nshmp-haz/www/hazard-service'; import {Parameter} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {AppState} from '../state/app.state'; +import {AppState, ControlForm} from '../state/app.state'; import {createHazardPlots} from './hazard-plots.utils'; import {createSpectraPlots} from './spectra-plots.utils'; @@ -59,7 +60,10 @@ export function combineUsages( * * @param state The app state */ -export function createPlots(state: AppState): Map<string, NshmpPlot> { +export function createPlots( + state: AppState, + formGroup: FormGroupControls<ControlForm> +): Map<string, NshmpPlot> { if ( state.serviceResponses.modelA === null || state.serviceResponses.modelB === null @@ -67,8 +71,8 @@ export function createPlots(state: AppState): Map<string, NshmpPlot> { return state.plots; } - const hazardPlots = createHazardPlots(state); - const spectraPlots = createSpectraPlots(state); + const hazardPlots = createHazardPlots(state, formGroup); + const spectraPlots = createSpectraPlots(state, formGroup); return new Map([...hazardPlots.entries(), ...spectraPlots.entries()]); } diff --git a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/spectra-plots.utils.ts b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/spectra-plots.utils.ts index 7f4d63fbc8de9544240f969a3f742a76a4f821e8..39cdd9962f0bb0cc6622d4beb0a5c141c6e1c5f2 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/spectra-plots.utils.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/dynamic-compare/utils/spectra-plots.utils.ts @@ -1,4 +1,5 @@ -import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot'; +import {FormGroupControls} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; import {Imt, imtToPeriod} from '@ghsc/nshmp-utils-ts/libs/nshmp-lib/gmm'; import { SourceType, @@ -110,7 +111,10 @@ interface PlotColors { * * @param state The app state */ -export function createSpectraPlots(state: AppState): Map<string, NshmpPlot> { +export function createSpectraPlots( + state: AppState, + formGroup: FormGroupControls<ControlForm> +): Map<string, NshmpPlot> { const plots = new Map<string, NshmpPlot>(); const spectraPlot = state.plots.get(Plots.SPECTRUM); @@ -120,7 +124,7 @@ export function createSpectraPlots(state: AppState): Map<string, NshmpPlot> { const spectraCurves = createSpectraCurvesPlot( state.serviceResponses, state.availableModels, - state.controlPanelForm.value, + formGroup.getRawValue(), spectraPlot ); @@ -132,7 +136,7 @@ export function createSpectraPlots(state: AppState): Map<string, NshmpPlot> { const spectraComponents = createSpectraComponentsPlot( state.serviceResponses, state.availableModels, - state.controlPanelForm.value, + formGroup.getRawValue(), spectraPlot ); @@ -144,7 +148,7 @@ export function createSpectraPlots(state: AppState): Map<string, NshmpPlot> { const spectraDiff = createSpectraDiffPlot( state.serviceResponses, state.availableModels, - state.controlPanelForm.value, + formGroup.getRawValue(), spectraDiffPlot ); diff --git a/projects/nshmp-apps/src/app/dev/hazard/hazard.routes.ts b/projects/nshmp-apps/src/app/dev/hazard/hazard.routes.ts index 37259a446d29f50c4ffa28b662455f70ec89ec6c..5e7bea6247cfd40f5d9165367d3cae50b7e445bf 100644 --- a/projects/nshmp-apps/src/app/dev/hazard/hazard.routes.ts +++ b/projects/nshmp-apps/src/app/dev/hazard/hazard.routes.ts @@ -1,11 +1,7 @@ import {inject} from '@angular/core'; import {Routes} from '@angular/router'; -import {provideEffects} from '@ngrx/effects'; -import {provideState} from '@ngrx/store'; import {DynamicHazardCompareGuard} from './dynamic-compare/guards/dynamic-hazard-compare.guard'; -import {DynamicCompareAppEffects} from './dynamic-compare/state/app.effects'; -import {dynamicCompareAppFeature} from './dynamic-compare/state/app.reducer'; /** Routes for dev hazard applications */ const routes: Routes = [ @@ -14,10 +10,6 @@ const routes: Routes = [ loadComponent: () => import('./dynamic-compare/app.component').then(com => com.AppComponent), path: 'dynamic-compare', - providers: [ - provideState(dynamicCompareAppFeature), - provideEffects(DynamicCompareAppEffects), - ], }, ]; diff --git a/projects/nshmp-apps/src/shared/models/plot-settings-panel.model.ts b/projects/nshmp-apps/src/shared/models/plot-settings-panel.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..641b80c49384b755340ea4f69414ebc8e3887c23 --- /dev/null +++ b/projects/nshmp-apps/src/shared/models/plot-settings-panel.model.ts @@ -0,0 +1,5 @@ +import {PlotSettingsPanel} from '@ghsc/nshmp-lib-no-ngrx/plot'; + +export interface PlotSettingsPanelId extends PlotSettingsPanel { + id: string; +} diff --git a/projects/nshmp-apps/src/shared/utils/facade.utils.ts b/projects/nshmp-apps/src/shared/utils/facade.utils.ts index a1ff3e61b0d01d38538a06ede5c14a3111661626..bb3ba5d49fa434844b96c449aef62e3c39ae0809 100644 --- a/projects/nshmp-apps/src/shared/utils/facade.utils.ts +++ b/projects/nshmp-apps/src/shared/utils/facade.utils.ts @@ -1,3 +1,4 @@ +import {AbstractControl, ValidationErrors, ValidatorFn} from '@angular/forms'; import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; export interface ResetPlotSettingsProps { @@ -34,3 +35,11 @@ export function redrawPlots( return updatedPlots; } + +export function validateNan(): ValidatorFn { + return ( + control: AbstractControl<number, number> + ): ValidationErrors | null => { + return isNaN(control.value) ? {nan: {value: control.value}} : null; + }; +}