blob: b606fdd00ae4def14446b732daeb12f84af74faa [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 {AbstractControl, FormArray, FormControl, FormGroup} from "@angular/forms";
import {IAPITextBlockConfigurationModel, IAPITextBlockGroupModel, IAPITextBlockModel} from "../../../core/api/text";
import {createFormGroup} from "../../../util/forms";
import {arrayJoin, filterDistinctValues} from "../../../util/store";
export class TextBlockSettingsForm {
public selects = new FormGroup({});
public groups = new FormArray([]);
public negativeGroups = new FormArray([]);
public formGroup = createFormGroup<IAPITextBlockConfigurationModel>({
selects: this.selects,
groups: this.groups,
negativeGroups: this.negativeGroups
});
private defaultSelectEntry = "";
private defaultTextBlockModel: IAPITextBlockModel = {id: "", text: "", excludes: [], requires: []};
private defaultTextBlockGroupModel: IAPITextBlockGroupModel = {groupName: "", textBlocks: []};
public disable(disabled: boolean) {
if (disabled) {
this.formGroup.disable();
} else {
this.formGroup.enable();
}
}
public getValue(): IAPITextBlockConfigurationModel {
return this.formGroup.value;
}
public setForm(value: IAPITextBlockConfigurationModel) {
const disabled = this.formGroup.disabled;
Object.keys(this.selects.controls).forEach((key) => this.removeSelect(key));
Object.entries(value.selects).forEach(([key, options]) => this.addSelect(key, options));
this.groups.clear();
value.groups.forEach((group) => {
if (group.textBlocks?.length > 0) {
this.groups.push(this.createTextBlockGroupControl(group));
} else {
this.groups.push(this.createTextBlockGroupControl({...group, textBlocks: [this.defaultTextBlockModel]}));
}
});
this.negativeGroups.clear();
value.negativeGroups.forEach((group) => {
this.negativeGroups.push(this.createTextBlockGroupControl(group));
});
this.changeTextBlockReferences();
this.insertObjectsForEmptyGroups(false);
this.insertObjectsForEmptyGroups(true);
this.disable(disabled);
}
public getSelectKeys(): string[] {
return Object.keys(this.selects.value);
}
public getSelectEntries(key: string): string[] {
return this.getValue().selects[key];
}
public getSelectControl(key: string): FormArray {
const control = this.selects.get([key]);
return control instanceof FormArray ? control : undefined;
}
public addSelect(key: string, entries?: string[]) {
const disabled = this.formGroup.disabled;
this.selects.addControl(key, new FormArray([]));
arrayJoin(entries).forEach((entry) => this.addSelectEntry(key, entry));
this.disable(disabled);
}
public changeSelectKey(key: string, newKey: string) {
const disabled = this.formGroup.disabled;
const selectControl = this.getSelectControl(key);
if (key === newKey || selectControl == null) {
return;
}
this.patchTextBlockValues((value) => ({
text: value.text.replace(new RegExp(`<s:${key}>`, "g"), `<s:${newKey}>`)
}));
this.selects.removeControl(key);
this.selects.addControl(newKey, selectControl);
this.disable(disabled);
}
public removeSelect(key: string) {
this.selects.removeControl(key);
this.patchTextBlockValues((value) => ({
text: value.text.replace(new RegExp(`<s:${key}>`, "g"), "")
}));
}
public addSelectEntry(key: string, entry: string) {
const disabled = this.formGroup.disabled;
this.getSelectControl(key)?.push(this.createSelectEntryControl(entry));
this.disable(disabled);
}
public removeSelectEntry(key: string, index: number) {
this.getSelectControl(key)?.removeAt(index);
}
public getTextBlockGroups(negative?: boolean): FormArray {
return negative ? this.negativeGroups : this.groups;
}
public insertGroup(groupIndex: number, negative?: boolean) {
const disabled = this.formGroup.disabled;
const groups = this.getTextBlockGroups(negative);
const groupControl = this.createTextBlockGroupControl();
groups.insert(groupIndex, groupControl);
this.disable(disabled);
}
public removeGroup(groupIndex: number, negative?: boolean) {
const groups = this.getTextBlockGroups(negative);
groups.removeAt(groupIndex);
this.changeTextBlockReferences();
return groups.length;
}
public getTextBlockValue(groupIndex: number, textBlockIndex: number, negative?: boolean): IAPITextBlockModel {
return this.getTextBlockFormArrayForGroup(groupIndex, negative)?.at(textBlockIndex)?.value;
}
public getTextBlockFormArrayForGroup(groupIndex: number, negative?: boolean): FormArray {
const result = this.getTextBlockGroups(negative).at(groupIndex)?.get("textBlocks");
return result instanceof FormArray ? result : undefined;
}
public insertTextBlock(groupIndex: number, index: number, negative?: boolean) {
const textBlockGroup = this.getTextBlockFormArrayForGroup(groupIndex, negative);
if (textBlockGroup != null) {
const disabled = this.formGroup.disabled;
const control = this.createTextBlockModelControl();
textBlockGroup.insert(index, control);
this.changeTextBlockReferences();
this.disable(disabled);
return control;
}
}
public moveTextBlockInGroup(groupIndex: number, from: number, to: number, negative?: boolean) {
const textBlockGroup = this.getTextBlockFormArrayForGroup(groupIndex, negative);
const control = textBlockGroup.get([from]);
if (to != null && from !== to && control != null) {
textBlockGroup.removeAt(from);
textBlockGroup.insert(to, control);
this.changeTextBlockReferences();
return control;
}
}
public removeTextBlock(groupIndex: number, textBlockIndex: number, negative?: boolean) {
const group = this.getTextBlockFormArrayForGroup(groupIndex, negative);
if (group != null) {
group.removeAt(textBlockIndex);
this.changeTextBlockReferences(); // This method removes all references to the now deleted text block.
return group.length;
}
}
public compareIds(a: string, b: string): number {
const allIds = this.getTextBlockControls().map((control) => control.value?.id);
return allIds.indexOf(a) - allIds.indexOf(b);
}
public getTextBlockControls(): AbstractControl[] {
return arrayJoin(
...this.groups.controls.map((_, index) => this.getTextBlockFormArrayForGroup(index, false)?.controls),
...this.negativeGroups.controls.map((_, index) => this.getTextBlockFormArrayForGroup(index, true)?.controls)
);
}
private changeTextBlockReferences() {
const lookup: { [id: string]: string } = {};
let offset = 0;
this.groups.controls.forEach((_, groupIndex) => {
offset++;
this.getTextBlockFormArrayForGroup(groupIndex).controls.forEach((control, textBlockIndex) => {
const id = this.getTextBlockValue(groupIndex, textBlockIndex).id;
lookup[id] = `${groupIndex + 1}.${textBlockIndex + 1}`;
});
});
this.negativeGroups.controls.forEach((_, groupIndex) => {
this.getTextBlockFormArrayForGroup(groupIndex, true).controls.forEach((control, textBlockIndex) => {
const id = this.getTextBlockValue(groupIndex, textBlockIndex, true).id;
lookup[id] = `${offset + groupIndex + 1}.${textBlockIndex + 1}`;
});
});
this.mapAllTextBlockIds((id) => lookup[id]);
}
private createSelectEntryControl(value: string = this.defaultSelectEntry) {
return new FormControl(value);
}
private createTextBlockGroupControl(group?: IAPITextBlockGroupModel): FormGroup {
group = {
...this.defaultTextBlockGroupModel,
...group
};
return createFormGroup<IAPITextBlockGroupModel>({
groupName: new FormControl(group.groupName),
textBlocks: group.textBlocks.reduce((formArray, textBlock) => {
formArray.push(this.createTextBlockModelControl(textBlock));
return formArray;
}, new FormArray([]))
});
}
private createTextBlockModelControl(textBlock?: IAPITextBlockModel): FormGroup {
textBlock = {
...this.defaultTextBlockModel,
...textBlock
};
return createFormGroup<IAPITextBlockModel>({
id: new FormControl(textBlock.id),
requires: new FormControl(textBlock.requires),
excludes: new FormControl(textBlock.excludes),
text: new FormControl(textBlock.text)
});
}
private patchTextBlockValues(fn: (value: IAPITextBlockModel, index: number) => Partial<IAPITextBlockModel>) {
this.getTextBlockControls().forEach((control, index) => {
control.patchValue(fn(control.value, index));
});
}
private mapAllTextBlockIds(fn: (id: string) => string | null) {
this.patchTextBlockValues((value) => ({
id: fn(value.id),
excludes: filterDistinctValues(arrayJoin(value.excludes).map(fn)),
requires: arrayJoin(value.requires)
.map((rule) => ({
...rule,
ids: filterDistinctValues(arrayJoin(rule.ids).map(fn))
}))
.filter((rule) => rule.ids.length > 0)
}));
}
private insertObjectsForEmptyGroups(negative: boolean) {
const disabled = this.formGroup.disabled;
const value = this.getValue()[negative ? "negativeGroups" : "groups"];
if (value.length === 0) {
this.insertGroup(0, negative);
this.insertObjectsForEmptyGroups(negative);
}
value.forEach((group, groupIndex) => {
if (arrayJoin(group.textBlocks).length === 0) {
this.insertTextBlock(groupIndex, 0, negative);
}
});
this.disable(disabled);
}
}