19 changed files with 1168 additions and 28 deletions
@ -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> |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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> |
||||
@ -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%; |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue