22 changed files with 766 additions and 122 deletions
@ -0,0 +1,48 @@ |
|||
<!-- |
|||
|
|||
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]="behaviorRowFormGroup" class="tb-form-table-row tb-scada-symbol-metadata-behavior-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 iotSvgBehaviorTypes" [value]="type"> |
|||
{{ iotSvgBehaviorTypeTranslations.get(type) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<div class="tb-form-table-row-cell-buttons"> |
|||
<button type="button" |
|||
mat-icon-button |
|||
(click)="editBehavior()" |
|||
matTooltip="{{ 'scada.behavior.behavior-settings' | translate }}" |
|||
matTooltipPosition="above"> |
|||
<mat-icon>settings</mat-icon> |
|||
</button> |
|||
<button type="button" |
|||
mat-icon-button |
|||
(click)="behaviorRemoved.emit()" |
|||
matTooltip="{{ 'scada.behavior.remove-behavior' | 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-behavior-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,172 @@ |
|||
///
|
|||
/// 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, |
|||
ViewChild, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { |
|||
AbstractControl, |
|||
ControlValueAccessor, |
|||
NG_VALUE_ACCESSOR, |
|||
UntypedFormBuilder, |
|||
UntypedFormGroup, |
|||
ValidationErrors |
|||
} from '@angular/forms'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { |
|||
IotSvgBehavior, |
|||
IotSvgBehaviorAction, |
|||
IotSvgBehaviorType, |
|||
iotSvgBehaviorTypes, |
|||
iotSvgBehaviorTypeTranslations, |
|||
IotSvgBehaviorValue |
|||
} from '@home/components/widget/lib/svg/iot-svg.models'; |
|||
|
|||
export const behaviorValid = (behavior: IotSvgBehavior): boolean => { |
|||
if (!behavior.id || !behavior.name || !behavior.type) { |
|||
return false; |
|||
} |
|||
switch (behavior.type) { |
|||
case IotSvgBehaviorType.value: |
|||
const valueBehavior = behavior as IotSvgBehaviorValue; |
|||
if (!valueBehavior.valueType) { |
|||
return false; |
|||
} |
|||
break; |
|||
case IotSvgBehaviorType.action: |
|||
const actionBehavior = behavior as IotSvgBehaviorAction; |
|||
if (!actionBehavior.valueToDataType) { |
|||
return false; |
|||
} |
|||
break; |
|||
case IotSvgBehaviorType.widgetAction: |
|||
break; |
|||
} |
|||
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', |
|||
styleUrls: ['./scada-symbol-behavior-row.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => ScadaSymbolBehaviorRowComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class ScadaSymbolBehaviorRowComponent implements ControlValueAccessor, OnInit { |
|||
|
|||
@ViewChild('idInput') |
|||
idInput: ElementRef<HTMLInputElement>; |
|||
|
|||
iotSvgBehaviorTypes = iotSvgBehaviorTypes; |
|||
iotSvgBehaviorTypeTranslations = iotSvgBehaviorTypeTranslations; |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@Output() |
|||
behaviorRemoved = new EventEmitter(); |
|||
|
|||
behaviorRowFormGroup: UntypedFormGroup; |
|||
|
|||
modelValue: IotSvgBehavior; |
|||
|
|||
private propagateChange = (_val: any) => {}; |
|||
|
|||
constructor(private fb: UntypedFormBuilder, |
|||
private dialog: MatDialog, |
|||
private cd: ChangeDetectorRef) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.behaviorRowFormGroup = this.fb.group({ |
|||
id: [null, []], |
|||
name: [null, []], |
|||
type: [null, []] |
|||
}); |
|||
this.behaviorRowFormGroup.valueChanges.subscribe( |
|||
() => this.updateModel() |
|||
); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (isDisabled) { |
|||
this.behaviorRowFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.behaviorRowFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: IotSvgBehavior): void { |
|||
this.modelValue = value; |
|||
this.behaviorRowFormGroup.patchValue( |
|||
{ |
|||
id: value?.id, |
|||
name: value?.name, |
|||
type: value?.type |
|||
}, {emitEvent: false} |
|||
); |
|||
this.cd.markForCheck(); |
|||
} |
|||
|
|||
editBehavior() { |
|||
|
|||
} |
|||
|
|||
focus() { |
|||
this.idInput.nativeElement.scrollIntoView(); |
|||
this.idInput.nativeElement.focus(); |
|||
} |
|||
|
|||
private updateModel() { |
|||
const value: IotSvgBehavior = this.behaviorRowFormGroup.value; |
|||
this.modelValue = {...this.modelValue, ...value}; |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
<!-- |
|||
|
|||
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-behaviors"> |
|||
<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.behavior.id</div> |
|||
<div class="tb-form-table-header-cell tb-name-header" translate>scada.behavior.name</div> |
|||
<div class="tb-form-table-header-cell tb-type-header" translate>scada.behavior.type</div> |
|||
<div class="tb-form-table-header-cell tb-actions-header"></div> |
|||
</div> |
|||
<div *ngIf="behaviorsFormArray().controls.length; else noBehaviors" class="tb-form-table-body tb-drop-list" |
|||
cdkDropList cdkDropListOrientation="vertical" |
|||
[cdkDropListDisabled]="!dragEnabled" |
|||
(cdkDropListDropped)="behaviorDrop($event)"> |
|||
<div cdkDrag [cdkDragDisabled]="!dragEnabled" |
|||
class="tb-draggable-form-table-row" |
|||
*ngFor="let behaviorControl of behaviorsFormArray().controls; trackBy: trackByBehavior; let $index = index;"> |
|||
<tb-scada-symbol-metadata-behavior-row fxFlex |
|||
[formControl]="behaviorControl" |
|||
(behaviorRemoved)="removeBehavior($index)"> |
|||
</tb-scada-symbol-metadata-behavior-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> |
|||
</div> |
|||
<div> |
|||
<button type="button" mat-stroked-button color="primary" (click)="addBehavior()"> |
|||
{{ 'scada.behavior.add-behavior' | translate }} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<ng-template #noBehaviors> |
|||
<span fxLayoutAlign="center center" |
|||
class="tb-prompt">{{ 'scada.behavior.no-behaviors' | 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-behaviors { |
|||
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-behavior-row { |
|||
overflow: hidden; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,177 @@ |
|||
///
|
|||
/// 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 { IotSvgBehavior, IotSvgBehaviorType } from '@home/components/widget/lib/svg/iot-svg.models'; |
|||
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'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-scada-symbol-metadata-behaviors', |
|||
templateUrl: './scada-symbol-behaviors.component.html', |
|||
styleUrls: ['./scada-symbol-behaviors.component.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => ScadaSymbolBehaviorsComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => ScadaSymbolBehaviorsComponent), |
|||
multi: true |
|||
} |
|||
], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class ScadaSymbolBehaviorsComponent implements ControlValueAccessor, OnInit, Validator { |
|||
|
|||
@HostBinding('style.display') styleDisplay = 'flex'; |
|||
@HostBinding('style.overflow') styleOverflow = 'hidden'; |
|||
|
|||
@ViewChildren(ScadaSymbolBehaviorRowComponent) |
|||
behaviorRows: QueryList<ScadaSymbolBehaviorRowComponent>; |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
behaviorsFormGroup: UntypedFormGroup; |
|||
|
|||
get dragEnabled(): boolean { |
|||
return this.behaviorsFormArray().controls.length > 1; |
|||
} |
|||
|
|||
private propagateChange = (_val: any) => {}; |
|||
|
|||
constructor(private fb: UntypedFormBuilder) { |
|||
} |
|||
|
|||
ngOnInit() { |
|||
this.behaviorsFormGroup = this.fb.group({ |
|||
behaviors: this.fb.array([]) |
|||
}); |
|||
this.behaviorsFormGroup.valueChanges.subscribe( |
|||
() => { |
|||
let behaviors: IotSvgBehavior[] = this.behaviorsFormGroup.get('behaviors').value; |
|||
if (behaviors) { |
|||
behaviors = behaviors.filter(b => behaviorValid(b)); |
|||
} |
|||
this.propagateChange(behaviors); |
|||
} |
|||
); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (isDisabled) { |
|||
this.behaviorsFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.behaviorsFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: IotSvgBehavior[] | undefined): void { |
|||
const behaviors= value || []; |
|||
this.behaviorsFormGroup.setControl('behaviors', this.prepareBehaviorsFormArray(behaviors), {emitEvent: false}); |
|||
} |
|||
|
|||
public validate(c: UntypedFormControl) { |
|||
const valid = this.behaviorsFormGroup.valid; |
|||
return valid ? null : { |
|||
behaviors: { |
|||
valid: false, |
|||
}, |
|||
}; |
|||
} |
|||
|
|||
behaviorDrop(event: CdkDragDrop<string[]>) { |
|||
const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray; |
|||
const behavior = behaviorsArray.at(event.previousIndex); |
|||
behaviorsArray.removeAt(event.previousIndex); |
|||
behaviorsArray.insert(event.currentIndex, behavior); |
|||
} |
|||
|
|||
behaviorsFormArray(): UntypedFormArray { |
|||
return this.behaviorsFormGroup.get('behaviors') as UntypedFormArray; |
|||
} |
|||
|
|||
trackByBehavior(index: number, behaviorControl: AbstractControl): any { |
|||
return behaviorControl; |
|||
} |
|||
|
|||
removeBehavior(index: number, emitEvent = true) { |
|||
(this.behaviorsFormGroup.get('behaviors') as UntypedFormArray).removeAt(index, {emitEvent}); |
|||
} |
|||
|
|||
addBehavior() { |
|||
const behavior: IotSvgBehavior = { |
|||
id: '', |
|||
name: '', |
|||
type: IotSvgBehaviorType.value, |
|||
valueType: ValueType.BOOLEAN, |
|||
defaultValue: false |
|||
}; |
|||
const behaviorsArray = this.behaviorsFormGroup.get('behaviors') as UntypedFormArray; |
|||
const behaviorControl = this.fb.control(behavior, [behaviorValidator]); |
|||
behaviorsArray.push(behaviorControl); |
|||
setTimeout(() => { |
|||
const behaviorRow = this.behaviorRows.get(this.behaviorRows.length-1); |
|||
behaviorRow.focus(); |
|||
}); |
|||
} |
|||
|
|||
private prepareBehaviorsFormArray(behaviors: IotSvgBehavior[] | undefined): UntypedFormArray { |
|||
const behaviorsControls: Array<AbstractControl> = []; |
|||
if (behaviors) { |
|||
behaviors.forEach((behavior) => { |
|||
behaviorsControls.push(this.fb.control(behavior, [behaviorValidator])); |
|||
}); |
|||
} |
|||
return this.fb.array(behaviorsControls); |
|||
} |
|||
} |
|||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Loading…
Reference in new issue