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