UI: Scada widget test
This commit is contained in:
parent
e48bd45ab8
commit
eb50048c50
@ -129,6 +129,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
RadarChartBasicConfigComponent
|
RadarChartBasicConfigComponent
|
||||||
} from '@home/components/widget/config/basic/chart/radar-chart-basic-config.component';
|
} from '@home/components/widget/config/basic/chart/radar-chart-basic-config.component';
|
||||||
|
import {
|
||||||
|
ScadaTestBasicConfigComponent
|
||||||
|
} from '@home/components/widget/config/basic/scada/scada-test-basic-config.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -171,7 +174,8 @@ import {
|
|||||||
PieChartBasicConfigComponent,
|
PieChartBasicConfigComponent,
|
||||||
BarChartBasicConfigComponent,
|
BarChartBasicConfigComponent,
|
||||||
PolarAreaChartBasicConfigComponent,
|
PolarAreaChartBasicConfigComponent,
|
||||||
RadarChartBasicConfigComponent
|
RadarChartBasicConfigComponent,
|
||||||
|
ScadaTestBasicConfigComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -216,7 +220,8 @@ import {
|
|||||||
PieChartBasicConfigComponent,
|
PieChartBasicConfigComponent,
|
||||||
BarChartBasicConfigComponent,
|
BarChartBasicConfigComponent,
|
||||||
PolarAreaChartBasicConfigComponent,
|
PolarAreaChartBasicConfigComponent,
|
||||||
RadarChartBasicConfigComponent
|
RadarChartBasicConfigComponent,
|
||||||
|
ScadaTestBasicConfigComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class BasicWidgetConfigModule {
|
export class BasicWidgetConfigModule {
|
||||||
@ -255,5 +260,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
|
|||||||
'tb-pie-chart-basic-config': PieChartBasicConfigComponent,
|
'tb-pie-chart-basic-config': PieChartBasicConfigComponent,
|
||||||
'tb-bar-chart-basic-config': BarChartBasicConfigComponent,
|
'tb-bar-chart-basic-config': BarChartBasicConfigComponent,
|
||||||
'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent,
|
'tb-polar-area-chart-basic-config': PolarAreaChartBasicConfigComponent,
|
||||||
'tb-radar-chart-basic-config': RadarChartBasicConfigComponent
|
'tb-radar-chart-basic-config': RadarChartBasicConfigComponent,
|
||||||
|
'tb-scada-test-basic-config': ScadaTestBasicConfigComponent
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<ng-container [formGroup]="scadaTestWidgetConfigForm">
|
||||||
|
<tb-target-device formControlName="targetDevice"></tb-target-device>
|
||||||
|
<tb-scada-object-settings
|
||||||
|
formControlName="scadaObject"
|
||||||
|
[aliasController]="aliasController"
|
||||||
|
[targetDevice]="targetDevice"
|
||||||
|
[widgetType]="widgetType">
|
||||||
|
</tb-scada-object-settings>
|
||||||
|
</ng-container>
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
///
|
||||||
|
/// 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 } from '@angular/core';
|
||||||
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '@core/core.state';
|
||||||
|
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
|
||||||
|
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
|
||||||
|
import { TargetDevice, } from '@shared/models/widget.models';
|
||||||
|
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
|
||||||
|
import { ScadaTestWidgetSettings } from '@home/components/widget/lib/scada/scada-test-widget.models';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-scada-test-basic-config',
|
||||||
|
templateUrl: './scada-test-basic-config.component.html',
|
||||||
|
styleUrls: ['../basic-config.scss']
|
||||||
|
})
|
||||||
|
export class ScadaTestBasicConfigComponent extends BasicWidgetConfigComponent {
|
||||||
|
|
||||||
|
get targetDevice(): TargetDevice {
|
||||||
|
return this.scadaTestWidgetConfigForm.get('targetDevice').value;
|
||||||
|
}
|
||||||
|
|
||||||
|
scadaTestWidgetConfigForm: UntypedFormGroup;
|
||||||
|
|
||||||
|
constructor(protected store: Store<AppState>,
|
||||||
|
protected widgetConfigComponent: WidgetConfigComponent,
|
||||||
|
private fb: UntypedFormBuilder) {
|
||||||
|
super(store, widgetConfigComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected configForm(): UntypedFormGroup {
|
||||||
|
return this.scadaTestWidgetConfigForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onConfigSet(configData: WidgetConfigComponentData) {
|
||||||
|
const settings: ScadaTestWidgetSettings = {...(configData.config.settings as ScadaTestWidgetSettings || { scadaObject: {}})};
|
||||||
|
this.scadaTestWidgetConfigForm = this.fb.group({
|
||||||
|
targetDevice: [configData.config.targetDevice, []],
|
||||||
|
scadaObject: [settings.scadaObject, []]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
|
||||||
|
this.widgetConfig.config.targetDevice = config.targetDevice;
|
||||||
|
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
|
||||||
|
this.widgetConfig.config.settings.scadaObject = config.scadaObject;
|
||||||
|
return this.widgetConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
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 #scadaShape class="tb-scada-shape">
|
||||||
|
</div>
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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-shape {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
///
|
||||||
|
/// 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 {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Renderer2,
|
||||||
|
ViewChild,
|
||||||
|
ViewEncapsulation
|
||||||
|
} from '@angular/core';
|
||||||
|
import { BasicActionWidgetComponent } from '@home/components/widget/lib/action/action-widget.models';
|
||||||
|
import { ImagePipe } from '@shared/pipe/image.pipe';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
import { SVG, Svg } from '@svgdotjs/svg.js';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { ScadaObject, ScadaObjectSettings } from '@home/components/widget/lib/scada/scada.models';
|
||||||
|
import { ResizeObserver } from '@juggle/resize-observer';
|
||||||
|
import { ScadaTestWidgetSettings } from '@home/components/widget/lib/scada/scada-test-widget.models';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-scada-test-widget',
|
||||||
|
templateUrl: './scada-test-widget.component.html',
|
||||||
|
styleUrls: ['../action/action-widget.scss', './scada-test-widget.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class ScadaTestWidgetComponent extends
|
||||||
|
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
|
@ViewChild('scadaShape', {static: false})
|
||||||
|
scadaShape: ElementRef<HTMLElement>;
|
||||||
|
|
||||||
|
private settings: ScadaTestWidgetSettings;
|
||||||
|
|
||||||
|
private autoScale = true;
|
||||||
|
private scadaObject: ScadaObject;
|
||||||
|
|
||||||
|
private shapeResize$: ResizeObserver;
|
||||||
|
|
||||||
|
constructor(protected imagePipe: ImagePipe,
|
||||||
|
protected sanitizer: DomSanitizer,
|
||||||
|
private renderer: Renderer2,
|
||||||
|
protected cd: ChangeDetectorRef,
|
||||||
|
private http: HttpClient) {
|
||||||
|
super(cd);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
this.settings = {...this.ctx.settings};
|
||||||
|
this.scadaObject = new ScadaObject(this.ctx, '/assets/widget/scada/drawing.svg', this.settings.scadaObject);
|
||||||
|
this.scadaObject.init().subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.scadaObject.addTo(this.scadaShape.nativeElement);
|
||||||
|
if (this.autoScale) {
|
||||||
|
this.shapeResize$ = new ResizeObserver(() => {
|
||||||
|
this.onResize();
|
||||||
|
});
|
||||||
|
this.shapeResize$.observe(this.scadaShape.nativeElement);
|
||||||
|
this.onResize();
|
||||||
|
}
|
||||||
|
super.ngAfterViewInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.shapeResize$) {
|
||||||
|
this.shapeResize$.disconnect();
|
||||||
|
}
|
||||||
|
if (this.scadaObject) {
|
||||||
|
this.scadaObject.destroy();
|
||||||
|
}
|
||||||
|
super.ngOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInit() {
|
||||||
|
super.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onResize() {
|
||||||
|
const shapeWidth = this.scadaShape.nativeElement.getBoundingClientRect().width;
|
||||||
|
const shapeHeight = this.scadaShape.nativeElement.getBoundingClientRect().height;
|
||||||
|
this.scadaObject.setSize(shapeWidth, shapeHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
///
|
||||||
|
/// 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 { ScadaObjectSettings } from '@home/components/widget/lib/scada/scada.models';
|
||||||
|
|
||||||
|
export interface ScadaTestWidgetSettings {
|
||||||
|
scadaObject: ScadaObjectSettings;
|
||||||
|
}
|
||||||
@ -0,0 +1,398 @@
|
|||||||
|
///
|
||||||
|
/// 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 { ValueType } from '@shared/models/constants';
|
||||||
|
import { Box, Element, Runner, Svg, SVG, Timeline } from '@svgdotjs/svg.js';
|
||||||
|
import { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
|
||||||
|
import { insertVariable, isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, mergeDeep } from '@core/utils';
|
||||||
|
import { BehaviorSubject, forkJoin, Observable } from 'rxjs';
|
||||||
|
import { map, share } from 'rxjs/operators';
|
||||||
|
import { ValueAction, ValueGetter } from '@home/components/widget/lib/action/action-widget.models';
|
||||||
|
import { WidgetContext } from '@home/models/widget-component.models';
|
||||||
|
|
||||||
|
export type ValueMatcherType = 'any' | 'constant' | 'range';
|
||||||
|
|
||||||
|
export interface ValueMatcher {
|
||||||
|
type: ValueMatcherType;
|
||||||
|
value?: any;
|
||||||
|
range?: {from: number; to: number};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScadaObjectAttributeValueType = 'input' | 'property';
|
||||||
|
|
||||||
|
export interface ScadaObjectAttributeValue {
|
||||||
|
type: ScadaObjectAttributeValueType;
|
||||||
|
propertyId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectAttributeState {
|
||||||
|
name: string;
|
||||||
|
value: ScadaObjectAttributeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectState {
|
||||||
|
tag: string;
|
||||||
|
attributes: ScadaObjectAttributeState[];
|
||||||
|
animate?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectUpdateState {
|
||||||
|
matcher: ValueMatcher;
|
||||||
|
state: ScadaObjectState[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ScadaObjectBehaviorType {
|
||||||
|
setValue = 'setValue',
|
||||||
|
getValue = 'getValue'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectBehaviorBase {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: ScadaObjectBehaviorType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectBehaviorGet extends ScadaObjectBehaviorBase {
|
||||||
|
valueType: ValueType;
|
||||||
|
defaultValue: any;
|
||||||
|
onUpdate: ScadaObjectUpdateState[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectBehaviorSet extends ScadaObjectBehaviorBase {
|
||||||
|
todo: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScadaObjectBehavior = ScadaObjectBehaviorGet | ScadaObjectBehaviorSet;
|
||||||
|
|
||||||
|
export type ScadaObjectPropertyType = 'string' | 'number' | 'color';
|
||||||
|
|
||||||
|
export interface ScadaObjectPropertyBase {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: ScadaObjectPropertyType;
|
||||||
|
default: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectNumberProperty extends ScadaObjectPropertyBase {
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScadaObjectProperty = ScadaObjectPropertyBase & ScadaObjectNumberProperty;
|
||||||
|
|
||||||
|
export interface ScadaObjectMetadata {
|
||||||
|
title: string;
|
||||||
|
initial: ScadaObjectState[];
|
||||||
|
behavior: ScadaObjectBehavior[];
|
||||||
|
properties: ScadaObjectProperty[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const emptyMetadata: ScadaObjectMetadata = {
|
||||||
|
title: '',
|
||||||
|
initial: [],
|
||||||
|
behavior: [],
|
||||||
|
properties: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const parseScadaObjectMetadataFromContent = (svgContent: string): ScadaObjectMetadata => {
|
||||||
|
try {
|
||||||
|
const svgDoc = new DOMParser().parseFromString(svgContent, 'image/svg+xml');
|
||||||
|
return parseScadaObjectMetadataFromDom(svgDoc);
|
||||||
|
} catch (_e) {
|
||||||
|
return emptyMetadata;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseScadaObjectMetadataFromDom = (svgDoc: Document): ScadaObjectMetadata => {
|
||||||
|
try {
|
||||||
|
const elements = svgDoc.getElementsByTagName('tb:metadata');
|
||||||
|
if (elements.length) {
|
||||||
|
return JSON.parse(elements[0].innerHTML);
|
||||||
|
} else {
|
||||||
|
return emptyMetadata;
|
||||||
|
}
|
||||||
|
} catch (_e) {
|
||||||
|
return emptyMetadata;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultGetValueSettings = (get: ScadaObjectBehaviorGet): GetValueSettings<any> => ({
|
||||||
|
action: GetValueAction.DO_NOTHING,
|
||||||
|
defaultValue: get.defaultValue,
|
||||||
|
executeRpc: {
|
||||||
|
method: 'getState',
|
||||||
|
requestTimeout: 5000,
|
||||||
|
requestPersistent: false,
|
||||||
|
persistentPollingInterval: 1000
|
||||||
|
},
|
||||||
|
getAttribute: {
|
||||||
|
key: 'state',
|
||||||
|
scope: null
|
||||||
|
},
|
||||||
|
getTimeSeries: {
|
||||||
|
key: 'state'
|
||||||
|
},
|
||||||
|
dataToValue: {
|
||||||
|
type: DataToValueType.NONE,
|
||||||
|
compareToValue: true,
|
||||||
|
dataToValueFunction: '/* Should return boolean value */\nreturn data;'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export const defaultScadaObjectSettings = (metadata: ScadaObjectMetadata): ScadaObjectSettings => {
|
||||||
|
const settings: ScadaObjectSettings = {};
|
||||||
|
for (const behaviour of metadata.behavior) {
|
||||||
|
//behaviour.id
|
||||||
|
if (behaviour.type === ScadaObjectBehaviorType.getValue) {
|
||||||
|
settings[behaviour.id] = defaultGetValueSettings(behaviour as ScadaObjectBehaviorGet);
|
||||||
|
} else if (behaviour.type === ScadaObjectBehaviorType.setValue) {
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const property of metadata.properties) {
|
||||||
|
settings[property.id] = property.default;
|
||||||
|
}
|
||||||
|
return settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ScadaObjectSettings = {[id: string]: any};
|
||||||
|
|
||||||
|
export class ScadaObject {
|
||||||
|
|
||||||
|
private metadata: ScadaObjectMetadata;
|
||||||
|
private settings: ScadaObjectSettings;
|
||||||
|
|
||||||
|
private rootElement: HTMLElement;
|
||||||
|
private svgShape: Svg;
|
||||||
|
private box: Box;
|
||||||
|
private targetWidth: number;
|
||||||
|
private targetHeight: number;
|
||||||
|
|
||||||
|
private loadingSubject = new BehaviorSubject(false);
|
||||||
|
private valueGetters: ValueGetter<any>[] = [];
|
||||||
|
private valueActions: ValueAction[] = [];
|
||||||
|
|
||||||
|
private animationTimeline: Timeline;
|
||||||
|
|
||||||
|
loading$ = this.loadingSubject.asObservable().pipe(share());
|
||||||
|
|
||||||
|
constructor(private ctx: WidgetContext,
|
||||||
|
private svgPath: string,
|
||||||
|
private inputSettings: ScadaObjectSettings) {}
|
||||||
|
|
||||||
|
public init(): Observable<any> {
|
||||||
|
return this.ctx.http.get(this.svgPath, {responseType: 'text'}).pipe(
|
||||||
|
map((inputSvgContent) => {
|
||||||
|
const doc: XMLDocument = new DOMParser().parseFromString(inputSvgContent, 'image/svg+xml');
|
||||||
|
this.metadata = parseScadaObjectMetadataFromDom(doc);
|
||||||
|
const defaults = defaultScadaObjectSettings(this.metadata);
|
||||||
|
this.settings = mergeDeep<ScadaObjectSettings>({}, defaults, this.inputSettings || {});
|
||||||
|
this.prepareSvgShape(doc);
|
||||||
|
this.prepareStates();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTo(element: HTMLElement) {
|
||||||
|
this.rootElement = element;
|
||||||
|
if (this.svgShape) {
|
||||||
|
this.svgShape.addTo(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
this.valueActions.forEach(v => v.destroy());
|
||||||
|
this.loadingSubject.complete();
|
||||||
|
this.loadingSubject.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSize(targetWidth: number, targetHeight: number) {
|
||||||
|
this.targetWidth = targetWidth;
|
||||||
|
this.targetHeight = targetHeight;
|
||||||
|
if (this.svgShape) {
|
||||||
|
this.resize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepareSvgShape(doc: XMLDocument) {
|
||||||
|
const elements = doc.getElementsByTagName('tb:metadata');
|
||||||
|
for (let i=0;i<elements.length;i++) {
|
||||||
|
elements.item(i).remove();
|
||||||
|
}
|
||||||
|
let svgContent = doc.documentElement.innerHTML;
|
||||||
|
for (const property of this.metadata.properties) {
|
||||||
|
const value = this.settings[property.id] || '';
|
||||||
|
svgContent = insertVariable(svgContent, property.id, value);
|
||||||
|
}
|
||||||
|
this.svgShape = SVG().svg(svgContent);
|
||||||
|
this.svgShape.node.style.overflow = 'visible';
|
||||||
|
this.svgShape.node.style['user-select'] = 'none';
|
||||||
|
this.box = this.svgShape.bbox();
|
||||||
|
this.svgShape.size(this.box.width, this.box.height);
|
||||||
|
if (this.rootElement) {
|
||||||
|
this.svgShape.addTo(this.rootElement);
|
||||||
|
}
|
||||||
|
if (this.targetWidth && this.targetHeight) {
|
||||||
|
this.resize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepareStates() {
|
||||||
|
for (const behavior of this.metadata.behavior) {
|
||||||
|
if (behavior.type === ScadaObjectBehaviorType.getValue) {
|
||||||
|
const getBehavior = behavior as ScadaObjectBehaviorGet;
|
||||||
|
let getValueSettings: GetValueSettings<any> = this.settings[getBehavior.id];
|
||||||
|
getValueSettings = {...getValueSettings, actionLabel: getBehavior.name};
|
||||||
|
const valueGetter =
|
||||||
|
ValueGetter.fromSettings(this.ctx, getValueSettings, getBehavior.valueType, {
|
||||||
|
next: (val) => {this.onValue(getBehavior.id, val);},
|
||||||
|
error: (e) => {}
|
||||||
|
});
|
||||||
|
this.valueGetters.push(valueGetter);
|
||||||
|
this.valueActions.push(valueGetter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.metadata.initial) {
|
||||||
|
this.updateState(this.metadata.initial);
|
||||||
|
}
|
||||||
|
if (this.valueGetters.length) {
|
||||||
|
const getValueObservables: Array<Observable<any>> = [];
|
||||||
|
this.valueGetters.forEach(valueGetter => {
|
||||||
|
getValueObservables.push(valueGetter.getValue());
|
||||||
|
});
|
||||||
|
this.loadingSubject.next(true);
|
||||||
|
forkJoin(getValueObservables).subscribe(
|
||||||
|
{
|
||||||
|
next: () => {
|
||||||
|
this.loadingSubject.next(false);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.loadingSubject.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private resize() {
|
||||||
|
let scale: number;
|
||||||
|
if (this.targetWidth < this.targetHeight) {
|
||||||
|
scale = this.targetWidth / this.box.width;
|
||||||
|
} else {
|
||||||
|
scale = this.targetHeight / this.box.height;
|
||||||
|
}
|
||||||
|
this.svgShape.node.style.transform = `scale(${scale})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private onValue(id: string, value: any) {
|
||||||
|
const getBehavior = this.metadata.behavior.find(b => b.id === id) as ScadaObjectBehaviorGet;
|
||||||
|
value = this.normalizeValue(value, getBehavior.valueType);
|
||||||
|
const updateStates = this.filterUpdateStates(getBehavior.onUpdate, value);
|
||||||
|
if (this.animationTimeline) {
|
||||||
|
this.animationTimeline.finish();
|
||||||
|
}
|
||||||
|
for (const updateState of updateStates) {
|
||||||
|
this.updateState(updateState.state, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateState(state: ScadaObjectState[], value?: any) {
|
||||||
|
for (const stateEntry of state) {
|
||||||
|
const tag = stateEntry.tag;
|
||||||
|
const elements = this.svgShape.find(`[tb\\:tag="${tag}"]`);
|
||||||
|
const attrs = this.computeAttributes(stateEntry.attributes, value);
|
||||||
|
elements.forEach(e => {
|
||||||
|
this.setElementAttributes(e, attrs, stateEntry.animate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeValue(value: any, type: ValueType): any {
|
||||||
|
if (isUndefinedOrNull(value)) {
|
||||||
|
switch (type) {
|
||||||
|
case ValueType.STRING:
|
||||||
|
return '';
|
||||||
|
case ValueType.INTEGER:
|
||||||
|
case ValueType.DOUBLE:
|
||||||
|
return 0;
|
||||||
|
case ValueType.BOOLEAN:
|
||||||
|
return false;
|
||||||
|
case ValueType.JSON:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeAttributes(attributes: ScadaObjectAttributeState[], value: any): {[attr: string]: any} {
|
||||||
|
const res: {[attr: string]: any} = {};
|
||||||
|
for (const attribute of attributes) {
|
||||||
|
const attr = attribute.name;
|
||||||
|
res[attr] = this.getAttributeValue(attribute, value);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setElementAttributes(element: Element, attrs: {[attr: string]: any}, animate?: number) {
|
||||||
|
if (isDefinedAndNotNull(animate)) {
|
||||||
|
this.animation(element, animate).attr(attrs);
|
||||||
|
} else {
|
||||||
|
element.attr(attrs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private animation(element: Element, duration: number): Runner {
|
||||||
|
if (!this.animationTimeline) {
|
||||||
|
this.animationTimeline = new Timeline();
|
||||||
|
}
|
||||||
|
element.timeline(this.animationTimeline);
|
||||||
|
return element.animate(duration, 0, 'now');
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAttributeValue(attribute: ScadaObjectAttributeState, value: any): any {
|
||||||
|
if (attribute.value.type === 'input') {
|
||||||
|
return value;
|
||||||
|
} else if (attribute.value.type === 'property') {
|
||||||
|
const id = attribute.value.propertyId;
|
||||||
|
return this.settings[id] || '';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterUpdateStates(states: ScadaObjectUpdateState[], val: any): ScadaObjectUpdateState[] {
|
||||||
|
return states.filter(s => this.valueMatches(s.matcher, val));
|
||||||
|
}
|
||||||
|
|
||||||
|
private valueMatches(matcher: ValueMatcher, val: any): boolean {
|
||||||
|
switch (matcher.type) {
|
||||||
|
case 'any':
|
||||||
|
return true;
|
||||||
|
case 'constant':
|
||||||
|
return matcher.value === val;
|
||||||
|
case 'range':
|
||||||
|
if (isDefinedAndNotNull(val) && isNumeric(val)) {
|
||||||
|
const num = Number(val);
|
||||||
|
const range = matcher.range;
|
||||||
|
return ((!isNumber(range.from) || num >= range.from) && (!isNumber(range.to) || num < range.to));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<ng-container [formGroup]="scadaObjectSettingsFormGroup">
|
||||||
|
<div>{{ metadata?.title }}</div>
|
||||||
|
<div *ngIf="metadata?.behavior?.length" class="tb-form-panel">
|
||||||
|
<div class="tb-form-panel-title" translate>widgets.slider.behavior</div>
|
||||||
|
<div *ngFor="let behaviour of metadata.behavior" class="tb-form-row">
|
||||||
|
<div class="fixed-title-width">{{ behaviour.name }}</div>
|
||||||
|
<tb-get-value-action-settings *ngIf="behaviour.type === ScadaObjectBehaviorType.getValue"
|
||||||
|
fxFlex
|
||||||
|
panelTitle="{{ behaviour.name }}"
|
||||||
|
[valueType]="behaviour.valueType"
|
||||||
|
[aliasController]="aliasController"
|
||||||
|
[targetDevice]="targetDevice"
|
||||||
|
[widgetType]="widgetType"
|
||||||
|
formControlName="{{ behaviour.id }}">
|
||||||
|
</tb-get-value-action-settings>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="metadata?.properties?.length" class="tb-form-panel">
|
||||||
|
<div *ngFor="let property of metadata.properties" class="tb-form-row space-between">
|
||||||
|
<div class="fixed-title-width">{{ property.name }}</div>
|
||||||
|
<tb-color-input *ngIf="property.type === 'color'"
|
||||||
|
asBoxInput
|
||||||
|
colorClearButton
|
||||||
|
formControlName="{{ property.id }}">
|
||||||
|
</tb-color-input>
|
||||||
|
<mat-form-field *ngIf="property.type === 'number'" appearance="outline" class="number" subscriptSizing="dynamic">
|
||||||
|
<input matInput formControlName="{{ property.id }}" [min]="property.min" [max]="property.max" type="number" placeholder="{{ 'widget-config.set' | translate }}">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
@ -0,0 +1,168 @@
|
|||||||
|
///
|
||||||
|
/// 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, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
|
import {
|
||||||
|
ControlValueAccessor,
|
||||||
|
NG_VALUE_ACCESSOR,
|
||||||
|
UntypedFormBuilder,
|
||||||
|
UntypedFormGroup,
|
||||||
|
ValidatorFn,
|
||||||
|
Validators
|
||||||
|
} from '@angular/forms';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '@core/core.state';
|
||||||
|
import {
|
||||||
|
defaultScadaObjectSettings,
|
||||||
|
parseScadaObjectMetadataFromContent,
|
||||||
|
ScadaObjectBehaviorType,
|
||||||
|
ScadaObjectMetadata,
|
||||||
|
ScadaObjectSettings
|
||||||
|
} from '@home/components/widget/lib/scada/scada.models';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { ValueType } from '@shared/models/constants';
|
||||||
|
import { IAliasController } from '@core/api/widget-api.models';
|
||||||
|
import { TargetDevice, widgetType } from '@shared/models/widget.models';
|
||||||
|
import { isDefinedAndNotNull } from '@core/utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-scada-object-settings',
|
||||||
|
templateUrl: './scada-object-settings.component.html',
|
||||||
|
styleUrls: ['./../../widget-settings.scss'],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => ScadaObjectSettingsComponent),
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlValueAccessor {
|
||||||
|
|
||||||
|
ScadaObjectBehaviorType = ScadaObjectBehaviorType;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
disabled: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
svgPath = '/assets/widget/scada/drawing.svg';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
aliasController: IAliasController;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
targetDevice: TargetDevice;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
widgetType: widgetType;
|
||||||
|
|
||||||
|
private modelValue: ScadaObjectSettings;
|
||||||
|
|
||||||
|
private propagateChange = null;
|
||||||
|
|
||||||
|
public scadaObjectSettingsFormGroup: UntypedFormGroup;
|
||||||
|
|
||||||
|
metadata: ScadaObjectMetadata;
|
||||||
|
|
||||||
|
constructor(protected store: Store<AppState>,
|
||||||
|
private fb: UntypedFormBuilder,
|
||||||
|
private http: HttpClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.scadaObjectSettingsFormGroup = this.fb.group({});
|
||||||
|
this.scadaObjectSettingsFormGroup.valueChanges.subscribe(() => {
|
||||||
|
this.updateModel();
|
||||||
|
});
|
||||||
|
this.loadMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
for (const propName of Object.keys(changes)) {
|
||||||
|
const change = changes[propName];
|
||||||
|
if (!change.firstChange && change.currentValue !== change.previousValue) {
|
||||||
|
if (['svgPath'].includes(propName)) {
|
||||||
|
this.loadMetadata();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnChange(fn: any): void {
|
||||||
|
this.propagateChange = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnTouched(_fn: any): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
setDisabledState(isDisabled: boolean): void {
|
||||||
|
this.disabled = isDisabled;
|
||||||
|
if (isDisabled) {
|
||||||
|
this.scadaObjectSettingsFormGroup.disable({emitEvent: false});
|
||||||
|
} else {
|
||||||
|
this.scadaObjectSettingsFormGroup.enable({emitEvent: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeValue(value: ScadaObjectSettings): void {
|
||||||
|
this.modelValue = value || {};
|
||||||
|
this.setupValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadMetadata() {
|
||||||
|
this.http.get(this.svgPath, {responseType: 'text'}).subscribe(
|
||||||
|
(svgContent) => {
|
||||||
|
this.metadata = parseScadaObjectMetadataFromContent(svgContent);
|
||||||
|
for (const control of Object.keys(this.scadaObjectSettingsFormGroup.controls)) {
|
||||||
|
this.scadaObjectSettingsFormGroup.removeControl(control, {emitEvent: false});
|
||||||
|
}
|
||||||
|
for (const behaviour of this.metadata.behavior) {
|
||||||
|
this.scadaObjectSettingsFormGroup.addControl(behaviour.id, this.fb.control(null, []), {emitEvent: false});
|
||||||
|
}
|
||||||
|
for (const property of this.metadata.properties) {
|
||||||
|
const validators: ValidatorFn[] = [];
|
||||||
|
if (property.type === 'number') {
|
||||||
|
if (isDefinedAndNotNull(property.min)) {
|
||||||
|
validators.push(Validators.min(property.min));
|
||||||
|
}
|
||||||
|
if (isDefinedAndNotNull(property.max)) {
|
||||||
|
validators.push(Validators.max(property.max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.scadaObjectSettingsFormGroup.addControl(property.id, this.fb.control(null, validators), {emitEvent: false});
|
||||||
|
}
|
||||||
|
this.setupValue();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupValue() {
|
||||||
|
if (this.metadata) {
|
||||||
|
const defaults = defaultScadaObjectSettings(this.metadata);
|
||||||
|
this.modelValue = {...defaults, ...this.modelValue};
|
||||||
|
this.scadaObjectSettingsFormGroup.patchValue(
|
||||||
|
this.modelValue, {emitEvent: false}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateModel() {
|
||||||
|
this.modelValue = this.scadaObjectSettingsFormGroup.getRawValue();
|
||||||
|
this.propagateChange(this.modelValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly ValueType = ValueType;
|
||||||
|
}
|
||||||
@ -149,6 +149,9 @@ import {
|
|||||||
StatusWidgetStateSettingsComponent
|
StatusWidgetStateSettingsComponent
|
||||||
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
|
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
|
||||||
import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component';
|
import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/common/chart/chart-bar-settings.component';
|
||||||
|
import {
|
||||||
|
ScadaObjectSettingsComponent
|
||||||
|
} from '@home/components/widget/lib/settings/common/scada/scada-object-settings.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -204,6 +207,7 @@ import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/
|
|||||||
TimeSeriesChartStateRowComponent,
|
TimeSeriesChartStateRowComponent,
|
||||||
TimeSeriesChartGridSettingsComponent,
|
TimeSeriesChartGridSettingsComponent,
|
||||||
StatusWidgetStateSettingsComponent,
|
StatusWidgetStateSettingsComponent,
|
||||||
|
ScadaObjectSettingsComponent,
|
||||||
DataKeyInputComponent,
|
DataKeyInputComponent,
|
||||||
EntityAliasInputComponent
|
EntityAliasInputComponent
|
||||||
],
|
],
|
||||||
@ -265,6 +269,7 @@ import { ChartBarSettingsComponent } from '@home/components/widget/lib/settings/
|
|||||||
TimeSeriesChartStateRowComponent,
|
TimeSeriesChartStateRowComponent,
|
||||||
TimeSeriesChartGridSettingsComponent,
|
TimeSeriesChartGridSettingsComponent,
|
||||||
StatusWidgetStateSettingsComponent,
|
StatusWidgetStateSettingsComponent,
|
||||||
|
ScadaObjectSettingsComponent,
|
||||||
DataKeyInputComponent,
|
DataKeyInputComponent,
|
||||||
EntityAliasInputComponent
|
EntityAliasInputComponent
|
||||||
],
|
],
|
||||||
|
|||||||
@ -92,6 +92,7 @@ import { PieChartWidgetComponent } from '@home/components/widget/lib/chart/pie-c
|
|||||||
import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-chart-widget.component';
|
import { BarChartWidgetComponent } from '@home/components/widget/lib/chart/bar-chart-widget.component';
|
||||||
import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component';
|
import { PolarAreaWidgetComponent } from '@home/components/widget/lib/chart/polar-area-widget.component';
|
||||||
import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component';
|
import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/radar-chart-widget.component';
|
||||||
|
import { ScadaTestWidgetComponent } from '@home/components/widget/lib/scada/scada-test-widget.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations:
|
declarations:
|
||||||
@ -150,7 +151,8 @@ import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/rad
|
|||||||
PieChartWidgetComponent,
|
PieChartWidgetComponent,
|
||||||
BarChartWidgetComponent,
|
BarChartWidgetComponent,
|
||||||
PolarAreaWidgetComponent,
|
PolarAreaWidgetComponent,
|
||||||
RadarChartWidgetComponent
|
RadarChartWidgetComponent,
|
||||||
|
ScadaTestWidgetComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -212,7 +214,8 @@ import { RadarChartWidgetComponent } from '@home/components/widget/lib/chart/rad
|
|||||||
PieChartWidgetComponent,
|
PieChartWidgetComponent,
|
||||||
BarChartWidgetComponent,
|
BarChartWidgetComponent,
|
||||||
PolarAreaWidgetComponent,
|
PolarAreaWidgetComponent,
|
||||||
RadarChartWidgetComponent
|
RadarChartWidgetComponent,
|
||||||
|
ScadaTestWidgetComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
|
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
|
||||||
|
|||||||
149
ui-ngx/src/assets/widget/scada/drawing.svg
Normal file
149
ui-ngx/src/assets/widget/scada/drawing.svg
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
<svg width="100" height="100" version="1.1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg">
|
||||||
|
<tb:metadata>
|
||||||
|
{
|
||||||
|
"title": "My first SCADA Object",
|
||||||
|
"initial": [
|
||||||
|
{
|
||||||
|
"tag": "RECT",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "fill",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "background"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stroke",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "strokeColor"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stroke-width",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "strokeWidth"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"behavior": [
|
||||||
|
{
|
||||||
|
"id": "initialState",
|
||||||
|
"name": "Initial state",
|
||||||
|
"type": "getValue",
|
||||||
|
"valueType": "DOUBLE",
|
||||||
|
"defaultValue": 0,
|
||||||
|
"onUpdate": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"type": "any"
|
||||||
|
},
|
||||||
|
"state": [
|
||||||
|
{
|
||||||
|
"tag": "RECT",
|
||||||
|
"animate": 200,
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "height",
|
||||||
|
"value": {
|
||||||
|
"type": "input"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "width",
|
||||||
|
"value": {
|
||||||
|
"type": "input"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "disabledState",
|
||||||
|
"name": "Disabled state",
|
||||||
|
"type": "getValue",
|
||||||
|
"valueType": "BOOLEAN",
|
||||||
|
"defaultValue": false,
|
||||||
|
"onUpdate": [
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"type": "constant",
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
"state": [
|
||||||
|
{
|
||||||
|
"tag": "RECT",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "fill",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "disabledBackground"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matcher": {
|
||||||
|
"type": "constant",
|
||||||
|
"value": false
|
||||||
|
},
|
||||||
|
"state": [
|
||||||
|
{
|
||||||
|
"tag": "RECT",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "fill",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "background"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "strokeColor",
|
||||||
|
"name": "Stroke color",
|
||||||
|
"type": "color",
|
||||||
|
"default": "#aaa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "strokeWidth",
|
||||||
|
"name": "Stroke width",
|
||||||
|
"type": "number",
|
||||||
|
"default": 5,
|
||||||
|
"min": 0,
|
||||||
|
"max": 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "background",
|
||||||
|
"name": "Background",
|
||||||
|
"type": "color",
|
||||||
|
"default": "green"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "disabledBackground",
|
||||||
|
"name": "Disabled background",
|
||||||
|
"type": "color",
|
||||||
|
"default": "#ccc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</tb:metadata>
|
||||||
|
<rect tb:tag="RECT" width="100" height="100" rx="0" fill="none" stroke="#ccc" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.1 KiB |
Loading…
x
Reference in New Issue
Block a user