UI: Scada metadata and configuration improvements.
This commit is contained in:
parent
eb50048c50
commit
f01f99e738
@ -15,13 +15,23 @@
|
|||||||
///
|
///
|
||||||
|
|
||||||
import { ValueType } from '@shared/models/constants';
|
import { ValueType } from '@shared/models/constants';
|
||||||
import { Box, Element, Runner, Svg, SVG, Timeline } from '@svgdotjs/svg.js';
|
import { Box, Element, Runner, Svg, SVG, Timeline, Text } from '@svgdotjs/svg.js';
|
||||||
import { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
|
import { DataToValueType, GetValueAction, GetValueSettings } from '@shared/models/action-widget-settings.models';
|
||||||
import { insertVariable, isDefinedAndNotNull, isNumber, isNumeric, isUndefinedOrNull, mergeDeep } from '@core/utils';
|
import {
|
||||||
|
formatValue,
|
||||||
|
insertVariable,
|
||||||
|
isDefinedAndNotNull,
|
||||||
|
isNumber,
|
||||||
|
isNumeric,
|
||||||
|
isUndefinedOrNull,
|
||||||
|
mergeDeep,
|
||||||
|
parseFunction
|
||||||
|
} from '@core/utils';
|
||||||
import { BehaviorSubject, forkJoin, Observable } from 'rxjs';
|
import { BehaviorSubject, forkJoin, Observable } from 'rxjs';
|
||||||
import { map, share } from 'rxjs/operators';
|
import { map, share } from 'rxjs/operators';
|
||||||
import { ValueAction, ValueGetter } from '@home/components/widget/lib/action/action-widget.models';
|
import { ValueAction, ValueGetter } from '@home/components/widget/lib/action/action-widget.models';
|
||||||
import { WidgetContext } from '@home/models/widget-component.models';
|
import { WidgetContext } from '@home/models/widget-component.models';
|
||||||
|
import { Font } from '@shared/models/widget-settings.models';
|
||||||
|
|
||||||
export type ValueMatcherType = 'any' | 'constant' | 'range';
|
export type ValueMatcherType = 'any' | 'constant' | 'range';
|
||||||
|
|
||||||
@ -31,29 +41,85 @@ export interface ValueMatcher {
|
|||||||
range?: {from: number; to: number};
|
range?: {from: number; to: number};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScadaObjectAttributeValueType = 'input' | 'property';
|
const 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export interface ScadaObjectAttributeValue {
|
export type ScadaObjectValueType = 'input' | 'constant' | 'property' | 'function' | 'valueFormat';
|
||||||
type: ScadaObjectAttributeValueType;
|
|
||||||
propertyId?: string;
|
export interface ScadaObjectValueBase {
|
||||||
|
type: ScadaObjectValueType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScadaObjectAttributeState {
|
export interface ScadaObjectValueConstant extends ScadaObjectValueBase {
|
||||||
|
constantValue?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectValueProperty extends ScadaObjectValueBase {
|
||||||
|
propertyId?: string;
|
||||||
|
computedPropertyValue?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectValueFunction extends ScadaObjectValueBase {
|
||||||
|
valueConvertFunction?: string;
|
||||||
|
valueConverter?: (val: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectValueFormat extends ScadaObjectValueBase {
|
||||||
|
units?: ScadaObjectValue;
|
||||||
|
decimals?: ScadaObjectValue;
|
||||||
|
computedUnits?: string;
|
||||||
|
computedDecimals?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScadaObjectValue = ScadaObjectValueProperty & ScadaObjectValueConstant & ScadaObjectValueFunction & ScadaObjectValueFormat;
|
||||||
|
|
||||||
|
export interface ScadaObjectAttribute {
|
||||||
name: string;
|
name: string;
|
||||||
value: ScadaObjectAttributeValue;
|
value: ScadaObjectValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectText {
|
||||||
|
content?: ScadaObjectValue;
|
||||||
|
font?: ScadaObjectValue;
|
||||||
|
color?: ScadaObjectValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScadaObjectElementState {
|
||||||
|
tag: string;
|
||||||
|
show?: ScadaObjectValue;
|
||||||
|
text?: ScadaObjectText;
|
||||||
|
attributes?: ScadaObjectAttribute[];
|
||||||
|
animate?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScadaObjectState {
|
export interface ScadaObjectState {
|
||||||
tag: string;
|
initial?: boolean;
|
||||||
attributes: ScadaObjectAttributeState[];
|
value?: any;
|
||||||
animate?: number;
|
state: ScadaObjectElementState[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScadaObjectUpdateState {
|
export interface ScadaObjectUpdateState {
|
||||||
matcher: ValueMatcher;
|
matcher: ValueMatcher;
|
||||||
state: ScadaObjectState[];
|
stateId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const filterUpdateStates = (states: ScadaObjectUpdateState[], val: any): ScadaObjectUpdateState[] =>
|
||||||
|
states.filter(s => valueMatches(s.matcher, val));
|
||||||
|
|
||||||
export enum ScadaObjectBehaviorType {
|
export enum ScadaObjectBehaviorType {
|
||||||
setValue = 'setValue',
|
setValue = 'setValue',
|
||||||
getValue = 'getValue'
|
getValue = 'getValue'
|
||||||
@ -77,32 +143,38 @@ export interface ScadaObjectBehaviorSet extends ScadaObjectBehaviorBase {
|
|||||||
|
|
||||||
export type ScadaObjectBehavior = ScadaObjectBehaviorGet | ScadaObjectBehaviorSet;
|
export type ScadaObjectBehavior = ScadaObjectBehaviorGet | ScadaObjectBehaviorSet;
|
||||||
|
|
||||||
export type ScadaObjectPropertyType = 'string' | 'number' | 'color';
|
export type ScadaObjectPropertyType = 'string' | 'number' | 'color' | 'font' | 'units' | 'switch';
|
||||||
|
|
||||||
export interface ScadaObjectPropertyBase {
|
export interface ScadaObjectPropertyBase {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: ScadaObjectPropertyType;
|
type: ScadaObjectPropertyType;
|
||||||
default: any;
|
default: any;
|
||||||
|
required?: boolean;
|
||||||
|
subLabel?: string;
|
||||||
|
divider?: boolean;
|
||||||
|
fieldSuffix?: string;
|
||||||
|
disableOnProperty?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScadaObjectNumberProperty extends ScadaObjectPropertyBase {
|
export interface ScadaObjectNumberProperty extends ScadaObjectPropertyBase {
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
|
step?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScadaObjectProperty = ScadaObjectPropertyBase & ScadaObjectNumberProperty;
|
export type ScadaObjectProperty = ScadaObjectPropertyBase & ScadaObjectNumberProperty;
|
||||||
|
|
||||||
export interface ScadaObjectMetadata {
|
export interface ScadaObjectMetadata {
|
||||||
title: string;
|
title: string;
|
||||||
initial: ScadaObjectState[];
|
states: {[id: string]: ScadaObjectState};
|
||||||
behavior: ScadaObjectBehavior[];
|
behavior: ScadaObjectBehavior[];
|
||||||
properties: ScadaObjectProperty[];
|
properties: ScadaObjectProperty[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const emptyMetadata: ScadaObjectMetadata = {
|
export const emptyMetadata: ScadaObjectMetadata = {
|
||||||
title: '',
|
title: '',
|
||||||
initial: [],
|
states: {},
|
||||||
behavior: [],
|
behavior: [],
|
||||||
properties: []
|
properties: []
|
||||||
};
|
};
|
||||||
@ -126,6 +198,7 @@ const parseScadaObjectMetadataFromDom = (svgDoc: Document): ScadaObjectMetadata
|
|||||||
return emptyMetadata;
|
return emptyMetadata;
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
|
console.error(_e);
|
||||||
return emptyMetadata;
|
return emptyMetadata;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -201,8 +274,9 @@ export class ScadaObject {
|
|||||||
this.metadata = parseScadaObjectMetadataFromDom(doc);
|
this.metadata = parseScadaObjectMetadataFromDom(doc);
|
||||||
const defaults = defaultScadaObjectSettings(this.metadata);
|
const defaults = defaultScadaObjectSettings(this.metadata);
|
||||||
this.settings = mergeDeep<ScadaObjectSettings>({}, defaults, this.inputSettings || {});
|
this.settings = mergeDeep<ScadaObjectSettings>({}, defaults, this.inputSettings || {});
|
||||||
|
this.prepareMetadata();
|
||||||
this.prepareSvgShape(doc);
|
this.prepareSvgShape(doc);
|
||||||
this.prepareStates();
|
this.initStates();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -228,17 +302,58 @@ export class ScadaObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private prepareMetadata() {
|
||||||
|
for (const stateId of Object.keys(this.metadata.states)) {
|
||||||
|
const state = this.metadata.states[stateId];
|
||||||
|
for (const elementState of state.state) {
|
||||||
|
this.prepareValue(elementState.show);
|
||||||
|
this.prepareValue(elementState.text?.content);
|
||||||
|
this.prepareValue(elementState.text?.font);
|
||||||
|
this.prepareValue(elementState.text?.color);
|
||||||
|
if (elementState.attributes) {
|
||||||
|
for (const attribute of elementState.attributes) {
|
||||||
|
this.prepareValue(attribute.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepareValue(value: ScadaObjectValue) {
|
||||||
|
if (value) {
|
||||||
|
if (value.type === 'function' && value.valueConvertFunction) {
|
||||||
|
try {
|
||||||
|
value.valueConverter = parseFunction(this.insertVariables(value.valueConvertFunction), ['value']);
|
||||||
|
} catch (e) {
|
||||||
|
value.valueConverter = (v) => v;
|
||||||
|
}
|
||||||
|
} else if (value.type === 'property') {
|
||||||
|
value.computedPropertyValue = this.getPropertyValue(value.propertyId);
|
||||||
|
} else if (value.type === 'valueFormat') {
|
||||||
|
if (value.units) {
|
||||||
|
this.prepareValue(value.units);
|
||||||
|
}
|
||||||
|
if (value.decimals) {
|
||||||
|
this.prepareValue(value.decimals);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private insertVariables(content: string): string {
|
||||||
|
for (const property of this.metadata.properties) {
|
||||||
|
const value = this.getPropertyValue(property.id);
|
||||||
|
content = insertVariable(content, property.id, value);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
private prepareSvgShape(doc: XMLDocument) {
|
private prepareSvgShape(doc: XMLDocument) {
|
||||||
const elements = doc.getElementsByTagName('tb:metadata');
|
const elements = doc.getElementsByTagName('tb:metadata');
|
||||||
for (let i=0;i<elements.length;i++) {
|
for (let i=0;i<elements.length;i++) {
|
||||||
elements.item(i).remove();
|
elements.item(i).remove();
|
||||||
}
|
}
|
||||||
let svgContent = doc.documentElement.innerHTML;
|
this.svgShape = SVG().svg(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.overflow = 'visible';
|
||||||
this.svgShape.node.style['user-select'] = 'none';
|
this.svgShape.node.style['user-select'] = 'none';
|
||||||
this.box = this.svgShape.bbox();
|
this.box = this.svgShape.bbox();
|
||||||
@ -251,7 +366,7 @@ export class ScadaObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private prepareStates() {
|
private initStates() {
|
||||||
for (const behavior of this.metadata.behavior) {
|
for (const behavior of this.metadata.behavior) {
|
||||||
if (behavior.type === ScadaObjectBehaviorType.getValue) {
|
if (behavior.type === ScadaObjectBehaviorType.getValue) {
|
||||||
const getBehavior = behavior as ScadaObjectBehaviorGet;
|
const getBehavior = behavior as ScadaObjectBehaviorGet;
|
||||||
@ -266,8 +381,9 @@ export class ScadaObject {
|
|||||||
this.valueActions.push(valueGetter);
|
this.valueActions.push(valueGetter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.metadata.initial) {
|
const initialState = Object.values(this.metadata.states).find(s => s.initial);
|
||||||
this.updateState(this.metadata.initial);
|
if (initialState) {
|
||||||
|
this.updateState(initialState);
|
||||||
}
|
}
|
||||||
if (this.valueGetters.length) {
|
if (this.valueGetters.length) {
|
||||||
const getValueObservables: Array<Observable<any>> = [];
|
const getValueObservables: Array<Observable<any>> = [];
|
||||||
@ -301,23 +417,61 @@ export class ScadaObject {
|
|||||||
private onValue(id: string, value: any) {
|
private onValue(id: string, value: any) {
|
||||||
const getBehavior = this.metadata.behavior.find(b => b.id === id) as ScadaObjectBehaviorGet;
|
const getBehavior = this.metadata.behavior.find(b => b.id === id) as ScadaObjectBehaviorGet;
|
||||||
value = this.normalizeValue(value, getBehavior.valueType);
|
value = this.normalizeValue(value, getBehavior.valueType);
|
||||||
const updateStates = this.filterUpdateStates(getBehavior.onUpdate, value);
|
const updateStates = filterUpdateStates(getBehavior.onUpdate, value);
|
||||||
if (this.animationTimeline) {
|
if (this.animationTimeline) {
|
||||||
this.animationTimeline.finish();
|
this.animationTimeline.finish();
|
||||||
}
|
}
|
||||||
for (const updateState of updateStates) {
|
for (const updateState of updateStates) {
|
||||||
this.updateState(updateState.state, value);
|
const state = this.metadata.states[updateState.stateId];
|
||||||
|
this.updateState(state, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateState(state: ScadaObjectState[], value?: any) {
|
private updateState(state: ScadaObjectState, value?: any) {
|
||||||
for (const stateEntry of state) {
|
if (state) {
|
||||||
const tag = stateEntry.tag;
|
for (const elementState of state.state) {
|
||||||
const elements = this.svgShape.find(`[tb\\:tag="${tag}"]`);
|
const tag = elementState.tag;
|
||||||
const attrs = this.computeAttributes(stateEntry.attributes, value);
|
const elements = this.svgShape.find(`[tb\\:tag="${tag}"]`);
|
||||||
elements.forEach(e => {
|
if (elements.length) {
|
||||||
this.setElementAttributes(e, attrs, stateEntry.animate);
|
if (elementState.show) {
|
||||||
});
|
const show: boolean = this.computeValue(elementState.show, value);
|
||||||
|
elements.forEach(e => {
|
||||||
|
if (show) {
|
||||||
|
e.show();
|
||||||
|
} else {
|
||||||
|
e.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (elementState.attributes) {
|
||||||
|
const attrs = this.computeAttributes(elementState.attributes, value);
|
||||||
|
elements.forEach(e => {
|
||||||
|
this.setElementAttributes(e, attrs, elementState.animate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (elementState.text) {
|
||||||
|
if (elementState.text.content) {
|
||||||
|
const text: string = this.computeValue(elementState.text.content, value);
|
||||||
|
elements.forEach(e => {
|
||||||
|
this.setElementText(e, text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (elementState.text.font || elementState.text.color) {
|
||||||
|
let font: Font = this.computeValue(elementState.text.font, value);
|
||||||
|
if (typeof font !== 'object') {
|
||||||
|
font = undefined;
|
||||||
|
}
|
||||||
|
let color: string = this.computeValue(elementState.text.color, value);
|
||||||
|
if (typeof color !== 'string') {
|
||||||
|
color = undefined;
|
||||||
|
}
|
||||||
|
elements.forEach(e => {
|
||||||
|
this.setElementFont(e, font, color);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,11 +493,11 @@ export class ScadaObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private computeAttributes(attributes: ScadaObjectAttributeState[], value: any): {[attr: string]: any} {
|
private computeAttributes(attributes: ScadaObjectAttribute[], value: any): {[attr: string]: any} {
|
||||||
const res: {[attr: string]: any} = {};
|
const res: {[attr: string]: any} = {};
|
||||||
for (const attribute of attributes) {
|
for (const attribute of attributes) {
|
||||||
const attr = attribute.name;
|
const attr = attribute.name;
|
||||||
res[attr] = this.getAttributeValue(attribute, value);
|
res[attr] = this.computeValue(attribute.value, value);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@ -356,6 +510,41 @@ export class ScadaObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setElementText(element: Element, text: string) {
|
||||||
|
let textElement: Text;
|
||||||
|
if (element.type === 'text') {
|
||||||
|
const children = element.children();
|
||||||
|
if (children.length && children[0].type === 'tspan') {
|
||||||
|
textElement = children[0] as Text;
|
||||||
|
} else {
|
||||||
|
textElement = element as Text;
|
||||||
|
}
|
||||||
|
} else if (element.type === 'tspan') {
|
||||||
|
textElement = element as Text;
|
||||||
|
}
|
||||||
|
if (textElement) {
|
||||||
|
textElement.text(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setElementFont(element: Element, font: Font, color: string) {
|
||||||
|
if (element.type === 'text') {
|
||||||
|
const textElement = element as Text;
|
||||||
|
if (font) {
|
||||||
|
textElement.font({
|
||||||
|
family: font.family,
|
||||||
|
size: (isDefinedAndNotNull(font.size) && isDefinedAndNotNull(font.sizeUnit)) ?
|
||||||
|
font.size + font.sizeUnit : null,
|
||||||
|
weight: font.weight,
|
||||||
|
style: font.style
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (color) {
|
||||||
|
textElement.fill(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private animation(element: Element, duration: number): Runner {
|
private animation(element: Element, duration: number): Runner {
|
||||||
if (!this.animationTimeline) {
|
if (!this.animationTimeline) {
|
||||||
this.animationTimeline = new Timeline();
|
this.animationTimeline = new Timeline();
|
||||||
@ -364,35 +553,55 @@ export class ScadaObject {
|
|||||||
return element.animate(duration, 0, 'now');
|
return element.animate(duration, 0, 'now');
|
||||||
}
|
}
|
||||||
|
|
||||||
private getAttributeValue(attribute: ScadaObjectAttributeState, value: any): any {
|
private computeValue(objectValue: ScadaObjectValue, value: any): any {
|
||||||
if (attribute.value.type === 'input') {
|
if (objectValue) {
|
||||||
return value;
|
switch (objectValue.type) {
|
||||||
} else if (attribute.value.type === 'property') {
|
case 'input':
|
||||||
const id = attribute.value.propertyId;
|
return value;
|
||||||
return this.settings[id] || '';
|
case 'constant':
|
||||||
|
return objectValue.constantValue;
|
||||||
|
case 'property':
|
||||||
|
return objectValue.computedPropertyValue;
|
||||||
|
case 'function':
|
||||||
|
try {
|
||||||
|
return objectValue.valueConverter(value);
|
||||||
|
} catch (_e) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
case 'valueFormat':
|
||||||
|
let units = '';
|
||||||
|
let decimals = 0;
|
||||||
|
if (objectValue.units) {
|
||||||
|
units = this.computeValue(objectValue.units, value);
|
||||||
|
}
|
||||||
|
if (objectValue.decimals) {
|
||||||
|
decimals = this.computeValue(objectValue.decimals, value);
|
||||||
|
}
|
||||||
|
return formatValue(value, decimals, units, false);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterUpdateStates(states: ScadaObjectUpdateState[], val: any): ScadaObjectUpdateState[] {
|
private getPropertyValue(id: string): any {
|
||||||
return states.filter(s => this.valueMatches(s.matcher, val));
|
const property = this.metadata.properties.find(p => p.id === id);
|
||||||
}
|
if (property) {
|
||||||
|
const value = this.settings[id];
|
||||||
private valueMatches(matcher: ValueMatcher, val: any): boolean {
|
if (isDefinedAndNotNull(value)) {
|
||||||
switch (matcher.type) {
|
return value;
|
||||||
case 'any':
|
} else {
|
||||||
return true;
|
switch (property.type) {
|
||||||
case 'constant':
|
case 'string':
|
||||||
return matcher.value === val;
|
return '';
|
||||||
case 'range':
|
case 'number':
|
||||||
if (isDefinedAndNotNull(val) && isNumeric(val)) {
|
return 0;
|
||||||
const num = Number(val);
|
case 'color':
|
||||||
const range = matcher.range;
|
return '#000';
|
||||||
return ((!isNumber(range.from) || num >= range.from) && (!isNumber(range.to) || num < range.to));
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,17 +32,42 @@
|
|||||||
</tb-get-value-action-settings>
|
</tb-get-value-action-settings>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="metadata?.properties?.length" class="tb-form-panel">
|
<div *ngIf="propertyRows?.length" class="tb-form-panel">
|
||||||
<div *ngFor="let property of metadata.properties" class="tb-form-row space-between">
|
<div *ngFor="let propertyRow of propertyRows" class="tb-form-row space-between">
|
||||||
<div class="fixed-title-width">{{ property.name }}</div>
|
<mat-slide-toggle *ngIf="propertyRow.switch" class="mat-slide fixed-title-width" formControlName="{{ propertyRow.switch.id }}">
|
||||||
<tb-color-input *ngIf="property.type === 'color'"
|
{{ propertyRow.label }}
|
||||||
asBoxInput
|
</mat-slide-toggle>
|
||||||
colorClearButton
|
<div *ngIf="!propertyRow.switch" class="fixed-title-width">{{ propertyRow.label }}</div>
|
||||||
formControlName="{{ property.id }}">
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
|
||||||
</tb-color-input>
|
<ng-container *ngFor="let property of propertyRow.properties">
|
||||||
<mat-form-field *ngIf="property.type === 'number'" appearance="outline" class="number" subscriptSizing="dynamic">
|
<div *ngIf="property.subLabel" class="tb-small-label">{{ property.subLabel }}</div>
|
||||||
<input matInput formControlName="{{ property.id }}" [min]="property.min" [max]="property.max" type="number" placeholder="{{ 'widget-config.set' | translate }}">
|
<mat-form-field *ngIf="property.type === 'string'" appearance="outline" subscriptSizing="dynamic">
|
||||||
</mat-form-field>
|
<input matInput formControlName="{{ property.id }}" [required]="property.required" placeholder="{{ 'widget-config.set' | translate }}">
|
||||||
|
<div matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix }}</div>
|
||||||
|
</mat-form-field>
|
||||||
|
<tb-color-input *ngIf="property.type === 'color'"
|
||||||
|
[required]="property.required"
|
||||||
|
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 }}" [required]="property.required"
|
||||||
|
[min]="property.min" [max]="property.max" [step]="property.step"
|
||||||
|
type="number" placeholder="{{ 'widget-config.set' | translate }}">
|
||||||
|
<div matSuffix *ngIf="property.fieldSuffix">{{ property.fieldSuffix }}</div>
|
||||||
|
</mat-form-field>
|
||||||
|
<tb-font-settings *ngIf="property.type === 'font'"
|
||||||
|
formControlName="{{ property.id }}"
|
||||||
|
clearButton
|
||||||
|
disabledLineHeight>
|
||||||
|
</tb-font-settings>
|
||||||
|
<tb-unit-input *ngIf="property.type === 'units'"
|
||||||
|
[required]="property.required"
|
||||||
|
formControlName="{{ property.id }}"></tb-unit-input>
|
||||||
|
<mat-divider *ngIf="property.divider" vertical></mat-divider>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@ -14,12 +14,15 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ControlValueAccessor,
|
ControlValueAccessor,
|
||||||
|
NG_VALIDATORS,
|
||||||
NG_VALUE_ACCESSOR,
|
NG_VALUE_ACCESSOR,
|
||||||
UntypedFormBuilder,
|
UntypedFormBuilder,
|
||||||
|
UntypedFormControl,
|
||||||
UntypedFormGroup,
|
UntypedFormGroup,
|
||||||
|
Validator,
|
||||||
ValidatorFn,
|
ValidatorFn,
|
||||||
Validators
|
Validators
|
||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
@ -37,6 +40,11 @@ import { ValueType } from '@shared/models/constants';
|
|||||||
import { IAliasController } from '@core/api/widget-api.models';
|
import { IAliasController } from '@core/api/widget-api.models';
|
||||||
import { TargetDevice, widgetType } from '@shared/models/widget.models';
|
import { TargetDevice, widgetType } from '@shared/models/widget.models';
|
||||||
import { isDefinedAndNotNull } from '@core/utils';
|
import { isDefinedAndNotNull } from '@core/utils';
|
||||||
|
import {
|
||||||
|
ScadaPropertyRow,
|
||||||
|
toPropertyRows
|
||||||
|
} from '@home/components/widget/lib/settings/common/scada/scada-object-settings.models';
|
||||||
|
import { merge, Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tb-scada-object-settings',
|
selector: 'tb-scada-object-settings',
|
||||||
@ -47,10 +55,15 @@ import { isDefinedAndNotNull } from '@core/utils';
|
|||||||
provide: NG_VALUE_ACCESSOR,
|
provide: NG_VALUE_ACCESSOR,
|
||||||
useExisting: forwardRef(() => ScadaObjectSettingsComponent),
|
useExisting: forwardRef(() => ScadaObjectSettingsComponent),
|
||||||
multi: true
|
multi: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => ScadaObjectSettingsComponent),
|
||||||
|
multi: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlValueAccessor {
|
export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlValueAccessor, Validator {
|
||||||
|
|
||||||
ScadaObjectBehaviorType = ScadaObjectBehaviorType;
|
ScadaObjectBehaviorType = ScadaObjectBehaviorType;
|
||||||
|
|
||||||
@ -73,13 +86,18 @@ export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlV
|
|||||||
|
|
||||||
private propagateChange = null;
|
private propagateChange = null;
|
||||||
|
|
||||||
|
private validatorTriggers: string[];
|
||||||
|
private validatorSubscription: Subscription;
|
||||||
|
|
||||||
public scadaObjectSettingsFormGroup: UntypedFormGroup;
|
public scadaObjectSettingsFormGroup: UntypedFormGroup;
|
||||||
|
|
||||||
metadata: ScadaObjectMetadata;
|
metadata: ScadaObjectMetadata;
|
||||||
|
propertyRows: ScadaPropertyRow[];
|
||||||
|
|
||||||
constructor(protected store: Store<AppState>,
|
constructor(protected store: Store<AppState>,
|
||||||
private fb: UntypedFormBuilder,
|
private fb: UntypedFormBuilder,
|
||||||
private http: HttpClient) {
|
private http: HttpClient,
|
||||||
|
private cd: ChangeDetectorRef) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -114,6 +132,7 @@ export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlV
|
|||||||
this.scadaObjectSettingsFormGroup.disable({emitEvent: false});
|
this.scadaObjectSettingsFormGroup.disable({emitEvent: false});
|
||||||
} else {
|
} else {
|
||||||
this.scadaObjectSettingsFormGroup.enable({emitEvent: false});
|
this.scadaObjectSettingsFormGroup.enable({emitEvent: false});
|
||||||
|
this.updateValidators();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,10 +141,25 @@ export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlV
|
|||||||
this.setupValue();
|
this.setupValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validate(c: UntypedFormControl) {
|
||||||
|
const valid = this.scadaObjectSettingsFormGroup.valid;
|
||||||
|
return valid ? null : {
|
||||||
|
scadaObject: {
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private loadMetadata() {
|
private loadMetadata() {
|
||||||
|
if (this.validatorSubscription) {
|
||||||
|
this.validatorSubscription.unsubscribe();
|
||||||
|
this.validatorSubscription = null;
|
||||||
|
}
|
||||||
|
this.validatorTriggers = [];
|
||||||
this.http.get(this.svgPath, {responseType: 'text'}).subscribe(
|
this.http.get(this.svgPath, {responseType: 'text'}).subscribe(
|
||||||
(svgContent) => {
|
(svgContent) => {
|
||||||
this.metadata = parseScadaObjectMetadataFromContent(svgContent);
|
this.metadata = parseScadaObjectMetadataFromContent(svgContent);
|
||||||
|
this.propertyRows = toPropertyRows(this.metadata.properties);
|
||||||
for (const control of Object.keys(this.scadaObjectSettingsFormGroup.controls)) {
|
for (const control of Object.keys(this.scadaObjectSettingsFormGroup.controls)) {
|
||||||
this.scadaObjectSettingsFormGroup.removeControl(control, {emitEvent: false});
|
this.scadaObjectSettingsFormGroup.removeControl(control, {emitEvent: false});
|
||||||
}
|
}
|
||||||
@ -133,7 +167,15 @@ export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlV
|
|||||||
this.scadaObjectSettingsFormGroup.addControl(behaviour.id, this.fb.control(null, []), {emitEvent: false});
|
this.scadaObjectSettingsFormGroup.addControl(behaviour.id, this.fb.control(null, []), {emitEvent: false});
|
||||||
}
|
}
|
||||||
for (const property of this.metadata.properties) {
|
for (const property of this.metadata.properties) {
|
||||||
|
if (property.disableOnProperty) {
|
||||||
|
if (!this.validatorTriggers.includes(property.disableOnProperty)) {
|
||||||
|
this.validatorTriggers.push(property.disableOnProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
const validators: ValidatorFn[] = [];
|
const validators: ValidatorFn[] = [];
|
||||||
|
if (property.required) {
|
||||||
|
validators.push(Validators.required);
|
||||||
|
}
|
||||||
if (property.type === 'number') {
|
if (property.type === 'number') {
|
||||||
if (isDefinedAndNotNull(property.min)) {
|
if (isDefinedAndNotNull(property.min)) {
|
||||||
validators.push(Validators.min(property.min));
|
validators.push(Validators.min(property.min));
|
||||||
@ -144,11 +186,37 @@ export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlV
|
|||||||
}
|
}
|
||||||
this.scadaObjectSettingsFormGroup.addControl(property.id, this.fb.control(null, validators), {emitEvent: false});
|
this.scadaObjectSettingsFormGroup.addControl(property.id, this.fb.control(null, validators), {emitEvent: false});
|
||||||
}
|
}
|
||||||
|
if (this.validatorTriggers.length) {
|
||||||
|
const observables: Observable<any>[] = [];
|
||||||
|
for (const trigger of this.validatorTriggers) {
|
||||||
|
observables.push(this.scadaObjectSettingsFormGroup.get(trigger).valueChanges);
|
||||||
|
}
|
||||||
|
this.validatorSubscription = merge(...observables).subscribe(() => {
|
||||||
|
this.updateValidators();
|
||||||
|
});
|
||||||
|
}
|
||||||
this.setupValue();
|
this.setupValue();
|
||||||
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateValidators() {
|
||||||
|
for (const trigger of this.validatorTriggers) {
|
||||||
|
const value: boolean = this.scadaObjectSettingsFormGroup.get(trigger).value;
|
||||||
|
this.metadata.properties.filter(p => p.disableOnProperty === trigger).forEach(
|
||||||
|
(p) => {
|
||||||
|
const control = this.scadaObjectSettingsFormGroup.get(p.id);
|
||||||
|
if (value) {
|
||||||
|
control.enable({emitEvent: false});
|
||||||
|
} else {
|
||||||
|
control.disable({emitEvent: false});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private setupValue() {
|
private setupValue() {
|
||||||
if (this.metadata) {
|
if (this.metadata) {
|
||||||
const defaults = defaultScadaObjectSettings(this.metadata);
|
const defaults = defaultScadaObjectSettings(this.metadata);
|
||||||
@ -156,6 +224,7 @@ export class ScadaObjectSettingsComponent implements OnInit, OnChanges, ControlV
|
|||||||
this.scadaObjectSettingsFormGroup.patchValue(
|
this.scadaObjectSettingsFormGroup.patchValue(
|
||||||
this.modelValue, {emitEvent: false}
|
this.modelValue, {emitEvent: false}
|
||||||
);
|
);
|
||||||
|
this.setDisabledState(this.disabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
///
|
||||||
|
|
||||||
|
import { ScadaObjectProperty } from '@home/components/widget/lib/scada/scada.models';
|
||||||
|
|
||||||
|
export interface ScadaPropertyRow {
|
||||||
|
label: string;
|
||||||
|
properties: ScadaObjectProperty[];
|
||||||
|
switch?: ScadaObjectProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toPropertyRows = (properties: ScadaObjectProperty[]): ScadaPropertyRow[] => {
|
||||||
|
const result: ScadaPropertyRow[] = [];
|
||||||
|
for (const property of properties) {
|
||||||
|
let propertyRow = result.find(r => r.label === property.name);
|
||||||
|
if (!propertyRow) {
|
||||||
|
propertyRow = {
|
||||||
|
label: property.name,
|
||||||
|
properties: []
|
||||||
|
};
|
||||||
|
result.push(propertyRow);
|
||||||
|
}
|
||||||
|
if (property.type === 'switch') {
|
||||||
|
propertyRow.switch = property;
|
||||||
|
} else {
|
||||||
|
propertyRow.properties.push(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
@ -1,39 +1,160 @@
|
|||||||
<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">
|
<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>
|
<tb:metadata>{
|
||||||
{
|
|
||||||
"title": "My first SCADA Object",
|
"title": "My first SCADA Object",
|
||||||
"initial": [
|
"states": {
|
||||||
{
|
"initialState": {
|
||||||
"tag": "RECT",
|
"initial": true,
|
||||||
"attributes": [
|
"state": [
|
||||||
{
|
{
|
||||||
"name": "fill",
|
"tag": "level",
|
||||||
"value": {
|
"attributes": [
|
||||||
"type": "property",
|
{
|
||||||
"propertyId": "background"
|
"name": "fill",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "levelBackground"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "levelValueBackground",
|
||||||
|
"show": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "showValue"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stroke",
|
"tag": "levelValue",
|
||||||
"value": {
|
"show": {
|
||||||
"type": "property",
|
"type": "property",
|
||||||
"propertyId": "strokeColor"
|
"propertyId": "showValue"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"font": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "valueFont"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "valueColor"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stroke-width",
|
"tag": "minLevel",
|
||||||
"value": {
|
"show": {
|
||||||
"type": "property",
|
"type": "property",
|
||||||
"propertyId": "strokeWidth"
|
"propertyId": "showMinMaxLevel"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"content": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "minLevel"
|
||||||
|
},
|
||||||
|
"font": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "minMaxLevelFont"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "minMaxLevelColor"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
]
|
{
|
||||||
|
"tag": "maxLevel",
|
||||||
|
"show": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "showMinMaxLevel"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"content": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "maxLevel"
|
||||||
|
},
|
||||||
|
"font": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "minMaxLevelFont"
|
||||||
|
},
|
||||||
|
"color": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "minMaxLevelColor"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"updateLevelState": {
|
||||||
|
"state": [
|
||||||
|
{
|
||||||
|
"tag": "level",
|
||||||
|
"animate": 200,
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "height",
|
||||||
|
"value": {
|
||||||
|
"type": "function",
|
||||||
|
"valueConvertFunction": "var level = (value - ${minLevel}) / (${maxLevel} - ${minLevel});\nlevel = Math.max(0, Math.min(1, level));\nreturn level*80;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tag": "levelValue",
|
||||||
|
"text": {
|
||||||
|
"content": {
|
||||||
|
"type": "valueFormat",
|
||||||
|
"units": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "valueUnits"
|
||||||
|
},
|
||||||
|
"decimals": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "valueDecimals"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"disabledState": {
|
||||||
|
"state": [
|
||||||
|
{
|
||||||
|
"tag": "level",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "fill",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "disabledLevelBackground"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"enabledState": {
|
||||||
|
"state": [
|
||||||
|
{
|
||||||
|
"tag": "level",
|
||||||
|
"attributes": [
|
||||||
|
{
|
||||||
|
"name": "fill",
|
||||||
|
"value": {
|
||||||
|
"type": "property",
|
||||||
|
"propertyId": "levelBackground"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
"behavior": [
|
"behavior": [
|
||||||
{
|
{
|
||||||
"id": "initialState",
|
"id": "levelState",
|
||||||
"name": "Initial state",
|
"name": "Level",
|
||||||
"type": "getValue",
|
"type": "getValue",
|
||||||
"valueType": "DOUBLE",
|
"valueType": "DOUBLE",
|
||||||
"defaultValue": 0,
|
"defaultValue": 0,
|
||||||
@ -42,26 +163,7 @@
|
|||||||
"matcher": {
|
"matcher": {
|
||||||
"type": "any"
|
"type": "any"
|
||||||
},
|
},
|
||||||
"state": [
|
"stateId": "updateLevelState"
|
||||||
{
|
|
||||||
"tag": "RECT",
|
|
||||||
"animate": 200,
|
|
||||||
"attributes": [
|
|
||||||
{
|
|
||||||
"name": "height",
|
|
||||||
"value": {
|
|
||||||
"type": "input"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "width",
|
|
||||||
"value": {
|
|
||||||
"type": "input"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -77,73 +179,129 @@
|
|||||||
"type": "constant",
|
"type": "constant",
|
||||||
"value": true
|
"value": true
|
||||||
},
|
},
|
||||||
"state": [
|
"stateId": "disabledState"
|
||||||
{
|
|
||||||
"tag": "RECT",
|
|
||||||
"attributes": [
|
|
||||||
{
|
|
||||||
"name": "fill",
|
|
||||||
"value": {
|
|
||||||
"type": "property",
|
|
||||||
"propertyId": "disabledBackground"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matcher": {
|
"matcher": {
|
||||||
"type": "constant",
|
"type": "constant",
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"state": [
|
"stateId": "enabledState"
|
||||||
{
|
|
||||||
"tag": "RECT",
|
|
||||||
"attributes": [
|
|
||||||
{
|
|
||||||
"name": "fill",
|
|
||||||
"value": {
|
|
||||||
"type": "property",
|
|
||||||
"propertyId": "background"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"properties": [
|
"properties": [
|
||||||
{
|
{
|
||||||
"id": "strokeColor",
|
"id": "showValue",
|
||||||
"name": "Stroke color",
|
"name": "Value",
|
||||||
"type": "color",
|
"type": "switch",
|
||||||
"default": "#aaa"
|
"default": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "strokeWidth",
|
"id": "valueUnits",
|
||||||
"name": "Stroke width",
|
"name": "Value",
|
||||||
|
"type": "units",
|
||||||
|
"default": "",
|
||||||
|
"disableOnProperty": "showValue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valueDecimals",
|
||||||
|
"name": "Value",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 5,
|
"default": 2,
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": 20
|
"max": 15,
|
||||||
|
"step": 1,
|
||||||
|
"fieldSuffix": "decimals",
|
||||||
|
"disableOnProperty": "showValue"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "background",
|
"id": "valueFont",
|
||||||
"name": "Background",
|
"name": "Value",
|
||||||
|
"type": "font",
|
||||||
|
"default": {
|
||||||
|
"size": 6,
|
||||||
|
"sizeUnit": "px",
|
||||||
|
"family": "Roboto",
|
||||||
|
"weight": "normal",
|
||||||
|
"style": "normal"
|
||||||
|
},
|
||||||
|
"disableOnProperty": "showValue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "valueColor",
|
||||||
|
"name": "Value",
|
||||||
|
"type": "color",
|
||||||
|
"default": "#000000",
|
||||||
|
"disableOnProperty": "showValue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minLevel",
|
||||||
|
"name": "Level range",
|
||||||
|
"subLabel": "min",
|
||||||
|
"type": "number",
|
||||||
|
"required": true,
|
||||||
|
"default": 0,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "maxLevel",
|
||||||
|
"name": "Level range",
|
||||||
|
"subLabel": "max",
|
||||||
|
"type": "number",
|
||||||
|
"required": true,
|
||||||
|
"default": 100,
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "showMinMaxLevel",
|
||||||
|
"name": "Min/Max label",
|
||||||
|
"type": "switch",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minMaxLevelFont",
|
||||||
|
"name": "Min/Max label",
|
||||||
|
"type": "font",
|
||||||
|
"default": {
|
||||||
|
"size": 6,
|
||||||
|
"sizeUnit": "px",
|
||||||
|
"family": "Roboto",
|
||||||
|
"weight": "normal",
|
||||||
|
"style": "normal"
|
||||||
|
},
|
||||||
|
"disableOnProperty": "showMinMaxLevel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "minMaxLevelColor",
|
||||||
|
"name": "Min/Max label",
|
||||||
|
"type": "color",
|
||||||
|
"default": "#666",
|
||||||
|
"disableOnProperty": "showMinMaxLevel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "levelBackground",
|
||||||
|
"name": "Level background",
|
||||||
|
"subLabel": "Enabled",
|
||||||
"type": "color",
|
"type": "color",
|
||||||
"default": "green"
|
"default": "#1abb48",
|
||||||
},
|
"divider": true
|
||||||
{
|
},
|
||||||
"id": "disabledBackground",
|
{
|
||||||
"name": "Disabled background",
|
"id": "disabledLevelBackground",
|
||||||
"type": "color",
|
"name": "Level background",
|
||||||
"default": "#ccc"
|
"subLabel": "Disabled",
|
||||||
}
|
"type": "color",
|
||||||
|
"default": "#ccc"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}</tb:metadata>
|
||||||
</tb:metadata>
|
<rect width="100" height="100" rx="0" fill="none" stroke="#ccc" stroke-width="2"/>
|
||||||
<rect tb:tag="RECT" width="100" height="100" rx="0" fill="none" stroke="#ccc" stroke-width="2"/>
|
<rect x="8" y="15" width="20" height="81" rx="2" fill="#ececec" stroke="#000" stroke-width="1.0359"/>
|
||||||
|
<rect transform="scale(1 -1)" x="8.5" y="-95.5" width="19" height="40" rx="1.5" fill="#1abb48" tb:tag="level"/>
|
||||||
|
<text x="32" y="95" fill="#666" font-family="Roboto" font-size="6px" tb:tag="minLevel" xml:space="preserve"><tspan x="31.354307" y="96.626251">min</tspan></text>
|
||||||
|
<rect x="10.941" y="49.544" width="14.155" height="12" rx="2.8668" fill="#fff" fill-opacity=".45148" tb:tag="levelValueBackground"/>
|
||||||
|
<text x="18" y="56" dominant-baseline="middle" fill="#000000" font-family="Roboto" font-size="6px" text-anchor="middle" tb:tag="levelValue" xml:space="preserve"><tspan>N/A</tspan></text>
|
||||||
|
<text x="32" y="20" fill="#666" font-family="Roboto" font-size="6px" tb:tag="maxLevel" xml:space="preserve"><tspan x="29.865496" y="18.716127">max</tspan></text>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 9.3 KiB |
Loading…
x
Reference in New Issue
Block a user