UI: Improved knob control widgets
This commit is contained in:
parent
0047168bfc
commit
c37b6d6d46
@ -12,10 +12,9 @@
|
||||
"templateHtml": "<tb-knob [ctx]='ctx'></tb-knob>",
|
||||
"templateCss": "",
|
||||
"controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n",
|
||||
"settingsSchema": "",
|
||||
"dataKeySettingsSchema": "{}\n",
|
||||
"dataKeySettingsForm": [],
|
||||
"settingsDirective": "tb-knob-control-widget-settings",
|
||||
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}"
|
||||
"defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2,\"units\":null}"
|
||||
},
|
||||
"tags": [
|
||||
"dial",
|
||||
|
||||
@ -27,9 +27,6 @@
|
||||
<div #knobTopPointer class="top-pointer"></div>
|
||||
</div>
|
||||
<div class="top"></div>
|
||||
<div #knobErrorContainer class="error-container flex flex-row items-center justify-center" [style.background]="error?.length ? 'rgba(255,255,255,0.25)' : 'none'">
|
||||
<span #knobError class="knob-error" [innerHTML]="error"></span>
|
||||
</div>
|
||||
<div #knobTitleContainer class="title-container flex flex-row items-center justify-center">
|
||||
<span #knobTitle class="knob-title">{{ title }}</span>
|
||||
</div>
|
||||
|
||||
@ -14,37 +14,27 @@
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { ChangeDetectorRef, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { WidgetContext } from '@home/models/widget-component.models';
|
||||
import { UtilsService } from '@core/services/utils.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { isDefined, isNumber } from '@core/utils';
|
||||
import { deepClone, isDefined } from '@core/utils';
|
||||
import { CanvasDigitalGaugeOptions } from '@home/components/widget/lib/canvas-digital-gauge';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { ColorProcessor, gradientColor } from '@shared/models/widget-settings.models';
|
||||
import { getSourceTbUnitSymbol } from '@shared/models/unit.models';
|
||||
import { ColorProcessor, gradientColor, ValueFormatProcessor } from '@shared/models/widget-settings.models';
|
||||
import {
|
||||
KnobSettings,
|
||||
knobWidgetDefaultSettings,
|
||||
prepareKnobSettings
|
||||
} from '@shared/models/widget/rpc/knob.component.models';
|
||||
import { ValueType } from '@shared/models/constants';
|
||||
import { BasicActionWidgetComponent, ValueSetter } from '@home/components/widget/lib/action/action-widget.models';
|
||||
import GenericOptions = CanvasGauges.GenericOptions;
|
||||
|
||||
interface KnobSettings {
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
initialValue: number;
|
||||
title: string;
|
||||
getValueMethod: string;
|
||||
setValueMethod: string;
|
||||
requestTimeout: number;
|
||||
requestPersistent: boolean;
|
||||
persistentPollingInterval: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tb-knob',
|
||||
templateUrl: './knob.component.html',
|
||||
styleUrls: ['./knob.component.scss']
|
||||
})
|
||||
export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
export class KnobComponent extends BasicActionWidgetComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('knob', {static: true}) knobRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobContainer', {static: true}) knobContainerRef: ElementRef<HTMLElement>;
|
||||
@ -52,8 +42,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('knobTopPointer', {static: true}) knobTopPointerRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobValueContainer', {static: true}) knobValueContainerRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobValue', {static: true}) knobValueRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobErrorContainer', {static: true}) knobErrorContainerRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobError', {static: true}) knobErrorRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobTitleContainer', {static: true}) knobTitleContainerRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobTitle', {static: true}) knobTitleRef: ElementRef<HTMLElement>;
|
||||
@ViewChild('knobMinmaxContainer', {static: true}) knobMinmaxContainerRef: ElementRef<HTMLElement>;
|
||||
@ -64,7 +52,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
ctx: WidgetContext;
|
||||
|
||||
value = '0';
|
||||
error = '';
|
||||
title = '';
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
@ -79,13 +66,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
private minDeg = -45;
|
||||
private maxDeg = 225;
|
||||
|
||||
private isSimulated: boolean;
|
||||
private requestTimeout: number;
|
||||
private requestPersistent: boolean;
|
||||
private persistentPollingInterval: number;
|
||||
private getValueMethod: string;
|
||||
private setValueMethod: string;
|
||||
|
||||
private executingUpdateValue: boolean;
|
||||
private scheduledValue: number;
|
||||
private rpcValue: number;
|
||||
@ -98,8 +78,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
private knobValue: JQuery<HTMLElement>;
|
||||
private knobTitleContainer: JQuery<HTMLElement>;
|
||||
private knobTitle: JQuery<HTMLElement>;
|
||||
private knobErrorContainer: JQuery<HTMLElement>;
|
||||
private knobError: JQuery<HTMLElement>;
|
||||
private knobMinmaxContainer: JQuery<HTMLElement>;
|
||||
private minmaxLabel: JQuery<HTMLElement>;
|
||||
private textMeasure: JQuery<HTMLElement>;
|
||||
@ -109,9 +87,11 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
|
||||
private knobResize$: ResizeObserver;
|
||||
|
||||
constructor(private utils: UtilsService,
|
||||
protected store: Store<AppState>) {
|
||||
super(store);
|
||||
private valueSetter: ValueSetter<number>;
|
||||
private valueFormat: ValueFormatProcessor;
|
||||
|
||||
constructor(protected cd: ChangeDetectorRef) {
|
||||
super(cd);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -123,8 +103,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
this.knobValue = $(this.knobValueRef.nativeElement);
|
||||
this.knobTitleContainer = $(this.knobTitleContainerRef.nativeElement);
|
||||
this.knobTitle = $(this.knobTitleRef.nativeElement);
|
||||
this.knobErrorContainer = $(this.knobErrorContainerRef.nativeElement);
|
||||
this.knobError = $(this.knobErrorRef.nativeElement);
|
||||
this.knobMinmaxContainer = $(this.knobMinmaxContainerRef.nativeElement);
|
||||
this.minmaxLabel = this.knobMinmaxContainer.find<HTMLElement>('.minmax-label');
|
||||
this.textMeasure = $(this.textMeasureRef.nativeElement);
|
||||
@ -145,7 +123,31 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private init() {
|
||||
const settings: KnobSettings = this.ctx.settings;
|
||||
const settings: KnobSettings = prepareKnobSettings({...deepClone(knobWidgetDefaultSettings), ...this.ctx.settings});
|
||||
|
||||
let initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
|
||||
|
||||
const getInitialStateSettings =
|
||||
{...settings.initialState, actionLabel: this.ctx.translate.instant('widgets.slider.initial-value')};
|
||||
this.createValueGetter(getInitialStateSettings, ValueType.DOUBLE, {
|
||||
next: (value) => {
|
||||
if (this.canvasBar) {
|
||||
this.setValue(value);
|
||||
} else {
|
||||
initialValue = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const valueChangeSettings = {...settings.valueChange,
|
||||
actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')};
|
||||
this.valueSetter = this.createValueSetter(valueChangeSettings);
|
||||
|
||||
this.valueFormat = ValueFormatProcessor.fromSettings(this.ctx.$injector, {
|
||||
units: this.ctx.units,
|
||||
decimals: this.ctx.decimals,
|
||||
showZeroDecimals: true
|
||||
});
|
||||
|
||||
this.minValue = isDefined(settings.minValue) ? settings.minValue : 0;
|
||||
this.maxValue = isDefined(settings.maxValue) ? settings.maxValue : 100;
|
||||
@ -279,44 +281,10 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
|
||||
});
|
||||
|
||||
const subscription = this.ctx.defaultSubscription;
|
||||
const rpcEnabled = subscription.rpcEnabled;
|
||||
|
||||
this.isSimulated = this.utils.widgetEditMode;
|
||||
|
||||
this.requestTimeout = 500;
|
||||
if (settings.requestTimeout) {
|
||||
this.requestTimeout = settings.requestTimeout;
|
||||
}
|
||||
this.requestPersistent = false;
|
||||
if (settings.requestPersistent) {
|
||||
this.requestPersistent = settings.requestPersistent;
|
||||
}
|
||||
this.persistentPollingInterval = 5000;
|
||||
if (settings.persistentPollingInterval) {
|
||||
this.persistentPollingInterval = settings.persistentPollingInterval;
|
||||
}
|
||||
this.getValueMethod = 'getValue';
|
||||
if (settings.getValueMethod && settings.getValueMethod.length) {
|
||||
this.getValueMethod = settings.getValueMethod;
|
||||
}
|
||||
this.setValueMethod = 'setValue';
|
||||
if (settings.setValueMethod && settings.setValueMethod.length) {
|
||||
this.setValueMethod = settings.setValueMethod;
|
||||
}
|
||||
|
||||
import('@home/components/widget/lib/canvas-digital-gauge').then(
|
||||
(gauge) => {
|
||||
this.canvasBar = new gauge.CanvasDigitalGauge(canvasBarData).draw();
|
||||
const initialValue = isDefined(settings.initialValue) ? settings.initialValue : this.minValue;
|
||||
this.setValue(initialValue);
|
||||
if (!rpcEnabled) {
|
||||
this.onError('Target device is not set!');
|
||||
} else {
|
||||
if (!this.isSimulated) {
|
||||
this.rpcRequestValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -348,7 +316,6 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
this.canvasBar.update({width: size, height: size} as GenericOptions);
|
||||
}
|
||||
this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width());
|
||||
this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
|
||||
const minmaxHeight = this.knobMinmaxContainer.height();
|
||||
this.minmaxLabel.css({fontSize: minmaxHeight + 'px', lineHeight: minmaxHeight + 'px'});
|
||||
this.checkValueSize();
|
||||
@ -408,27 +375,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private formatValue(value: any): string {
|
||||
return this.ctx.utils.formatValue(value, this.ctx.decimals, getSourceTbUnitSymbol(this.ctx.units), true);
|
||||
}
|
||||
|
||||
private rpcRequestValue() {
|
||||
this.error = '';
|
||||
this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout,
|
||||
this.requestPersistent, this.persistentPollingInterval).subscribe(
|
||||
(responseBody) => {
|
||||
if (isNumber(responseBody)) {
|
||||
const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals));
|
||||
this.setValue(numValue);
|
||||
} else {
|
||||
const errorText = `Unable to parse response: ${responseBody}`;
|
||||
this.onError(errorText);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
const errorText = this.ctx.defaultSubscription.rpcErrorText;
|
||||
this.onError(errorText);
|
||||
}
|
||||
);
|
||||
return this.valueFormat.format(value);
|
||||
}
|
||||
|
||||
private rpcUpdateValue(value: number) {
|
||||
@ -440,27 +387,19 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy {
|
||||
this.rpcValue = value;
|
||||
this.executingUpdateValue = true;
|
||||
}
|
||||
this.error = '';
|
||||
this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout,
|
||||
this.requestPersistent, this.persistentPollingInterval).subscribe(
|
||||
() => {
|
||||
if (!this.ctx.isEdit && !this.ctx.isPreview) {
|
||||
this.updateValue(this.valueSetter, value, {
|
||||
next: () => {
|
||||
this.executingUpdateValue = false;
|
||||
if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) {
|
||||
this.rpcUpdateValue(this.scheduledValue);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
error: () => {
|
||||
this.executingUpdateValue = false;
|
||||
const errorText = this.ctx.defaultSubscription.rpcErrorText;
|
||||
this.onError(errorText);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private onError(error: string) {
|
||||
this.error = error;
|
||||
this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width());
|
||||
this.ctx.detectChanges();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -15,66 +15,66 @@
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<section class="tb-widget-settings flex flex-col" [formGroup]="knobControlWidgetSettingsForm">
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.rpc.common-settings</legend>
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.knob-title</mat-label>
|
||||
<input matInput formControlName="title">
|
||||
<ng-container [formGroup]="knobControlWidgetSettingsForm">
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title" translate>widgets.knob.behavior</div>
|
||||
<div class="tb-form-row">
|
||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.knob.initial-value-hint' | translate}}" translate>widgets.knob.initial-value</div>
|
||||
<tb-get-value-action-settings class="flex-1"
|
||||
panelTitle="{{ 'widgets.knob.initial-value' | translate }}"
|
||||
[valueType]="valueType.DOUBLE"
|
||||
[aliasController]="aliasController"
|
||||
[targetDevice]="targetDevice"
|
||||
[widgetType]="widgetType"
|
||||
formControlName="initialState"></tb-get-value-action-settings>
|
||||
</div>
|
||||
<div class="tb-form-row space-between">
|
||||
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.knob.on-value-change-hint' | translate}}" translate>widgets.knob.on-value-change</div>
|
||||
<tb-set-value-action-settings class="flex-1"
|
||||
panelTitle="{{ 'widgets.knob.on-value-change' | translate }}"
|
||||
[valueType]="valueType.DOUBLE"
|
||||
[aliasController]="aliasController"
|
||||
[targetDevice]="targetDevice"
|
||||
[widgetType]="widgetType"
|
||||
formControlName="valueChange"></tb-set-value-action-settings>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-panel">
|
||||
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
|
||||
<div class="tb-form-row column-xs">
|
||||
<div class="fixed-title-width" translate>widgets.rpc.knob-title</div>
|
||||
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
|
||||
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.rpc.value-settings</legend>
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.initial-value</mat-label>
|
||||
<input matInput type="number" formControlName="initialValue">
|
||||
</div>
|
||||
<div class="tb-form-row space-between column-xs">
|
||||
<div>{{ 'widgets.knob.range' | translate }}</div>
|
||||
<div class="flex flex-row items-center justify-start gap-2">
|
||||
<div class="tb-small-label" translate>widgets.knob.min</div>
|
||||
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
|
||||
<input matInput formControlName="minValue" type="number" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
<section class="flex flex-row xs:flex-col gt-xs:gap-2">
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.min-value</mat-label>
|
||||
<input required matInput type="number" formControlName="minValue">
|
||||
<div class="tb-small-label" translate>widgets.knob.max</div>
|
||||
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic">
|
||||
<input matInput formControlName="maxValue" type="number" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.max-value</mat-label>
|
||||
<input required matInput type="number" formControlName="maxValue">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row column-xs">
|
||||
<div class="fixed-title-width">{{ 'widgets.knob.value' | translate }}</div>
|
||||
<div class="flex flex-1 flex-row items-center justify-start gap-2">
|
||||
<tb-unit-input class="flex" formControlName="valueUnits" supportsUnitConversion></tb-unit-input>
|
||||
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic">
|
||||
<input matInput formControlName="valueDecimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
<div matSuffix class="lt-md:!hidden" translate>widget-config.decimals-suffix</div>
|
||||
</mat-form-field>
|
||||
</section>
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.get-value-method</mat-label>
|
||||
<input required matInput formControlName="getValueMethod">
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-form-row space-between column-xs">
|
||||
<div class="fixed-title-width" translate>widgets.knob.fallback-initial-value</div>
|
||||
<mat-form-field class="number" appearance="outline" subscriptSizing="dynamic">
|
||||
<input matInput formControlName="initialValue" type="number" placeholder="{{ 'widget-config.set' | translate }}">
|
||||
</mat-form-field>
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.set-value-method</mat-label>
|
||||
<input required matInput formControlName="setValueMethod">
|
||||
</mat-form-field>
|
||||
</fieldset>
|
||||
<fieldset class="fields-group">
|
||||
<legend class="group-title" translate>widgets.rpc.rpc-settings</legend>
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.request-timeout</mat-label>
|
||||
<input required matInput type="number" min="0" formControlName="requestTimeout">
|
||||
</mat-form-field>
|
||||
<fieldset class="fields-group fields-group-slider">
|
||||
<legend class="group-title" translate>widgets.rpc.persistent-rpc-settings</legend>
|
||||
<mat-expansion-panel class="tb-settings" [expanded]="knobControlWidgetSettingsForm.get('requestPersistent').value">
|
||||
<mat-expansion-panel-header class="flex flex-row flex-wrap">
|
||||
<mat-panel-title>
|
||||
<mat-slide-toggle formControlName="requestPersistent" (click)="$event.stopPropagation()"
|
||||
class="flex items-stretch justify-center">
|
||||
{{ 'widgets.rpc.request-persistent' | translate }}
|
||||
</mat-slide-toggle>
|
||||
</mat-panel-title>
|
||||
<mat-panel-description class="flex items-center justify-end xs:!hidden" translate>
|
||||
widget-config.advanced-settings
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<mat-form-field class="mat-block flex-1">
|
||||
<mat-label translate>widgets.rpc.persistent-polling-interval</mat-label>
|
||||
<input matInput type="number" min="1000" formControlName="persistentPollingInterval">
|
||||
</mat-form-field>
|
||||
</ng-template>
|
||||
</mat-expansion-panel>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@ -15,10 +15,13 @@
|
||||
///
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
|
||||
import { TargetDevice, WidgetSettings, WidgetSettingsComponent, widgetType } from '@shared/models/widget.models';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { knobWidgetDefaultSettings, prepareKnobSettings } from '@shared/models/widget/rpc/knob.component.models';
|
||||
import { ValueType } from '@shared/models/constants';
|
||||
import { deepClone } from '@core/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-knob-control-widget-settings',
|
||||
@ -27,6 +30,16 @@ import { AppState } from '@core/core.state';
|
||||
})
|
||||
export class KnobControlWidgetSettingsComponent extends WidgetSettingsComponent {
|
||||
|
||||
get targetDevice(): TargetDevice {
|
||||
return this.widgetConfig?.config?.targetDevice;
|
||||
}
|
||||
|
||||
get widgetType(): widgetType {
|
||||
return this.widgetConfig?.widgetType;
|
||||
}
|
||||
|
||||
valueType = ValueType;
|
||||
|
||||
knobControlWidgetSettingsForm: UntypedFormGroup;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
@ -39,17 +52,25 @@ export class KnobControlWidgetSettingsComponent extends WidgetSettingsComponent
|
||||
}
|
||||
|
||||
protected defaultSettings(): WidgetSettings {
|
||||
return {
|
||||
title: '',
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
initialValue: 50,
|
||||
getValueMethod: 'getValue',
|
||||
setValueMethod: 'setValue',
|
||||
requestTimeout: 500,
|
||||
requestPersistent: false,
|
||||
persistentPollingInterval: 5000
|
||||
};
|
||||
return knobWidgetDefaultSettings;
|
||||
}
|
||||
|
||||
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
|
||||
const knobSettings = prepareKnobSettings(deepClone(settings) as any) as WidgetSettings;
|
||||
knobSettings.valueDecimals = this.widgetConfig?.config?.decimals ?? 2;
|
||||
knobSettings.valueUnits = deepClone(this.widgetConfig?.config?.units);
|
||||
return super.prepareInputSettings(knobSettings);
|
||||
}
|
||||
|
||||
protected prepareOutputSettings(settings: any): WidgetSettings {
|
||||
const newSettings = deepClone(settings);
|
||||
if (this.widgetConfig?.config) {
|
||||
this.widgetConfig.config.units = settings.valueUnits;
|
||||
this.widgetConfig.config.decimals = settings.valueDecimals;
|
||||
}
|
||||
delete newSettings.valueUnits;
|
||||
delete newSettings.valueDecimals;
|
||||
return super.prepareOutputSettings(newSettings);
|
||||
}
|
||||
|
||||
protected onSettingsSet(settings: WidgetSettings) {
|
||||
@ -61,35 +82,16 @@ export class KnobControlWidgetSettingsComponent extends WidgetSettingsComponent
|
||||
|
||||
// Value settings
|
||||
|
||||
initialValue: [settings.initialValue, []],
|
||||
initialState: [settings.initialState, []],
|
||||
valueChange: [settings.valueChange, []],
|
||||
|
||||
minValue: [settings.minValue, [Validators.required]],
|
||||
maxValue: [settings.maxValue, [Validators.required]],
|
||||
|
||||
getValueMethod: [settings.getValueMethod, [Validators.required]],
|
||||
setValueMethod: [settings.setValueMethod, [Validators.required]],
|
||||
valueUnits: [settings.valueUnits, []],
|
||||
valueDecimals: [settings.valueDecimals, []],
|
||||
initialValue: [settings.initialValue, []],
|
||||
|
||||
// RPC settings
|
||||
|
||||
requestTimeout: [settings.requestTimeout, [Validators.min(0), Validators.required]],
|
||||
|
||||
// --> Persistent RPC settings
|
||||
|
||||
requestPersistent: [settings.requestPersistent, []],
|
||||
persistentPollingInterval: [settings.persistentPollingInterval, [Validators.min(1000)]]
|
||||
});
|
||||
}
|
||||
|
||||
protected validatorTriggers(): string[] {
|
||||
return ['requestPersistent'];
|
||||
}
|
||||
|
||||
protected updateValidators(emitEvent: boolean): void {
|
||||
const requestPersistent: boolean = this.knobControlWidgetSettingsForm.get('requestPersistent').value;
|
||||
if (requestPersistent) {
|
||||
this.knobControlWidgetSettingsForm.get('persistentPollingInterval').enable({emitEvent});
|
||||
} else {
|
||||
this.knobControlWidgetSettingsForm.get('persistentPollingInterval').disable({emitEvent});
|
||||
}
|
||||
this.knobControlWidgetSettingsForm.get('persistentPollingInterval').updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
}
|
||||
|
||||
120
ui-ngx/src/app/shared/models/widget/rpc/knob.component.models.ts
Normal file
120
ui-ngx/src/app/shared/models/widget/rpc/knob.component.models.ts
Normal file
@ -0,0 +1,120 @@
|
||||
///
|
||||
/// Copyright © 2016-2025 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 {
|
||||
DataToValueType,
|
||||
GetValueAction,
|
||||
GetValueSettings,
|
||||
SetValueAction,
|
||||
SetValueSettings,
|
||||
ValueToDataType
|
||||
} from '@shared/models/action-widget-settings.models';
|
||||
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
|
||||
import { isDefinedAndNotNull } from '@core/utils';
|
||||
|
||||
export interface KnobSettings {
|
||||
initialState: GetValueSettings<number>;
|
||||
valueChange: SetValueSettings;
|
||||
minValue: number;
|
||||
maxValue: number;
|
||||
initialValue: number;
|
||||
title: string;
|
||||
getValueMethod?: string; //deprecated
|
||||
setValueMethod?: string; //deprecated
|
||||
requestTimeout?: number; //deprecated
|
||||
requestPersistent?: boolean; //deprecated
|
||||
persistentPollingInterval?: number; //deprecated
|
||||
}
|
||||
|
||||
export const knobWidgetDefaultSettings: KnobSettings = {
|
||||
initialState: {
|
||||
action: GetValueAction.EXECUTE_RPC,
|
||||
defaultValue: 50,
|
||||
executeRpc: {
|
||||
method: 'getValue',
|
||||
requestTimeout: 500,
|
||||
requestPersistent: false,
|
||||
persistentPollingInterval: 5000
|
||||
},
|
||||
getAttribute: {
|
||||
key: 'value',
|
||||
scope: null
|
||||
},
|
||||
getTimeSeries: {
|
||||
key: 'value'
|
||||
},
|
||||
getAlarmStatus: {
|
||||
severityList: null,
|
||||
typeList: null
|
||||
},
|
||||
dataToValue: {
|
||||
type: DataToValueType.NONE,
|
||||
compareToValue: true,
|
||||
dataToValueFunction: '/* Should return double value */\nreturn data;'
|
||||
}
|
||||
},
|
||||
valueChange: {
|
||||
action: SetValueAction.EXECUTE_RPC,
|
||||
executeRpc: {
|
||||
method: 'setValue',
|
||||
requestTimeout: 500,
|
||||
requestPersistent: false,
|
||||
persistentPollingInterval: 5000
|
||||
},
|
||||
setAttribute: {
|
||||
key: 'value',
|
||||
scope: AttributeScope.SERVER_SCOPE
|
||||
},
|
||||
putTimeSeries: {
|
||||
key: 'value'
|
||||
},
|
||||
valueToData: {
|
||||
type: ValueToDataType.VALUE,
|
||||
constantValue: 0,
|
||||
valueToDataFunction: '/* Convert input double value to RPC parameters or attribute/time-series value */\nreturn value;'
|
||||
}
|
||||
},
|
||||
title: '',
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
initialValue: 50
|
||||
}
|
||||
|
||||
export const prepareKnobSettings = (settings: KnobSettings): KnobSettings => {
|
||||
if (isDefinedAndNotNull(settings.getValueMethod)) {
|
||||
settings.initialState.executeRpc.method = settings.getValueMethod;
|
||||
}
|
||||
|
||||
if (isDefinedAndNotNull(settings.setValueMethod)) {
|
||||
settings.valueChange.executeRpc.method = settings.setValueMethod;
|
||||
}
|
||||
|
||||
if (isDefinedAndNotNull(settings.requestPersistent)) {
|
||||
settings.initialState.executeRpc.requestPersistent = settings.requestPersistent;
|
||||
settings.valueChange.executeRpc.requestPersistent = settings.requestPersistent;
|
||||
}
|
||||
|
||||
if (isDefinedAndNotNull(settings.persistentPollingInterval)) {
|
||||
settings.initialState.executeRpc.persistentPollingInterval = settings.persistentPollingInterval;
|
||||
settings.valueChange.executeRpc.persistentPollingInterval = settings.persistentPollingInterval;
|
||||
}
|
||||
|
||||
if (isDefinedAndNotNull(settings.requestTimeout)) {
|
||||
settings.initialState.executeRpc.requestTimeout = settings.requestTimeout;
|
||||
settings.valueChange.executeRpc.requestTimeout = settings.requestTimeout;
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
@ -7960,6 +7960,17 @@
|
||||
"fill-area-opacity": "Fill area opacity",
|
||||
"range-chart-style": "Range chart style"
|
||||
},
|
||||
"knob": {
|
||||
"behavior": "Behavior",
|
||||
"initial-value": "Initial value",
|
||||
"initial-value-hint": "Action to get the initial value of the knob.",
|
||||
"on-value-change": "On value change",
|
||||
"on-value-change-hint": "Action triggered when the knob value is changed.",
|
||||
"range": "Range",
|
||||
"min": "min",
|
||||
"max": "max",
|
||||
"fallback-initial-value": "Fallback initial value"
|
||||
},
|
||||
"rpc": {
|
||||
"value-settings": "Value settings",
|
||||
"initial-value": "Initial value",
|
||||
@ -8015,10 +8026,7 @@
|
||||
"led-status-value-attribute": "Device attribute containing led status value",
|
||||
"led-status-value-timeseries": "Device time series containing led status value",
|
||||
"check-status-method": "RPC check device status method",
|
||||
"parse-led-status-value-function": "Parse led status value function",
|
||||
"knob-title": "Knob title",
|
||||
"min-value": "Minimum value",
|
||||
"max-value": "Maximum value"
|
||||
"parse-led-status-value-function": "Parse led status value function"
|
||||
},
|
||||
"maps": {
|
||||
"map-type": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user