Commit 833de6465374f28dcadafcedd9d948e077dc01ee

Authored by Igor Kulikov
1 parent f345092b

UI: RuleChain improvements.

... ... @@ -356,7 +356,7 @@ function utf8ToBytes(input: string, units?: number): number[] {
356 356 return bytes;
357 357 }
358 358
359   -export function deepClone<T>(target: T): T {
  359 +export function deepClone<T>(target: T, ignoreFields?: string[]): T {
360 360 if (target === null) {
361 361 return target;
362 362 }
... ... @@ -371,7 +371,9 @@ export function deepClone<T>(target: T): T {
371 371 if (typeof target === 'object' && target !== {}) {
372 372 const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any };
373 373 Object.keys(cp).forEach(k => {
374   - cp[k] = deepClone<any>(cp[k]);
  374 + if (!ignoreFields || ignoreFields.indexOf(k) === -1) {
  375 + cp[k] = deepClone<any>(cp[k]);
  376 + }
375 377 });
376 378 return cp as T;
377 379 }
... ...
... ... @@ -16,39 +16,25 @@
16 16
17 17 import {
18 18 AfterViewInit,
19   - Component, ElementRef,
20   - EventEmitter, forwardRef,
  19 + Component,
  20 + ComponentRef,
  21 + forwardRef,
21 22 Input,
22   - OnChanges,
  23 + OnDestroy,
23 24 OnInit,
24   - Output,
25   - SimpleChanges,
26 25 ViewChild,
27   - Compiler,
28   - Injector, ComponentRef, OnDestroy
  26 + ViewContainerRef
29 27 } from '@angular/core';
30   -import { PageComponent } from '@shared/components/page.component';
31   -import { Store } from '@ngrx/store';
32   -import { AppState } from '@core/core.state';
33   -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, NgForm, Validators } from '@angular/forms';
34   -import { FcRuleNode, FcRuleEdge } from './rulechain-page.models';
35   -import { RuleNodeType, LinkLabel, RuleNodeDefinition, RuleNodeConfiguration, IRuleNodeConfigurationComponent } from '@shared/models/rule-node.models';
36   -import { EntityType } from '@shared/models/entity-type.models';
37   -import { Observable, of, Subscription } from 'rxjs';
  28 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
  29 +import {
  30 + IRuleNodeConfigurationComponent,
  31 + RuleNodeConfiguration,
  32 + RuleNodeDefinition
  33 +} from '@shared/models/rule-node.models';
  34 +import { Subscription } from 'rxjs';
38 35 import { RuleChainService } from '@core/http/rule-chain.service';
39 36 import { coerceBooleanProperty } from '@angular/cdk/coercion';
40   -import { deepClone } from '@core/utils';
41   -import { EntityAlias } from '@shared/models/alias.models';
42   -import { TruncatePipe } from '@shared/pipe/truncate.pipe';
43   -import { MatChipList, MatAutocomplete, MatChipInputEvent, MatAutocompleteSelectedEvent } from '@angular/material';
44 37 import { TranslateService } from '@ngx-translate/core';
45   -import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
46   -import { catchError, map, mergeMap, share } from 'rxjs/operators';
47   -import { DynamicWidgetComponent } from '@home/components/widget/dynamic-widget.component';
48   -import { SharedModule } from '@shared/shared.module';
49   -import { WidgetComponentsModule } from '@home/components/widget/widget-components.module';
50   -import { DynamicComponentFactoryService } from '@core/services/dynamic-component-factory.service';
51   -import { ViewContainerRef } from '@angular/core';
52 38 import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component';
53 39
54 40 @Component({
... ...
... ... @@ -341,9 +341,9 @@ export class RuleChainPageComponent extends PageComponent
341 341 this.nextNodeID = 1;
342 342 this.nextConnectorID = 1;
343 343
344   - this.selectedObjects.length = 0;
345   - this.ruleChainModel.nodes.length = 0;
346   - this.ruleChainModel.edges.length = 0;
  344 + this.selectedObjects = [];
  345 + this.ruleChainModel.nodes = [];
  346 + this.ruleChainModel.edges = [];
347 347
348 348 this.inputConnectorId = this.nextConnectorID++;
349 349 this.ruleChainModel.nodes.push(
... ... @@ -535,7 +535,7 @@ export class RuleChainPageComponent extends PageComponent
535 535 this.editingRuleNodeLink = null;
536 536 this.isEditingRuleNode = true;
537 537 this.editingRuleNodeIndex = this.ruleChainModel.nodes.indexOf(node);
538   - this.editingRuleNode = deepClone(node);
  538 + this.editingRuleNode = deepClone(node, ['component']);
539 539 setTimeout(() => {
540 540 this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine();
541 541 }, 0);
... ... @@ -576,7 +576,7 @@ export class RuleChainPageComponent extends PageComponent
576 576 onRevertRuleNodeEdit() {
577 577 this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine();
578 578 const node = this.ruleChainModel.nodes[this.editingRuleNodeIndex];
579   - this.editingRuleNode = deepClone(node);
  579 + this.editingRuleNode = deepClone(node, ['component']);
580 580 }
581 581
582 582 onRevertRuleNodeLinkEdit() {
... ... @@ -593,7 +593,7 @@ export class RuleChainPageComponent extends PageComponent
593 593 delete this.editingRuleNode.error;
594 594 }
595 595 this.ruleChainModel.nodes[this.editingRuleNodeIndex] = this.editingRuleNode;
596   - this.editingRuleNode = deepClone(this.editingRuleNode);
  596 + this.editingRuleNode = deepClone(this.editingRuleNode, ['component']);
597 597 this.onModelChanged();
598 598 this.updateRuleNodesHighlight();
599 599 }
... ...
... ... @@ -33,13 +33,14 @@
33 33 <div class="drop-area tb-flow-drop"
34 34 flowDrop
35 35 [flow]="flow.flowJs">
36   - <label for="select">{{ dropLabel }}</label>
37   - <input class="file-input" flowButton [flow]="flow.flowJs" [flowAttributes]="{accept: accept}" id="select">
  36 + <label for="{{inputId}}">{{ dropLabel }}</label>
  37 + <input class="file-input" flowButton [flow]="flow.flowJs" [flowAttributes]="{accept: accept}" id="{{inputId}}">
38 38 </div>
39 39 </div>
40 40 </ng-container>
41 41 </div>
42 42 <div>
43   - <div *ngIf="!fileName" translate>import.no-file</div>
  43 + <tb-error *ngIf="!fileName && required && requiredAsError" error="{{ noFileText | translate }}"></tb-error>
  44 + <div *ngIf="!fileName && !requiredAsError" translate>{{ noFileText }}</div>
44 45 <div *ngIf="fileName">{{ fileName }}</div>
45 46 </div>
... ...
... ... @@ -14,7 +14,17 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { AfterViewInit, Component, forwardRef, Input, OnDestroy, ViewChild } from '@angular/core';
  17 +import {
  18 + AfterViewInit,
  19 + Component,
  20 + EventEmitter,
  21 + forwardRef,
  22 + Input,
  23 + OnChanges,
  24 + OnDestroy,
  25 + Output, SimpleChanges,
  26 + ViewChild
  27 +} from '@angular/core';
18 28 import { PageComponent } from '@shared/components/page.component';
19 29 import { Store } from '@ngrx/store';
20 30 import { AppState } from '@core/core.state';
... ... @@ -22,6 +32,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
22 32 import { Subscription } from 'rxjs';
23 33 import { coerceBooleanProperty } from '@angular/cdk/coercion';
24 34 import { FlowDirective } from '@flowjs/ngx-flow';
  35 +import { TranslateService } from '@ngx-translate/core';
25 36
26 37 @Component({
27 38 selector: 'tb-file-input',
... ... @@ -35,7 +46,7 @@ import { FlowDirective } from '@flowjs/ngx-flow';
35 46 }
36 47 ]
37 48 })
38   -export class FileInputComponent extends PageComponent implements AfterViewInit, OnDestroy, ControlValueAccessor {
  49 +export class FileInputComponent extends PageComponent implements AfterViewInit, OnDestroy, ControlValueAccessor, OnChanges {
39 50
40 51 @Input()
41 52 label: string;
... ... @@ -44,6 +55,12 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
44 55 accept = '*/*';
45 56
46 57 @Input()
  58 + noFileText = 'import.no-file';
  59 +
  60 + @Input()
  61 + inputId = 'select';
  62 +
  63 + @Input()
47 64 allowedExtensions: string;
48 65
49 66 @Input()
... ... @@ -64,9 +81,27 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
64 81 }
65 82 }
66 83
  84 + private requiredAsErrorValue: boolean;
  85 + get requiredAsError(): boolean {
  86 + return this.requiredAsErrorValue;
  87 + }
  88 + @Input()
  89 + set requiredAsError(value: boolean) {
  90 + const newVal = coerceBooleanProperty(value);
  91 + if (this.requiredAsErrorValue !== newVal) {
  92 + this.requiredAsErrorValue = newVal;
  93 + }
  94 + }
  95 +
67 96 @Input()
68 97 disabled: boolean;
69 98
  99 + @Input()
  100 + existingFileName: string;
  101 +
  102 + @Output()
  103 + fileNameChanged = new EventEmitter<string>();
  104 +
70 105 fileName: string;
71 106 fileContent: any;
72 107
... ... @@ -77,7 +112,8 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
77 112
78 113 private propagateChange = null;
79 114
80   - constructor(protected store: Store<AppState>) {
  115 + constructor(protected store: Store<AppState>,
  116 + public translate: TranslateService) {
81 117 super(store);
82 118 }
83 119
... ... @@ -135,11 +171,23 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
135 171 }
136 172
137 173 writeValue(value: any): void {
138   - this.fileName = null;
  174 + this.fileName = this.existingFileName || null;
  175 + }
  176 +
  177 + ngOnChanges(changes: SimpleChanges): void {
  178 + for (const propName of Object.keys(changes)) {
  179 + const change = changes[propName];
  180 + if (change.currentValue !== change.previousValue) {
  181 + if (propName === 'existingFileName') {
  182 + this.fileName = this.existingFileName || null;
  183 + }
  184 + }
  185 + }
139 186 }
140 187
141 188 private updateModel() {
142 189 this.propagateChange(this.fileContent);
  190 + this.fileNameChanged.emit(this.fileName);
143 191 }
144 192
145 193 clearFile() {
... ...
... ... @@ -24,10 +24,11 @@ import { ComponentDescriptor, ComponentType } from '@shared/models/component-des
24 24 import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models';
25 25 import { Observable } from 'rxjs';
26 26 import { PageComponent } from '@shared/components/page.component';
27   -import { ComponentFactory, EventEmitter, Inject, OnDestroy, OnInit } from '@angular/core';
  27 +import { AfterViewInit, ComponentFactory, EventEmitter, Inject, OnDestroy, OnInit } from '@angular/core';
28 28 import { RafService } from '@core/services/raf.service';
29 29 import { Store } from '@ngrx/store';
30 30 import { AppState } from '@core/core.state';
  31 +import { AbstractControl, FormGroup } from '@angular/forms';
31 32
32 33 export enum MsgDataType {
33 34 JSON = 'JSON',
... ... @@ -83,10 +84,28 @@ export interface IRuleNodeConfigurationComponent {
83 84 }
84 85
85 86 export abstract class RuleNodeConfigurationComponent extends PageComponent implements
86   - IRuleNodeConfigurationComponent, OnInit {
  87 + IRuleNodeConfigurationComponent, OnInit, AfterViewInit {
87 88
88 89 ruleNodeId: string;
89   - configuration: RuleNodeConfiguration;
  90 +
  91 + configurationValue: RuleNodeConfiguration;
  92 +
  93 + private configurationSet = false;
  94 +
  95 + set configuration(value: RuleNodeConfiguration) {
  96 + this.configurationValue = value;
  97 + if (!this.configurationSet) {
  98 + this.configurationSet = true;
  99 + this.setupConfiguration(value);
  100 + } else {
  101 + this.updateConfiguration(value);
  102 + }
  103 + }
  104 +
  105 + get configuration(): RuleNodeConfiguration {
  106 + return this.configurationValue;
  107 + }
  108 +
90 109 configurationChangedEmiter = new EventEmitter<RuleNodeConfiguration>();
91 110 configurationChanged = this.configurationChangedEmiter.asObservable();
92 111
... ... @@ -94,21 +113,77 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple
94 113 super(store);
95 114 }
96 115
97   - ngOnInit() {
98   - this.onConfigurationSet(this.configuration);
  116 + ngOnInit() {}
  117 +
  118 + ngAfterViewInit(): void {
  119 + setTimeout(() => {
  120 + if (!this.validateConfig()) {
  121 + this.configurationChangedEmiter.emit(null);
  122 + }
  123 + }, 0);
99 124 }
100 125
101 126 validate() {
102 127 this.onValidate();
103 128 }
104 129
105   - protected abstract onConfigurationSet(configuration: RuleNodeConfiguration);
  130 + protected setupConfiguration(configuration: RuleNodeConfiguration) {
  131 + this.onConfigurationSet(this.prepareInputConfig(configuration));
  132 + this.updateValidators(false);
  133 + for (const trigger of this.validatorTriggers()) {
  134 + const path = trigger.split('.');
  135 + let control: AbstractControl = this.configForm();
  136 + for (const part of path) {
  137 + control = control.get(part);
  138 + }
  139 + control.valueChanges.subscribe(() => {
  140 + this.updateValidators(true);
  141 + });
  142 + }
  143 + this.configForm().valueChanges.subscribe((updated: RuleNodeConfiguration) => {
  144 + this.onConfigurationChanged(updated);
  145 + });
  146 + }
  147 +
  148 + protected updateConfiguration(configuration: RuleNodeConfiguration) {
  149 + this.configForm().reset(this.prepareInputConfig(configuration), {emitEvent: false});
  150 + this.updateValidators(false);
  151 + }
  152 +
  153 + protected updateValidators(emitEvent: boolean) {
  154 + }
  155 +
  156 + protected validatorTriggers(): string[] {
  157 + return [];
  158 + }
  159 +
  160 + protected onConfigurationChanged(updated: RuleNodeConfiguration) {
  161 + this.configurationValue = updated;
  162 + if (this.validateConfig()) {
  163 + this.configurationChangedEmiter.emit(this.prepareOutputConfig(updated));
  164 + } else {
  165 + this.configurationChangedEmiter.emit(null);
  166 + }
  167 + }
106 168
107   - protected notifyConfigurationUpdated(configuration: RuleNodeConfiguration) {
108   - this.configurationChangedEmiter.emit(configuration);
  169 + protected prepareInputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration {
  170 + return configuration;
  171 + }
  172 +
  173 + protected prepareOutputConfig(configuration: RuleNodeConfiguration): RuleNodeConfiguration {
  174 + return configuration;
  175 + }
  176 +
  177 + protected validateConfig(): boolean {
  178 + return this.configForm().valid;
109 179 }
110 180
111 181 protected onValidate() {}
  182 +
  183 + protected abstract configForm(): FormGroup;
  184 +
  185 + protected abstract onConfigurationSet(configuration: RuleNodeConfiguration);
  186 +
112 187 }
113 188
114 189
... ...