| /******************************************************************************** |
| * Copyright (c) 2020 Contributors to the Eclipse Foundation |
| * |
| * See the NOTICE file(s) distributed with this work for additional |
| * information regarding copyright ownership. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| ********************************************************************************/ |
| |
| import {Component, OnDestroy, OnInit} from "@angular/core"; |
| import {FormArray} from "@angular/forms"; |
| import {select, Store} from "@ngrx/store"; |
| import {defer, Observable} from "rxjs"; |
| import {filter, map, skip, switchMap, take, takeUntil} from "rxjs/operators"; |
| import { |
| EAPIProcessTaskDefinitionKey, |
| EAPIStaticAttachmentTagIds, |
| IAPITextArrangementErrorModel, |
| TCompleteTaskVariable |
| } from "../../../../../core"; |
| import { |
| compileStatementArrangementAction, |
| createStatementEditorForm, |
| fetchStatementTextArrangementAction, |
| getContributionsSelector, |
| getStatementArrangementErrorSelector, |
| getStatementEditorControlConfigurationSelector, |
| getStatementStaticTextReplacementsSelector, |
| getStatementTextBlockGroups, |
| IStatementEditorFormValue, |
| queryParamsIdSelector, |
| requiredContributionsGroupsSelector, |
| requiredContributionsOptionsSelector, |
| statementArrangementSelector, |
| statementFileSelector, |
| statementLoadingSelector, |
| submitStatementEditorFormAction, |
| taskSelector, |
| updateStatementEntityAction, |
| userRolesSelector, |
| validateStatementArrangementAction |
| } from "../../../../../store"; |
| import {arrayJoin, filterDistinctValues} from "../../../../../util"; |
| import {AbstractReactiveFormComponent} from "../../../abstract"; |
| |
| @Component({ |
| selector: "app-statement-editor-form", |
| templateUrl: "./statement-editor-form.component.html", |
| styleUrls: ["./statement-editor-form.component.scss"] |
| }) |
| export class StatementEditorFormComponent extends AbstractReactiveFormComponent<IStatementEditorFormValue> implements OnInit, OnDestroy { |
| |
| public outboxTagId = EAPIStaticAttachmentTagIds.OUTBOX; |
| |
| public statementTagId = EAPIStaticAttachmentTagIds.STATEMENT; |
| |
| public appShortMode: boolean; |
| |
| public appShowPreview: boolean; |
| |
| public appFormGroup = createStatementEditorForm(); |
| |
| public task$ = this.store.pipe(select(taskSelector)); |
| |
| public statementId$ = this.store.pipe(select(queryParamsIdSelector)); |
| |
| public showContributions$ = defer(() => this.task$).pipe( |
| map((task) => task?.taskDefinitionKey), |
| map((taskDefinitionKey) => { |
| return taskDefinitionKey === EAPIProcessTaskDefinitionKey.CREATE_DRAFT |
| || taskDefinitionKey === EAPIProcessTaskDefinitionKey.CHECK_AND_FORMULATE_RESPONSE; |
| }) |
| ); |
| |
| public requiredContributionOptions$ = this.store.pipe(select(requiredContributionsOptionsSelector)); |
| |
| public requiredContributionGroups$ = this.store.pipe(select(requiredContributionsGroupsSelector)); |
| |
| public contributions$ = this.store.pipe(select(getContributionsSelector)); |
| |
| public selectedContributionsCount$ = this.value$.pipe( |
| map((value) => value?.contributions?.selected?.length) |
| ); |
| |
| public userRoles$ = this.store.pipe(select(userRolesSelector)); |
| |
| public controls$ = this.value$.pipe( |
| filter((value) => value != null), |
| switchMap((value) => { |
| return this.store.pipe(select(getStatementEditorControlConfigurationSelector, arrayJoin(value.arrangement))); |
| }) |
| ); |
| |
| public replacements$ = this.store.pipe(select(getStatementStaticTextReplacementsSelector)); |
| |
| public selectedTextBlockIds$: Observable<string[]> = this.value$.pipe( |
| map((value) => filterDistinctValues(value.arrangement.map((item) => item.textblockId))) |
| ); |
| |
| public arrangement$ = this.store.pipe(select(statementArrangementSelector)); |
| |
| public file$ = this.store.pipe(select(statementFileSelector)); |
| |
| public textBlockGroups$ = this.store.pipe(select(getStatementTextBlockGroups)); |
| |
| public arrangementError$ = this.store.pipe(select(getStatementArrangementErrorSelector)); |
| |
| public isLoading$ = this.store.pipe(select(statementLoadingSelector)); |
| |
| public constructor(public store: Store) { |
| super(); |
| } |
| |
| public ngOnInit() { |
| this.updateForm(); |
| this.fetchTextArrangement(); |
| this.deleteStatementFile(); |
| } |
| |
| public ngOnDestroy() { |
| super.ngOnDestroy(); |
| this.deleteStatementFile(); |
| } |
| |
| public setArrangementErrors(errors: IAPITextArrangementErrorModel[]) { |
| const array = this.appFormGroup.get("arrangement"); |
| if (array instanceof FormArray) { |
| array.controls |
| .forEach((control) => control.setErrors({arrangement: null})); |
| arrayJoin(errors) |
| .forEach((e) => array.get([e?.arrangementId])?.setErrors({arrangement: e})); |
| } |
| } |
| |
| public async validate() { |
| const task = await this.task$.pipe(take(1)).toPromise(); |
| this.store.dispatch(validateStatementArrangementAction({ |
| statementId: task.statementId, |
| taskId: task.taskId, |
| arrangement: this.getValue().arrangement |
| })); |
| } |
| |
| public async compile() { |
| const task = await this.task$.pipe(take(1)).toPromise(); |
| this.store.dispatch(compileStatementArrangementAction({ |
| statementId: task.statementId, |
| taskId: task.taskId, |
| arrangement: this.getValue().arrangement |
| })); |
| } |
| |
| public async submit(options?: { |
| completeTask?: TCompleteTaskVariable, |
| claimNext?: boolean | EAPIProcessTaskDefinitionKey, |
| compile?: boolean, |
| contribute?: boolean, |
| file?: File |
| }) { |
| const task = await this.task$.pipe(take(1)).toPromise(); |
| const value = this.getValue(); |
| this.store.dispatch(submitStatementEditorFormAction({ |
| statementId: task.statementId, |
| taskId: task.taskId, |
| value: { |
| ...value, |
| contributions: await this.showContributions$.pipe(take(1)).toPromise() ? value.contributions : null |
| }, |
| options |
| })); |
| } |
| |
| public async finalize(complete?: boolean) { |
| if (complete) { |
| const file = await this.file$.pipe(take(1)).toPromise(); |
| return this.submit({ |
| completeTask: { |
| data_complete: {type: "Boolean", value: true}, |
| response_created: {type: "Boolean", value: true} |
| }, |
| file |
| }); |
| } else { |
| return this.deleteStatementFile(); |
| } |
| } |
| |
| private async deleteStatementFile() { |
| const file = await this.file$.pipe(take(1)).toPromise(); |
| if (file != null) { |
| const statementId = await this.statementId$.pipe(take(1)).toPromise(); |
| this.store.dispatch(updateStatementEntityAction({statementId, entity: {file: null}})); |
| } |
| } |
| |
| private fetchTextArrangement() { |
| this.task$.pipe(takeUntil(this.destroy$), filter((task) => task != null)) |
| .subscribe(({statementId}) => this.store.dispatch(fetchStatementTextArrangementAction({statementId}))); |
| } |
| |
| private updateForm() { |
| this.isLoading$.pipe(takeUntil(this.destroy$)).subscribe((loading) => this.disable(loading)); |
| this.arrangement$.pipe(takeUntil(this.destroy$)).subscribe((arrangement) => { |
| this.setValueForArray(arrangement, "arrangement"); |
| }); |
| this.arrangementError$.pipe( |
| skip(1), // The first value is skipped when the use enters the site. |
| switchMap((errors) => { |
| // Errors are only displayed when the form is ready to use. |
| return this.appFormGroup.statusChanges.pipe( |
| filter(() => this.appFormGroup.enabled), |
| map(() => errors), |
| take(1) |
| ); |
| }), |
| takeUntil(this.destroy$), |
| ).subscribe((errors) => this.setArrangementErrors(errors)); |
| this.contributions$.pipe(takeUntil(this.destroy$)).subscribe((contributions) => { |
| this.patchValue({contributions}); |
| }); |
| } |
| |
| } |