diff --git a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts index 34a40e6e0fceb67ea7c10614e2bb82f18c2c5666..13c202d0d3bd2d631228fc2585f6914e6f30a742 100644 --- a/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts +++ b/projects/nshmp-apps/src/app/dev/gmm/hanging-wall-effects/state/app.facade.ts @@ -1,105 +1,251 @@ -import {Injectable} from '@angular/core'; -import {ServiceCallInfo} from '@ghsc/nshmp-lib-ng/nshmp'; -import {NshmpPlot} from '@ghsc/nshmp-lib-ng/plot'; +import {Injectable, signal} from '@angular/core'; +import {AbstractControl, FormBuilder, Validators} from '@angular/forms'; +import {gmmUtils} from '@ghsc/nshmp-lib-no-ngrx/gmm'; +import { + FormGroupControls, + NshmpService, + ServiceCallInfo, + SpinnerService, +} from '@ghsc/nshmp-lib-no-ngrx/nshmp'; +import {NshmpPlot} from '@ghsc/nshmp-lib-no-ngrx/plot'; import { GmmDistanceResponse, GmmDistanceUsage, } from '@ghsc/nshmp-utils-ts/libs/nshmp-ws/gmm-services'; -import {EnumParameterValues} from '@ghsc/nshmp-utils-ts/libs/nshmp-ws-utils/metadata'; -import {select, Store} from '@ngrx/store'; -import {FormGroupState} from 'ngrx-forms'; -import {Observable} from 'rxjs'; - -import {appActions} from './app.actions'; -import {hangingWallEffectsAppFeature} from './app.reducer'; -import {ControlPanelForm} from './app.state'; - -/** - * Entrypoint to NGRX store for hanging wall effects application. - */ -@Injectable({ - providedIn: 'root', -}) +import {environment} from 'projects/nshmp-apps/src/environments/environment'; +import {catchError} from 'rxjs'; + +import {createPlots} from '../utils/response-handler.utils'; +import { + AppState, + ControlPanelForm, + defaultFormValues, + defaultPlots, + initialState, + usageFormValues, +} from './app.state'; + +@Injectable({providedIn: 'root'}) export class AppFacade { - constructor(private store: Store) {} + /** Application state */ + readonly state = signal<AppState>({ + ...initialState(), + }); + + /** Control panel form group */ + private formGroup = this.formBuilder.group({ + ...defaultFormValues(), + gmmSource: [], + MwMulti: [], + vs30Multi: [], + }) as FormGroupControls<ControlPanelForm>; + + /** nshmp-ws base URL */ + private baseUrl = environment.webServices.data.url; + /** GMM service URL */ + private serviceUrl = `${this.baseUrl}${environment.webServices.data.services.gmmHwFw}`; + + constructor( + private spinnerService: SpinnerService, + private nshmpService: NshmpService, + private formBuilder: FormBuilder + ) { + this.validators(this.formGroup); + this.formGroup.controls.gmmSource.setValue([]); + this.formGroup.controls.showEpistemicUncertainty.disable(); + } /** - * Returns the control panel form state. + * Call the GMM distance service. */ - get controlPanelForm$(): Observable<FormGroupState<ControlPanelForm>> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectControlPanelForm) + callServices(): void { + if (this.formGroup.invalid) { + return; + } + + this.spinnerService.show(SpinnerService.MESSAGE_SERVICE); + + const urls = gmmUtils.serviceEndpoints( + this.serviceUrl, + this.formGroup.getRawValue(), + this.formGroup.getRawValue().multiSelectableParam ); + + this.nshmpService + .callServices$<GmmDistanceResponse>(urls) + .pipe(catchError((error: Error) => this.nshmpService.throwError$(error))) + .subscribe(serviceResponses => + this.handleServiceResponses(serviceResponses) + ); } /** - * Returns the plots. + * Returns the control panel form state. */ - get plots$(): Observable<Map<string, NshmpPlot>> { - return this.store.pipe(select(hangingWallEffectsAppFeature.selectPlots)); + get controlPanelForm(): FormGroupControls<ControlPanelForm> { + return this.formGroup; } /** - * Returns the service call info. + * Create plots based on current state and form group. */ - get serviceCallInfo$(): Observable<ServiceCallInfo> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectServiceCallInfo) - ); + createPlots(): void { + this.updateState({ + plots: createPlots(this.state(), this.formGroup), + }); } /** - * Returns the service responses. + * Initialize application. */ - get serviceResponses$(): Observable<GmmDistanceResponse[]> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectServiceResponses) - ); + init(): void { + this.spinnerService.show(SpinnerService.MESSAGE_METADATA); + + this.nshmpService + .callService$<GmmDistanceUsage>(this.serviceUrl) + .pipe(catchError((error: Error) => this.nshmpService.throwError$(error))) + .subscribe(usageResponse => this.handleUsageResponse(usageResponse)); } /** - * Returns the supported IMTs. + * Reset the control panel. */ - get supportedImts$(): Observable<EnumParameterValues[]> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectSupportedImts) + resetControlPanel(): void { + this.formGroup.reset( + usageFormValues(this.state().usageResponse.response.parameters) ); } /** - * Return the usage for the selected model. + * Reset the plot settings. */ - get usage$(): Observable<GmmDistanceUsage> { - return this.store.pipe( - select(hangingWallEffectsAppFeature.selectUsageResponse) - ); + resetPlotSettings(): void { + this.state().plots.forEach((plot, id) => { + const defaultSettings = defaultPlots().get(id).settingsForm; + plot.settingsForm.patchValue(defaultSettings.getRawValue()); + }); } /** - * Call the GMM distance service. + * Reset the state. */ - callServices(): void { - this.store.dispatch(appActions.callServices()); + resetState(): void { + const serviceCallInfo = gmmUtils.serviceCallInfo({ + multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam, + serviceName: this.state().serviceCallInfo.serviceName, + serviceResponses: this.state().serviceResponses, + serviceUrl: this.serviceUrl, + values: this.formGroup.getRawValue(), + }); + + this.updateState({ + plots: createPlots(this.state(), this.formGroup), + serviceCallInfo, + serviceResponses: null, + }); } /** - * Initialize the application. + * Update a single plot in the state. + * + * @param plot Updated plot + * @param id Id of plot to update */ - init(): void { - this.store.dispatch(appActions.init()); + updatePlot(plot: NshmpPlot, id: string): void { + const plots = this.state().plots; + plots.set(id, plot); + this.updateState({ + plots, + }); } /** - * Reset the control panel. + * Update state. + * + * @param state Partial new state to update */ - resetControlPanel(): void { - this.store.dispatch(appActions.resetControlPanel()); + updateState(state: Partial<AppState>): void { + this.state.set({ + ...this.state(), + ...state, + }); } /** - * Reset the control panel. + * Handle service response result. + * + * @param serviceResponses The service responses */ - resetPlotSettings(): void { - this.store.dispatch(appActions.resetSettings()); + private handleServiceResponses( + serviceResponses: GmmDistanceResponse[] + ): void { + const means = serviceResponses.map(s => s.response.means); + const hasLogicTree = gmmUtils.hasTree(means); + + if (hasLogicTree) { + this.formGroup.controls.showEpistemicUncertainty.enable(); + } else { + this.formGroup.controls.showEpistemicUncertainty.disable(); + } + + this.updateState({ + serviceCallInfo: gmmUtils.serviceCallInfo({ + multiSelectableParam: this.formGroup.getRawValue().multiSelectableParam, + serviceName: this.state().serviceCallInfo.serviceName, + serviceResponses, + serviceUrl: this.serviceUrl, + values: this.formGroup.getRawValue(), + }), + serviceResponses, + }); + + this.createPlots(); + + this.spinnerService.remove(); + } + + /** + * Handle usage response. + * + * @param usageResponse Usage response + */ + private handleUsageResponse(usageResponse: GmmDistanceUsage): void { + this.spinnerService.remove(); + const parameters = usageResponse.response.parameters; + const values = usageFormValues(parameters); + + this.formGroup.patchValue({ + ...values, + }); + + const serviceCallInfo: ServiceCallInfo = { + ...this.state().serviceCallInfo, + usage: [this.serviceUrl], + }; + + this.updateState({ + serviceCallInfo, + usageResponse, + }); + + this.spinnerService.remove(); + } + + /** + * Add validators to form controls. + * + * @param form The form group + */ + private validators(form: FormGroupControls<ControlPanelForm>): void { + const required = (control: AbstractControl) => Validators.required(control); + + form.controls.Mw.addValidators(required); + form.controls.dip.addValidators(required); + form.controls.gmmSource.addValidators(required); + form.controls.imt.addValidators(required); + form.controls.multiSelectableParam.addValidators(required); + form.controls.vs30.addValidators(required); + form.controls.width.addValidators(required); + form.controls.zTor.addValidators(required); } }