blob: 73b2d7dcf48c3971b2daec739d55a6cb1ec77fdf [file] [log] [blame]
/********************************************************************************
* 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});
});
}
}