import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { TypeaheadSelectOption, TypeToken } from '@yourcause/common'; import { I18nService } from '@yourcause/common/i18n'; import { ConstructiveFormulaStep, DestructiveFormulaStep, FormulaEvaluationType, FormulaStep, FormulaStepValue, FormulaStepValueType } from '../formula-builder.typing'; @Component({ selector: 'gc-formula-builder-step', templateUrl: './formula-builder-step.component.html', styleUrls: ['./formula-builder-step.component.scss'] }) export class FormulaBuilderStepComponent implements OnInit { @Input() step: FormulaStep; @Input() columns: { label: string; value: string; }[]; @Input() currentDepth: number; @Input() maxDepth: number; @Output() stepChange = new EventEmitter>(); @Output() removeStep = new EventEmitter(); @Output() validityChanged = new EventEmitter(); FormulaStepValueType = FormulaStepValueType; FormulaEvaluationType = FormulaEvaluationType; nestedEvaluationOptions: TypeaheadSelectOption[] = [{ label: '+', value: FormulaEvaluationType.Add }, { label: '-', value: FormulaEvaluationType.Subtract }, { label: '÷', value: FormulaEvaluationType.Divide }, { label: '•', value: FormulaEvaluationType.Multiply }]; evaluationOptions: TypeaheadSelectOption[] = [{ label: this.i18n.translate('common:textAdded', {}, 'Added'), value: FormulaEvaluationType.Add }, { label: this.i18n.translate('common:textSubtracted', {}, 'Subtracted'), value: FormulaEvaluationType.Subtract }, { label: this.i18n.translate('common:textDivided', {}, 'Divided'), value: FormulaEvaluationType.Divide }, { label: this.i18n.translate('common:textMultiplied', {}, 'Multiplied'), value: FormulaEvaluationType.Multiply }, { label: this.i18n.translate('common:textAveraged', {}, 'Averaged'), value: FormulaEvaluationType.Average }]; valueTypeOptions: TypeaheadSelectOption[] = [{ label: this.i18n.translate('common:textValueOfComponent', {}, 'Value of component'), value: FormulaStepValueType.ParentValue }, { label: this.i18n.translate('common:textNumericValue', {}, 'Numeric value'), value: FormulaStepValueType.Fixed }]; shouldShowRemoveButton: boolean; shouldShowAddButton: boolean; isDestructive: boolean; nestedStepValidity: Record = {}; $formulaStepType = new TypeToken>(); $number = new TypeToken(); constructor ( private i18n: I18nService ) { } ngOnInit () { this.determineButtonVisibility(); this.determineIsDestructive(this.step.type); this.emitValidity(); } determineButtonVisibility () { this.shouldShowRemoveButton = this.step.values.length > 2; this.shouldShowAddButton = !this.isDestructive; } updateEvaluationType (type: FormulaEvaluationType) { this.step.type = type; const wasDestructive = this.isDestructive; this.determineIsDestructive(type); // if going to an operation that only supports 2 values, and we had more than 2 values, we need to reset if (!wasDestructive && this.isDestructive && (this.step.values.length > 2)) { this.step.values = [{ type: FormulaStepValueType.Fixed, value: 0 }, { type: FormulaStepValueType.Fixed, value: 0 }]; } this.determineButtonVisibility(); this.handleStepChange(); } private determineIsDestructive (type: FormulaEvaluationType) { this.isDestructive = ( type === FormulaEvaluationType.Subtract || type === FormulaEvaluationType.Divide ); } private addValue (value: FormulaStepValue) { const constructiveStep = this.step as ConstructiveFormulaStep; this.step = { ...constructiveStep, values: [ ...constructiveStep.values, value ] }; this.determineButtonVisibility(); this.handleStepChange(); } addFixedValue () { this.addValue({ type: FormulaStepValueType.Fixed, value: 0 }); } addNestedOperation () { const nestedOperation = { type: FormulaEvaluationType.Add, values: [ { type: FormulaStepValueType.Fixed, value: 0 }, { type: FormulaStepValueType.Fixed, value: 0 } ] } as FormulaStep; this.addValue({ type: FormulaStepValueType.NestedStep, value: nestedOperation }); } updateValueType (newType: FormulaStepValueType, index: number) { const value = this.step.values[index]; value.type = newType; switch (newType) { case FormulaStepValueType.Fixed: value.value = 0; break; case FormulaStepValueType.ParentValue: value.value = null; break; } this.determineButtonVisibility(); this.handleStepChange(); } swapOrder () { this.step = { ...this.step, values: [...this.step.values].reverse() } as DestructiveFormulaStep; this.handleStepChange(); } updateValue (newValue: number|string|FormulaStep, index: number) { this.step.values[index].value = newValue as number; this.determineButtonVisibility(); this.handleStepChange(); } removeValue (index: number) { this.step.values = [ ...this.step.values.slice(0, index), ...this.step.values.slice(index + 1) ]; this.determineButtonVisibility(); this.handleStepChange(); } nestedValidityChange (valid: boolean, index: number) { this.nestedStepValidity[index] = valid; this.emitValidity(); } handleStepChange () { this.stepChange.emit(this.step); this.emitValidity(); } emitValidity () { const values: FormulaStepValue[] = this.step.values; const allValuesValid = values.reduce((valid: boolean, value: FormulaStepValue, index: number) => { return valid && this.checkValueValidity(value as any, index as any); }, true); const isValid = allValuesValid && values.length > 0; this.validityChanged.emit(isValid); } private checkValueValidity (value: FormulaStepValue, index: number): boolean { const val = value.value; switch (value.type) { case FormulaStepValueType.Fixed: return typeof (val) === 'number'; case FormulaStepValueType.ParentValue: return this.columns.some(column => (column.value as string) === (val as string)); case FormulaStepValueType.NestedStep: return !!this.nestedStepValidity[index]; } } }