Showing
6 changed files
with
161 additions
and
49 deletions
@@ -356,7 +356,7 @@ function utf8ToBytes(input: string, units?: number): number[] { | @@ -356,7 +356,7 @@ function utf8ToBytes(input: string, units?: number): number[] { | ||
356 | return bytes; | 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 | if (target === null) { | 360 | if (target === null) { |
361 | return target; | 361 | return target; |
362 | } | 362 | } |
@@ -371,7 +371,9 @@ export function deepClone<T>(target: T): T { | @@ -371,7 +371,9 @@ export function deepClone<T>(target: T): T { | ||
371 | if (typeof target === 'object' && target !== {}) { | 371 | if (typeof target === 'object' && target !== {}) { |
372 | const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any }; | 372 | const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any }; |
373 | Object.keys(cp).forEach(k => { | 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 | return cp as T; | 378 | return cp as T; |
377 | } | 379 | } |
@@ -16,39 +16,25 @@ | @@ -16,39 +16,25 @@ | ||
16 | 16 | ||
17 | import { | 17 | import { |
18 | AfterViewInit, | 18 | AfterViewInit, |
19 | - Component, ElementRef, | ||
20 | - EventEmitter, forwardRef, | 19 | + Component, |
20 | + ComponentRef, | ||
21 | + forwardRef, | ||
21 | Input, | 22 | Input, |
22 | - OnChanges, | 23 | + OnDestroy, |
23 | OnInit, | 24 | OnInit, |
24 | - Output, | ||
25 | - SimpleChanges, | ||
26 | ViewChild, | 25 | ViewChild, |
27 | - Compiler, | ||
28 | - Injector, ComponentRef, OnDestroy | 26 | + ViewContainerRef |
29 | } from '@angular/core'; | 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 | import { RuleChainService } from '@core/http/rule-chain.service'; | 35 | import { RuleChainService } from '@core/http/rule-chain.service'; |
39 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; | 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 | import { TranslateService } from '@ngx-translate/core'; | 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 | import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; | 38 | import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component'; |
53 | 39 | ||
54 | @Component({ | 40 | @Component({ |
@@ -341,9 +341,9 @@ export class RuleChainPageComponent extends PageComponent | @@ -341,9 +341,9 @@ export class RuleChainPageComponent extends PageComponent | ||
341 | this.nextNodeID = 1; | 341 | this.nextNodeID = 1; |
342 | this.nextConnectorID = 1; | 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 | this.inputConnectorId = this.nextConnectorID++; | 348 | this.inputConnectorId = this.nextConnectorID++; |
349 | this.ruleChainModel.nodes.push( | 349 | this.ruleChainModel.nodes.push( |
@@ -535,7 +535,7 @@ export class RuleChainPageComponent extends PageComponent | @@ -535,7 +535,7 @@ export class RuleChainPageComponent extends PageComponent | ||
535 | this.editingRuleNodeLink = null; | 535 | this.editingRuleNodeLink = null; |
536 | this.isEditingRuleNode = true; | 536 | this.isEditingRuleNode = true; |
537 | this.editingRuleNodeIndex = this.ruleChainModel.nodes.indexOf(node); | 537 | this.editingRuleNodeIndex = this.ruleChainModel.nodes.indexOf(node); |
538 | - this.editingRuleNode = deepClone(node); | 538 | + this.editingRuleNode = deepClone(node, ['component']); |
539 | setTimeout(() => { | 539 | setTimeout(() => { |
540 | this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); | 540 | this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); |
541 | }, 0); | 541 | }, 0); |
@@ -576,7 +576,7 @@ export class RuleChainPageComponent extends PageComponent | @@ -576,7 +576,7 @@ export class RuleChainPageComponent extends PageComponent | ||
576 | onRevertRuleNodeEdit() { | 576 | onRevertRuleNodeEdit() { |
577 | this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); | 577 | this.ruleNodeComponent.ruleNodeFormGroup.markAsPristine(); |
578 | const node = this.ruleChainModel.nodes[this.editingRuleNodeIndex]; | 578 | const node = this.ruleChainModel.nodes[this.editingRuleNodeIndex]; |
579 | - this.editingRuleNode = deepClone(node); | 579 | + this.editingRuleNode = deepClone(node, ['component']); |
580 | } | 580 | } |
581 | 581 | ||
582 | onRevertRuleNodeLinkEdit() { | 582 | onRevertRuleNodeLinkEdit() { |
@@ -593,7 +593,7 @@ export class RuleChainPageComponent extends PageComponent | @@ -593,7 +593,7 @@ export class RuleChainPageComponent extends PageComponent | ||
593 | delete this.editingRuleNode.error; | 593 | delete this.editingRuleNode.error; |
594 | } | 594 | } |
595 | this.ruleChainModel.nodes[this.editingRuleNodeIndex] = this.editingRuleNode; | 595 | this.ruleChainModel.nodes[this.editingRuleNodeIndex] = this.editingRuleNode; |
596 | - this.editingRuleNode = deepClone(this.editingRuleNode); | 596 | + this.editingRuleNode = deepClone(this.editingRuleNode, ['component']); |
597 | this.onModelChanged(); | 597 | this.onModelChanged(); |
598 | this.updateRuleNodesHighlight(); | 598 | this.updateRuleNodesHighlight(); |
599 | } | 599 | } |
@@ -33,13 +33,14 @@ | @@ -33,13 +33,14 @@ | ||
33 | <div class="drop-area tb-flow-drop" | 33 | <div class="drop-area tb-flow-drop" |
34 | flowDrop | 34 | flowDrop |
35 | [flow]="flow.flowJs"> | 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 | </div> | 38 | </div> |
39 | </div> | 39 | </div> |
40 | </ng-container> | 40 | </ng-container> |
41 | </div> | 41 | </div> |
42 | <div> | 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 | <div *ngIf="fileName">{{ fileName }}</div> | 45 | <div *ngIf="fileName">{{ fileName }}</div> |
45 | </div> | 46 | </div> |
@@ -14,7 +14,17 @@ | @@ -14,7 +14,17 @@ | ||
14 | /// limitations under the License. | 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 | import { PageComponent } from '@shared/components/page.component'; | 28 | import { PageComponent } from '@shared/components/page.component'; |
19 | import { Store } from '@ngrx/store'; | 29 | import { Store } from '@ngrx/store'; |
20 | import { AppState } from '@core/core.state'; | 30 | import { AppState } from '@core/core.state'; |
@@ -22,6 +32,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | @@ -22,6 +32,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | ||
22 | import { Subscription } from 'rxjs'; | 32 | import { Subscription } from 'rxjs'; |
23 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; | 33 | import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
24 | import { FlowDirective } from '@flowjs/ngx-flow'; | 34 | import { FlowDirective } from '@flowjs/ngx-flow'; |
35 | +import { TranslateService } from '@ngx-translate/core'; | ||
25 | 36 | ||
26 | @Component({ | 37 | @Component({ |
27 | selector: 'tb-file-input', | 38 | selector: 'tb-file-input', |
@@ -35,7 +46,7 @@ import { FlowDirective } from '@flowjs/ngx-flow'; | @@ -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 | @Input() | 51 | @Input() |
41 | label: string; | 52 | label: string; |
@@ -44,6 +55,12 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -44,6 +55,12 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
44 | accept = '*/*'; | 55 | accept = '*/*'; |
45 | 56 | ||
46 | @Input() | 57 | @Input() |
58 | + noFileText = 'import.no-file'; | ||
59 | + | ||
60 | + @Input() | ||
61 | + inputId = 'select'; | ||
62 | + | ||
63 | + @Input() | ||
47 | allowedExtensions: string; | 64 | allowedExtensions: string; |
48 | 65 | ||
49 | @Input() | 66 | @Input() |
@@ -64,9 +81,27 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -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 | @Input() | 96 | @Input() |
68 | disabled: boolean; | 97 | disabled: boolean; |
69 | 98 | ||
99 | + @Input() | ||
100 | + existingFileName: string; | ||
101 | + | ||
102 | + @Output() | ||
103 | + fileNameChanged = new EventEmitter<string>(); | ||
104 | + | ||
70 | fileName: string; | 105 | fileName: string; |
71 | fileContent: any; | 106 | fileContent: any; |
72 | 107 | ||
@@ -77,7 +112,8 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -77,7 +112,8 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
77 | 112 | ||
78 | private propagateChange = null; | 113 | private propagateChange = null; |
79 | 114 | ||
80 | - constructor(protected store: Store<AppState>) { | 115 | + constructor(protected store: Store<AppState>, |
116 | + public translate: TranslateService) { | ||
81 | super(store); | 117 | super(store); |
82 | } | 118 | } |
83 | 119 | ||
@@ -135,11 +171,23 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | @@ -135,11 +171,23 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, | ||
135 | } | 171 | } |
136 | 172 | ||
137 | writeValue(value: any): void { | 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 | private updateModel() { | 188 | private updateModel() { |
142 | this.propagateChange(this.fileContent); | 189 | this.propagateChange(this.fileContent); |
190 | + this.fileNameChanged.emit(this.fileName); | ||
143 | } | 191 | } |
144 | 192 | ||
145 | clearFile() { | 193 | clearFile() { |
@@ -24,10 +24,11 @@ import { ComponentDescriptor, ComponentType } from '@shared/models/component-des | @@ -24,10 +24,11 @@ import { ComponentDescriptor, ComponentType } from '@shared/models/component-des | ||
24 | import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models'; | 24 | import { EntityType, EntityTypeResource } from '@shared/models/entity-type.models'; |
25 | import { Observable } from 'rxjs'; | 25 | import { Observable } from 'rxjs'; |
26 | import { PageComponent } from '@shared/components/page.component'; | 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 | import { RafService } from '@core/services/raf.service'; | 28 | import { RafService } from '@core/services/raf.service'; |
29 | import { Store } from '@ngrx/store'; | 29 | import { Store } from '@ngrx/store'; |
30 | import { AppState } from '@core/core.state'; | 30 | import { AppState } from '@core/core.state'; |
31 | +import { AbstractControl, FormGroup } from '@angular/forms'; | ||
31 | 32 | ||
32 | export enum MsgDataType { | 33 | export enum MsgDataType { |
33 | JSON = 'JSON', | 34 | JSON = 'JSON', |
@@ -83,10 +84,28 @@ export interface IRuleNodeConfigurationComponent { | @@ -83,10 +84,28 @@ export interface IRuleNodeConfigurationComponent { | ||
83 | } | 84 | } |
84 | 85 | ||
85 | export abstract class RuleNodeConfigurationComponent extends PageComponent implements | 86 | export abstract class RuleNodeConfigurationComponent extends PageComponent implements |
86 | - IRuleNodeConfigurationComponent, OnInit { | 87 | + IRuleNodeConfigurationComponent, OnInit, AfterViewInit { |
87 | 88 | ||
88 | ruleNodeId: string; | 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 | configurationChangedEmiter = new EventEmitter<RuleNodeConfiguration>(); | 109 | configurationChangedEmiter = new EventEmitter<RuleNodeConfiguration>(); |
91 | configurationChanged = this.configurationChangedEmiter.asObservable(); | 110 | configurationChanged = this.configurationChangedEmiter.asObservable(); |
92 | 111 | ||
@@ -94,21 +113,77 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple | @@ -94,21 +113,77 @@ export abstract class RuleNodeConfigurationComponent extends PageComponent imple | ||
94 | super(store); | 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 | validate() { | 126 | validate() { |
102 | this.onValidate(); | 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 | protected onValidate() {} | 181 | protected onValidate() {} |
182 | + | ||
183 | + protected abstract configForm(): FormGroup; | ||
184 | + | ||
185 | + protected abstract onConfigurationSet(configuration: RuleNodeConfiguration); | ||
186 | + | ||
112 | } | 187 | } |
113 | 188 | ||
114 | 189 |