| /******************************************************************************** |
| * 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); |
| } |
| |
| } |