36 changed files with 2625 additions and 16 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,271 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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]="segmentedButtonWidgetConfigForm"> |
||||
|
<tb-datasources |
||||
|
*ngIf="!widgetEditMode" |
||||
|
[configMode]="basicMode" |
||||
|
hideDatasourceLabel |
||||
|
hideDataKeys |
||||
|
forceSingleDatasource |
||||
|
formControlName="datasources"> |
||||
|
</tb-datasources> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.action-button.behavior</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.selected-state-hint' | translate}}" translate>widgets.button-state.selected-state</div> |
||||
|
<tb-get-value-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.button-state.selected-state' | translate }}" |
||||
|
[valueType]="valueType.BOOLEAN" |
||||
|
trueLabel="{{ 'widgets.button-state.first' | translate }}" |
||||
|
falseLabel="{{ 'widgets.button-state.second' | translate }}" |
||||
|
stateLabel="{{ 'widgets.button-state.initial' | translate }}" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="initialState"></tb-get-value-action-settings> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.action-button.button-click-hint' | translate}}" translate>widgets.action-button.first-button-click</div> |
||||
|
<tb-widget-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.action-button.first-button-click' | translate }}" |
||||
|
[callbacks]="callbacks" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="leftButtonClick"> |
||||
|
</tb-widget-action-settings> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.action-button.button-click-hint' | translate}}" translate>widgets.action-button.second-button-click</div> |
||||
|
<tb-widget-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.action-button.second-button-click' | translate }}" |
||||
|
[callbacks]="callbacks" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="rightButtonClick"> |
||||
|
</tb-widget-action-settings> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.disabled-state-hint' | translate}}" translate>widgets.button-state.disabled-state</div> |
||||
|
<tb-get-value-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.button-state.disabled-state' | translate }}" |
||||
|
[valueType]="valueType.BOOLEAN" |
||||
|
stateLabel="{{ 'widgets.button-state.disabled' | translate }}" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="disabledState"></tb-get-value-action-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel" formGroupName="appearance"> |
||||
|
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
||||
|
|
||||
|
<tb-image-cards-select rowHeight="3:1" |
||||
|
|
||||
|
[cols]="{columns: 2, |
||||
|
breakpoints: { |
||||
|
'lt-sm': 1 |
||||
|
}}" |
||||
|
label="{{ 'widgets.segmented-button.layout' | translate }}" formControlName="layout"> |
||||
|
<tb-image-cards-select-option *ngFor="let layout of segmentedButtonLayouts" |
||||
|
[value]="layout" |
||||
|
[image]="segmentedButtonLayoutImageMap.get(layout)"> |
||||
|
{{ segmentedButtonLayoutTranslationMap.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.button.auto-scale' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<div translate>widgets.segmented-button.card-border</div> |
||||
|
<div class="flex flex-row items-center gap-2"> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="cardBorder" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
<div matSuffix class="lt-md:!hidden">px</div> |
||||
|
</mat-form-field> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="cardBorderColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-panel" formGroupName="appearance"> |
||||
|
<div class="flex flex-row items-center justify-between"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.segmented-button.button-appearance</div> |
||||
|
<tb-toggle-select [(ngModel)]="segmentedButtonAppearanceType" [ngModelOptions]="{standalone: true}"> |
||||
|
<tb-toggle-option value="first">{{ 'widgets.segmented-button.first' | translate }}</tb-toggle-option> |
||||
|
<tb-toggle-option value="second">{{ 'widgets.segmented-button.second' | translate }}</tb-toggle-option> |
||||
|
</tb-toggle-select> |
||||
|
</div> |
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="leftAppearance" [class.!hidden]="segmentedButtonAppearanceType !== 'first'"> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel"> |
||||
|
{{ 'widgets.button.label' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-font-settings formControlName="labelFont" |
||||
|
[previewText]="segmentedButtonWidgetConfigForm.get('appearance.leftAppearance.label').value"> |
||||
|
</tb-font-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> |
||||
|
{{ 'widgets.button.icon' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
iconClearButton |
||||
|
formControlName="icon"> |
||||
|
</tb-material-icon-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="rightAppearance" [class.!hidden]="segmentedButtonAppearanceType !== 'second'"> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel"> |
||||
|
{{ 'widgets.button.label' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-font-settings formControlName="labelFont" |
||||
|
[previewText]="segmentedButtonWidgetConfigForm.get('appearance.rightAppearance.label').value"> |
||||
|
</tb-font-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> |
||||
|
{{ 'widgets.button.icon' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
iconClearButton |
||||
|
formControlName="icon"> |
||||
|
</tb-material-icon-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-panel" formGroupName="appearance"> |
||||
|
<div class="flex flex-row items-center justify-between"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.segmented-button.color-styles</div> |
||||
|
<tb-toggle-select [(ngModel)]="segmentedButtonColorStylesType" [ngModelOptions]="{standalone: true}"> |
||||
|
<tb-toggle-option value="selected">{{ 'widgets.segmented-button.selected' | translate }}</tb-toggle-option> |
||||
|
<tb-toggle-option value="unselected">{{ 'widgets.segmented-button.unselected' | translate }}</tb-toggle-option> |
||||
|
</tb-toggle-select> |
||||
|
</div> |
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="selectedStyle" [class.!hidden]="segmentedButtonColorStylesType !== 'selected'"> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.button.color-palette' | translate }}</div> |
||||
|
<div class="flex flex-row items-center justify-start gap-3"> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel stroked" formGroupName="customStyle"> |
||||
|
<mat-expansion-panel class="tb-settings"> |
||||
|
<mat-expansion-panel-header class="flex flex-row flex-wrap"> |
||||
|
<mat-panel-title> |
||||
|
<div class="tb-form-panel-title" translate>widgets.button.custom-styles</div> |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<div class="tb-form-row space-between" *ngFor="let state of widgetButtonToggleStates"> |
||||
|
<div>{{ widgetButtonToggleStatesTranslationsMap.get(state) | translate }}</div> |
||||
|
<tb-widget-button-toggle-custom-style |
||||
|
[value]="true" |
||||
|
[state]="state" |
||||
|
[appearance]="segmentedButtonWidgetConfigForm.get('appearance').value" |
||||
|
[autoScale]="true" |
||||
|
[formControlName]="state"> |
||||
|
</tb-widget-button-toggle-custom-style> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="unselectedStyle" [class.!hidden]="segmentedButtonColorStylesType !== 'unselected'"> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.button.color-palette' | translate }}</div> |
||||
|
<div class="flex flex-row items-center justify-start gap-3"> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel stroked" formGroupName="customStyle"> |
||||
|
<mat-expansion-panel class="tb-settings"> |
||||
|
<mat-expansion-panel-header class="flex flex-row flex-wrap"> |
||||
|
<mat-panel-title> |
||||
|
<div class="tb-form-panel-title" translate>widgets.button.custom-styles</div> |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<div class="tb-form-row space-between" *ngFor="let state of widgetButtonToggleStates"> |
||||
|
<div>{{ widgetButtonToggleStatesTranslationsMap.get(state) | translate }}</div> |
||||
|
<tb-widget-button-toggle-custom-style |
||||
|
[value]="false" |
||||
|
[state]="state" |
||||
|
[appearance]="segmentedButtonWidgetConfigForm.get('appearance').value" |
||||
|
[autoScale]="true" |
||||
|
[formControlName]="state"> |
||||
|
</tb-widget-button-toggle-custom-style> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
@ -0,0 +1,145 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component } from '@angular/core'; |
||||
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models'; |
||||
|
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; |
||||
|
import { Datasource, TargetDevice, } from '@shared/models/widget.models'; |
||||
|
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models'; |
||||
|
import { |
||||
|
SegmentedButtonAppearanceType, |
||||
|
SegmentedButtonColorStylesType, |
||||
|
segmentedButtonDefaultSettings, |
||||
|
segmentedButtonLayoutBorder, |
||||
|
segmentedButtonLayoutImages, |
||||
|
segmentedButtonLayouts, |
||||
|
segmentedButtonLayoutTranslations, |
||||
|
SegmentedButtonWidgetSettings, |
||||
|
WidgetButtonToggleState, |
||||
|
widgetButtonToggleStatesTranslations |
||||
|
} from '@home/components/widget/lib/button/segmented-button-widget.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-segmented-button-basic-config', |
||||
|
templateUrl: './segmented-button-basic-config.component.html', |
||||
|
styleUrls: ['../basic-config.scss'] |
||||
|
}) |
||||
|
export class SegmentedButtonBasicConfigComponent extends BasicWidgetConfigComponent { |
||||
|
|
||||
|
get targetDevice(): TargetDevice { |
||||
|
const datasources: Datasource[] = this.segmentedButtonWidgetConfigForm.get('datasources').value; |
||||
|
return getTargetDeviceFromDatasources(datasources); |
||||
|
} |
||||
|
|
||||
|
segmentedButtonAppearanceType: SegmentedButtonAppearanceType = 'first'; |
||||
|
segmentedButtonColorStylesType: SegmentedButtonColorStylesType = 'selected'; |
||||
|
|
||||
|
widgetButtonToggleStates = Object.keys(WidgetButtonToggleState) as WidgetButtonToggleState[]; |
||||
|
widgetButtonToggleStatesTranslationsMap = widgetButtonToggleStatesTranslations; |
||||
|
|
||||
|
segmentedButtonLayouts = segmentedButtonLayouts; |
||||
|
segmentedButtonLayoutTranslationMap = segmentedButtonLayoutTranslations; |
||||
|
segmentedButtonLayoutImageMap = segmentedButtonLayoutImages; |
||||
|
segmentedButtonLayoutBorderMap = segmentedButtonLayoutBorder; |
||||
|
|
||||
|
valueType = ValueType; |
||||
|
|
||||
|
segmentedButtonWidgetConfigForm: UntypedFormGroup; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected widgetConfigComponent: WidgetConfigComponent, |
||||
|
private fb: UntypedFormBuilder) { |
||||
|
super(store, widgetConfigComponent); |
||||
|
} |
||||
|
|
||||
|
protected configForm(): UntypedFormGroup { |
||||
|
return this.segmentedButtonWidgetConfigForm; |
||||
|
} |
||||
|
|
||||
|
protected onConfigSet(configData: WidgetConfigComponentData) { |
||||
|
const settings: SegmentedButtonWidgetSettings = {...segmentedButtonDefaultSettings, ...(configData.config.settings || {})}; |
||||
|
|
||||
|
this.segmentedButtonWidgetConfigForm = this.fb.group({ |
||||
|
datasources: [configData.config.datasources, []], |
||||
|
|
||||
|
initialState: [settings.initialState, []], |
||||
|
leftButtonClick: [settings.leftButtonClick, []], |
||||
|
rightButtonClick: [settings.rightButtonClick, []], |
||||
|
disabledState: [settings.disabledState, []], |
||||
|
|
||||
|
appearance: this.fb.group({ |
||||
|
layout: [settings.appearance.layout, []], |
||||
|
autoScale: [settings.appearance.autoScale, []], |
||||
|
cardBorder: [settings.appearance.cardBorder, []], |
||||
|
cardBorderColor: [settings.appearance.cardBorderColor, []], |
||||
|
leftAppearance: this.fb.group({ |
||||
|
showLabel: [settings.appearance.leftAppearance.showLabel, []], |
||||
|
label: [settings.appearance.leftAppearance.label, []], |
||||
|
labelFont: [settings.appearance.leftAppearance.labelFont, []], |
||||
|
showIcon: [settings.appearance.leftAppearance.showIcon, []], |
||||
|
icon: [settings.appearance.leftAppearance.icon, []], |
||||
|
iconSize: [settings.appearance.leftAppearance.iconSize, []], |
||||
|
iconSizeUnit: [settings.appearance.leftAppearance.iconSizeUnit, []], |
||||
|
}), |
||||
|
rightAppearance: this.fb.group({ |
||||
|
showLabel: [settings.appearance.rightAppearance.showLabel, []], |
||||
|
label: [settings.appearance.rightAppearance.label, []], |
||||
|
labelFont: [settings.appearance.rightAppearance.labelFont, []], |
||||
|
showIcon: [settings.appearance.rightAppearance.showIcon, []], |
||||
|
icon: [settings.appearance.rightAppearance.icon, []], |
||||
|
iconSize: [settings.appearance.rightAppearance.iconSize, []], |
||||
|
iconSizeUnit: [settings.appearance.rightAppearance.iconSizeUnit, []], |
||||
|
}), |
||||
|
selectedStyle: this.fb.group({ |
||||
|
mainColor: [settings.appearance.selectedStyle.mainColor, []], |
||||
|
backgroundColor: [settings.appearance.selectedStyle.backgroundColor, []], |
||||
|
customStyle: this.fb.group({ |
||||
|
enabled: [settings.appearance.selectedStyle.customStyle.enabled, []], |
||||
|
hovered: [settings.appearance.selectedStyle.customStyle.hovered, []], |
||||
|
disabled: [settings.appearance.selectedStyle.customStyle.disabled, []], |
||||
|
}) |
||||
|
}), |
||||
|
unselectedStyle: this.fb.group({ |
||||
|
mainColor: [settings.appearance.unselectedStyle.mainColor, []], |
||||
|
backgroundColor: [settings.appearance.unselectedStyle.backgroundColor, []], |
||||
|
customStyle: this.fb.group({ |
||||
|
enabled: [settings.appearance.unselectedStyle.customStyle.enabled, []], |
||||
|
hovered: [settings.appearance.unselectedStyle.customStyle.hovered, []], |
||||
|
disabled: [settings.appearance.unselectedStyle.customStyle.disabled, []], |
||||
|
}) |
||||
|
}), |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected prepareOutputConfig(config: any): WidgetConfigComponentData { |
||||
|
this.widgetConfig.config.datasources = config.datasources; |
||||
|
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {}; |
||||
|
this.widgetConfig.config.settings.initialState = config.initialState; |
||||
|
this.widgetConfig.config.settings.disabledState = config.disabledState; |
||||
|
this.widgetConfig.config.settings.leftButtonClick = config.leftButtonClick; |
||||
|
this.widgetConfig.config.settings.rightButtonClick = config.rightButtonClick; |
||||
|
this.widgetConfig.config.settings.appearance = config.appearance; |
||||
|
this.widgetConfig.config.borderRadius = this.segmentedButtonLayoutBorderMap.get(config.appearance.layout); |
||||
|
return this.widgetConfig; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,346 @@ |
|||||
|
///
|
||||
|
/// 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 } from '@shared/models/action-widget-settings.models'; |
||||
|
import { cssUnit, Font } from '@shared/models/widget-settings.models'; |
||||
|
import { defaultWidgetAction, WidgetAction } from '@shared/models/widget.models'; |
||||
|
|
||||
|
const defaultMainColor = '#305680'; |
||||
|
const defaultBackgroundColor = '#E8E8E8'; |
||||
|
|
||||
|
export const defaultMainColorDisabled = 'rgba(0, 0, 0, 0.38)'; |
||||
|
export const defaultBackgroundColorDisabled = 'rgba(0, 0, 0, 0.03)'; |
||||
|
export const defaultBorderColorDisabled = defaultBackgroundColorDisabled; |
||||
|
|
||||
|
const defaultBorderColor = defaultBackgroundColor; |
||||
|
|
||||
|
export enum SegmentedButtonLayout { |
||||
|
squared = 'squared', |
||||
|
rounded = 'rounded' |
||||
|
} |
||||
|
|
||||
|
export type SegmentedButtonAppearanceType = 'first' | 'second'; |
||||
|
export type SegmentedButtonColorStylesType = 'selected' | 'unselected'; |
||||
|
|
||||
|
export const segmentedButtonLayouts = Object.keys(SegmentedButtonLayout) as SegmentedButtonLayout[]; |
||||
|
|
||||
|
export const segmentedButtonLayoutTranslations = new Map<SegmentedButtonLayout, string>( |
||||
|
[ |
||||
|
[SegmentedButtonLayout.squared, 'widgets.segmented-button.layout-squared'], |
||||
|
[SegmentedButtonLayout.rounded, 'widgets.segmented-button.layout-rounded'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export const segmentedButtonLayoutImages = new Map<SegmentedButtonLayout, string>( |
||||
|
[ |
||||
|
[SegmentedButtonLayout.squared, 'assets/widget/segmented-button/squared-layout.svg'], |
||||
|
[SegmentedButtonLayout.rounded, 'assets/widget/segmented-button/rounded-layout.svg'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export const segmentedButtonLayoutBorder = new Map<SegmentedButtonLayout, string>( |
||||
|
[ |
||||
|
[SegmentedButtonLayout.squared, '4px'], |
||||
|
[SegmentedButtonLayout.rounded, '40px'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export interface SegmentedButtonAppearance { |
||||
|
showLabel: boolean; |
||||
|
label: string; |
||||
|
labelFont: Font; |
||||
|
showIcon: boolean; |
||||
|
icon: string; |
||||
|
iconSize: number; |
||||
|
iconSizeUnit: cssUnit; |
||||
|
} |
||||
|
|
||||
|
export interface SegmentedButtonStyles { |
||||
|
mainColor: string; |
||||
|
backgroundColor: string; |
||||
|
customStyle: WidgetButtonToggleCustomStyles; |
||||
|
} |
||||
|
|
||||
|
export interface ButtonToggleAppearance { |
||||
|
layout: SegmentedButtonLayout; |
||||
|
autoScale: boolean; |
||||
|
cardBorder: number; |
||||
|
cardBorderColor: string; |
||||
|
leftAppearance: SegmentedButtonAppearance; |
||||
|
rightAppearance: SegmentedButtonAppearance; |
||||
|
selectedStyle: SegmentedButtonStyles; |
||||
|
unselectedStyle: SegmentedButtonStyles; |
||||
|
} |
||||
|
|
||||
|
export interface SegmentedButtonWidgetSettings { |
||||
|
initialState: GetValueSettings<boolean>; |
||||
|
disabledState: GetValueSettings<boolean>; |
||||
|
leftButtonClick: WidgetAction; |
||||
|
rightButtonClick: WidgetAction; |
||||
|
|
||||
|
appearance: ButtonToggleAppearance; |
||||
|
} |
||||
|
|
||||
|
export const segmentedButtonDefaultAppearance: ButtonToggleAppearance = { |
||||
|
layout: SegmentedButtonLayout.squared, |
||||
|
autoScale: true, |
||||
|
cardBorder: 1, |
||||
|
cardBorderColor: '#305680', |
||||
|
leftAppearance: { |
||||
|
showLabel: true, |
||||
|
label: 'Traditional', |
||||
|
labelFont: { |
||||
|
family: 'Roboto', |
||||
|
weight: '500', |
||||
|
style: 'normal', |
||||
|
size: 14, |
||||
|
sizeUnit: 'px', |
||||
|
lineHeight: '18px' |
||||
|
}, |
||||
|
showIcon: true, |
||||
|
icon: 'home', |
||||
|
iconSize: 24, |
||||
|
iconSizeUnit: 'px', |
||||
|
}, |
||||
|
rightAppearance: { |
||||
|
showLabel: true, |
||||
|
label: 'Hi-Perf', |
||||
|
labelFont: { |
||||
|
family: 'Roboto', |
||||
|
weight: '500', |
||||
|
style: 'normal', |
||||
|
size: 14, |
||||
|
sizeUnit: 'px', |
||||
|
lineHeight: '18px' |
||||
|
}, |
||||
|
showIcon: true, |
||||
|
icon: 'home', |
||||
|
iconSize: 24, |
||||
|
iconSizeUnit: 'px', |
||||
|
}, |
||||
|
selectedStyle: { |
||||
|
mainColor: '#FFFFFF', |
||||
|
backgroundColor: '#00695C', |
||||
|
customStyle: { |
||||
|
enabled: null, |
||||
|
hovered: null, |
||||
|
disabled: null |
||||
|
} |
||||
|
}, |
||||
|
unselectedStyle: { |
||||
|
mainColor: '#000000C2', |
||||
|
backgroundColor: '#E8E8E8', |
||||
|
customStyle: { |
||||
|
enabled: null, |
||||
|
hovered: null, |
||||
|
disabled: null |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export const segmentedButtonDefaultSettings: SegmentedButtonWidgetSettings = { |
||||
|
initialState: { |
||||
|
action: GetValueAction.DO_NOTHING, |
||||
|
defaultValue: true, |
||||
|
getAttribute: { |
||||
|
key: 'state', |
||||
|
scope: null |
||||
|
}, |
||||
|
getTimeSeries: { |
||||
|
key: 'state' |
||||
|
}, |
||||
|
getAlarmStatus: { |
||||
|
severityList: null, |
||||
|
typeList: null |
||||
|
}, |
||||
|
dataToValue: { |
||||
|
type: DataToValueType.NONE, |
||||
|
compareToValue: true, |
||||
|
dataToValueFunction: '/* Should return boolean value */\nreturn data;' |
||||
|
} |
||||
|
}, |
||||
|
disabledState: { |
||||
|
action: GetValueAction.DO_NOTHING, |
||||
|
defaultValue: false, |
||||
|
getAttribute: { |
||||
|
key: 'state', |
||||
|
scope: null |
||||
|
}, |
||||
|
getTimeSeries: { |
||||
|
key: 'state' |
||||
|
}, |
||||
|
getAlarmStatus: { |
||||
|
severityList: null, |
||||
|
typeList: null |
||||
|
}, |
||||
|
dataToValue: { |
||||
|
type: DataToValueType.NONE, |
||||
|
compareToValue: true, |
||||
|
dataToValueFunction: '/* Should return boolean value */\nreturn data;' |
||||
|
} |
||||
|
}, |
||||
|
leftButtonClick: defaultWidgetAction(), |
||||
|
rightButtonClick: defaultWidgetAction(), |
||||
|
appearance: segmentedButtonDefaultAppearance |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
const mainCheckedColorVarPrefix = '--tb-widget-button-toggle-main-checked-color-'; |
||||
|
const backgroundCheckedColorVarPrefix = '--tb-widget-button-toggle-background-checked-color-'; |
||||
|
const borderCheckedColorVarPrefix = '--tb-widget-button-toggle-border-checked-color-'; |
||||
|
|
||||
|
const mainUncheckedColorVarPrefix = '--tb-widget-button-toggle-main-unchecked-color-'; |
||||
|
const backgroundUncheckedColorVarPrefix = '--tb-widget-button-toggle-background-unchecked-color-'; |
||||
|
const borderUncheckedColorVarPrefix = '--tb-widget-button-toggle-border-unchecked-color-'; |
||||
|
|
||||
|
export enum WidgetButtonToggleState { |
||||
|
enabled = 'enabled', |
||||
|
hovered = 'hovered', |
||||
|
disabled = 'disabled' |
||||
|
} |
||||
|
|
||||
|
export const widgetButtonToggleStates = Object.keys(WidgetButtonToggleState) as WidgetButtonToggleState[]; |
||||
|
|
||||
|
export const widgetButtonToggleStatesTranslations = new Map<WidgetButtonToggleState, string>( |
||||
|
[ |
||||
|
[WidgetButtonToggleState.enabled, 'widgets.button-state.enabled'], |
||||
|
[WidgetButtonToggleState.hovered, 'widgets.button-state.hovered'], |
||||
|
[WidgetButtonToggleState.disabled, 'widgets.button-state.disabled'] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
export interface WidgetButtonToggleCustomStyle { |
||||
|
overrideMainColor?: boolean; |
||||
|
mainColor?: string; |
||||
|
overrideBackgroundColor?: boolean; |
||||
|
backgroundColor?: string; |
||||
|
overrideBorderColor?: boolean; |
||||
|
borderColor?: string; |
||||
|
} |
||||
|
|
||||
|
export type WidgetButtonToggleCustomStyles = Record<WidgetButtonToggleState, WidgetButtonToggleCustomStyle>; |
||||
|
|
||||
|
export abstract class ButtonToggleStateCssGenerator { |
||||
|
|
||||
|
constructor() {} |
||||
|
|
||||
|
public generateStateCss(selectedAppearance: SegmentedButtonStyles, unselectedAppearance: SegmentedButtonStyles): string { |
||||
|
const selectedColor = this.getColors(selectedAppearance); |
||||
|
const unselectedColor = this.getColors(unselectedAppearance); |
||||
|
|
||||
|
return `${mainCheckedColorVarPrefix}${this.state}: ${selectedColor.mainColor};\n`+ |
||||
|
`${backgroundCheckedColorVarPrefix}${this.state}: ${selectedColor.backgroundColor};\n`+ |
||||
|
`${borderCheckedColorVarPrefix}${this.state}: ${selectedColor.borderColor};\n`+ |
||||
|
`${mainUncheckedColorVarPrefix}${this.state}: ${unselectedColor.mainColor};\n`+ |
||||
|
`${backgroundUncheckedColorVarPrefix}${this.state}: ${unselectedColor.backgroundColor};\n`+ |
||||
|
`${borderUncheckedColorVarPrefix}${this.state}: ${unselectedColor.borderColor};`; |
||||
|
} |
||||
|
|
||||
|
private getColors(appearance: SegmentedButtonStyles) { |
||||
|
let mainColor = this.getMainColor(appearance); |
||||
|
let backgroundColor = this.getBackgroundColor(appearance); |
||||
|
let borderColor = this.getBorderColor(); |
||||
|
const stateCustomStyle = appearance.customStyle[this.state]; |
||||
|
if (stateCustomStyle?.overrideMainColor && stateCustomStyle?.mainColor) { |
||||
|
mainColor = stateCustomStyle.mainColor; |
||||
|
} |
||||
|
if (stateCustomStyle?.overrideBackgroundColor && stateCustomStyle?.backgroundColor) { |
||||
|
backgroundColor = stateCustomStyle.backgroundColor; |
||||
|
} |
||||
|
if (stateCustomStyle?.overrideBorderColor && stateCustomStyle?.borderColor) { |
||||
|
borderColor = stateCustomStyle.borderColor; |
||||
|
} |
||||
|
return { |
||||
|
mainColor, |
||||
|
backgroundColor, |
||||
|
borderColor |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected abstract get state(): WidgetButtonToggleState; |
||||
|
|
||||
|
protected getMainColor(appearance: SegmentedButtonStyles): string { |
||||
|
return appearance.mainColor || defaultMainColor; |
||||
|
} |
||||
|
|
||||
|
protected getBackgroundColor(appearance: SegmentedButtonStyles): string { |
||||
|
return appearance.backgroundColor || defaultBackgroundColor; |
||||
|
} |
||||
|
|
||||
|
protected getBorderColor(): string { |
||||
|
return defaultBorderColor; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class EnabledButtonStateCssGenerator extends ButtonToggleStateCssGenerator { |
||||
|
|
||||
|
protected get state(): WidgetButtonToggleState { |
||||
|
return WidgetButtonToggleState.enabled; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class HoveredButtonStateCssGenerator extends ButtonToggleStateCssGenerator { |
||||
|
|
||||
|
protected get state(): WidgetButtonToggleState { |
||||
|
return WidgetButtonToggleState.hovered; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class DisabledButtonStateCssGenerator extends ButtonToggleStateCssGenerator { |
||||
|
|
||||
|
protected get state(): WidgetButtonToggleState { |
||||
|
return WidgetButtonToggleState.disabled; |
||||
|
} |
||||
|
|
||||
|
protected getMainColor(): string { |
||||
|
return defaultMainColorDisabled; |
||||
|
} |
||||
|
|
||||
|
protected getBackgroundColor(): string { |
||||
|
return defaultBackgroundColorDisabled; |
||||
|
} |
||||
|
|
||||
|
protected getBorderColor(): string { |
||||
|
return defaultBorderColorDisabled; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const buttonToggleStateCssGeneratorsMap = new Map<WidgetButtonToggleState, ButtonToggleStateCssGenerator>( |
||||
|
[ |
||||
|
[WidgetButtonToggleState.enabled, new EnabledButtonStateCssGenerator()], |
||||
|
[WidgetButtonToggleState.hovered, new HoveredButtonStateCssGenerator()], |
||||
|
[WidgetButtonToggleState.disabled, new DisabledButtonStateCssGenerator()] |
||||
|
] |
||||
|
); |
||||
|
|
||||
|
const widgetButtonCssSelector = '.mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header'; |
||||
|
|
||||
|
export const generateWidgetButtonToggleAppearanceCss = (selectedAppearance: SegmentedButtonStyles, unselectedAppearance: SegmentedButtonStyles): string => { |
||||
|
let statesCss = ''; |
||||
|
for (const state of widgetButtonToggleStates) { |
||||
|
const generator = buttonToggleStateCssGeneratorsMap.get(state); |
||||
|
statesCss += `\n${generator.generateStateCss(selectedAppearance, unselectedAppearance)}`; |
||||
|
} |
||||
|
return `${widgetButtonCssSelector} {\n`+ |
||||
|
`${statesCss}\n`+ |
||||
|
`}`; |
||||
|
}; |
||||
|
|
||||
|
export const generateWidgetButtonToggleBorderLayout = (layout: SegmentedButtonLayout): string => { |
||||
|
return `${widgetButtonCssSelector} {\n`+ |
||||
|
`--tb-widget-button-toggle-border-radius: ${segmentedButtonLayoutBorder.get(layout)}\n`+ |
||||
|
`}`; |
||||
|
}; |
||||
@ -0,0 +1,33 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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-segmented-button-widget" [class.no-pointer-events]="ctx.isEdit"> |
||||
|
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> |
||||
|
<div class="tb-segmented-button-container"> |
||||
|
<tb-widget-button-toggle |
||||
|
class="tb-widget-button-toggle" |
||||
|
[appearance]="appearance" |
||||
|
[autoScale]="autoScale" |
||||
|
[disabled]="disabled || (loading$ | async)" |
||||
|
[value]="value" |
||||
|
[ctx]="ctx" |
||||
|
(clicked)="onClick($event)"> |
||||
|
</tb-widget-button-toggle> |
||||
|
</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,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
.tb-segmented-button-widget { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
position: relative; |
||||
|
|
||||
|
.tb-segmented-button-container { |
||||
|
height: 100%; |
||||
|
|
||||
|
.tb-widget-button-toggle { |
||||
|
display: block; |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
> div.tb-segmented-button-widget-title-panel { |
||||
|
position: absolute; |
||||
|
top: 12px; |
||||
|
left: 12px; |
||||
|
right: 12px; |
||||
|
z-index: 2; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
///
|
||||
|
/// 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, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; |
||||
|
import { BasicActionWidgetComponent } from '@home/components/widget/lib/action/action-widget.models'; |
||||
|
import { ImagePipe } from '@shared/pipe/image.pipe'; |
||||
|
import { DomSanitizer } from '@angular/platform-browser'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { |
||||
|
ButtonToggleAppearance, |
||||
|
segmentedButtonDefaultSettings, |
||||
|
SegmentedButtonWidgetSettings |
||||
|
} from '@home/components/widget/lib/button/segmented-button-widget.models'; |
||||
|
import { ComponentStyle } from '@shared/models/widget-settings.models'; |
||||
|
import { MatButtonToggleChange } from '@angular/material/button-toggle'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-two-segment-button-widget', |
||||
|
templateUrl: './two-segment-button-widget.component.html', |
||||
|
styleUrls: ['../action/action-widget.scss', './two-segment-button-widget.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class TwoSegmentButtonWidgetComponent extends |
||||
|
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { |
||||
|
|
||||
|
settings: SegmentedButtonWidgetSettings; |
||||
|
|
||||
|
overlayStyle: ComponentStyle = {}; |
||||
|
|
||||
|
value = false; |
||||
|
disabled = false; |
||||
|
|
||||
|
autoScale: boolean; |
||||
|
appearance: ButtonToggleAppearance; |
||||
|
|
||||
|
constructor(protected imagePipe: ImagePipe, |
||||
|
protected sanitizer: DomSanitizer, |
||||
|
protected cd: ChangeDetectorRef) { |
||||
|
super(cd); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
super.ngOnInit(); |
||||
|
this.settings = {...segmentedButtonDefaultSettings, ...this.ctx.settings}; |
||||
|
|
||||
|
this.autoScale = this.settings.appearance.autoScale; |
||||
|
|
||||
|
const getInitialStateSettings = |
||||
|
{...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')}; |
||||
|
this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, { |
||||
|
next: (value) => this.onValue(value) |
||||
|
}); |
||||
|
|
||||
|
const disabledStateSettings = |
||||
|
{...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.button-state.disabled-state')}; |
||||
|
this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { |
||||
|
next: (value) => this.onDisabled(value) |
||||
|
}); |
||||
|
|
||||
|
this.appearance = this.settings.appearance; |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
super.ngAfterViewInit(); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
super.ngOnDestroy(); |
||||
|
} |
||||
|
|
||||
|
public onInit() { |
||||
|
super.onInit(); |
||||
|
const borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
||||
|
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}}; |
||||
|
this.cd.detectChanges(); |
||||
|
} |
||||
|
|
||||
|
private onValue(value: boolean): void { |
||||
|
const newValue = !!value; |
||||
|
if (this.value !== newValue) { |
||||
|
this.value = newValue; |
||||
|
this.appearance = this.settings.appearance; |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private onDisabled(value: boolean): void { |
||||
|
const newDisabled = !!value; |
||||
|
if (this.disabled !== newDisabled) { |
||||
|
this.disabled = newDisabled; |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public onClick(_$event: MatButtonToggleChange) { |
||||
|
if (!this.ctx.isEdit && !this.ctx.isPreview) { |
||||
|
if (_$event.value) { |
||||
|
this.ctx.actionsApi.onWidgetAction(event, this.settings.leftButtonClick); |
||||
|
} else { |
||||
|
this.ctx.actionsApi.onWidgetAction(event, this.settings.rightButtonClick); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,263 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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]="segmentedButtonWidgetSettingsForm"> |
||||
|
<div class="tb-form-panel"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.action-button.behavior</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.selected-state-hint' | translate}}" translate>widgets.button-state.selected-state</div> |
||||
|
<tb-get-value-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.button-state.selected-state' | translate }}" |
||||
|
[valueType]="valueType.BOOLEAN" |
||||
|
trueLabel="{{ 'widgets.button-state.first' | translate }}" |
||||
|
falseLabel="{{ 'widgets.button-state.right' | translate }}" |
||||
|
stateLabel="{{ 'widgets.button-state.initial' | translate }}" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="initialState"></tb-get-value-action-settings> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.action-button.first-button-click-hint' | translate}}" translate>widgets.action-button.first-button-click</div> |
||||
|
<tb-widget-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.action-button.first-button-click' | translate }}" |
||||
|
[callbacks]="callbacks" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="leftButtonClick"> |
||||
|
</tb-widget-action-settings> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.action-button.right-button-click-hint' | translate}}" translate>widgets.action-button.second-button-click</div> |
||||
|
<tb-widget-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.action-button.second-button-click' | translate }}" |
||||
|
[callbacks]="callbacks" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="rightButtonClick"> |
||||
|
</tb-widget-action-settings> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-row"> |
||||
|
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.button-state.disabled-state-hint' | translate}}" translate>widgets.button-state.disabled-state</div> |
||||
|
<tb-get-value-action-settings class="flex-1" |
||||
|
panelTitle="{{ 'widgets.button-state.disabled-state' | translate }}" |
||||
|
[valueType]="valueType.BOOLEAN" |
||||
|
stateLabel="{{ 'widgets.button-state.disabled' | translate }}" |
||||
|
[aliasController]="aliasController" |
||||
|
[targetDevice]="targetDevice" |
||||
|
[widgetType]="widgetType" |
||||
|
formControlName="disabledState"></tb-get-value-action-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel" formGroupName="appearance"> |
||||
|
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
||||
|
|
||||
|
<tb-image-cards-select rowHeight="3:1" |
||||
|
|
||||
|
[cols]="{columns: 2, |
||||
|
breakpoints: { |
||||
|
'lt-sm': 1 |
||||
|
}}" |
||||
|
label="{{ 'widgets.segmented-button.layout' | translate }}" formControlName="layout"> |
||||
|
<tb-image-cards-select-option *ngFor="let layout of segmentedButtonLayouts" |
||||
|
[value]="layout" |
||||
|
[image]="segmentedButtonLayoutImageMap.get(layout)"> |
||||
|
{{ segmentedButtonLayoutTranslationMap.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.button.auto-scale' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<div translate>widgets.segmented-button.card-border</div> |
||||
|
<div class="flex flex-row items-center gap-2"> |
||||
|
<mat-form-field appearance="outline" class="number" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="cardBorder" type="number" min="0" step="1" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
<div matSuffix class="lt-md:!hidden">px</div> |
||||
|
</mat-form-field> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="cardBorderColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-panel" formGroupName="appearance"> |
||||
|
<div class="flex flex-row items-center justify-between"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.segmented-button.button-appearance</div> |
||||
|
<tb-toggle-select [(ngModel)]="segmentedButtonAppearanceType" [ngModelOptions]="{standalone: true}"> |
||||
|
<tb-toggle-option value="first">{{ 'widgets.segmented-button.first' | translate }}</tb-toggle-option> |
||||
|
<tb-toggle-option value="second">{{ 'widgets.segmented-button.second' | translate }}</tb-toggle-option> |
||||
|
</tb-toggle-select> |
||||
|
</div> |
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="leftAppearance" [class.!hidden]="segmentedButtonAppearanceType !== 'first'"> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel"> |
||||
|
{{ 'widgets.button.label' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-font-settings formControlName="labelFont" |
||||
|
[previewText]="segmentedButtonWidgetSettingsForm.get('appearance.leftAppearance.label').value"> |
||||
|
</tb-font-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> |
||||
|
{{ 'widgets.button.icon' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
iconClearButton |
||||
|
formControlName="icon"> |
||||
|
</tb-material-icon-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="rightAppearance" [class.!hidden]="segmentedButtonAppearanceType !== 'second'"> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel"> |
||||
|
{{ 'widgets.button.label' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-font-settings formControlName="labelFont" |
||||
|
[previewText]="segmentedButtonWidgetSettingsForm.get('appearance.rightAppearance.label').value"> |
||||
|
</tb-font-settings> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-row"> |
||||
|
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon"> |
||||
|
{{ 'widgets.button.icon' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<div class="flex flex-1 flex-row items-center justify-start gap-2"> |
||||
|
<mat-form-field appearance="outline" class="number flex" subscriptSizing="dynamic"> |
||||
|
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}"> |
||||
|
</mat-form-field> |
||||
|
<tb-css-unit-select class="flex-1" formControlName="iconSizeUnit"></tb-css-unit-select> |
||||
|
<tb-material-icon-select asBoxInput |
||||
|
iconClearButton |
||||
|
formControlName="icon"> |
||||
|
</tb-material-icon-select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="tb-form-panel" formGroupName="appearance"> |
||||
|
<div class="flex flex-row items-center justify-between"> |
||||
|
<div class="tb-form-panel-title" translate>widgets.segmented-button.color-styles</div> |
||||
|
<tb-toggle-select [(ngModel)]="segmentedButtonColorStylesType" [ngModelOptions]="{standalone: true}"> |
||||
|
<tb-toggle-option value="selected">{{ 'widgets.segmented-button.selected' | translate }}</tb-toggle-option> |
||||
|
<tb-toggle-option value="unselected">{{ 'widgets.segmented-button.unselected' | translate }}</tb-toggle-option> |
||||
|
</tb-toggle-select> |
||||
|
</div> |
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="selectedStyle" [class.!hidden]="segmentedButtonColorStylesType !== 'selected'"> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.button.color-palette' | translate }}</div> |
||||
|
<div class="flex flex-row items-center justify-start gap-3"> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel stroked" formGroupName="customStyle"> |
||||
|
<mat-expansion-panel class="tb-settings"> |
||||
|
<mat-expansion-panel-header class="flex flex-row flex-wrap"> |
||||
|
<mat-panel-title> |
||||
|
<div class="tb-form-panel-title" translate>widgets.button.custom-styles</div> |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<div class="tb-form-row space-between" *ngFor="let state of widgetButtonToggleStates"> |
||||
|
<div>{{ widgetButtonToggleStatesTranslationsMap.get(state) | translate }}</div> |
||||
|
<tb-widget-button-toggle-custom-style |
||||
|
[value]="true" |
||||
|
[state]="state" |
||||
|
[appearance]="segmentedButtonWidgetSettingsForm.get('appearance').value" |
||||
|
[autoScale]="true" |
||||
|
[formControlName]="state"> |
||||
|
</tb-widget-button-toggle-custom-style> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel no-border no-padding" formGroupName="unselectedStyle" [class.!hidden]="segmentedButtonColorStylesType !== 'unselected'"> |
||||
|
<div class="tb-form-row space-between column-xs"> |
||||
|
<div>{{ 'widgets.button.color-palette' | translate }}</div> |
||||
|
<div class="flex flex-row items-center justify-start gap-3"> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.main</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<mat-divider vertical></mat-divider> |
||||
|
<div class="flex flex-row items-center justify-start gap-2"> |
||||
|
<div translate>widgets.button.background</div> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-form-panel stroked" formGroupName="customStyle"> |
||||
|
<mat-expansion-panel class="tb-settings"> |
||||
|
<mat-expansion-panel-header class="flex flex-row flex-wrap"> |
||||
|
<mat-panel-title> |
||||
|
<div class="tb-form-panel-title" translate>widgets.button.custom-styles</div> |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<div class="tb-form-row space-between" *ngFor="let state of widgetButtonToggleStates"> |
||||
|
<div>{{ widgetButtonToggleStatesTranslationsMap.get(state) | translate }}</div> |
||||
|
<tb-widget-button-toggle-custom-style |
||||
|
[value]="false" |
||||
|
[state]="state" |
||||
|
[appearance]="segmentedButtonWidgetSettingsForm.get('appearance').value" |
||||
|
[autoScale]="true" |
||||
|
[formControlName]="state"> |
||||
|
</tb-widget-button-toggle-custom-style> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</ng-container> |
||||
@ -0,0 +1,134 @@ |
|||||
|
///
|
||||
|
/// 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 } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { ValueType } from '@shared/models/constants'; |
||||
|
import { getTargetDeviceFromDatasources } from '@shared/models/widget-settings.models'; |
||||
|
import { |
||||
|
SegmentedButtonAppearanceType, |
||||
|
SegmentedButtonColorStylesType, |
||||
|
segmentedButtonDefaultSettings, |
||||
|
segmentedButtonLayoutBorder, |
||||
|
segmentedButtonLayoutImages, |
||||
|
segmentedButtonLayouts, |
||||
|
segmentedButtonLayoutTranslations, |
||||
|
WidgetButtonToggleState, |
||||
|
widgetButtonToggleStatesTranslations |
||||
|
} from '@home/components/widget/lib/button/segmented-button-widget.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-segmented-button-widget-settings', |
||||
|
templateUrl: './segmented-button-widget-settings.component.html', |
||||
|
styleUrls: ['./../widget-settings.scss'] |
||||
|
}) |
||||
|
export class SegmentedButtonWidgetSettingsComponent extends WidgetSettingsComponent { |
||||
|
|
||||
|
get targetDevice(): TargetDevice { |
||||
|
const datasources = this.widgetConfig?.config?.datasources; |
||||
|
return getTargetDeviceFromDatasources(datasources); |
||||
|
} |
||||
|
|
||||
|
get widgetType(): widgetType { |
||||
|
return this.widgetConfig?.widgetType; |
||||
|
} |
||||
|
get borderRadius(): string { |
||||
|
return this.segmentedButtonLayoutBorderMap.get(this.segmentedButtonWidgetSettingsForm.get('appearance.layout').value); |
||||
|
} |
||||
|
|
||||
|
segmentedButtonAppearanceType: SegmentedButtonAppearanceType = 'first'; |
||||
|
segmentedButtonColorStylesType: SegmentedButtonColorStylesType = 'selected'; |
||||
|
|
||||
|
widgetButtonToggleStates = Object.keys(WidgetButtonToggleState) as WidgetButtonToggleState[]; |
||||
|
widgetButtonToggleStatesTranslationsMap = widgetButtonToggleStatesTranslations; |
||||
|
|
||||
|
segmentedButtonLayouts = segmentedButtonLayouts; |
||||
|
segmentedButtonLayoutTranslationMap = segmentedButtonLayoutTranslations; |
||||
|
segmentedButtonLayoutImageMap = segmentedButtonLayoutImages; |
||||
|
segmentedButtonLayoutBorderMap = segmentedButtonLayoutBorder; |
||||
|
|
||||
|
valueType = ValueType; |
||||
|
|
||||
|
segmentedButtonWidgetSettingsForm: UntypedFormGroup; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private fb: UntypedFormBuilder) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
protected settingsForm(): UntypedFormGroup { |
||||
|
return this.segmentedButtonWidgetSettingsForm; |
||||
|
} |
||||
|
|
||||
|
protected defaultSettings(): WidgetSettings { |
||||
|
return {...segmentedButtonDefaultSettings}; |
||||
|
} |
||||
|
|
||||
|
protected onSettingsSet(settings: WidgetSettings) { |
||||
|
this.segmentedButtonWidgetSettingsForm = this.fb.group({ |
||||
|
initialState: [settings.initialState, []], |
||||
|
leftButtonClick: [settings.leftButtonClick, []], |
||||
|
rightButtonClick: [settings.rightButtonClick, []], |
||||
|
disabledState: [settings.disabledState, []], |
||||
|
|
||||
|
appearance: this.fb.group({ |
||||
|
layout: [settings.appearance.layout, []], |
||||
|
autoScale: [settings.appearance.autoScale, []], |
||||
|
cardBorder: [settings.appearance.cardBorder, []], |
||||
|
cardBorderColor: [settings.appearance.cardBorderColor, []], |
||||
|
leftAppearance: this.fb.group({ |
||||
|
showLabel: [settings.appearance.leftAppearance.showLabel, []], |
||||
|
label: [settings.appearance.leftAppearance.label, []], |
||||
|
labelFont: [settings.appearance.leftAppearance.labelFont, []], |
||||
|
showIcon: [settings.appearance.leftAppearance.showIcon, []], |
||||
|
icon: [settings.appearance.leftAppearance.icon, []], |
||||
|
iconSize: [settings.appearance.leftAppearance.iconSize, []], |
||||
|
iconSizeUnit: [settings.appearance.leftAppearance.iconSizeUnit, []], |
||||
|
}), |
||||
|
rightAppearance: this.fb.group({ |
||||
|
showLabel: [settings.appearance.rightAppearance.showLabel, []], |
||||
|
label: [settings.appearance.rightAppearance.label, []], |
||||
|
labelFont: [settings.appearance.rightAppearance.labelFont, []], |
||||
|
showIcon: [settings.appearance.rightAppearance.showIcon, []], |
||||
|
icon: [settings.appearance.rightAppearance.icon, []], |
||||
|
iconSize: [settings.appearance.rightAppearance.iconSize, []], |
||||
|
iconSizeUnit: [settings.appearance.rightAppearance.iconSizeUnit, []], |
||||
|
}), |
||||
|
selectedStyle: this.fb.group({ |
||||
|
mainColor: [settings.appearance.selectedStyle.mainColor, []], |
||||
|
backgroundColor: [settings.appearance.selectedStyle.backgroundColor, []], |
||||
|
customStyle: this.fb.group({ |
||||
|
enabled: [settings.appearance.selectedStyle.customStyle.enabled, []], |
||||
|
hovered: [settings.appearance.selectedStyle.customStyle.hovered, []], |
||||
|
disabled: [settings.appearance.selectedStyle.customStyle.disabled, []], |
||||
|
}) |
||||
|
}), |
||||
|
unselectedStyle: this.fb.group({ |
||||
|
mainColor: [settings.appearance.unselectedStyle.mainColor, []], |
||||
|
backgroundColor: [settings.appearance.unselectedStyle.backgroundColor, []], |
||||
|
customStyle: this.fb.group({ |
||||
|
enabled: [settings.appearance.unselectedStyle.customStyle.enabled, []], |
||||
|
hovered: [settings.appearance.unselectedStyle.customStyle.hovered, []], |
||||
|
disabled: [settings.appearance.unselectedStyle.customStyle.disabled, []], |
||||
|
}) |
||||
|
}), |
||||
|
}) |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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-widget-button-custom-style-panel" [formGroup]="customStyleFormGroup"> |
||||
|
<div class="tb-widget-button-custom-style-title">{{ widgetButtonToggleStatesTranslationsMap.get(state) | translate }}</div> |
||||
|
<div class="tb-widget-button-custom-style-panel-content"> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="overrideMainColor"> |
||||
|
{{ 'widgets.button.main' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="mainColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="overrideBackgroundColor"> |
||||
|
{{ 'widgets.button.background' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="backgroundColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<div class="tb-form-row space-between"> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="overrideBorderColor"> |
||||
|
{{ 'widgets.button.border' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<tb-color-input asBoxInput |
||||
|
formControlName="borderColor"> |
||||
|
</tb-color-input> |
||||
|
</div> |
||||
|
<div class="tb-widget-button-custom-style-preview"> |
||||
|
<div class="tb-widget-button-custom-style-preview-title" translate> |
||||
|
widgets.button.preview |
||||
|
</div> |
||||
|
<tb-widget-button-toggle |
||||
|
#widgetButtonTogglePreview |
||||
|
[appearance]="previewAppearance" |
||||
|
[autoScale]="autoScale" |
||||
|
[value]="value" |
||||
|
disableEvents |
||||
|
[hovered]="state === WidgetButtonToggleState.hovered" |
||||
|
[disabled]="state === WidgetButtonToggleState.disabled"> |
||||
|
</tb-widget-button-toggle> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-widget-button-custom-style-panel-buttons"> |
||||
|
<button *ngIf="copyFromStates?.length" |
||||
|
#copyStyleButton |
||||
|
class="tb-nowrap" |
||||
|
mat-stroked-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
[matMenuTriggerFor]="styleSourcesMenu" [matMenuTriggerData]="{menuWidth: copyStyleButton._elementRef.nativeElement.clientWidth}"> |
||||
|
{{ 'widgets.button.copy-style-from' | translate }} |
||||
|
</button> |
||||
|
<mat-menu #styleSourcesMenu="matMenu"> |
||||
|
<ng-template matMenuContent let-menuWidth="menuWidth"> |
||||
|
<div [style.min-width.px]="menuWidth"> |
||||
|
<button mat-menu-item *ngFor="let state of copyFromStates" (click)="copyStyle(state)">{{ widgetButtonToggleStatesTranslationsMap.get(state) | translate }}</button> |
||||
|
</div> |
||||
|
</ng-template> |
||||
|
</mat-menu> |
||||
|
<span class="flex-1"></span> |
||||
|
<button mat-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="cancel()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="applyCustomStyle()" |
||||
|
[disabled]="customStyleFormGroup.invalid || !customStyleFormGroup.dirty"> |
||||
|
{{ 'action.apply' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,68 @@ |
|||||
|
/** |
||||
|
* 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 '../../../../../../../../../scss/constants'; |
||||
|
|
||||
|
.tb-widget-button-custom-style-panel { |
||||
|
width: 530px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 16px; |
||||
|
@media #{$mat-lt-md} { |
||||
|
width: 90vw; |
||||
|
} |
||||
|
.tb-widget-button-custom-style-panel-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 16px; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
.tb-widget-button-custom-style-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
line-height: 24px; |
||||
|
letter-spacing: 0.25px; |
||||
|
color: rgba(0, 0, 0, 0.87); |
||||
|
} |
||||
|
.tb-widget-button-custom-style-preview { |
||||
|
flex: 1; |
||||
|
background: rgba(0, 0, 0, 0.04); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
padding: 12px 16px 24px 16px; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
.tb-widget-button-custom-style-preview-title { |
||||
|
align-self: stretch; |
||||
|
font-size: 16px; |
||||
|
font-style: normal; |
||||
|
font-weight: 500; |
||||
|
line-height: 24px; |
||||
|
color: rgba(0, 0, 0, 0.38); |
||||
|
} |
||||
|
tb-widget-button { |
||||
|
width: 200px; |
||||
|
height: 60px; |
||||
|
} |
||||
|
} |
||||
|
.tb-widget-button-custom-style-panel-buttons { |
||||
|
height: 40px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
gap: 16px; |
||||
|
justify-content: flex-end; |
||||
|
align-items: flex-end; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,210 @@ |
|||||
|
///
|
||||
|
/// 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 { |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
EventEmitter, |
||||
|
Input, |
||||
|
OnInit, |
||||
|
Output, |
||||
|
ViewChild, |
||||
|
ViewEncapsulation |
||||
|
} from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { TbPopoverComponent } from '@shared/components/popover.component'; |
||||
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { |
||||
|
defaultBackgroundColorDisabled, |
||||
|
defaultMainColorDisabled |
||||
|
} from '@shared/components/button/widget-button.models'; |
||||
|
import { merge } from 'rxjs'; |
||||
|
import { deepClone } from '@core/utils'; |
||||
|
import { WidgetButtonToggleComponent } from '@shared/components/button/widget-button-toggle.component'; |
||||
|
import { |
||||
|
ButtonToggleAppearance, |
||||
|
SegmentedButtonStyles, |
||||
|
WidgetButtonToggleCustomStyle, |
||||
|
WidgetButtonToggleState, |
||||
|
widgetButtonToggleStates, |
||||
|
widgetButtonToggleStatesTranslations |
||||
|
} from '@home/components/widget/lib/button/segmented-button-widget.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-widget-button-toggle-custom-style-panel', |
||||
|
templateUrl: './widget-button-toggle-custom-style-panel.component.html', |
||||
|
providers: [], |
||||
|
styleUrls: ['./widget-button-toggle-custom-style-panel.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class WidgetButtonToggleCustomStylePanelComponent extends PageComponent implements OnInit { |
||||
|
|
||||
|
@ViewChild('widgetButtonTogglePreview') |
||||
|
widgetButtonTogglePreview: WidgetButtonToggleComponent; |
||||
|
|
||||
|
@Input() |
||||
|
appearance: ButtonToggleAppearance; |
||||
|
|
||||
|
@Input() |
||||
|
value: boolean = false; |
||||
|
|
||||
|
@Input() |
||||
|
borderRadius: string; |
||||
|
|
||||
|
@Input() |
||||
|
autoScale: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
state: WidgetButtonToggleState; |
||||
|
|
||||
|
@Input() |
||||
|
customStyle: WidgetButtonToggleCustomStyle; |
||||
|
|
||||
|
private popoverValue: TbPopoverComponent<WidgetButtonToggleCustomStylePanelComponent>; |
||||
|
|
||||
|
@Input() |
||||
|
set popover(popover: TbPopoverComponent<WidgetButtonToggleCustomStylePanelComponent>) { |
||||
|
this.popoverValue = popover; |
||||
|
popover.tbAnimationDone.subscribe(() => { |
||||
|
this.widgetButtonTogglePreview?.validateSize(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
get popover(): TbPopoverComponent<WidgetButtonToggleCustomStylePanelComponent> { |
||||
|
return this.popoverValue; |
||||
|
} |
||||
|
|
||||
|
@Output() |
||||
|
customStyleApplied = new EventEmitter<WidgetButtonToggleCustomStyle>(); |
||||
|
|
||||
|
widgetButtonToggleStatesTranslationsMap = widgetButtonToggleStatesTranslations; |
||||
|
|
||||
|
WidgetButtonToggleState = WidgetButtonToggleState; |
||||
|
|
||||
|
previewAppearance: ButtonToggleAppearance; |
||||
|
|
||||
|
copyFromStates: WidgetButtonToggleState[]; |
||||
|
|
||||
|
customStyleFormGroup: UntypedFormGroup; |
||||
|
|
||||
|
style: SegmentedButtonStyles; |
||||
|
|
||||
|
constructor(private fb: UntypedFormBuilder, |
||||
|
protected store: Store<AppState>, |
||||
|
private cd: ChangeDetectorRef) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.style = this.value ? this.appearance.selectedStyle : this.appearance.unselectedStyle; |
||||
|
this.copyFromStates = widgetButtonToggleStates.filter(state => |
||||
|
state !== this.state && !!this.style.customStyle[state]); |
||||
|
this.customStyleFormGroup = this.fb.group( |
||||
|
{ |
||||
|
overrideMainColor: [false, []], |
||||
|
mainColor: [null, []], |
||||
|
overrideBackgroundColor: [false, []], |
||||
|
backgroundColor: [null, []], |
||||
|
overrideBorderColor: [false, []], |
||||
|
borderColor: [null, []] |
||||
|
} |
||||
|
); |
||||
|
merge(this.customStyleFormGroup.get('overrideMainColor').valueChanges, |
||||
|
this.customStyleFormGroup.get('overrideBackgroundColor').valueChanges, |
||||
|
this.customStyleFormGroup.get('overrideBorderColor').valueChanges) |
||||
|
.subscribe(() => { |
||||
|
this.updateValidators(); |
||||
|
}); |
||||
|
this.customStyleFormGroup.valueChanges.subscribe(() => { |
||||
|
this.updatePreviewAppearance(); |
||||
|
}); |
||||
|
this.setStyle(this.customStyle); |
||||
|
} |
||||
|
|
||||
|
copyStyle(state: WidgetButtonToggleState) { |
||||
|
this.customStyle = deepClone(this.style[state]); |
||||
|
this.setStyle(this.customStyle); |
||||
|
this.customStyleFormGroup.markAsDirty(); |
||||
|
} |
||||
|
|
||||
|
cancel() { |
||||
|
this.popover?.hide(); |
||||
|
} |
||||
|
|
||||
|
applyCustomStyle() { |
||||
|
const customStyle: SegmentedButtonStyles = this.customStyleFormGroup.value; |
||||
|
this.customStyleApplied.emit(customStyle); |
||||
|
} |
||||
|
|
||||
|
private setStyle(customStyle?: WidgetButtonToggleCustomStyle): void { |
||||
|
let mainColor = this.state === WidgetButtonToggleState.disabled ? defaultMainColorDisabled : this.style.mainColor; |
||||
|
if (customStyle?.overrideMainColor) { |
||||
|
mainColor = customStyle?.mainColor; |
||||
|
} |
||||
|
let backgroundColor = this.state === WidgetButtonToggleState.disabled ? defaultBackgroundColorDisabled : this.style.backgroundColor; |
||||
|
if (customStyle?.overrideBackgroundColor) { |
||||
|
backgroundColor = customStyle?.backgroundColor; |
||||
|
} |
||||
|
let borderColor = this.state === WidgetButtonToggleState.disabled ? defaultBackgroundColorDisabled : this.style.backgroundColor; |
||||
|
if (customStyle?.overrideBorderColor) { |
||||
|
borderColor = customStyle?.borderColor; |
||||
|
} |
||||
|
this.customStyleFormGroup.patchValue({ |
||||
|
overrideMainColor: customStyle?.overrideMainColor, |
||||
|
mainColor, |
||||
|
overrideBackgroundColor: customStyle?.overrideBackgroundColor, |
||||
|
backgroundColor, |
||||
|
overrideBorderColor: customStyle?.overrideBorderColor, |
||||
|
borderColor |
||||
|
}, {emitEvent: false}); |
||||
|
this.updateValidators(); |
||||
|
this.updatePreviewAppearance(); |
||||
|
} |
||||
|
|
||||
|
private updateValidators() { |
||||
|
const overrideMainColor: boolean = this.customStyleFormGroup.get('overrideMainColor').value; |
||||
|
const overrideBackgroundColor: boolean = this.customStyleFormGroup.get('overrideBackgroundColor').value; |
||||
|
const overrideBorderColor: boolean = this.customStyleFormGroup.get('overrideBorderColor').value; |
||||
|
|
||||
|
if (overrideMainColor) { |
||||
|
this.customStyleFormGroup.get('mainColor').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.customStyleFormGroup.get('mainColor').disable({emitEvent: false}); |
||||
|
} |
||||
|
if (overrideBackgroundColor) { |
||||
|
this.customStyleFormGroup.get('backgroundColor').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.customStyleFormGroup.get('backgroundColor').disable({emitEvent: false}); |
||||
|
} |
||||
|
if (overrideBorderColor) { |
||||
|
this.customStyleFormGroup.get('borderColor').enable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.customStyleFormGroup.get('borderColor').disable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updatePreviewAppearance() { |
||||
|
this.previewAppearance = deepClone(this.appearance); |
||||
|
if (this.value) { |
||||
|
this.previewAppearance.selectedStyle.customStyle[this.state] = this.customStyleFormGroup.value; |
||||
|
} else { |
||||
|
this.previewAppearance.unselectedStyle.customStyle[this.state] = this.customStyleFormGroup.value; |
||||
|
} |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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-widget-button-custom-style"> |
||||
|
<div class="tb-widget-button-preview-panel tb-primary-fill"> |
||||
|
<tb-widget-button-toggle |
||||
|
[value]="value" |
||||
|
[appearance]="previewAppearance" |
||||
|
[borderRadius]="borderRadius" |
||||
|
[autoScale]="autoScale" |
||||
|
disableEvents |
||||
|
[hovered]="state === widgetButtonToggleState.hovered" |
||||
|
[disabled]="state === widgetButtonToggleState.disabled"> |
||||
|
</tb-widget-button-toggle> |
||||
|
<button *ngIf="modelValue" |
||||
|
mat-icon-button |
||||
|
[matTooltip]="'widgets.button.clear-style' | translate" |
||||
|
matTooltipPosition="above" |
||||
|
class="tb-mat-32" |
||||
|
(click)="clearStyle()"> |
||||
|
<tb-icon>mdi:broom</tb-icon> |
||||
|
</button> |
||||
|
</div> |
||||
|
<button mat-icon-button |
||||
|
class="tb-mat-32" |
||||
|
#matIconButton |
||||
|
(click)="openButtonCustomStylePopup($event, matIconButton)"> |
||||
|
<mat-icon>edit</mat-icon> |
||||
|
</button> |
||||
|
</div> |
||||
@ -0,0 +1,46 @@ |
|||||
|
/** |
||||
|
* 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 '../../../../../../../../../scss/constants'; |
||||
|
|
||||
|
.tb-widget-button-custom-style { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
button.mat-mdc-icon-button { |
||||
|
color: rgba(0,0,0,0.56); |
||||
|
} |
||||
|
.tb-widget-button-preview-panel { |
||||
|
width: 148px; |
||||
|
height: 48px; |
||||
|
padding: 8px 12px; |
||||
|
border-radius: 4px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
align-items: center; |
||||
|
justify-content: space-between; |
||||
|
tb-widget-button-toggle { |
||||
|
width: 84px; |
||||
|
height: 100%; |
||||
|
} |
||||
|
@media #{$mat-gt-xs} { |
||||
|
width: 188px; |
||||
|
tb-widget-button-toggle { |
||||
|
width: 124px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,169 @@ |
|||||
|
///
|
||||
|
/// 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 { |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
forwardRef, |
||||
|
Input, |
||||
|
OnChanges, |
||||
|
OnInit, |
||||
|
Renderer2, |
||||
|
SimpleChanges, |
||||
|
ViewContainerRef, |
||||
|
ViewEncapsulation |
||||
|
} from '@angular/core'; |
||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { TbPopoverService } from '@shared/components/popover.service'; |
||||
|
import { MatIconButton } from '@angular/material/button'; |
||||
|
import { deepClone } from '@core/utils'; |
||||
|
import { |
||||
|
ButtonToggleAppearance, |
||||
|
WidgetButtonToggleCustomStyle, |
||||
|
WidgetButtonToggleState |
||||
|
} from '@home/components/widget/lib/button/segmented-button-widget.models'; |
||||
|
import { |
||||
|
WidgetButtonToggleCustomStylePanelComponent |
||||
|
} from '@home/components/widget/lib/settings/common/button/widget-button-toggle-custom-style-panel.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-widget-button-toggle-custom-style', |
||||
|
templateUrl: './widget-button-toggle-custom-style.component.html', |
||||
|
styleUrls: ['./widget-button-toggle-custom-style.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => WidgetButtonToggleCustomStyleComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class WidgetButtonToggleCustomStyleComponent implements OnInit, OnChanges, ControlValueAccessor { |
||||
|
|
||||
|
@Input() |
||||
|
disabled = false; |
||||
|
|
||||
|
@Input() |
||||
|
value = false; |
||||
|
|
||||
|
@Input() |
||||
|
appearance: ButtonToggleAppearance; |
||||
|
|
||||
|
@Input() |
||||
|
borderRadius: string; |
||||
|
|
||||
|
@Input() |
||||
|
autoScale: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
state: WidgetButtonToggleState; |
||||
|
|
||||
|
widgetButtonToggleState = WidgetButtonToggleState; |
||||
|
|
||||
|
modelValue: WidgetButtonToggleCustomStyle; |
||||
|
|
||||
|
previewAppearance: ButtonToggleAppearance; |
||||
|
|
||||
|
private propagateChange = (_val: any) => {}; |
||||
|
|
||||
|
constructor(private popoverService: TbPopoverService, |
||||
|
private renderer: Renderer2, |
||||
|
private viewContainerRef: ViewContainerRef, |
||||
|
private cd: ChangeDetectorRef) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.updatePreviewAppearance(); |
||||
|
} |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges): void { |
||||
|
for (const propName of Object.keys(changes)) { |
||||
|
const change = changes[propName]; |
||||
|
if (!change.firstChange) { |
||||
|
if (propName === 'appearance') { |
||||
|
this.updatePreviewAppearance(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
setDisabledState(_isDisabled: boolean): void { |
||||
|
} |
||||
|
|
||||
|
writeValue(value: WidgetButtonToggleCustomStyle): void { |
||||
|
this.modelValue = value; |
||||
|
this.updatePreviewAppearance(); |
||||
|
} |
||||
|
|
||||
|
clearStyle() { |
||||
|
this.updateModel(null); |
||||
|
} |
||||
|
|
||||
|
openButtonCustomStylePopup($event: Event, matButton: MatIconButton) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
const trigger = matButton._elementRef.nativeElement; |
||||
|
if (this.popoverService.hasPopover(trigger)) { |
||||
|
this.popoverService.hidePopover(trigger); |
||||
|
} else { |
||||
|
const ctx: any = { |
||||
|
appearance: this.appearance, |
||||
|
borderRadius: this.borderRadius, |
||||
|
autoScale: this.autoScale, |
||||
|
state: this.state, |
||||
|
value: this.value, |
||||
|
customStyle: this.modelValue |
||||
|
}; |
||||
|
const widgetButtonCustomStylePanelPopover = this.popoverService.displayPopover(trigger, this.renderer, |
||||
|
this.viewContainerRef, WidgetButtonToggleCustomStylePanelComponent, |
||||
|
['leftTopOnly', 'leftOnly', 'leftBottomOnly'], true, null, |
||||
|
ctx, |
||||
|
{}, |
||||
|
{}, {}, true); |
||||
|
widgetButtonCustomStylePanelPopover.tbComponentRef.instance.popover = widgetButtonCustomStylePanelPopover; |
||||
|
widgetButtonCustomStylePanelPopover.tbComponentRef.instance.customStyleApplied.subscribe((customStyle) => { |
||||
|
widgetButtonCustomStylePanelPopover.hide(); |
||||
|
this.updateModel(customStyle); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateModel(value: WidgetButtonToggleCustomStyle): void { |
||||
|
this.modelValue = value; |
||||
|
this.updatePreviewAppearance(); |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
|
||||
|
private updatePreviewAppearance() { |
||||
|
this.previewAppearance = deepClone(this.appearance); |
||||
|
if (this.modelValue) { |
||||
|
if (this.value) { |
||||
|
this.previewAppearance.selectedStyle.customStyle[this.state] = this.modelValue; |
||||
|
} else { |
||||
|
this.previewAppearance.unselectedStyle.customStyle[this.state] = this.modelValue; |
||||
|
} |
||||
|
} |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
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 #toggleGroupContainer class="tb-toggle-container"> |
||||
|
<mat-button-toggle-group #widgetButton |
||||
|
class="tb-toggle-header" |
||||
|
[ngModel]="value" |
||||
|
(change)="clicked.emit($event)" |
||||
|
[style.border-radius]="computedBorderRadius" |
||||
|
[style.border-color]="disabled ? 'rgba(0,0,0,0.38)': computedBorderColor" |
||||
|
[style.border-width]="computedBorderWidth" |
||||
|
[class.tb-pointer-events]="disableEvents" |
||||
|
[class.tb-disabled-state]="disabled" |
||||
|
[class.tb-disabled]="disabled"> |
||||
|
<mat-button-toggle [value]="true" [disabled]="disabled" [class.tb-hover-state]="hovered"> |
||||
|
<div #leftButtonContent class="tb-widget-button-content" *ngIf="appearance.leftAppearance.showIcon || appearance.leftAppearance.showLabel"> |
||||
|
<tb-icon matButtonIcon *ngIf="appearance.leftAppearance.showIcon" [style]="leftIconStyle">{{ appearance.leftAppearance.icon }}</tb-icon> |
||||
|
<span *ngIf="appearance.leftAppearance.showLabel" [style]="leftLabelStyle" class="tb-widget-button-label">{{ leftLabel$ | async }}</span> |
||||
|
</div> |
||||
|
</mat-button-toggle> |
||||
|
<mat-button-toggle [value]="false" [disabled]="disabled" [class.tb-hover-state]="hovered"> |
||||
|
<div #rightButtonContent class="tb-widget-button-content" *ngIf="appearance.rightAppearance.showIcon || appearance.rightAppearance.showLabel"> |
||||
|
<tb-icon matButtonIcon *ngIf="appearance.rightAppearance.showIcon" [style]="rightIconStyle">{{ appearance.rightAppearance.icon }}</tb-icon> |
||||
|
<span *ngIf="appearance.rightAppearance.showLabel" [style]="rightLabelStyle" class="tb-widget-button-label">{{ rightLabel$ | async }}</span> |
||||
|
</div> |
||||
|
</mat-button-toggle> |
||||
|
</mat-button-toggle-group> |
||||
|
</div> |
||||
@ -0,0 +1,189 @@ |
|||||
|
/** |
||||
|
* 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. |
||||
|
*/ |
||||
|
|
||||
|
$defaultBorderRadius: 4px; |
||||
|
|
||||
|
$defaultCheckedMainColor: #FFFFFF; |
||||
|
$defaultCheckedBackgroundColor: #305680; |
||||
|
$defaultCheckedBorderColor: #305680; |
||||
|
|
||||
|
$defaultUncheckedMainColor: rgba(0, 0, 0, 0.76); |
||||
|
$defaultUncheckedBackgroundColor: #E8E8E8; |
||||
|
$defaultUncheckedBorderColor: #E8E8E8; |
||||
|
|
||||
|
$defaultCheckedMainColorDisabled: #FFFFFFCC; |
||||
|
$defaultCheckedBackgroundColorDisabled: rgba(0, 0, 0, 0.12); |
||||
|
$defaultCheckedBorderColorDisabled: rgba(0, 0, 0, 0.12); |
||||
|
|
||||
|
$defaultUncheckedMainColorDisabled: rgba(0, 0, 0, 0.12); |
||||
|
$defaultUncheckedBackgroundColorDisabled: #E8E8E8; |
||||
|
$defaultUncheckedBorderColorDisabled: #E8E8E8; |
||||
|
|
||||
|
$buttonBorderRadius: var(--tb-widget-button-toggle-border-radius, $defaultBorderRadius); |
||||
|
|
||||
|
$mainCheckedColorEnabled: var(--tb-widget-button-toggle-main-checked-color-enabled, $defaultCheckedMainColor); |
||||
|
$backgroundCheckedColorEnabled: var(--tb-widget-button-toggle-background-checked-color-enabled, $defaultCheckedBackgroundColor); |
||||
|
$borderCheckedColorEnabled: var(--tb-widget-button-toggle-border-checked-color-enabled, $defaultCheckedBorderColor); |
||||
|
|
||||
|
$mainUncheckedColorEnabled: var(--tb-widget-button-toggle-main-unchecked-color-enabled, $defaultUncheckedMainColor); |
||||
|
$backgroundUncheckedColorEnabled: var(--tb-widget-button-toggle-background-unchecked-color-enabled, $defaultUncheckedBackgroundColor); |
||||
|
$borderUncheckedColorEnabled: var(--tb-widget-button-toggle-border-unchecked-color-enabled, $defaultUncheckedBorderColor); |
||||
|
|
||||
|
$mainCheckedColorDisabled: var(--tb-widget-button-toggle-main-checked-color-disabled, $defaultCheckedMainColorDisabled); |
||||
|
$backgroundCheckedColorDisabled: var(--tb-widget-button-toggle-background-checked-color-disabled, $defaultCheckedBackgroundColorDisabled); |
||||
|
$borderCheckedColorDisabled: var(--tb-widget-button-toggle-border-checked-color-disabled, $defaultCheckedBorderColorDisabled); |
||||
|
|
||||
|
$mainUncheckedColorDisabled: var(--tb-widget-button-toggle-main-unchecked-color-disabled, $defaultUncheckedMainColorDisabled); |
||||
|
$backgroundUncheckedColorDisabled: var(--tb-widget-button-toggle-background-unchecked-color-disabled, $defaultUncheckedBackgroundColorDisabled); |
||||
|
$borderUncheckedColorDisabled: var(--tb-widget-button-toggle-border-unchecked-color-disabled, $defaultUncheckedBorderColorDisabled); |
||||
|
|
||||
|
$mainCheckedColorHovered: var(--tb-widget-button-toggle-main-checked-color-hovered, $defaultCheckedMainColor); |
||||
|
$backgroundCheckedColorHovered: var(--tb-widget-button-toggle-background-checked-color-hovered, $defaultCheckedBackgroundColor); |
||||
|
$borderCheckedColorHovered: var(--tb-widget-button-toggle-border-checked-color-hovered, $defaultCheckedBorderColor); |
||||
|
|
||||
|
$mainUncheckedColorHovered: var(--tb-widget-button-toggle-main-unchecked-color-hovered, $defaultCheckedMainColor); |
||||
|
$backgroundUncheckedColorHovered: var(--tb-widget-button-toggle-background-unchecked-color-hovered, $defaultCheckedBackgroundColor); |
||||
|
$borderUncheckedColorHovered: var(--tb-widget-button-toggle-border-unchecked-color-hovered, $defaultCheckedBorderColor); |
||||
|
|
||||
|
@mixin _tb-widget-button-styles($main, $background, $boxShadow) { |
||||
|
color: $main; |
||||
|
background-color: $background; |
||||
|
border: 1px solid $boxShadow; |
||||
|
} |
||||
|
|
||||
|
:host { |
||||
|
max-width: 100%; |
||||
|
.tb-toggle-container { |
||||
|
display: block; |
||||
|
height: 100%; |
||||
|
} |
||||
|
.tb-toggle-header { |
||||
|
transition: transform 500ms cubic-bezier(0.35, 0, 0.25, 1); |
||||
|
} |
||||
|
|
||||
|
.tb-widget-button-content { |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
gap: 4px; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
.mat-icon { |
||||
|
margin: 0; |
||||
|
} |
||||
|
span.tb-widget-button-label { |
||||
|
line-height: normal; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { |
||||
|
overflow: visible; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
padding: 3px; |
||||
|
border: 1px solid; |
||||
|
background: transparent; |
||||
|
.mat-button-toggle + .mat-button-toggle { |
||||
|
border-left: none; |
||||
|
} |
||||
|
.mat-button-toggle.mat-button-toggle-appearance-standard { |
||||
|
flex: 1; |
||||
|
width: 50%; |
||||
|
border-radius: $buttonBorderRadius; |
||||
|
|
||||
|
&.tb-hover-state { |
||||
|
.mat-button-toggle-button { |
||||
|
@include _tb-widget-button-styles($mainUncheckedColorHovered, $backgroundUncheckedColorHovered, $borderUncheckedColorHovered); |
||||
|
} |
||||
|
&.mat-button-toggle-checked { |
||||
|
.mat-button-toggle-button { |
||||
|
@include _tb-widget-button-styles($mainCheckedColorHovered, $backgroundCheckedColorHovered, $borderCheckedColorHovered); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-button-toggle-button { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
height: 100%; |
||||
|
border-radius: $buttonBorderRadius; |
||||
|
@include _tb-widget-button-styles($mainUncheckedColorEnabled, $backgroundUncheckedColorEnabled, $borderUncheckedColorEnabled); |
||||
|
.mat-button-toggle-label-content { |
||||
|
.mat-pseudo-checkbox { |
||||
|
display: none; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
&.mat-button-toggle-checked { |
||||
|
.mat-button-toggle-button { |
||||
|
@include _tb-widget-button-styles($mainCheckedColorEnabled, $backgroundCheckedColorEnabled, $borderCheckedColorEnabled); |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
&.tb-pointer-events { |
||||
|
.mat-button-toggle.mat-button-toggle-appearance-standard { |
||||
|
.mat-button-toggle-button { |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
.mat-button-toggle-focus-overlay, .mat-button-toggle-ripple { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
&:not(:disabled):not(.tb-disabled-state):not(.tb-pointer-events) { |
||||
|
.mat-button-toggle.mat-button-toggle-appearance-standard { |
||||
|
&:hover { |
||||
|
.mat-button-toggle-button { |
||||
|
@include _tb-widget-button-styles($mainUncheckedColorHovered, $backgroundUncheckedColorHovered, $borderUncheckedColorHovered); |
||||
|
} |
||||
|
&.mat-button-toggle-checked { |
||||
|
.mat-button-toggle-button { |
||||
|
@include _tb-widget-button-styles($mainCheckedColorHovered, $backgroundCheckedColorHovered, $borderCheckedColorHovered); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
&:disabled, &.tb-disabled-state { |
||||
|
pointer-events: none; |
||||
|
.mat-button-toggle.mat-button-toggle-appearance-standard { |
||||
|
.mat-button-toggle-button { |
||||
|
@include _tb-widget-button-styles($mainUncheckedColorDisabled, $backgroundUncheckedColorDisabled, $borderUncheckedColorDisabled); |
||||
|
} |
||||
|
|
||||
|
&.mat-button-toggle-checked { |
||||
|
.mat-button-toggle-button { |
||||
|
@include _tb-widget-button-styles($mainCheckedColorDisabled, $backgroundCheckedColorDisabled, $borderCheckedColorDisabled); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.mat-button-toggle-focus-overlay, .mat-button-toggle-ripple { |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
.mat-button-toggle-focus-overlay, .mat-button-toggle-ripple { |
||||
|
border-radius: inherit; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,234 @@ |
|||||
|
///
|
||||
|
/// 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, |
||||
|
Component, |
||||
|
ElementRef, |
||||
|
EventEmitter, |
||||
|
Input, |
||||
|
OnChanges, |
||||
|
OnDestroy, |
||||
|
OnInit, |
||||
|
Output, |
||||
|
Renderer2, |
||||
|
SimpleChanges, |
||||
|
ViewChild |
||||
|
} from '@angular/core'; |
||||
|
import { coerceBoolean } from '@shared/decorators/coercion'; |
||||
|
import { ComponentStyle, iconStyle, textStyle, validateCssSize } from '@shared/models/widget-settings.models'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { WidgetContext } from '@home/models/widget-component.models'; |
||||
|
import { isDefinedAndNotNull } from '@core/utils'; |
||||
|
import { |
||||
|
generateWidgetButtonToggleAppearanceCss, |
||||
|
generateWidgetButtonToggleBorderLayout, |
||||
|
segmentedButtonDefaultAppearance, |
||||
|
segmentedButtonLayoutBorder |
||||
|
} from '@home/components/widget/lib/button/segmented-button-widget.models'; |
||||
|
import { MatButtonToggleChange } from '@angular/material/button-toggle'; |
||||
|
|
||||
|
const initialButtonHeight = 60; |
||||
|
const horizontalLayoutPadding = 10; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-widget-button-toggle', |
||||
|
templateUrl: './widget-button-toggle.component.html', |
||||
|
styleUrls: ['./widget-button-toggle.component.scss'] |
||||
|
}) |
||||
|
export class WidgetButtonToggleComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { |
||||
|
|
||||
|
@ViewChild('toggleGroupContainer', {static: false}) |
||||
|
toggleGroupContainer: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('widgetButton', {read: ElementRef}) |
||||
|
widgetButton: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@ViewChild('leftButtonContent', {static: false}) |
||||
|
leftButtonContent: ElementRef<HTMLElement>; |
||||
|
@ViewChild('rightButtonContent', {static: false}) |
||||
|
rightButtonContent: ElementRef<HTMLElement>; |
||||
|
|
||||
|
@Input() |
||||
|
appearance = segmentedButtonDefaultAppearance; |
||||
|
|
||||
|
@Input() |
||||
|
borderRadius: string; |
||||
|
|
||||
|
@Input() |
||||
|
autoScale: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
@coerceBoolean() |
||||
|
value = false; |
||||
|
|
||||
|
@Input() |
||||
|
@coerceBoolean() |
||||
|
disabled = false; |
||||
|
|
||||
|
@Input() |
||||
|
@coerceBoolean() |
||||
|
hovered = false; |
||||
|
|
||||
|
@Input() |
||||
|
@coerceBoolean() |
||||
|
disableEvents = false; |
||||
|
|
||||
|
@Input() |
||||
|
ctx: WidgetContext; |
||||
|
|
||||
|
@Output() |
||||
|
clicked = new EventEmitter<MatButtonToggleChange>(); |
||||
|
|
||||
|
leftLabel$: Observable<string>; |
||||
|
rightLabel$: Observable<string>; |
||||
|
|
||||
|
leftIconStyle: ComponentStyle = {}; |
||||
|
rightIconStyle: ComponentStyle = {}; |
||||
|
|
||||
|
leftLabelStyle: ComponentStyle = {}; |
||||
|
rightLabelStyle: ComponentStyle = {}; |
||||
|
|
||||
|
computedBorderColor: string; |
||||
|
computedBorderWidth: string; |
||||
|
computedBorderRadius: string; |
||||
|
|
||||
|
private buttonResize$: ResizeObserver; |
||||
|
|
||||
|
private appearanceCssClass: string; |
||||
|
|
||||
|
constructor(private renderer: Renderer2, |
||||
|
private elementRef: ElementRef, |
||||
|
private utils: UtilsService) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.updateAppearance(); |
||||
|
} |
||||
|
|
||||
|
ngOnChanges(changes: SimpleChanges): void { |
||||
|
for (const propName of Object.keys(changes)) { |
||||
|
const change = changes[propName]; |
||||
|
if (!change.firstChange) { |
||||
|
if (propName === 'appearance') { |
||||
|
this.updateAppearance(); |
||||
|
} else if (propName === 'borderRadius') { |
||||
|
this.updateBorderRadius(); |
||||
|
} else if (propName === 'autoScale') { |
||||
|
this.updateAutoScale(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
this.updateAutoScale(); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
if (this.buttonResize$) { |
||||
|
this.buttonResize$.disconnect(); |
||||
|
} |
||||
|
this.clearAppearanceCss(); |
||||
|
} |
||||
|
|
||||
|
public validateSize() { |
||||
|
if (this.appearance.autoScale && this.widgetButton.nativeElement) { |
||||
|
this.onResize(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateAppearance(): void { |
||||
|
this.clearAppearanceCss(); |
||||
|
this.computedBorderColor = this.appearance.cardBorderColor; |
||||
|
this.computedBorderWidth = validateCssSize(this.appearance.cardBorder.toString()); |
||||
|
if (this.appearance.leftAppearance.showIcon) { |
||||
|
this.leftIconStyle = iconStyle(this.appearance.leftAppearance.iconSize, this.appearance.leftAppearance.iconSizeUnit); |
||||
|
} |
||||
|
if (this.appearance.rightAppearance.showIcon) { |
||||
|
this.rightIconStyle = iconStyle(this.appearance.rightAppearance.iconSize, this.appearance.rightAppearance.iconSizeUnit); |
||||
|
} |
||||
|
if (this.appearance.leftAppearance.showLabel) { |
||||
|
this.leftLabelStyle = textStyle(this.appearance.leftAppearance.labelFont); |
||||
|
this.leftLabel$ = this.ctx ? this.ctx.registerLabelPattern(this.appearance.leftAppearance.label, this.leftLabel$) : of(this.appearance.leftAppearance.label); |
||||
|
} |
||||
|
if (this.appearance.rightAppearance.showLabel) { |
||||
|
this.rightLabelStyle = textStyle(this.appearance.rightAppearance.labelFont); |
||||
|
this.rightLabel$ = this.ctx ? this.ctx.registerLabelPattern(this.appearance.rightAppearance.label, this.rightLabel$) : of(this.appearance.rightAppearance.label); |
||||
|
} |
||||
|
this.updateBorderRadius(); |
||||
|
const appearanceCss = generateWidgetButtonToggleAppearanceCss(this.appearance.selectedStyle, this.appearance.unselectedStyle); |
||||
|
const layoutCss = generateWidgetButtonToggleBorderLayout(this.appearance.layout); |
||||
|
this.appearanceCssClass = this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement, |
||||
|
'tb-widget-button', appearanceCss + layoutCss); |
||||
|
this.updateAutoScale(); |
||||
|
} |
||||
|
|
||||
|
private updateBorderRadius(): void { |
||||
|
if (this.borderRadius?.length) { |
||||
|
const validatedBorderRadius = validateCssSize(this.borderRadius); |
||||
|
if (validatedBorderRadius) { |
||||
|
this.computedBorderRadius = validatedBorderRadius; |
||||
|
} else { |
||||
|
this.computedBorderRadius = this.borderRadius; |
||||
|
} |
||||
|
} else { |
||||
|
this.computedBorderRadius = segmentedButtonLayoutBorder.get(this.appearance.layout); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private clearAppearanceCss(): void { |
||||
|
if (this.appearanceCssClass) { |
||||
|
this.utils.clearCssElement(this.renderer, this.appearanceCssClass, this.elementRef?.nativeElement); |
||||
|
this.appearanceCssClass = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private updateAutoScale() { |
||||
|
if (this.buttonResize$) { |
||||
|
this.buttonResize$.disconnect(); |
||||
|
} |
||||
|
if (this.widgetButton && this.rightButtonContent && this.leftButtonContent) { |
||||
|
const autoScale = isDefinedAndNotNull(this.autoScale) ? this.autoScale : this.appearance.autoScale; |
||||
|
if (autoScale) { |
||||
|
this.buttonResize$ = new ResizeObserver(() => { |
||||
|
this.onResize(); |
||||
|
}); |
||||
|
this.buttonResize$.observe(this.widgetButton.nativeElement); |
||||
|
this.onResize(); |
||||
|
} else { |
||||
|
this.renderer.setStyle(this.widgetButton.nativeElement, 'transform', 'none'); |
||||
|
this.renderer.setStyle(this.widgetButton.nativeElement, 'width', '100%'); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private onResize() { |
||||
|
const height = this.widgetButton.nativeElement.getBoundingClientRect().height; |
||||
|
const buttonScale = height / initialButtonHeight; |
||||
|
const buttonWidth = this.widgetButton.nativeElement.getBoundingClientRect().width; |
||||
|
const buttonHeight = this.widgetButton.nativeElement.getBoundingClientRect().height; |
||||
|
this.renderer.setStyle(this.leftButtonContent.nativeElement, 'transform', `scale(1)`); |
||||
|
this.renderer.setStyle(this.rightButtonContent.nativeElement, 'transform', `scale(1)`); |
||||
|
const contentWidth = this.leftButtonContent.nativeElement.getBoundingClientRect().width; |
||||
|
const contentHeight = this.leftButtonContent.nativeElement.getBoundingClientRect().height; |
||||
|
const maxScale = Math.max(1, buttonScale); |
||||
|
const scale = Math.min(Math.min((buttonWidth / 2 - horizontalLayoutPadding) / contentWidth, buttonHeight / contentHeight), maxScale); |
||||
|
this.renderer.setStyle(this.leftButtonContent.nativeElement, 'transform', `scale(${scale})`); |
||||
|
this.renderer.setStyle(this.rightButtonContent.nativeElement, 'transform', `scale(${scale})`); |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
Loading…
Reference in new issue