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