Browse Source

UI: Implement SCADA symbol properties section.

pull/11391/head
Igor Kulikov 2 years ago
parent
commit
1aca952eca
  1. 6
      ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts
  2. 2
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html
  3. 70
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts
  4. 2
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.html
  5. 36
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts
  6. 22
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts
  7. 5
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html
  8. 1
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts
  9. 63
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.html
  10. 43
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.scss
  11. 207
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.ts
  12. 184
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html
  13. 49
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.scss
  14. 129
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.ts
  15. 49
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html
  16. 26
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.scss
  17. 280
      ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.ts
  18. 3
      ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts
  19. 19
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
ui-ngx/src/app/modules/home/components/widget/lib/svg/iot-svg.models.ts

@ -145,6 +145,12 @@ export const iotSvgPropertyTypeTranslations = new Map<IotSvgPropertyType, string
]
);
export const iotSvgPropertyRowClasses =
['column', 'column-xs', 'column-lt-md', 'align-start', 'no-border', 'no-gap', 'no-padding', 'same-padding'];
export const iotSvgPropertyFieldClasses =
['medium-width', 'flex', 'flex-xs', 'flex-lt-md'];
export interface IotSvgPropertyBase {
id: string;
name: string;

2
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component.html

@ -50,7 +50,7 @@
<div class="fixed-title-width" translate>scada.behavior.value-type</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex tb-value-type">
<mat-select formControlName="valueType">
<mat-select-trigger>
<mat-select-trigger *ngIf="behaviorFormGroup.get('valueType').value">
<div class="tb-value-type-row">
<mat-icon class="tb-mat-18" svgIcon="{{ valueTypesMap.get(behaviorFormGroup.get('valueType').value).icon }}"></mat-icon>
<span>{{ valueTypesMap.get(behaviorFormGroup.get('valueType').value).name | translate }}</span>

70
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component.ts

@ -30,11 +30,11 @@ import {
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
ControlValueAccessor, NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormBuilder, UntypedFormControl,
UntypedFormGroup,
ValidationErrors,
ValidationErrors, Validator, ValidatorFn,
Validators
} from '@angular/forms';
import {
@ -50,6 +50,9 @@ import {
ScadaSymbolBehaviorPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component';
import { ValueToDataType } from '@shared/models/action-widget-settings.models';
import {
ScadaSymbolBehaviorsComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component';
export const behaviorValid = (behavior: IotSvgBehavior): boolean => {
if (!behavior.id || !behavior.name || !behavior.type) {
@ -80,16 +83,6 @@ export const behaviorValid = (behavior: IotSvgBehavior): boolean => {
return true;
};
export const behaviorValidator = (control: AbstractControl): ValidationErrors | null => {
const behavior: IotSvgBehavior = control.value;
if (!behaviorValid(behavior)) {
return {
behavior: true
};
}
return null;
};
@Component({
selector: 'tb-scada-symbol-metadata-behavior-row',
templateUrl: './scada-symbol-behavior-row.component.html',
@ -99,11 +92,16 @@ export const behaviorValidator = (control: AbstractControl): ValidationErrors |
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolBehaviorRowComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ScadaSymbolBehaviorRowComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, OnInit {
export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, OnInit, Validator {
@ViewChild('idInput')
idInput: ElementRef<HTMLInputElement>;
@ -117,6 +115,9 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
@Input()
disabled: boolean;
@Input()
index: number;
@Output()
behaviorRemoved = new EventEmitter();
@ -130,12 +131,13 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
private cd: ChangeDetectorRef,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef) {
private viewContainerRef: ViewContainerRef,
private behaviorsComponent: ScadaSymbolBehaviorsComponent) {
}
ngOnInit() {
this.behaviorRowFormGroup = this.fb.group({
id: [null, [Validators.required]],
id: [null, [this.behaviorIdValidator()]],
name: [null, [Validators.required]],
type: [null, [Validators.required]]
});
@ -223,6 +225,42 @@ export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, On
this.editBehavior(null, this.editButton, true, onCanceled);
}
public validate(c: UntypedFormControl) {
const idControl = this.behaviorRowFormGroup.get('id');
if (idControl.hasError('behaviorIdNotUnique')) {
idControl.updateValueAndValidity({onlySelf: false, emitEvent: false});
}
if (idControl.hasError('behaviorIdNotUnique')) {
this.behaviorRowFormGroup.get('id').markAsTouched();
return {
behaviorIdNotUnique: true
};
}
const behavior: IotSvgBehavior = {...this.modelValue, ...this.behaviorRowFormGroup.value};
if (!behaviorValid(behavior)) {
return {
behavior: true
};
}
return null;
}
private behaviorIdValidator(): ValidatorFn {
return control => {
if (!control.value) {
return {
required: true
};
}
if (!this.behaviorsComponent.behaviorIdUnique(control.value, this.index)) {
return {
behaviorIdNotUnique: true
};
}
return null;
};
}
private onTypeChanged(newType: IotSvgBehaviorType) {
const prevType = this.modelValue.type;
this.modelValue = {...this.modelValue, ...{type: newType}};

2
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.html

@ -31,6 +31,7 @@
class="tb-draggable-form-table-row"
*ngFor="let behaviorControl of behaviorsFormArray().controls; trackBy: trackByBehavior; let $index = index;">
<tb-scada-symbol-metadata-behavior-row fxFlex
[index]="$index"
[formControl]="behaviorControl"
(behaviorRemoved)="removeBehavior($index)">
</tb-scada-symbol-metadata-behavior-row>
@ -46,6 +47,7 @@
</div>
</div>
</div>
<tb-error *ngIf="errorText" noMargin [error]="errorText" style="padding-left: 12px;"></tb-error>
</div>
<div>
<button type="button" mat-stroked-button color="primary" (click)="addBehavior()">

36
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-behaviors.component.ts

@ -40,10 +40,10 @@ import { ValueType } from '@shared/models/constants';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import {
behaviorValid,
behaviorValidator,
ScadaSymbolBehaviorRowComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behavior-row.component';
import { ValueToDataType } from '@shared/models/action-widget-settings.models';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'tb-scada-symbol-metadata-behaviors',
@ -76,13 +76,16 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
behaviorsFormGroup: UntypedFormGroup;
errorText = '';
get dragEnabled(): boolean {
return this.behaviorsFormArray().controls.length > 1;
}
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder) {
constructor(private fb: UntypedFormBuilder,
private translate: TranslateService) {
}
ngOnInit() {
@ -122,7 +125,17 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
}
public validate(c: UntypedFormControl) {
const valid = this.behaviorsFormGroup.valid;
this.errorText = '';
const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray;
const notUniqueControls =
behaviorsArray.controls.filter(control => control.hasError('behaviorIdNotUnique'));
for (const control of notUniqueControls) {
control.updateValueAndValidity({onlySelf: false, emitEvent: false});
if (control.hasError('behaviorIdNotUnique')) {
this.errorText = this.translate.instant('scada.behavior.not-unique-behavior-ids-error');
}
}
const valid = this.behaviorsFormGroup.valid;
return valid ? null : {
behaviors: {
valid: false,
@ -130,6 +143,19 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
};
}
public behaviorIdUnique(id: string, index: number): boolean {
const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray;
for (let i = 0; i < behaviorsArray.controls.length; i++) {
if (i !== index) {
const otherControl = behaviorsArray.controls[i];
if (id === otherControl.value.id) {
return false;
}
}
}
return true;
}
behaviorDrop(event: CdkDragDrop<string[]>) {
const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray;
const behavior = behaviorsArray.at(event.previousIndex);
@ -161,7 +187,7 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
valueToDataFunction: ''
};
const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray;
const behaviorControl = this.fb.control(behavior, [behaviorValidator]);
const behaviorControl = this.fb.control(behavior, []);
behaviorsArray.push(behaviorControl);
setTimeout(() => {
const behaviorRow = this.behaviorRows.get(this.behaviorRows.length-1);
@ -175,7 +201,7 @@ export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnIn
const behaviorsControls: Array<AbstractControl> = [];
if (behaviors) {
behaviors.forEach((behavior) => {
behaviorsControls.push(this.fb.control(behavior, [behaviorValidator]));
behaviorsControls.push(this.fb.control(behavior, []));
});
}
return this.fb.array(behaviorsControls);

22
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata-components.module.ts

@ -20,7 +20,9 @@ import { SharedModule } from '@shared/shared.module';
import {
ScadaSymbolMetadataTagComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tag.component';
import { ScadaSymbolMetadataComponent } from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component';
import {
ScadaSymbolMetadataComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component';
import {
ScadaSymbolMetadataTagsComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-metadata-tags.component';
@ -33,6 +35,16 @@ import {
import {
ScadaSymbolBehaviorPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-behavior-panel.component';
import {
ScadaSymbolPropertiesComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-properties.component';
import {
ScadaSymbolPropertyRowComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component';
import {
ScadaSymbolPropertyPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component';
import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings/common/widget-settings-common.module';
@NgModule({
declarations:
@ -42,11 +54,15 @@ import {
ScadaSymbolMetadataTagsComponent,
ScadaSymbolBehaviorsComponent,
ScadaSymbolBehaviorRowComponent,
ScadaSymbolBehaviorPanelComponent
ScadaSymbolBehaviorPanelComponent,
ScadaSymbolPropertiesComponent,
ScadaSymbolPropertyRowComponent,
ScadaSymbolPropertyPanelComponent
],
imports: [
CommonModule,
SharedModule
SharedModule,
WidgetSettingsCommonModule
],
exports: [
ScadaSymbolMetadataComponent

5
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.html

@ -63,5 +63,10 @@
formControlName="behavior">
</tb-scada-symbol-metadata-behaviors>
</div>
<div [fxShow]="selectedOption === 'properties'" class="mat-content overflow-hidden">
<tb-scada-symbol-metadata-properties
formControlName="properties">
</tb-scada-symbol-metadata-properties>
</div>
</div>
</div>

1
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-metadata.component.ts

@ -128,7 +128,6 @@ export class ScadaSymbolMetadataComponent extends PageComponent implements OnIni
}
writeValue(value: IotSvgMetadata): void {
this.selectedOption = 'general';
this.modelValue = value;
this.metadataFormGroup.patchValue(
value, {emitEvent: false}

63
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.html

@ -0,0 +1,63 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-form-panel tb-scada-symbol-properties">
<div class="tb-form-table">
<div class="tb-form-table-header no-padding-right">
<div class="tb-form-table-header-cell tb-id-header" translate>scada.property.id</div>
<div class="tb-form-table-header-cell tb-name-header" translate>scada.property.name</div>
<div class="tb-form-table-header-cell tb-type-header" translate>scada.property.type</div>
<div class="tb-form-table-header-cell tb-actions-header"></div>
</div>
<div *ngIf="propertiesFormArray().controls.length; else noProperties" class="tb-form-table-body tb-drop-list"
cdkDropList cdkDropListOrientation="vertical"
[cdkDropListDisabled]="!dragEnabled"
(cdkDropListDropped)="propertyDrop($event)">
<div cdkDrag [cdkDragDisabled]="!dragEnabled"
class="tb-draggable-form-table-row"
*ngFor="let propertyControl of propertiesFormArray().controls; trackBy: trackByProperty; let $index = index;">
<tb-scada-symbol-metadata-property-row fxFlex
[index]="$index"
[booleanPropertyIds]="booleanPropertyIds"
[formControl]="propertyControl"
(propertyRemoved)="removeProperty($index)">
</tb-scada-symbol-metadata-property-row>
<div class="tb-form-table-row-cell-buttons">
<button mat-icon-button
type="button"
cdkDragHandle
[ngClass]="{'tb-hidden': !dragEnabled}"
matTooltip="{{ 'action.drag' | translate }}"
matTooltipPosition="above">
<mat-icon>drag_indicator</mat-icon>
</button>
</div>
</div>
</div>
<tb-error *ngIf="errorText" noMargin [error]="errorText" style="padding-left: 12px;"></tb-error>
</div>
<div>
<button type="button" mat-stroked-button color="primary" (click)="addProperty()">
{{ 'scada.property.add-property' | translate }}
</button>
</div>
</div>
<ng-template #noProperties>
<span fxLayoutAlign="center center"
class="tb-prompt">{{ 'scada.property.no-properties' | translate }}</span>
</ng-template>

43
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.scss

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-scada-symbol-properties {
flex: 1;
margin: 12px;
.tb-form-table-header-cell {
&.tb-id-header {
flex: 1 1 40%;
}
&.tb-name-header {
flex: 1 1 40%;
}
&.tb-type-header {
flex: 1 1 20%;
}
&.tb-actions-header {
width: 120px;
min-width: 120px;
}
}
.tb-form-table {
overflow: hidden;
}
.tb-form-table-body {
overflow: auto;
tb-scada-symbol-metadata-property-row {
overflow: hidden;
}
}
}

207
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-properties.component.ts

@ -0,0 +1,207 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
Component,
forwardRef,
HostBinding,
Input,
OnInit,
QueryList,
ViewChildren,
ViewEncapsulation
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormArray,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validator
} from '@angular/forms';
import { IotSvgProperty, IotSvgPropertyType } from '@home/components/widget/lib/svg/iot-svg.models';
import { CdkDragDrop } from '@angular/cdk/drag-drop';
import {
propertyValid,
ScadaSymbolPropertyRowComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'tb-scada-symbol-metadata-properties',
templateUrl: './scada-symbol-properties.component.html',
styleUrls: ['./scada-symbol-properties.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolPropertiesComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ScadaSymbolPropertiesComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolPropertiesComponent implements ControlValueAccessor, OnInit, Validator {
@HostBinding('style.display') styleDisplay = 'flex';
@HostBinding('style.overflow') styleOverflow = 'hidden';
@ViewChildren(ScadaSymbolPropertyRowComponent)
propertyRows: QueryList<ScadaSymbolPropertyRowComponent>;
@Input()
disabled: boolean;
booleanPropertyIds: string[] = [];
propertiesFormGroup: UntypedFormGroup;
errorText = '';
get dragEnabled(): boolean {
return this.propertiesFormArray().controls.length > 1;
}
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private translate: TranslateService) {
}
ngOnInit() {
this.propertiesFormGroup = this.fb.group({
properties: this.fb.array([])
});
this.propertiesFormGroup.valueChanges.subscribe(
() => {
let properties: IotSvgProperty[] = this.propertiesFormGroup.get('properties').value;
if (properties) {
properties = properties.filter(p => propertyValid(p));
}
this.booleanPropertyIds = properties.filter(p => p.type === IotSvgPropertyType.switch).map(p => p.id);
this.propagateChange(properties);
}
);
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.propertiesFormGroup.disable({emitEvent: false});
} else {
this.propertiesFormGroup.enable({emitEvent: false});
}
}
writeValue(value: IotSvgProperty[] | undefined): void {
const properties= value || [];
this.propertiesFormGroup.setControl('properties', this.preparePropertiesFormArray(properties), {emitEvent: false});
this.booleanPropertyIds = properties.filter(p => p.type === IotSvgPropertyType.switch).map(p => p.id);
}
public validate(c: UntypedFormControl) {
this.errorText = '';
const propertiesArray = this.propertiesFormGroup.get('properties') as UntypedFormArray;
const notUniqueControls =
propertiesArray.controls.filter(control => control.hasError('propertyIdNotUnique'));
for (const control of notUniqueControls) {
control.updateValueAndValidity({onlySelf: false, emitEvent: false});
if (control.hasError('propertyIdNotUnique')) {
this.errorText = this.translate.instant('scada.property.not-unique-property-ids-error');
}
}
const valid = this.propertiesFormGroup.valid;
return valid ? null : {
properties: {
valid: false,
},
};
}
public propertyIdUnique(id: string, index: number): boolean {
const propertiesArray = this.propertiesFormGroup.get('properties') as UntypedFormArray;
for (let i = 0; i < propertiesArray.controls.length; i++) {
if (i !== index) {
const otherControl = propertiesArray.controls[i];
if (id === otherControl.value.id) {
return false;
}
}
}
return true;
}
propertyDrop(event: CdkDragDrop<string[]>) {
const propertiesArray = this.propertiesFormGroup.get('properties') as UntypedFormArray;
const property = propertiesArray.at(event.previousIndex);
propertiesArray.removeAt(event.previousIndex);
propertiesArray.insert(event.currentIndex, property);
}
propertiesFormArray(): UntypedFormArray {
return this.propertiesFormGroup.get('properties') as UntypedFormArray;
}
trackByProperty(index: number, propertyControl: AbstractControl): any {
return propertyControl;
}
removeProperty(index: number, emitEvent = true) {
(this.propertiesFormGroup.get('properties') as UntypedFormArray).removeAt(index, {emitEvent});
}
addProperty() {
const property: IotSvgProperty = {
id: '',
name: '',
type: IotSvgPropertyType.text,
default: ''
};
const propertiesArray = this.propertiesFormGroup.get('properties') as UntypedFormArray;
const propertyControl = this.fb.control(property, []);
propertiesArray.push(propertyControl);
setTimeout(() => {
const propertyRow = this.propertyRows.get(this.propertyRows.length-1);
propertyRow.onAdd(() => {
this.removeProperty(propertiesArray.length-1);
});
});
}
private preparePropertiesFormArray(properties: IotSvgProperty[] | undefined): UntypedFormArray {
const propertiesControls: Array<AbstractControl> = [];
if (properties) {
properties.forEach((property) => {
propertiesControls.push(this.fb.control(property, []));
});
}
return this.fb.array(propertiesControls);
}
}

184
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.html

@ -0,0 +1,184 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-scada-symbol-property-settings-panel" [formGroup]="propertyFormGroup">
<div class="tb-scada-symbol-property-settings-title">{{ panelTitle | translate }}</div>
<div class="tb-scada-symbol-property-settings-panel-content">
<div class="tb-form-row">
<div class="fixed-title-width" translate>scada.property.id</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input required matInput formControlName="id" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>scada.property.name</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input required matInput formControlName="name" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>scada.property.type</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgPropertyTypes" [value]="type">
{{ iotSvgPropertyTypeTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" translate>scada.property.default-value</div>
<mat-form-field *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.text"
class="flex"
appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="default" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-color-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.color"
asBoxInput
colorClearButton
formControlName="default">
</tb-color-input>
<tb-color-settings *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.color_settings"
formControlName="default"
settingsKey="{{ propertyFormGroup.get('id').value }}">
</tb-color-settings>
<mat-form-field *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.number"
appearance="outline"
class="number"
subscriptSizing="dynamic">
<input matInput formControlName="default"
[min]="propertyFormGroup.get('min').value"
[max]="propertyFormGroup.get('max').value"
[step]="propertyFormGroup.get('step').value"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.font"
formControlName="default"
clearButton
disabledLineHeight>
</tb-font-settings>
<tb-unit-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.units"
formControlName="default"></tb-unit-input>
<tb-value-input *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.switch"
[valueType]="ValueType.BOOLEAN"
formControlName="default"></tb-value-input>
</div>
<div *ngIf="propertyFormGroup.get('type').value === IotSvgPropertyType.number" class="tb-form-row space-between">
<div translate>scada.property.number-settings</div>
<div fxLayout="row" fxFlex fxLayoutAlign="end center" fxLayoutGap="8px">
<div class="tb-small-label" translate>scada.property.min</div>
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput formControlName="min"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-divider vertical></mat-divider>
<div class="tb-small-label" translate>scada.property.max</div>
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput formControlName="max"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-divider vertical></mat-divider>
<div class="tb-small-label" translate>scada.property.step</div>
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
<input matInput formControlName="step" min="0"
type="number" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="required">
{{ 'scada.property.value-required' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-panel stroked">
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
{{ 'scada.property.advanced-ui-settings' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row space-between">
<div class="fixed-title-width" translate>scada.property.sub-label</div>
<mat-form-field class="flex"
appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="subLabel" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="divider">
{{ 'scada.property.vertical-divider-after' | translate }}
</mat-slide-toggle>
</div>
<div *ngIf="[IotSvgPropertyType.number, IotSvgPropertyType.text].includes(propertyFormGroup.get('type').value)"
class="tb-form-row space-between">
<div class="fixed-title-width" translate>scada.property.input-field-suffix</div>
<mat-form-field class="flex"
appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="fieldSuffix" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" translate>scada.property.disable-on-property</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="disableOnProperty" placeholder="{{ 'widget-config.set' | translate }}">
<mat-option *ngFor="let prop of booleanPropertyIds" [value]="prop">
{{ prop }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" translate>scada.property.property-row-classes</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="rowClass" multiple placeholder="{{ 'widget-config.set' | translate }}">
<mat-option *ngFor="let clazz of iotSvgPropertyRowClasses" [value]="clazz">
{{ clazz }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="propertyFormGroup.get('type').value !== IotSvgPropertyType.switch" class="tb-form-row">
<div class="fixed-title-width" translate>scada.property.property-field-classes</div>
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="fieldClass" multiple placeholder="{{ 'widget-config.set' | translate }}">
<mat-option *ngFor="let clazz of iotSvgPropertyFieldClasses" [value]="clazz">
{{ clazz }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</div>
</div>
<div class="tb-scada-symbol-property-settings-panel-buttons">
<button mat-button
color="primary"
type="button"
(click)="cancel()">
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button
color="primary"
type="button"
(click)="applyPropertySettings()"
[disabled]="propertyFormGroup.invalid || !propertyFormGroup.dirty">
{{ (isAdd ? 'action.add' : 'action.apply') | translate }}
</button>
</div>
</div>

49
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.scss

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../../scss/constants';
.tb-scada-symbol-property-settings-panel {
width: 540px;
display: flex;
flex-direction: column;
gap: 16px;
@media #{$mat-lt-md} {
width: 90vw;
}
.tb-scada-symbol-property-settings-panel-content {
display: flex;
flex-direction: column;
gap: 16px;
overflow: auto;
margin: -10px;
padding: 10px;
}
.tb-scada-symbol-property-settings-title {
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: 0.25px;
color: rgba(0, 0, 0, 0.87);
}
.tb-scada-symbol-property-settings-panel-buttons {
height: 40px;
display: flex;
flex-direction: row;
gap: 16px;
justify-content: flex-end;
align-items: flex-end;
}
}

129
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component.ts

@ -0,0 +1,129 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { TbPopoverComponent } from '@shared/components/popover.component';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import {
IotSvgProperty, iotSvgPropertyFieldClasses, iotSvgPropertyRowClasses,
IotSvgPropertyType,
iotSvgPropertyTypes,
iotSvgPropertyTypeTranslations
} from '@home/components/widget/lib/svg/iot-svg.models';
import { WidgetService } from '@core/http/widget.service';
import { defaultPropertyValue } from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component';
import { ValueType } from '@shared/models/constants';
@Component({
selector: 'tb-scada-symbol-property-panel',
templateUrl: './scada-symbol-property-panel.component.html',
styleUrls: ['./scada-symbol-property-panel.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolPropertyPanelComponent implements OnInit {
ValueType = ValueType;
IotSvgPropertyType = IotSvgPropertyType;
iotSvgPropertyTypes = iotSvgPropertyTypes;
iotSvgPropertyTypeTranslations = iotSvgPropertyTypeTranslations;
iotSvgPropertyRowClasses = iotSvgPropertyRowClasses;
iotSvgPropertyFieldClasses = iotSvgPropertyFieldClasses;
@Input()
isAdd = false;
@Input()
property: IotSvgProperty;
@Input()
booleanPropertyIds: string[];
@Input()
popover: TbPopoverComponent<ScadaSymbolPropertyPanelComponent>;
@Output()
propertySettingsApplied = new EventEmitter<IotSvgProperty>();
panelTitle: string;
propertyFormGroup: UntypedFormGroup;
private propertyType: IotSvgPropertyType;
constructor(private fb: UntypedFormBuilder,
private widgetService: WidgetService) {
}
ngOnInit(): void {
this.panelTitle = this.isAdd ? 'scada.property.add-property' : 'scada.property.property-settings';
this.propertyType = this.property.type;
this.propertyFormGroup = this.fb.group(
{
id: [this.property.id, [Validators.required]],
name: [this.property.name, [Validators.required]],
type: [this.property.type, [Validators.required]],
default: [this.property.default, []],
required: [this.property.required, []],
subLabel: [this.property.subLabel, []],
divider: [this.property.divider, []],
fieldSuffix: [this.property.fieldSuffix, []],
disableOnProperty: [this.property.disableOnProperty, []],
rowClass: [(this.property.rowClass || '').split(' '), []],
fieldClass: [(this.property.fieldClass || '').split(' '), []],
min: [this.property.min, []],
max: [this.property.max, []],
step: [this.property.step, [Validators.min(0)]]
}
);
this.propertyFormGroup.get('type').valueChanges.subscribe(() => {
this.updateValidators();
});
this.updateValidators();
}
cancel() {
this.popover?.hide();
}
applyPropertySettings() {
const property = this.propertyFormGroup.getRawValue();
property.rowClass = (property.rowClass || []).join(' ');
property.fieldClass = (property.fieldClass || []).join(' ');
this.propertySettingsApplied.emit(property);
}
private updateValidators() {
const type: IotSvgPropertyType = this.propertyFormGroup.get('type').value;
if (type === IotSvgPropertyType.number) {
this.propertyFormGroup.get('min').enable({emitEvent: false});
this.propertyFormGroup.get('max').enable({emitEvent: false});
this.propertyFormGroup.get('step').enable({emitEvent: false});
} else {
this.propertyFormGroup.get('min').disable({emitEvent: false});
this.propertyFormGroup.get('max').disable({emitEvent: false});
this.propertyFormGroup.get('step').disable({emitEvent: false});
}
if (this.propertyType !== type) {
const defaultValue = defaultPropertyValue(type);
this.propertyFormGroup.get('default').patchValue(defaultValue, {emitEvent: false});
this.propertyType = type;
}
}
}

49
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.html

@ -0,0 +1,49 @@
<!--
Copyright © 2016-2024 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div [formGroup]="propertyRowFormGroup" class="tb-form-table-row tb-scada-symbol-metadata-property-row">
<mat-form-field class="tb-inline-field tb-id-field" appearance="outline" subscriptSizing="dynamic">
<input #idInput required matInput formControlName="id" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field class="tb-inline-field tb-name-field" appearance="outline" subscriptSizing="dynamic">
<input required matInput formControlName="name" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<mat-form-field class="tb-inline-field tb-type-field fixed-height" appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="type">
<mat-option *ngFor="let type of iotSvgPropertyTypes" [value]="type">
{{ iotSvgPropertyTypeTranslations.get(type) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<div class="tb-form-table-row-cell-buttons">
<button type="button"
mat-icon-button
#editButton
(click)="editProperty($event, editButton)"
matTooltip="{{ 'scada.property.property-settings' | translate }}"
matTooltipPosition="above">
<mat-icon>settings</mat-icon>
</button>
<button type="button"
mat-icon-button
(click)="propertyRemoved.emit()"
matTooltip="{{ 'scada.property.remove-property' | translate }}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>

26
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.scss

@ -0,0 +1,26 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-scada-symbol-metadata-property-row {
.tb-id-field {
flex: 1 1 40%;
}
.tb-name-field {
flex: 1 1 40%;
}
.tb-type-field {
flex: 1 1 20%;
}
}

280
ui-ngx/src/app/modules/home/pages/scada-symbol/metadata-components/scada-symbol-property-row.component.ts

@ -0,0 +1,280 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
forwardRef,
Input,
OnInit,
Output,
Renderer2,
ViewChild,
ViewContainerRef,
ViewEncapsulation
} from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormControl,
UntypedFormGroup,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import {
IotSvgProperty,
IotSvgPropertyType,
iotSvgPropertyTypes,
iotSvgPropertyTypeTranslations
} from '@home/components/widget/lib/svg/iot-svg.models';
import { deepClone } from '@core/utils';
import { MatButton } from '@angular/material/button';
import { TbPopoverService } from '@shared/components/popover.service';
import { constantColor, Font } from '@shared/models/widget-settings.models';
import {
ScadaSymbolPropertyPanelComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-property-panel.component';
import {
ScadaSymbolPropertiesComponent
} from '@home/pages/scada-symbol/metadata-components/scada-symbol-properties.component';
export const propertyValid = (property: IotSvgProperty): boolean => !(!property.id || !property.name || !property.type);
export const defaultPropertyValue = (type: IotSvgPropertyType): any => {
switch (type) {
case IotSvgPropertyType.text:
return '';
case IotSvgPropertyType.number:
return 0;
case IotSvgPropertyType.switch:
return false;
case IotSvgPropertyType.color:
return '#000';
case IotSvgPropertyType.color_settings:
return constantColor('#000');
case IotSvgPropertyType.font:
return {
size: 12,
sizeUnit: 'px',
family: 'Roboto',
weight: 'normal',
style: 'normal',
lineHeight: '1'
} as Font;
case IotSvgPropertyType.units:
return '';
}
};
@Component({
selector: 'tb-scada-symbol-metadata-property-row',
templateUrl: './scada-symbol-property-row.component.html',
styleUrls: ['./scada-symbol-property-row.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ScadaSymbolPropertyRowComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => ScadaSymbolPropertyRowComponent),
multi: true
}
],
encapsulation: ViewEncapsulation.None
})
export class ScadaSymbolPropertyRowComponent implements ControlValueAccessor, OnInit, Validator {
@ViewChild('idInput')
idInput: ElementRef<HTMLInputElement>;
@ViewChild('editButton')
editButton: MatButton;
iotSvgPropertyTypes = iotSvgPropertyTypes;
iotSvgPropertyTypeTranslations = iotSvgPropertyTypeTranslations;
@Input()
disabled: boolean;
@Input()
index: number;
@Input()
booleanPropertyIds: string[];
@Output()
propertyRemoved = new EventEmitter();
propertyRowFormGroup: UntypedFormGroup;
modelValue: IotSvgProperty;
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
private cd: ChangeDetectorRef,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private propertiesComponent: ScadaSymbolPropertiesComponent) {
}
ngOnInit() {
this.propertyRowFormGroup = this.fb.group({
id: [null, [this.propertyIdValidator()]],
name: [null, [Validators.required]],
type: [null, [Validators.required]]
});
this.propertyRowFormGroup.valueChanges.subscribe(
() => this.updateModel()
);
this.propertyRowFormGroup.get('type').valueChanges.subscribe((newType: IotSvgPropertyType) => {
this.onTypeChanged(newType);
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.propertyRowFormGroup.disable({emitEvent: false});
} else {
this.propertyRowFormGroup.enable({emitEvent: false});
}
}
writeValue(value: IotSvgProperty): void {
this.modelValue = value;
this.propertyRowFormGroup.patchValue(
{
id: value?.id,
name: value?.name,
type: value?.type
}, {emitEvent: false}
);
this.cd.markForCheck();
}
editProperty($event: Event, matButton: MatButton, add = false, editCanceled = () => {}) {
if ($event) {
$event.stopPropagation();
}
const trigger = matButton._elementRef.nativeElement;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const ctx: any = {
isAdd: add,
booleanPropertyIds: this.booleanPropertyIds,
property: deepClone(this.modelValue)
};
const scadaSymbolPropertyPanelPopover = this.popoverService.displayPopover(trigger, this.renderer,
this.viewContainerRef, ScadaSymbolPropertyPanelComponent, ['leftOnly', 'leftTopOnly', 'leftBottomOnly'], true, null,
ctx,
{},
{}, {}, true);
scadaSymbolPropertyPanelPopover.tbComponentRef.instance.popover = scadaSymbolPropertyPanelPopover;
scadaSymbolPropertyPanelPopover.tbComponentRef.instance.propertySettingsApplied.subscribe((property) => {
scadaSymbolPropertyPanelPopover.hide();
this.propertyRowFormGroup.patchValue(
{
id: property.id,
name: property.name,
type: property.type
}, {emitEvent: false}
);
this.modelValue = property;
this.propagateChange(this.modelValue);
});
scadaSymbolPropertyPanelPopover.tbDestroy.subscribe(() => {
if (!propertyValid(this.modelValue)) {
editCanceled();
}
});
}
}
focus() {
this.idInput.nativeElement.scrollIntoView();
this.idInput.nativeElement.focus();
}
onAdd(onCanceled: () => void) {
this.idInput.nativeElement.scrollIntoView();
this.editProperty(null, this.editButton, true, onCanceled);
}
public validate(c: UntypedFormControl) {
const idControl = this.propertyRowFormGroup.get('id');
if (idControl.hasError('propertyIdNotUnique')) {
idControl.updateValueAndValidity({onlySelf: false, emitEvent: false});
}
if (idControl.hasError('propertyIdNotUnique')) {
this.propertyRowFormGroup.get('id').markAsTouched();
return {
propertyIdNotUnique: true
};
}
const property: IotSvgProperty = {...this.modelValue, ...this.propertyRowFormGroup.value};
if (!propertyValid(property)) {
return {
property: true
};
}
return null;
}
private propertyIdValidator(): ValidatorFn {
return control => {
if (!control.value) {
return {
required: true
};
}
if (!this.propertiesComponent.propertyIdUnique(control.value, this.index)) {
return {
propertyIdNotUnique: true
};
}
return null;
};
}
private onTypeChanged(newType: IotSvgPropertyType) {
this.modelValue = {...this.modelValue, ...{type: newType}};
this.modelValue.default = defaultPropertyValue(newType);
}
private updateModel() {
const value: IotSvgProperty = this.propertyRowFormGroup.value;
this.modelValue = {...this.modelValue, ...value};
this.propagateChange(this.modelValue);
}
}

3
ui-ngx/src/app/modules/home/pages/scada-symbol/scada-symbol.component.ts

@ -320,6 +320,9 @@ export class ScadaSymbolComponent extends PageComponent
}
private reset(): void {
if (this.symbolMetadata) {
this.symbolMetadata.selectedOption = 'general';
}
this.previewMode = false;
}

19
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3439,7 +3439,8 @@
"true-label": "True label",
"false-label": "False label",
"state-label": "State label",
"default-payload": "Default payload"
"default-payload": "Default payload",
"not-unique-behavior-ids-error": "Behavior Ids must be unique!"
},
"property": {
"property": "Property",
@ -3456,7 +3457,21 @@
"no-properties": "No properties configured",
"add-property": "Add property",
"property-settings": "Property settings",
"remove-property": "Remove property"
"remove-property": "Remove property",
"default-value": "Default value",
"value-required": "Value required",
"number-settings": "Number settings",
"min": "Min",
"max": "Max",
"step": "Step",
"advanced-ui-settings": "Advanced UI settings",
"disable-on-property": "Disable on property",
"sub-label": "Sub label",
"vertical-divider-after": "Vertical divider after",
"input-field-suffix": "Input field suffix",
"property-row-classes": "Property row classes",
"property-field-classes": "Property field classes",
"not-unique-property-ids-error": "Property Ids must be unique!"
}
},
"item": {

Loading…
Cancel
Save