29 changed files with 1969 additions and 81 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,270 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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]="sliderWidgetConfigForm"> |
||||
|
<tb-target-device formControlName="targetDevice"></tb-target-device> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.slider.behavior</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.slider.initial-value-hint' | translate}}" translate>widgets.slider.initial-value</div> |
||||
|
<tb-get-value-action-settings fxFlex |
||||
|
panelTitle="widgets.slider.initial-value" |
||||
|
[valueType]="valueType.INTEGER" |
||||
|
[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.slider.on-value-change-hint' | translate}}" translate>widgets.slider.on-value-change</div> |
||||
|
<tb-set-value-action-settings fxFlex |
||||
|
panelTitle="widgets.slider.on-value-change" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="valueChange"></tb-set-value-action-settings> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div> |
||||
|
<tb-get-value-action-settings fxFlex |
||||
|
panelTitle="widgets.rpc-state.disabled-state" |
||||
|
[valueType]="valueType.BOOLEAN" |
||||
|
stateLabel="widgets.rpc-state.disabled" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="disabledState"></tb-get-value-action-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
||||
|
<tb-image-cards-select rowHeight="2:1" |
||||
|
[cols]="{columns: 3, |
||||
|
breakpoints: { |
||||
|
'lt-sm': 1, |
||||
|
'lt-md': 2 |
||||
|
}}" |
||||
|
label="{{ 'widgets.slider.layout' | translate }}" formControlName="layout"> |
||||
|
<tb-image-cards-select-option *ngFor="let layout of sliderLayouts" |
||||
|
[value]="layout" |
||||
|
[image]="sliderLayoutImageMap.get(layout)"> |
||||
|
{{ sliderLayoutTranslationMap.get(layout) | translate }} |
||||
|
</tb-image-cards-select-option> |
||||
|
</tb-image-cards-select> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="autoScale"> |
||||
|
{{ 'widgets.slider.auto-scale' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
</div> |
||||
|
<div class="tb-form-row column-xs"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle"> |
||||
|
{{ 'widget-config.title' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-font-settings formControlName="titleFont" |
||||
|
clearButton |
||||
|
[previewText]="sliderWidgetConfigForm.get('title').value" |
||||
|
[initialPreviewStyle]="widgetConfig.config.titleStyle"> |
||||
|
</tb-font-settings> |
||||
|
<tb-color-input asBoxInput |
||||
|
colorClearButton |
||||
|
formControlName="titleColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row column-xs"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> |
||||
|
{{ 'widgets.slider.icon' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
iconClearButton |
||||
|
[color]="sliderWidgetConfigForm.get('iconColor').value" |
||||
|
formControlName="icon"> |
||||
|
</tb-material-icon-select> |
||||
|
<tb-color-input asBoxInput |
||||
|
colorClearButton |
||||
|
formControlName="iconColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngIf="sliderWidgetConfigForm.get('layout').value !== sliderLayout.simplified" class="tb-form-row column-xs"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue"> |
||||
|
{{ 'widgets.slider.value' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<tb-unit-input class="flex" formControlName="valueUnits"></tb-unit-input> |
||||
|
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="valueDecimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
<div matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div> |
||||
|
</mat-form-field> |
||||
|
<tb-font-settings formControlName="valueFont" |
||||
|
[previewText]="valuePreviewFn"> |
||||
|
</tb-font-settings> |
||||
|
<tb-color-input asBoxInput |
||||
|
colorClearButton |
||||
|
formControlName="valueColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.slider.range' | translate }}</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div class="tb-small-label" translate>widgets.slider.min</div> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="tickMin" type="number" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<div class="tb-small-label" translate>widgets.slider.max</div> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="tickMax" type="number" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="showTicks"> |
||||
|
{{ 'widgets.slider.range-ticks' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<tb-font-settings formControlName="ticksFont" |
||||
|
previewText="100"> |
||||
|
</tb-font-settings> |
||||
|
<tb-color-input asBoxInput |
||||
|
colorClearButton |
||||
|
formControlName="ticksColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="showTickMarks"> |
||||
|
{{ 'widgets.slider.tick-marks' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="tickMarksCount" type="number" min="2" |
||||
|
step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="tickMarksColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.slider.colors' | translate }}</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.rpc-state.disabled-state' | translate }}</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColorDisabled"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColorDisabled"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngIf="sliderWidgetConfigForm.get('layout').value === sliderLayout.extended" |
||||
|
class="tb-form-row column-xs"> |
||||
|
<div class="fixed-title-width"> |
||||
|
{{ 'widgets.slider.left-icon' | translate }} |
||||
|
</div> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="leftIconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select fxFlex formControlName="leftIconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
[color]="sliderWidgetConfigForm.get('leftIconColor').value" |
||||
|
formControlName="leftIcon"> |
||||
|
</tb-material-icon-select> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="leftIconColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngIf="sliderWidgetConfigForm.get('layout').value === sliderLayout.extended" |
||||
|
class="tb-form-row column-xs"> |
||||
|
<div class="fixed-title-width"> |
||||
|
{{ 'widgets.slider.right-icon' | translate }} |
||||
|
</div> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="rightIconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select fxFlex formControlName="rightIconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
[color]="sliderWidgetConfigForm.get('rightIconColor').value" |
||||
|
formControlName="rightIcon"> |
||||
|
</tb-material-icon-select> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="rightIconColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<div>{{ 'widgets.background.background' | translate }}</div> |
||||
|
<tb-background-settings formControlName="background"> |
||||
|
</tb-background-settings> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between column-lt-md"> |
||||
|
<div translate>widget-config.show-card-buttons</div> |
||||
|
<mat-chip-listbox multiple formControlName="cardButtons"> |
||||
|
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option> |
||||
|
</mat-chip-listbox> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<div>{{ 'widget-config.card-border-radius' | translate }}</div> |
||||
|
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
<tb-widget-actions-panel |
||||
|
formControlName="actions"> |
||||
|
</tb-widget-actions-panel> |
||||
|
</ng-container> |
||||
@ -0,0 +1,309 @@ |
|||||
|
///
|
||||
|
/// 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, Validators } 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, WidgetConfig, } from '@shared/models/widget.models'; |
||||
|
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; |
||||
|
import { formatValue, isUndefined } from '@core/utils'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { |
||||
|
SliderLayout, |
||||
|
sliderLayoutImages, |
||||
|
sliderLayouts, |
||||
|
sliderLayoutTranslations, |
||||
|
sliderWidgetDefaultSettings, |
||||
|
SliderWidgetSettings |
||||
|
} from '@home/components/widget/lib/rpc/slider-widget.models'; |
||||
|
import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-slider-basic-config', |
||||
|
templateUrl: './slider-basic-config.component.html', |
||||
|
styleUrls: ['../basic-config.scss'] |
||||
|
}) |
||||
|
export class SliderBasicConfigComponent extends BasicWidgetConfigComponent { |
||||
|
|
||||
|
get targetDevice(): TargetDevice { |
||||
|
return this.sliderWidgetConfigForm.get('targetDevice').value; |
||||
|
} |
||||
|
|
||||
|
sliderLayout = SliderLayout; |
||||
|
|
||||
|
sliderLayouts = sliderLayouts; |
||||
|
|
||||
|
sliderLayoutTranslationMap = sliderLayoutTranslations; |
||||
|
sliderLayoutImageMap = sliderLayoutImages; |
||||
|
|
||||
|
valueType = ValueType; |
||||
|
|
||||
|
sliderWidgetConfigForm: UntypedFormGroup; |
||||
|
|
||||
|
valuePreviewFn = this._valuePreviewFn.bind(this); |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected widgetConfigComponent: WidgetConfigComponent, |
||||
|
private fb: UntypedFormBuilder) { |
||||
|
super(store, widgetConfigComponent); |
||||
|
} |
||||
|
|
||||
|
protected configForm(): UntypedFormGroup { |
||||
|
return this.sliderWidgetConfigForm; |
||||
|
} |
||||
|
|
||||
|
protected onConfigSet(configData: WidgetConfigComponentData) { |
||||
|
const settings: SliderWidgetSettings = {...sliderWidgetDefaultSettings, ...(configData.config.settings || {})}; |
||||
|
const iconSize = resolveCssSize(configData.config.iconSize); |
||||
|
this.sliderWidgetConfigForm = this.fb.group({ |
||||
|
targetDevice: [configData.config.targetDevice, []], |
||||
|
|
||||
|
initialState: [settings.initialState, []], |
||||
|
valueChange: [settings.valueChange, []], |
||||
|
disabledState: [settings.disabledState, []], |
||||
|
|
||||
|
layout: [settings.layout, []], |
||||
|
autoScale: [settings.autoScale, []], |
||||
|
|
||||
|
showTitle: [configData.config.showTitle, []], |
||||
|
title: [configData.config.title, []], |
||||
|
titleFont: [configData.config.titleFont, []], |
||||
|
titleColor: [configData.config.titleColor, []], |
||||
|
|
||||
|
showIcon: [configData.config.showTitleIcon, []], |
||||
|
iconSize: [iconSize[0], [Validators.min(0)]], |
||||
|
iconSizeUnit: [iconSize[1], []], |
||||
|
icon: [configData.config.titleIcon, []], |
||||
|
iconColor: [configData.config.iconColor, []], |
||||
|
|
||||
|
showValue: [settings.showValue, []], |
||||
|
valueUnits: [settings.valueUnits, []], |
||||
|
valueDecimals: [settings.valueDecimals, []], |
||||
|
valueFont: [settings.valueFont, []], |
||||
|
valueColor: [settings.valueColor, []], |
||||
|
|
||||
|
tickMin: [settings.tickMin, []], |
||||
|
tickMax: [settings.tickMax, []], |
||||
|
|
||||
|
showTicks: [settings.showTicks, []], |
||||
|
ticksFont: [settings.ticksFont, []], |
||||
|
ticksColor: [settings.ticksColor, []], |
||||
|
|
||||
|
showTickMarks: [settings.showTickMarks, []], |
||||
|
tickMarksCount: [settings.tickMarksCount, [Validators.min(2)]], |
||||
|
tickMarksColor: [settings.tickMarksColor, []], |
||||
|
|
||||
|
mainColor: [settings.mainColor, []], |
||||
|
backgroundColor: [settings.backgroundColor, []], |
||||
|
|
||||
|
mainColorDisabled: [settings.mainColorDisabled, []], |
||||
|
backgroundColorDisabled: [settings.backgroundColorDisabled, []], |
||||
|
|
||||
|
leftIconSize: [settings.leftIconSize, [Validators.min(0)]], |
||||
|
leftIconSizeUnit: [settings.leftIconSizeUnit, []], |
||||
|
leftIcon: [settings.leftIcon, []], |
||||
|
leftIconColor: [settings.leftIconColor, []], |
||||
|
|
||||
|
rightIconSize: [settings.rightIconSize, [Validators.min(0)]], |
||||
|
rightIconSizeUnit: [settings.rightIconSizeUnit, []], |
||||
|
rightIcon: [settings.rightIcon, []], |
||||
|
rightIconColor: [settings.rightIconColor, []], |
||||
|
|
||||
|
background: [settings.background, []], |
||||
|
|
||||
|
cardButtons: [this.getCardButtons(configData.config), []], |
||||
|
borderRadius: [configData.config.borderRadius, []], |
||||
|
|
||||
|
actions: [configData.config.actions || {}, []] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected prepareOutputConfig(config: any): WidgetConfigComponentData { |
||||
|
this.widgetConfig.config.targetDevice = config.targetDevice; |
||||
|
|
||||
|
this.widgetConfig.config.showTitle = config.showTitle; |
||||
|
this.widgetConfig.config.title = config.title; |
||||
|
this.widgetConfig.config.titleFont = config.titleFont; |
||||
|
this.widgetConfig.config.titleColor = config.titleColor; |
||||
|
|
||||
|
this.widgetConfig.config.showTitleIcon = config.showIcon; |
||||
|
this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit); |
||||
|
this.widgetConfig.config.titleIcon = config.icon; |
||||
|
this.widgetConfig.config.iconColor = config.iconColor; |
||||
|
|
||||
|
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; |
||||
|
|
||||
|
this.widgetConfig.config.settings.initialState = config.initialState; |
||||
|
this.widgetConfig.config.settings.valueChange = config.valueChange; |
||||
|
this.widgetConfig.config.settings.disabledState = config.disabledState; |
||||
|
|
||||
|
this.widgetConfig.config.settings.layout = config.layout; |
||||
|
this.widgetConfig.config.settings.autoScale = config.autoScale; |
||||
|
|
||||
|
this.widgetConfig.config.settings.showValue = config.showValue; |
||||
|
this.widgetConfig.config.settings.valueUnits = config.valueUnits; |
||||
|
this.widgetConfig.config.settings.valueDecimals = config.valueDecimals; |
||||
|
this.widgetConfig.config.settings.valueFont = config.valueFont; |
||||
|
this.widgetConfig.config.settings.valueColor = config.valueColor; |
||||
|
|
||||
|
this.widgetConfig.config.settings.tickMin = config.tickMin; |
||||
|
this.widgetConfig.config.settings.tickMax = config.tickMax; |
||||
|
|
||||
|
this.widgetConfig.config.settings.showTicks = config.showTicks; |
||||
|
this.widgetConfig.config.settings.ticksFont = config.ticksFont; |
||||
|
this.widgetConfig.config.settings.ticksColor = config.ticksColor; |
||||
|
|
||||
|
this.widgetConfig.config.settings.showTickMarks = config.showTickMarks; |
||||
|
this.widgetConfig.config.settings.tickMarksCount = config.tickMarksCount; |
||||
|
this.widgetConfig.config.settings.tickMarksColor = config.tickMarksColor; |
||||
|
|
||||
|
this.widgetConfig.config.settings.mainColor = config.mainColor; |
||||
|
this.widgetConfig.config.settings.backgroundColor = config.backgroundColor; |
||||
|
|
||||
|
this.widgetConfig.config.settings.mainColorDisabled = config.mainColorDisabled; |
||||
|
this.widgetConfig.config.settings.backgroundColorDisabled = config.backgroundColorDisabled; |
||||
|
|
||||
|
this.widgetConfig.config.settings.leftIconSize = config.leftIconSize; |
||||
|
this.widgetConfig.config.settings.leftIconSizeUnit = config.leftIconSizeUnit; |
||||
|
this.widgetConfig.config.settings.leftIcon = config.leftIcon; |
||||
|
this.widgetConfig.config.settings.leftIconColor = config.leftIconColor; |
||||
|
|
||||
|
this.widgetConfig.config.settings.rightIconSize = config.rightIconSize; |
||||
|
this.widgetConfig.config.settings.rightIconSizeUnit = config.rightIconSizeUnit; |
||||
|
this.widgetConfig.config.settings.rightIcon = config.rightIcon; |
||||
|
this.widgetConfig.config.settings.rightIconColor = config.rightIconColor; |
||||
|
|
||||
|
this.widgetConfig.config.settings.background = config.background; |
||||
|
|
||||
|
this.setCardButtons(config.cardButtons, this.widgetConfig.config); |
||||
|
this.widgetConfig.config.borderRadius = config.borderRadius; |
||||
|
|
||||
|
this.widgetConfig.config.actions = config.actions; |
||||
|
return this.widgetConfig; |
||||
|
} |
||||
|
|
||||
|
protected validatorTriggers(): string[] { |
||||
|
return ['showTitle', 'showIcon', 'showValue', 'showTicks', 'showTickMarks', 'layout']; |
||||
|
} |
||||
|
|
||||
|
protected updateValidators(emitEvent: boolean, trigger?: string) { |
||||
|
const showTitle: boolean = this.sliderWidgetConfigForm.get('showTitle').value; |
||||
|
const showIcon: boolean = this.sliderWidgetConfigForm.get('showIcon').value; |
||||
|
const showValue: boolean = this.sliderWidgetConfigForm.get('showValue').value; |
||||
|
const showTicks: boolean = this.sliderWidgetConfigForm.get('showTicks').value; |
||||
|
const showTickMarks: boolean = this.sliderWidgetConfigForm.get('showTickMarks').value; |
||||
|
const layout: SliderLayout = this.sliderWidgetConfigForm.get('layout').value; |
||||
|
|
||||
|
const valueEnabled = layout !== SliderLayout.simplified; |
||||
|
const leftRightIconsEnabled = layout === SliderLayout.extended; |
||||
|
|
||||
|
if (showTitle) { |
||||
|
this.sliderWidgetConfigForm.get('title').enable(); |
||||
|
this.sliderWidgetConfigForm.get('titleFont').enable(); |
||||
|
this.sliderWidgetConfigForm.get('titleColor').enable(); |
||||
|
this.sliderWidgetConfigForm.get('showIcon').enable({emitEvent: false}); |
||||
|
if (showIcon) { |
||||
|
this.sliderWidgetConfigForm.get('iconSize').enable(); |
||||
|
this.sliderWidgetConfigForm.get('iconSizeUnit').enable(); |
||||
|
this.sliderWidgetConfigForm.get('icon').enable(); |
||||
|
this.sliderWidgetConfigForm.get('iconColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetConfigForm.get('iconSize').disable(); |
||||
|
this.sliderWidgetConfigForm.get('iconSizeUnit').disable(); |
||||
|
this.sliderWidgetConfigForm.get('icon').disable(); |
||||
|
this.sliderWidgetConfigForm.get('iconColor').disable(); |
||||
|
} |
||||
|
} else { |
||||
|
this.sliderWidgetConfigForm.get('title').disable(); |
||||
|
this.sliderWidgetConfigForm.get('titleFont').disable(); |
||||
|
this.sliderWidgetConfigForm.get('titleColor').disable(); |
||||
|
this.sliderWidgetConfigForm.get('showIcon').disable({emitEvent: false}); |
||||
|
this.sliderWidgetConfigForm.get('iconSize').disable(); |
||||
|
this.sliderWidgetConfigForm.get('iconSizeUnit').disable(); |
||||
|
this.sliderWidgetConfigForm.get('icon').disable(); |
||||
|
this.sliderWidgetConfigForm.get('iconColor').disable(); |
||||
|
} |
||||
|
|
||||
|
if (valueEnabled && showValue) { |
||||
|
this.sliderWidgetConfigForm.get('valueUnits').enable(); |
||||
|
this.sliderWidgetConfigForm.get('valueDecimals').enable(); |
||||
|
this.sliderWidgetConfigForm.get('valueFont').enable(); |
||||
|
this.sliderWidgetConfigForm.get('valueColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetConfigForm.get('valueUnits').disable(); |
||||
|
this.sliderWidgetConfigForm.get('valueDecimals').disable(); |
||||
|
this.sliderWidgetConfigForm.get('valueFont').disable(); |
||||
|
this.sliderWidgetConfigForm.get('valueColor').disable(); |
||||
|
} |
||||
|
|
||||
|
if (showTicks) { |
||||
|
this.sliderWidgetConfigForm.get('ticksFont').enable(); |
||||
|
this.sliderWidgetConfigForm.get('ticksColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetConfigForm.get('ticksFont').disable(); |
||||
|
this.sliderWidgetConfigForm.get('ticksColor').disable(); |
||||
|
} |
||||
|
|
||||
|
if (showTickMarks) { |
||||
|
this.sliderWidgetConfigForm.get('tickMarksCount').enable(); |
||||
|
this.sliderWidgetConfigForm.get('tickMarksColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetConfigForm.get('tickMarksCount').disable(); |
||||
|
this.sliderWidgetConfigForm.get('tickMarksColor').disable(); |
||||
|
} |
||||
|
|
||||
|
if (leftRightIconsEnabled) { |
||||
|
this.sliderWidgetConfigForm.get('leftIconSize').enable(); |
||||
|
this.sliderWidgetConfigForm.get('leftIconSizeUnit').enable(); |
||||
|
this.sliderWidgetConfigForm.get('leftIcon').enable(); |
||||
|
this.sliderWidgetConfigForm.get('leftIconColor').enable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIconSize').enable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIconSizeUnit').enable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIcon').enable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIconColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetConfigForm.get('leftIconSize').disable(); |
||||
|
this.sliderWidgetConfigForm.get('leftIconSizeUnit').disable(); |
||||
|
this.sliderWidgetConfigForm.get('leftIcon').disable(); |
||||
|
this.sliderWidgetConfigForm.get('leftIconColor').disable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIconSize').disable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIconSizeUnit').disable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIcon').disable(); |
||||
|
this.sliderWidgetConfigForm.get('rightIconColor').disable(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private getCardButtons(config: WidgetConfig): string[] { |
||||
|
const buttons: string[] = []; |
||||
|
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) { |
||||
|
buttons.push('fullscreen'); |
||||
|
} |
||||
|
return buttons; |
||||
|
} |
||||
|
|
||||
|
private setCardButtons(buttons: string[], config: WidgetConfig) { |
||||
|
config.enableFullscreen = buttons.includes('fullscreen'); |
||||
|
} |
||||
|
|
||||
|
private _valuePreviewFn(): string { |
||||
|
const units: string = this.sliderWidgetConfigForm.get('valueUnits').value; |
||||
|
const decimals: number = this.sliderWidgetConfigForm.get('valueDecimals').value; |
||||
|
return formatValue(48, decimals, units, false); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2024 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-slider-panel" [style.pointer-events]="ctx.isEdit ? 'none' : 'all'" [style]="backgroundStyle$ | async"> |
||||
|
<div class="tb-slider-overlay" [style]="overlayStyle"></div> |
||||
|
<div *ngIf="showWidgetTitlePanel" class="tb-slider-title-panel"> |
||||
|
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> |
||||
|
</div> |
||||
|
<div #sliderContent class="tb-slider-content"> |
||||
|
<div #sliderValueContainer class="tb-slider-value-container" *ngIf="showValue" > |
||||
|
<div #sliderValue class="tb-slider-value" [style]="valueStyle">{{ valueText }}</div> |
||||
|
</div> |
||||
|
<div class="tb-slider-container" [class.tb-min-height]="!showValue"> |
||||
|
<div #leftSliderIconContainer *ngIf="showLeftRightIcon" > |
||||
|
<tb-icon #leftSliderIcon [style]="leftIconStyle" |
||||
|
[style.color]="(disabled || (loading$ | async)) ? settings.mainColorDisabled : settings.leftIconColor">{{ leftIcon }}</tb-icon> |
||||
|
</div> |
||||
|
<div class="tb-slider-column"> |
||||
|
<mat-slider class="tb-slider" |
||||
|
[disabled]="disabled || (loading$ | async)" |
||||
|
[displayWith]="sliderValueText" |
||||
|
[showTickMarks]="settings.showTickMarks" |
||||
|
[step]="sliderStep" |
||||
|
[min]="settings.tickMin" |
||||
|
[max]="settings.tickMax" |
||||
|
discrete> |
||||
|
<input matSliderThumb [(ngModel)]="value" (valueChange)="onSliderChange()"> |
||||
|
</mat-slider> |
||||
|
<div *ngIf="showTicks" class="tb-slider-ticks" [style]="ticksStyle"> |
||||
|
<div #sliderTickMinContainer> |
||||
|
<div #sliderTickMin>{{ settings.tickMin }}</div> |
||||
|
</div> |
||||
|
<div #sliderTickMaxContainer> |
||||
|
<div #sliderTickMax>{{ settings.tickMax }}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div #rightSliderIconContainer *ngIf="showLeftRightIcon" > |
||||
|
<tb-icon #rightSliderIcon [style]="rightIconStyle" |
||||
|
[style.color]="(disabled || (loading$ | async)) ? settings.mainColorDisabled : settings.rightIconColor">{{ rightIcon }}</tb-icon> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar> |
||||
|
</div> |
||||
@ -0,0 +1,135 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
$mainColor: var(--tb-slider-main-color, #5469FF); |
||||
|
$hoverRippleColor: var(--tb-slider-hover-ripple-color, rgba(84, 105, 255, 0.05)); |
||||
|
$focusRippleColor: var(--tb-slider-focus-ripple-color, rgba(84, 105, 255, 0.2)); |
||||
|
$backgroundColor: var(--tb-slider-background-color, #CCD2FF); |
||||
|
$tickMarksColor: var(--tb-slider-tick-marks-color, #5469FF); |
||||
|
|
||||
|
$mainColorDisabled: var(--tb-slider-main-color-disabled, #9BA2B0); |
||||
|
$backgroundColorDisabled: var(--tb-slider-background-color-disabled, #D5D7E5); |
||||
|
|
||||
|
.tb-slider-panel { |
||||
|
|
||||
|
.mat-mdc-slider.mat-primary.tb-slider { |
||||
|
--mdc-slider-active-track-color: #{$mainColor}; |
||||
|
--mdc-slider-handle-color: #{$mainColor}; |
||||
|
--mdc-slider-focus-handle-color: #{$mainColor}; |
||||
|
--mdc-slider-hover-handle-color: #{$mainColor}; |
||||
|
|
||||
|
--mdc-slider-with-tick-marks-inactive-container-color: #{$tickMarksColor}; |
||||
|
--mat-mdc-slider-ripple-color: #{$mainColor}; |
||||
|
|
||||
|
--mat-mdc-slider-hover-ripple-color: #{$hoverRippleColor}; |
||||
|
--mat-mdc-slider-focus-ripple-color: #{$focusRippleColor}; |
||||
|
|
||||
|
--mdc-slider-inactive-track-color: #{$backgroundColor}; |
||||
|
|
||||
|
--mdc-slider-disabled-active-track-color: #{$mainColorDisabled}; |
||||
|
--mdc-slider-disabled-handle-color: #{$mainColorDisabled}; |
||||
|
|
||||
|
--mdc-slider-disabled-inactive-track-color: #{$backgroundColorDisabled}; |
||||
|
--mdc-slider-with-tick-marks-disabled-container-color: #{$mainColorDisabled}; |
||||
|
|
||||
|
--mdc-slider-handle-width: 16px; |
||||
|
--mdc-slider-handle-height: 16px; |
||||
|
} |
||||
|
|
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
padding: 20px 24px 24px 24px; |
||||
|
gap: 8px; |
||||
|
> div:not(.tb-slider-overlay), > tb-icon { |
||||
|
z-index: 1; |
||||
|
} |
||||
|
.tb-slider-overlay { |
||||
|
position: absolute; |
||||
|
inset: 12px; |
||||
|
} |
||||
|
div.tb-slider-title-panel { |
||||
|
z-index: 2; |
||||
|
} |
||||
|
.tb-slider-content { |
||||
|
flex: 1; |
||||
|
min-height: 0; |
||||
|
display: flex; |
||||
|
position: relative; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
gap: 12px; |
||||
|
.tb-slider-value-container { |
||||
|
min-height: 0; |
||||
|
} |
||||
|
.tb-slider-value { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
.tb-slider-container { |
||||
|
align-self: stretch; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: flex-start; |
||||
|
gap: 8px; |
||||
|
&.tb-min-height { |
||||
|
height: 6px; |
||||
|
} |
||||
|
.tb-slider-column { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 4px; |
||||
|
.mat-mdc-slider.tb-slider { |
||||
|
margin: 0; |
||||
|
height: 6px; |
||||
|
min-height: 6px; |
||||
|
min-width: 0; |
||||
|
&.mdc-slider--disabled { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
.mdc-slider__track--inactive { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
.mdc-slider__tick-marks { |
||||
|
.mdc-slider__tick-mark--active { |
||||
|
display: none; |
||||
|
} |
||||
|
.mdc-slider__tick-mark--inactive { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
} |
||||
|
.mdc-slider__thumb.mat-mdc-slider-visual-thumb { |
||||
|
top: -21px; |
||||
|
.mat-ripple { |
||||
|
overflow: visible; |
||||
|
} |
||||
|
} |
||||
|
.mdc-slider__value-indicator-text { |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
} |
||||
|
.tb-slider-ticks { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: flex-start; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,347 @@ |
|||||
|
///
|
||||
|
/// 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, ValueSetter } from '@home/components/widget/lib/action/action-widget.models'; |
||||
|
import { |
||||
|
backgroundStyle, |
||||
|
ComponentStyle, |
||||
|
iconStyle, |
||||
|
overlayStyle, |
||||
|
textStyle |
||||
|
} from '@shared/models/widget-settings.models'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { ResizeObserver } from '@juggle/resize-observer'; |
||||
|
import { ImagePipe } from '@shared/pipe/image.pipe'; |
||||
|
import { DomSanitizer } from '@angular/platform-browser'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { |
||||
|
SliderLayout, |
||||
|
sliderWidgetDefaultSettings, |
||||
|
SliderWidgetSettings |
||||
|
} from '@home/components/widget/lib/rpc/slider-widget.models'; |
||||
|
import { formatValue, isDefinedAndNotNull, isNumeric } from '@core/utils'; |
||||
|
import { WidgetComponent } from '@home/components/widget/widget.component'; |
||||
|
import tinycolor from 'tinycolor2'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-slider-widget', |
||||
|
templateUrl: './slider-widget.component.html', |
||||
|
styleUrls: ['../action/action-widget.scss', './slider-widget.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class SliderWidgetComponent extends |
||||
|
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { |
||||
|
|
||||
|
@ViewChild('sliderContent', {static: false}) |
||||
|
sliderContent: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('sliderValueContainer', {static: false}) |
||||
|
sliderValueContainer: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('sliderValue', {static: false}) |
||||
|
sliderValue: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('sliderTickMinContainer', {static: false}) |
||||
|
sliderTickMinContainer: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('sliderTickMin', {static: false}) |
||||
|
sliderTickMin: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('sliderTickMaxContainer', {static: false}) |
||||
|
sliderTickMaxContainer: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('sliderTickMax', {static: false}) |
||||
|
sliderTickMax: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('leftSliderIconContainer', {static: false, read: ElementRef}) |
||||
|
leftSliderIconContainer: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('leftSliderIcon', {static: false, read: ElementRef}) |
||||
|
leftSliderIcon: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('rightSliderIconContainer', {static: false, read: ElementRef}) |
||||
|
rightSliderIconContainer: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('rightSliderIcon', {static: false, read: ElementRef}) |
||||
|
rightSliderIcon: ElementRef<HTMLElement>; |
||||
|
|
||||
|
settings: SliderWidgetSettings; |
||||
|
|
||||
|
backgroundStyle$: Observable<ComponentStyle>; |
||||
|
overlayStyle: ComponentStyle = {}; |
||||
|
|
||||
|
value: number = null; |
||||
|
private prevValue: number = null; |
||||
|
|
||||
|
disabled = false; |
||||
|
|
||||
|
layout: SliderLayout; |
||||
|
|
||||
|
showValue = true; |
||||
|
valueText = 'N/A'; |
||||
|
valueStyle: ComponentStyle = {}; |
||||
|
|
||||
|
showLeftRightIcon = false; |
||||
|
leftIcon = ''; |
||||
|
leftIconStyle: ComponentStyle = {}; |
||||
|
rightIcon = ''; |
||||
|
rightIconStyle: ComponentStyle = {}; |
||||
|
|
||||
|
showTicks = true; |
||||
|
ticksStyle: ComponentStyle = {}; |
||||
|
|
||||
|
sliderStep: number = undefined; |
||||
|
|
||||
|
autoScale = false; |
||||
|
|
||||
|
showWidgetTitlePanel = this.widgetComponent.dashboardWidget.showWidgetTitlePanel; |
||||
|
|
||||
|
sliderValueText = this._sliderValueText.bind(this); |
||||
|
|
||||
|
private panelResize$: ResizeObserver; |
||||
|
|
||||
|
private valueSetter: ValueSetter<number>; |
||||
|
|
||||
|
private sliderCssClass: string; |
||||
|
|
||||
|
constructor(protected imagePipe: ImagePipe, |
||||
|
protected sanitizer: DomSanitizer, |
||||
|
private renderer: Renderer2, |
||||
|
private utils: UtilsService, |
||||
|
private widgetComponent: WidgetComponent, |
||||
|
protected cd: ChangeDetectorRef, |
||||
|
private elementRef: ElementRef) { |
||||
|
super(cd); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
super.ngOnInit(); |
||||
|
this.settings = {...sliderWidgetDefaultSettings, ...this.ctx.settings}; |
||||
|
|
||||
|
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer); |
||||
|
this.overlayStyle = overlayStyle(this.settings.background.overlay); |
||||
|
|
||||
|
this.layout = this.settings.layout; |
||||
|
|
||||
|
this.autoScale = this.settings.autoScale; |
||||
|
|
||||
|
this.showValue = this.layout !== SliderLayout.simplified && this.settings.showValue; |
||||
|
this.valueStyle = textStyle(this.settings.valueFont); |
||||
|
this.valueStyle.color = this.settings.valueColor; |
||||
|
|
||||
|
this.showLeftRightIcon = this.layout === SliderLayout.extended; |
||||
|
if (this.showLeftRightIcon) { |
||||
|
this.leftIcon = this.settings.leftIcon; |
||||
|
this.leftIconStyle = iconStyle(this.settings.leftIconSize, this.settings.leftIconSizeUnit ); |
||||
|
this.rightIcon = this.settings.rightIcon; |
||||
|
this.rightIconStyle = iconStyle(this.settings.rightIconSize, this.settings.rightIconSizeUnit ); |
||||
|
if (!this.autoScale) { |
||||
|
const leftIconMargin = this.settings.leftIconSize / 2 + (this.settings.leftIconSizeUnit || 'px'); |
||||
|
this.leftIconStyle.marginTop = `calc(-${leftIconMargin} + 3px)`; |
||||
|
const rightIconMargin = this.settings.rightIconSize / 2 + (this.settings.rightIconSizeUnit || 'px'); |
||||
|
this.rightIconStyle.marginTop = `calc(-${rightIconMargin} + 3px)`; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.showTicks = this.settings.showTicks; |
||||
|
if (this.showTicks) { |
||||
|
this.ticksStyle = textStyle(this.settings.ticksFont); |
||||
|
this.ticksStyle.color = this.settings.ticksColor; |
||||
|
} |
||||
|
|
||||
|
if (this.settings.showTickMarks) { |
||||
|
const range = this.settings.tickMax - this.settings.tickMin; |
||||
|
this.sliderStep = Math.floor(range / (this.settings.tickMarksCount - 1)); |
||||
|
} |
||||
|
|
||||
|
const mainColorInstance = tinycolor(this.settings.mainColor); |
||||
|
const hoverRippleColor = mainColorInstance.clone().setAlpha(mainColorInstance.getAlpha() * 0.05).toRgbString(); |
||||
|
const focusRippleColor = mainColorInstance.clone().setAlpha(mainColorInstance.getAlpha() * 0.2).toRgbString(); |
||||
|
|
||||
|
const sliderVariablesCss = `.tb-slider-panel {\n`+ |
||||
|
`--tb-slider-main-color: ${this.settings.mainColor};\n`+ |
||||
|
`--tb-slider-background-color: ${this.settings.backgroundColor};\n`+ |
||||
|
`--tb-slider-hover-ripple-color: ${hoverRippleColor};\n`+ |
||||
|
`--tb-slider-focus-ripple-color: ${focusRippleColor};\n`+ |
||||
|
`--tb-slider-tick-marks-color: ${this.settings.tickMarksColor};\n`+ |
||||
|
`--tb-slider-main-color-disabled: ${this.settings.mainColorDisabled};\n`+ |
||||
|
`--tb-slider-background-disabled: ${this.settings.backgroundColorDisabled};\n`+ |
||||
|
`}`; |
||||
|
this.sliderCssClass = |
||||
|
this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, 'tb-slider', sliderVariablesCss); |
||||
|
|
||||
|
const getInitialStateSettings = |
||||
|
{...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.slider.initial-value')}; |
||||
|
this.createValueGetter(getInitialStateSettings, ValueType.INTEGER, { |
||||
|
next: (value) => this.onValue(value) |
||||
|
}); |
||||
|
|
||||
|
const disabledStateSettings = |
||||
|
{...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')}; |
||||
|
this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { |
||||
|
next: (value) => this.onDisabled(value) |
||||
|
}); |
||||
|
|
||||
|
const valueChangeSettings = {...this.settings.valueChange, |
||||
|
actionLabel: this.ctx.translate.instant('widgets.slider.on-value-change')}; |
||||
|
this.valueSetter = this.createValueSetter(valueChangeSettings); |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
if (this.autoScale) { |
||||
|
this.panelResize$ = new ResizeObserver(() => { |
||||
|
this.onResize(); |
||||
|
}); |
||||
|
this.panelResize$.observe(this.sliderContent.nativeElement); |
||||
|
if (this.showValue) { |
||||
|
this.panelResize$.observe(this.sliderValueContainer.nativeElement); |
||||
|
} |
||||
|
this.onResize(); |
||||
|
} |
||||
|
super.ngAfterViewInit(); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
if (this.panelResize$) { |
||||
|
this.panelResize$.disconnect(); |
||||
|
} |
||||
|
if (this.sliderCssClass) { |
||||
|
this.utils.clearCssElement(this.renderer, this.sliderCssClass); |
||||
|
} |
||||
|
super.ngOnDestroy(); |
||||
|
} |
||||
|
|
||||
|
public onInit() { |
||||
|
super.onInit(); |
||||
|
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
||||
|
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
||||
|
this.cd.detectChanges(); |
||||
|
} |
||||
|
|
||||
|
public onSliderChange() { |
||||
|
this.updateValueText(); |
||||
|
if (!this.ctx.isEdit && !this.ctx.isPreview) { |
||||
|
const prevValue = this.prevValue; |
||||
|
const targetValue = this.value; |
||||
|
this.updateValue(this.valueSetter, targetValue, { |
||||
|
next: () => this.onValue(targetValue), |
||||
|
error: () => this.onValue(prevValue) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private _sliderValueText(value: number): string { |
||||
|
return formatValue(value, this.settings.valueDecimals, this.settings.valueUnits, false); |
||||
|
} |
||||
|
|
||||
|
private onValue(value: number): void { |
||||
|
this.value = value; |
||||
|
this.prevValue = value; |
||||
|
this.updateValueText(); |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
|
||||
|
private updateValueText() { |
||||
|
if (isDefinedAndNotNull(this.value) && isNumeric(this.value)) { |
||||
|
this.valueText = formatValue(this.value, this.settings.valueDecimals, this.settings.valueUnits, false); |
||||
|
} else { |
||||
|
this.valueText = 'N/A'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private onDisabled(value: boolean): void { |
||||
|
this.disabled = !!value; |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
|
||||
|
private onResize() { |
||||
|
const panelWidth = this.sliderContent.nativeElement.getBoundingClientRect().width; |
||||
|
const panelHeight = this.sliderContent.nativeElement.getBoundingClientRect().height; |
||||
|
|
||||
|
if (this.showValue) { |
||||
|
this.resetScale(this.sliderValueContainer.nativeElement, this.sliderValue.nativeElement); |
||||
|
} |
||||
|
|
||||
|
if (this.showLeftRightIcon) { |
||||
|
this.resetScale(this.leftSliderIconContainer.nativeElement, this.leftSliderIcon.nativeElement); |
||||
|
this.resetScale(this.rightSliderIconContainer.nativeElement, this.rightSliderIcon.nativeElement); |
||||
|
} |
||||
|
|
||||
|
if (this.showTicks) { |
||||
|
this.resetScale(this.sliderTickMinContainer.nativeElement, this.sliderTickMin.nativeElement); |
||||
|
this.resetScale(this.sliderTickMaxContainer.nativeElement, this.sliderTickMax.nativeElement); |
||||
|
} |
||||
|
|
||||
|
let minAspect = 0.2; |
||||
|
let avgContentHeight = 45; |
||||
|
if (this.showTicks) { |
||||
|
minAspect += 0.1; |
||||
|
avgContentHeight += 20; |
||||
|
} |
||||
|
if (this.showValue) { |
||||
|
minAspect += 0.1; |
||||
|
avgContentHeight += 50; |
||||
|
} |
||||
|
const aspect = Math.min(panelHeight / panelWidth, minAspect); |
||||
|
const targetHeight = panelWidth * aspect; |
||||
|
const scale = targetHeight / avgContentHeight; |
||||
|
|
||||
|
if (this.showValue) { |
||||
|
this.updateScale(this.sliderValueContainer.nativeElement, this.sliderValue.nativeElement, scale); |
||||
|
} |
||||
|
if (this.showLeftRightIcon) { |
||||
|
const leftIconContainerRect = this.leftSliderIconContainer.nativeElement.getBoundingClientRect(); |
||||
|
const leftIconContainerMarginTop = -(leftIconContainerRect.width * scale) / 2 + 3; |
||||
|
this.renderer.setStyle(this.leftSliderIconContainer.nativeElement, 'marginTop', `${leftIconContainerMarginTop}px`); |
||||
|
this.updateScale(this.leftSliderIconContainer.nativeElement, this.leftSliderIcon.nativeElement, scale, true); |
||||
|
const rightIconContainerRect = this.rightSliderIconContainer.nativeElement.getBoundingClientRect(); |
||||
|
const rightIconContainerMarginTop = -(rightIconContainerRect.width * scale) / 2 + 3; |
||||
|
this.renderer.setStyle(this.rightSliderIconContainer.nativeElement, 'marginTop', `${rightIconContainerMarginTop}px`); |
||||
|
this.updateScale(this.rightSliderIconContainer.nativeElement, this.rightSliderIcon.nativeElement, scale, true); |
||||
|
} |
||||
|
if (this.showTicks) { |
||||
|
this.updateScale(this.sliderTickMinContainer.nativeElement, this.sliderTickMin.nativeElement, scale); |
||||
|
this.updateScale(this.sliderTickMaxContainer.nativeElement, this.sliderTickMax.nativeElement, scale); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private resetScale(container: HTMLElement, element: HTMLElement): void { |
||||
|
this.renderer.setStyle(container, 'width', ''); |
||||
|
this.renderer.setStyle(container, 'height', ''); |
||||
|
this.renderer.setStyle(element, 'transform', ''); |
||||
|
} |
||||
|
|
||||
|
private updateScale(container: HTMLElement, element: HTMLElement, scale: number, sameHeight = false): void { |
||||
|
const rect = container.getBoundingClientRect(); |
||||
|
this.renderer.setStyle(container, 'width', `${rect.width * scale}px`); |
||||
|
this.renderer.setStyle(container, 'height', `${(sameHeight ? rect.width : rect.height) * scale}px`); |
||||
|
this.renderer.setStyle(element, 'transform', `scale(${scale})`); |
||||
|
this.renderer.setStyle(element, 'transform-origin', 'left top'); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,196 @@ |
|||||
|
///
|
||||
|
/// 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 { |
||||
|
DataToValueType, |
||||
|
GetValueAction, |
||||
|
GetValueSettings, |
||||
|
SetValueAction, |
||||
|
SetValueSettings, |
||||
|
ValueToDataType |
||||
|
} from '@shared/models/action-widget-settings.models'; |
||||
|
import { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models'; |
||||
|
import { AttributeScope } from '@shared/models/telemetry/telemetry.models'; |
||||
|
|
||||
|
export enum SliderLayout { |
||||
|
default = 'default', |
||||
|
extended = 'extended', |
||||
|
simplified = 'simplified' |
||||
|
} |
||||
|
|
||||
|
export const sliderLayouts = Object.keys(SliderLayout) as SliderLayout[]; |
||||
|
|
||||
|
export const sliderLayoutTranslations = new Map<SliderLayout, string>( |
||||
|
[ |
||||
|
[SliderLayout.default, 'widgets.slider.layout-default'], |
||||
|
[SliderLayout.extended, 'widgets.slider.layout-extended'], |
||||
|
[SliderLayout.simplified, 'widgets.slider.layout-simplified'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export const sliderLayoutImages = new Map<SliderLayout, string>( |
||||
|
[ |
||||
|
[SliderLayout.default, 'assets/widget/slider/default-layout.svg'], |
||||
|
[SliderLayout.extended, 'assets/widget/slider/extended-layout.svg'], |
||||
|
[SliderLayout.simplified, 'assets/widget/slider/simplified-layout.svg'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export interface SliderWidgetSettings { |
||||
|
initialState: GetValueSettings<number>; |
||||
|
disabledState: GetValueSettings<boolean>; |
||||
|
valueChange: SetValueSettings; |
||||
|
layout: SliderLayout; |
||||
|
autoScale: boolean; |
||||
|
showValue: boolean; |
||||
|
valueUnits: string; |
||||
|
valueDecimals: number; |
||||
|
valueFont: Font; |
||||
|
valueColor: string; |
||||
|
showTicks: boolean; |
||||
|
tickMin: number; |
||||
|
tickMax: number; |
||||
|
ticksFont: Font; |
||||
|
ticksColor: string; |
||||
|
showTickMarks: boolean; |
||||
|
tickMarksCount: number; |
||||
|
tickMarksColor: string; |
||||
|
mainColor: string; |
||||
|
backgroundColor: string; |
||||
|
mainColorDisabled: string; |
||||
|
backgroundColorDisabled: string; |
||||
|
leftIcon: string; |
||||
|
leftIconSize: number; |
||||
|
leftIconSizeUnit: cssUnit; |
||||
|
leftIconColor: string; |
||||
|
rightIcon: string; |
||||
|
rightIconSize: number; |
||||
|
rightIconSizeUnit: cssUnit; |
||||
|
rightIconColor: string; |
||||
|
background: BackgroundSettings; |
||||
|
} |
||||
|
|
||||
|
export const sliderWidgetDefaultSettings: SliderWidgetSettings = { |
||||
|
initialState: { |
||||
|
action: GetValueAction.EXECUTE_RPC, |
||||
|
defaultValue: 0, |
||||
|
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 integer value */\nreturn data;' |
||||
|
} |
||||
|
}, |
||||
|
disabledState: { |
||||
|
action: GetValueAction.DO_NOTHING, |
||||
|
defaultValue: false, |
||||
|
getAttribute: { |
||||
|
key: 'state', |
||||
|
scope: null |
||||
|
}, |
||||
|
getTimeSeries: { |
||||
|
key: 'state' |
||||
|
}, |
||||
|
dataToValue: { |
||||
|
type: DataToValueType.NONE, |
||||
|
compareToValue: true, |
||||
|
dataToValueFunction: '/* Should return boolean value */\nreturn data;' |
||||
|
} |
||||
|
}, |
||||
|
valueChange: { |
||||
|
action: SetValueAction.EXECUTE_RPC, |
||||
|
executeRpc: { |
||||
|
method: 'setState', |
||||
|
requestTimeout: 5000, |
||||
|
requestPersistent: false, |
||||
|
persistentPollingInterval: 1000 |
||||
|
}, |
||||
|
setAttribute: { |
||||
|
key: 'state', |
||||
|
scope: AttributeScope.SHARED_SCOPE |
||||
|
}, |
||||
|
putTimeSeries: { |
||||
|
key: 'state' |
||||
|
}, |
||||
|
valueToData: { |
||||
|
type: ValueToDataType.FUNCTION, |
||||
|
constantValue: 0, |
||||
|
valueToDataFunction: '/* Convert input integer value to RPC parameters or attribute/time-series value */\nreturn value;' |
||||
|
} |
||||
|
}, |
||||
|
layout: SliderLayout.default, |
||||
|
autoScale: true, |
||||
|
showValue: true, |
||||
|
valueUnits: '%', |
||||
|
valueDecimals: 0, |
||||
|
valueFont: { |
||||
|
family: 'Roboto', |
||||
|
size: 36, |
||||
|
sizeUnit: 'px', |
||||
|
style: 'normal', |
||||
|
weight: '500', |
||||
|
lineHeight: '36px' |
||||
|
}, |
||||
|
valueColor: 'rgba(0, 0, 0, 0.87)', |
||||
|
showTicks: true, |
||||
|
tickMin: 0, |
||||
|
tickMax: 100, |
||||
|
ticksFont: { |
||||
|
family: 'Roboto', |
||||
|
size: 11, |
||||
|
sizeUnit: 'px', |
||||
|
style: 'normal', |
||||
|
weight: '400', |
||||
|
lineHeight: '16px' |
||||
|
}, |
||||
|
ticksColor: 'rgba(0,0,0,0.54)', |
||||
|
showTickMarks: true, |
||||
|
tickMarksCount: 11, |
||||
|
tickMarksColor: '#5469FF', |
||||
|
mainColor: '#5469FF', |
||||
|
backgroundColor: '#CCD2FF', |
||||
|
mainColorDisabled: '#9BA2B0', |
||||
|
backgroundColorDisabled: '#D5D7E5', |
||||
|
leftIcon: 'lightbulb', |
||||
|
leftIconSize: 24, |
||||
|
leftIconSizeUnit: 'px', |
||||
|
leftIconColor: '#5469FF', |
||||
|
rightIcon: 'mdi:lightbulb-on', |
||||
|
rightIconSize: 24, |
||||
|
rightIconSizeUnit: 'px', |
||||
|
rightIconColor: '#5469FF', |
||||
|
background: { |
||||
|
type: BackgroundType.color, |
||||
|
color: '#fff', |
||||
|
overlay: { |
||||
|
enabled: false, |
||||
|
color: 'rgba(255,255,255,0.72)', |
||||
|
blur: 3 |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
@ -0,0 +1,215 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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]="sliderWidgetSettingsForm"> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.slider.behavior</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.slider.initial-value-hint' | translate}}" translate>widgets.slider.initial-value</div> |
||||
|
<tb-get-value-action-settings fxFlex |
||||
|
panelTitle="widgets.slider.initial-value" |
||||
|
[valueType]="valueType.INTEGER" |
||||
|
[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.slider.on-value-change-hint' | translate}}" translate>widgets.slider.on-value-change</div> |
||||
|
<tb-set-value-action-settings fxFlex |
||||
|
panelTitle="widgets.slider.on-value-change" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="valueChange"></tb-set-value-action-settings> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div> |
||||
|
<tb-get-value-action-settings fxFlex |
||||
|
panelTitle="widgets.rpc-state.disabled-state" |
||||
|
[valueType]="valueType.BOOLEAN" |
||||
|
stateLabel="widgets.rpc-state.disabled" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="disabledState"></tb-get-value-action-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widget-config.card-style</div> |
||||
|
<tb-image-cards-select rowHeight="2:1" |
||||
|
[cols]="{columns: 3, |
||||
|
breakpoints: { |
||||
|
'lt-sm': 1, |
||||
|
'lt-md': 2 |
||||
|
}}" |
||||
|
label="{{ 'widgets.slider.layout' | translate }}" formControlName="layout"> |
||||
|
<tb-image-cards-select-option *ngFor="let layout of sliderLayouts" |
||||
|
[value]="layout" |
||||
|
[image]="sliderLayoutImageMap.get(layout)"> |
||||
|
{{ sliderLayoutTranslationMap.get(layout) | translate }} |
||||
|
</tb-image-cards-select-option> |
||||
|
</tb-image-cards-select> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="autoScale"> |
||||
|
{{ 'widgets.slider.auto-scale' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<div>{{ 'widgets.background.background' | translate }}</div> |
||||
|
<tb-background-settings formControlName="background"> |
||||
|
</tb-background-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.slider.slider</div> |
||||
|
<div *ngIf="sliderWidgetSettingsForm.get('layout').value !== sliderLayout.simplified" class="tb-form-row column-xs"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showValue"> |
||||
|
{{ 'widgets.slider.value' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<tb-unit-input class="flex" formControlName="valueUnits"></tb-unit-input> |
||||
|
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="valueDecimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
<div matSuffix fxHide.lt-md translate>widget-config.decimals-suffix</div> |
||||
|
</mat-form-field> |
||||
|
<tb-font-settings formControlName="valueFont" |
||||
|
[previewText]="valuePreviewFn"> |
||||
|
</tb-font-settings> |
||||
|
<tb-color-input asBoxInput |
||||
|
colorClearButton |
||||
|
formControlName="valueColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.slider.range' | translate }}</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div class="tb-small-label" translate>widgets.slider.min</div> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="tickMin" type="number" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<div class="tb-small-label" translate>widgets.slider.max</div> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="tickMax" type="number" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="showTicks"> |
||||
|
{{ 'widgets.slider.range-ticks' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<tb-font-settings formControlName="ticksFont" |
||||
|
previewText="100"> |
||||
|
</tb-font-settings> |
||||
|
<tb-color-input asBoxInput |
||||
|
colorClearButton |
||||
|
formControlName="ticksColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="showTickMarks"> |
||||
|
{{ 'widgets.slider.tick-marks' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="tickMarksCount" type="number" min="2" |
||||
|
step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="tickMarksColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.slider.colors' | translate }}</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.rpc-state.disabled-state' | translate }}</div> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px"> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColorDisabled"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<div translate>widgets.slider.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColorDisabled"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngIf="sliderWidgetSettingsForm.get('layout').value === sliderLayout.extended" |
||||
|
class="tb-form-row column-xs"> |
||||
|
<div class="fixed-title-width"> |
||||
|
{{ 'widgets.slider.left-icon' | translate }} |
||||
|
</div> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="leftIconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select fxFlex formControlName="leftIconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
[color]="sliderWidgetSettingsForm.get('leftIconColor').value" |
||||
|
formControlName="leftIcon"> |
||||
|
</tb-material-icon-select> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="leftIconColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div *ngIf="sliderWidgetSettingsForm.get('layout').value === sliderLayout.extended" |
||||
|
class="tb-form-row column-xs"> |
||||
|
<div class="fixed-title-width"> |
||||
|
{{ 'widgets.slider.right-icon' | translate }} |
||||
|
</div> |
||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
||||
|
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="rightIconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select fxFlex formControlName="rightIconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
[color]="sliderWidgetSettingsForm.get('rightIconColor').value" |
||||
|
formControlName="rightIcon"> |
||||
|
</tb-material-icon-select> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="rightIconColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
@ -0,0 +1,186 @@ |
|||||
|
///
|
||||
|
/// 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 { 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 { ValueType } from '@shared/models/constants'; |
||||
|
import { |
||||
|
SliderLayout, |
||||
|
sliderLayoutImages, |
||||
|
sliderLayouts, |
||||
|
sliderLayoutTranslations, |
||||
|
sliderWidgetDefaultSettings |
||||
|
} from '@home/components/widget/lib/rpc/slider-widget.models'; |
||||
|
import { formatValue } from '@core/utils'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-slider-widget-settings', |
||||
|
templateUrl: './slider-widget-settings.component.html', |
||||
|
styleUrls: ['./../widget-settings.scss'] |
||||
|
}) |
||||
|
export class SliderWidgetSettingsComponent extends WidgetSettingsComponent { |
||||
|
|
||||
|
get targetDevice(): TargetDevice { |
||||
|
return this.widgetConfig?.config?.targetDevice; |
||||
|
} |
||||
|
|
||||
|
get widgetType(): widgetType { |
||||
|
return this.widgetConfig?.widgetType; |
||||
|
} |
||||
|
|
||||
|
sliderLayout = SliderLayout; |
||||
|
|
||||
|
sliderLayouts = sliderLayouts; |
||||
|
|
||||
|
sliderLayoutTranslationMap = sliderLayoutTranslations; |
||||
|
sliderLayoutImageMap = sliderLayoutImages; |
||||
|
|
||||
|
valueType = ValueType; |
||||
|
|
||||
|
sliderWidgetSettingsForm: UntypedFormGroup; |
||||
|
|
||||
|
valuePreviewFn = this._valuePreviewFn.bind(this); |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private fb: UntypedFormBuilder) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
protected settingsForm(): UntypedFormGroup { |
||||
|
return this.sliderWidgetSettingsForm; |
||||
|
} |
||||
|
|
||||
|
protected defaultSettings(): WidgetSettings { |
||||
|
return {...sliderWidgetDefaultSettings}; |
||||
|
} |
||||
|
|
||||
|
protected onSettingsSet(settings: WidgetSettings) { |
||||
|
this.sliderWidgetSettingsForm = this.fb.group({ |
||||
|
initialState: [settings.initialState, []], |
||||
|
valueChange: [settings.valueChange, []], |
||||
|
disabledState: [settings.disabledState, []], |
||||
|
|
||||
|
layout: [settings.layout, []], |
||||
|
autoScale: [settings.autoScale, []], |
||||
|
|
||||
|
showValue: [settings.showValue, []], |
||||
|
valueUnits: [settings.valueUnits, []], |
||||
|
valueDecimals: [settings.valueDecimals, []], |
||||
|
valueFont: [settings.valueFont, []], |
||||
|
valueColor: [settings.valueColor, []], |
||||
|
|
||||
|
tickMin: [settings.tickMin, []], |
||||
|
tickMax: [settings.tickMax, []], |
||||
|
|
||||
|
showTicks: [settings.showTicks, []], |
||||
|
ticksFont: [settings.ticksFont, []], |
||||
|
ticksColor: [settings.ticksColor, []], |
||||
|
|
||||
|
showTickMarks: [settings.showTickMarks, []], |
||||
|
tickMarksCount: [settings.tickMarksCount, [Validators.min(2)]], |
||||
|
tickMarksColor: [settings.tickMarksColor, []], |
||||
|
|
||||
|
mainColor: [settings.mainColor, []], |
||||
|
backgroundColor: [settings.backgroundColor, []], |
||||
|
|
||||
|
mainColorDisabled: [settings.mainColorDisabled, []], |
||||
|
backgroundColorDisabled: [settings.backgroundColorDisabled, []], |
||||
|
|
||||
|
leftIconSize: [settings.leftIconSize, [Validators.min(0)]], |
||||
|
leftIconSizeUnit: [settings.leftIconSizeUnit, []], |
||||
|
leftIcon: [settings.leftIcon, []], |
||||
|
leftIconColor: [settings.leftIconColor, []], |
||||
|
|
||||
|
rightIconSize: [settings.rightIconSize, [Validators.min(0)]], |
||||
|
rightIconSizeUnit: [settings.rightIconSizeUnit, []], |
||||
|
rightIcon: [settings.rightIcon, []], |
||||
|
rightIconColor: [settings.rightIconColor, []], |
||||
|
|
||||
|
background: [settings.background, []], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected validatorTriggers(): string[] { |
||||
|
return ['showValue', 'showTicks', 'showTickMarks', 'layout']; |
||||
|
} |
||||
|
|
||||
|
protected updateValidators(_emitEvent: boolean): void { |
||||
|
const showValue: boolean = this.sliderWidgetSettingsForm.get('showValue').value; |
||||
|
const showTicks: boolean = this.sliderWidgetSettingsForm.get('showTicks').value; |
||||
|
const showTickMarks: boolean = this.sliderWidgetSettingsForm.get('showTickMarks').value; |
||||
|
const layout: SliderLayout = this.sliderWidgetSettingsForm.get('layout').value; |
||||
|
|
||||
|
const valueEnabled = layout !== SliderLayout.simplified; |
||||
|
const leftRightIconsEnabled = layout === SliderLayout.extended; |
||||
|
|
||||
|
if (valueEnabled && showValue) { |
||||
|
this.sliderWidgetSettingsForm.get('valueUnits').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('valueDecimals').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('valueFont').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('valueColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetSettingsForm.get('valueUnits').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('valueDecimals').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('valueFont').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('valueColor').disable(); |
||||
|
} |
||||
|
|
||||
|
if (showTicks) { |
||||
|
this.sliderWidgetSettingsForm.get('ticksFont').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('ticksColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetSettingsForm.get('ticksFont').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('ticksColor').disable(); |
||||
|
} |
||||
|
|
||||
|
if (showTickMarks) { |
||||
|
this.sliderWidgetSettingsForm.get('tickMarksCount').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('tickMarksColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetSettingsForm.get('tickMarksCount').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('tickMarksColor').disable(); |
||||
|
} |
||||
|
|
||||
|
if (leftRightIconsEnabled) { |
||||
|
this.sliderWidgetSettingsForm.get('leftIconSize').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('leftIconSizeUnit').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('leftIcon').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('leftIconColor').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIconSize').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIconSizeUnit').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIcon').enable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIconColor').enable(); |
||||
|
} else { |
||||
|
this.sliderWidgetSettingsForm.get('leftIconSize').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('leftIconSizeUnit').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('leftIcon').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('leftIconColor').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIconSize').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIconSizeUnit').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIcon').disable(); |
||||
|
this.sliderWidgetSettingsForm.get('rightIconColor').disable(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private _valuePreviewFn(): string { |
||||
|
const units: string = this.sliderWidgetSettingsForm.get('valueUnits').value; |
||||
|
const decimals: number = this.sliderWidgetSettingsForm.get('valueDecimals').value; |
||||
|
return formatValue(48, decimals, units, false); |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 14 KiB |
Loading…
Reference in new issue