Browse Source

UI: Implement toggle button widget.

pull/10212/head
Igor Kulikov 2 years ago
parent
commit
3d4b706181
  1. 1
      application/src/main/data/json/system/widget_bundles/buttons.json
  2. 1
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  3. 37
      application/src/main/data/json/system/widget_types/toggle_button.json
  4. 12
      ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts
  5. 2
      ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html
  6. 177
      ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.html
  7. 192
      ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.ts
  8. 34
      ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.component.html
  9. 70
      ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.component.scss
  10. 142
      ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.component.ts
  11. 143
      ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.models.ts
  12. 119
      ui-ngx/src/app/modules/home/components/widget/lib/settings/button/toggle-button-widget-settings.component.html
  13. 94
      ui-ngx/src/app/modules/home/components/widget/lib/settings/button/toggle-button-widget-settings.component.ts
  14. 2
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html
  15. 7
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html
  16. 19
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.ts
  17. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.html
  18. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts
  19. 1
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.html
  20. 4
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts
  21. 5
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-size-input.component.html
  22. 16
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-size-input.component.ts
  23. 12
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  24. 7
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  25. 3
      ui-ngx/src/app/shared/components/button/widget-button.component.html
  26. 4
      ui-ngx/src/app/shared/components/button/widget-button.component.scss
  27. 25
      ui-ngx/src/app/shared/components/button/widget-button.component.ts
  28. 1
      ui-ngx/src/app/shared/components/button/widget-button.models.ts
  29. 12
      ui-ngx/src/app/shared/models/widget-settings.models.ts
  30. 14
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  31. 3
      ui-ngx/src/form.scss

1
application/src/main/data/json/system/widget_bundles/buttons.json

@ -10,6 +10,7 @@
"widgetTypeFqns": [
"action_button",
"command_button",
"toggle_button",
"power_button"
]
}

1
application/src/main/data/json/system/widget_bundles/control_widgets.json

@ -10,6 +10,7 @@
"widgetTypeFqns": [
"single_switch",
"command_button",
"toggle_button",
"power_button",
"slider",
"control_widgets.switch_control",

37
application/src/main/data/json/system/widget_types/toggle_button.json

File diff suppressed because one or more lines are too long

12
ui-ngx/src/app/modules/home/components/widget/config/basic/basic-widget-config.module.ts

@ -104,6 +104,9 @@ import {
PowerButtonBasicConfigComponent
} from '@home/components/widget/config/basic/button/power-button-basic-config.component';
import { SliderBasicConfigComponent } from '@home/components/widget/config/basic/rpc/slider-basic-config.component';
import {
ToggleButtonBasicConfigComponent
} from '@home/components/widget/config/basic/button/toggle-button-basic-config.component';
@NgModule({
declarations: [
@ -137,7 +140,8 @@ import { SliderBasicConfigComponent } from '@home/components/widget/config/basic
ActionButtonBasicConfigComponent,
CommandButtonBasicConfigComponent,
PowerButtonBasicConfigComponent,
SliderBasicConfigComponent
SliderBasicConfigComponent,
ToggleButtonBasicConfigComponent
],
imports: [
CommonModule,
@ -175,7 +179,8 @@ import { SliderBasicConfigComponent } from '@home/components/widget/config/basic
ActionButtonBasicConfigComponent,
CommandButtonBasicConfigComponent,
PowerButtonBasicConfigComponent,
SliderBasicConfigComponent
SliderBasicConfigComponent,
ToggleButtonBasicConfigComponent
]
})
export class BasicWidgetConfigModule {
@ -207,5 +212,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
'tb-action-button-basic-config': ActionButtonBasicConfigComponent,
'tb-command-button-basic-config': CommandButtonBasicConfigComponent,
'tb-power-button-basic-config': PowerButtonBasicConfigComponent,
'tb-slider-basic-config': SliderBasicConfigComponent
'tb-slider-basic-config': SliderBasicConfigComponent,
'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent
};

2
ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html

@ -35,7 +35,7 @@
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.power-button.power-on-hint' | translate}}" translate>widgets.power-button.power-on</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.power-button.power-on "
panelTitle="widgets.power-button.power-on"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"

177
ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.html

@ -0,0 +1,177 @@
<!--
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]="toggleButtonWidgetConfigForm">
<tb-target-device formControlName="targetDevice"></tb-target-device>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.toggle-button.behavior</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
<tb-get-value-action-settings fxFlex
panelTitle="widgets.rpc-state.initial-state"
[valueType]="valueType.BOOLEAN"
trueLabel="widgets.toggle-button.checked"
falseLabel="widgets.toggle-button.unchecked"
stateLabel="widgets.toggle-button.checked"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="initialState"></tb-get-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.toggle-button.check-hint' | translate}}" translate>widgets.toggle-button.check</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.toggle-button.check"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="checkState"></tb-set-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.toggle-button.uncheck-hint' | translate}}" translate>widgets.toggle-button.uncheck</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.toggle-button.uncheck"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="uncheckState"></tb-set-value-action-settings>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div>
<tb-get-value-action-settings fxFlex
panelTitle="widgets.rpc-state.disabled-state"
[valueType]="valueType.BOOLEAN"
stateLabel="widgets.rpc-state.disabled"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="disabledState"></tb-get-value-action-settings>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="!toggleButtonWidgetConfigForm.get('autoScale').value" disabled>
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="autoScale" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.toggle-button.auto-scale' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="horizontalFill">
{{ 'widgets.toggle-button.horizontal-fill' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="verticalFill">
{{ 'widgets.toggle-button.vertical-fill' | translate }}
</mat-slide-toggle>
</div>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showTitle">
{{ 'widget-config.title' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="title" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="titleFont"
clearButton
[previewText]="toggleButtonWidgetConfigForm.get('title').value"
[initialPreviewStyle]="widgetConfig.config.titleStyle">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="titleColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showIcon">
{{ 'widget-config.card-icon' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic">
<input matInput type="number" min="0" formControlName="iconSize" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-css-unit-select fxFlex formControlName="iconSizeUnit"></tb-css-unit-select>
<tb-material-icon-select asBoxInput
iconClearButton
[color]="toggleButtonWidgetConfigForm.get('iconColor').value"
formControlName="icon">
</tb-material-icon-select>
<tb-color-input asBoxInput
colorClearButton
formControlName="iconColor">
</tb-color-input>
</div>
</div>
</div>
<div class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-form-panel-title" translate>widgets.toggle-button.button-appearance</div>
<tb-toggle-select [(ngModel)]="buttonAppearanceType" [ngModelOptions]="{standalone: true}">
<tb-toggle-option value="checked">{{ 'widgets.toggle-button.checked' | translate }}</tb-toggle-option>
<tb-toggle-option value="unchecked">{{ 'widgets.toggle-button.unchecked' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-widget-button-appearance
[fxShow]="buttonAppearanceType === 'checked'"
withBorderRadius
[withAutoScale]="false"
[autoScale]="toggleButtonWidgetConfigForm.get('autoScale').value"
formControlName="checkedAppearance">
</tb-widget-button-appearance>
<tb-widget-button-appearance
[fxShow]="buttonAppearanceType === 'unchecked'"
withBorderRadius
[withAutoScale]="false"
[autoScale]="toggleButtonWidgetConfigForm.get('autoScale').value"
formControlName="uncheckedAppearance">
</tb-widget-button-appearance>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.card-appearance</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
<div class="tb-form-row space-between column-lt-md">
<div translate>widget-config.show-card-buttons</div>
<mat-chip-listbox multiple formControlName="cardButtons">
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widget-config.card-border-radius' | translate }}</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

192
ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.ts

@ -0,0 +1,192 @@
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { BasicWidgetConfigComponent } from '@home/components/widget/config/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { TargetDevice, TargetDeviceType, WidgetConfig, } from '@shared/models/widget.models';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
import { isUndefined } from '@core/utils';
import { ValueType } from '@shared/models/constants';
import { cssSizeToStrSize, resolveCssSize } from '@shared/models/widget-settings.models';
import {
toggleButtonDefaultSettings,
ToggleButtonWidgetSettings
} from '@home/components/widget/lib/button/toggle-button-widget.models';
type ButtonAppearanceType = 'checked' | 'unchecked';
@Component({
selector: 'tb-toggle-button-basic-config',
templateUrl: './toggle-button-basic-config.component.html',
styleUrls: ['../basic-config.scss']
})
export class ToggleButtonBasicConfigComponent extends BasicWidgetConfigComponent {
get targetDevice(): TargetDevice {
return this.toggleButtonWidgetConfigForm.get('targetDevice').value;
}
valueType = ValueType;
buttonAppearanceType: ButtonAppearanceType = 'checked';
toggleButtonWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
protected widgetConfigComponent: WidgetConfigComponent,
private fb: UntypedFormBuilder) {
super(store, widgetConfigComponent);
}
protected configForm(): UntypedFormGroup {
return this.toggleButtonWidgetConfigForm;
}
protected onConfigSet(configData: WidgetConfigComponentData) {
const settings: ToggleButtonWidgetSettings = {...toggleButtonDefaultSettings, ...(configData.config.settings || {})};
const iconSize = resolveCssSize(configData.config.iconSize);
this.toggleButtonWidgetConfigForm = this.fb.group({
targetDevice: [configData.config.targetDevice, []],
initialState: [settings.initialState, []],
checkState: [settings.checkState, []],
uncheckState: [settings.uncheckState, []],
disabledState: [settings.disabledState, []],
showTitle: [configData.config.showTitle, []],
title: [configData.config.title, []],
titleFont: [configData.config.titleFont, []],
titleColor: [configData.config.titleColor, []],
showIcon: [configData.config.showTitleIcon, []],
iconSize: [iconSize[0], [Validators.min(0)]],
iconSizeUnit: [iconSize[1], []],
icon: [configData.config.titleIcon, []],
iconColor: [configData.config.iconColor, []],
autoScale: [settings.autoScale, []],
horizontalFill: [settings.horizontalFill, []],
verticalFill: [settings.verticalFill, []],
checkedAppearance: [settings.checkedAppearance, []],
uncheckedAppearance: [settings.uncheckedAppearance, []],
background: [settings.background, []],
cardButtons: [this.getCardButtons(configData.config), []],
borderRadius: [configData.config.borderRadius, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.targetDevice = config.targetDevice;
this.widgetConfig.config.showTitle = config.showTitle;
this.widgetConfig.config.title = config.title;
this.widgetConfig.config.titleFont = config.titleFont;
this.widgetConfig.config.titleColor = config.titleColor;
this.widgetConfig.config.showTitleIcon = config.showIcon;
this.widgetConfig.config.iconSize = cssSizeToStrSize(config.iconSize, config.iconSizeUnit);
this.widgetConfig.config.titleIcon = config.icon;
this.widgetConfig.config.iconColor = config.iconColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.initialState = config.initialState;
this.widgetConfig.config.settings.checkState = config.checkState;
this.widgetConfig.config.settings.uncheckState = config.uncheckState;
this.widgetConfig.config.settings.disabledState = config.disabledState;
this.widgetConfig.config.settings.autoScale = config.autoScale;
this.widgetConfig.config.settings.horizontalFill = config.horizontalFill;
this.widgetConfig.config.settings.verticalFill = config.verticalFill;
this.widgetConfig.config.settings.checkedAppearance = config.checkedAppearance;
this.widgetConfig.config.settings.uncheckedAppearance = config.uncheckedAppearance;
this.widgetConfig.config.settings.background = config.background;
this.setCardButtons(config.cardButtons, this.widgetConfig.config);
this.widgetConfig.config.borderRadius = config.borderRadius;
this.widgetConfig.config.actions = config.actions;
return this.widgetConfig;
}
protected validatorTriggers(): string[] {
return ['showTitle', 'showIcon', 'autoScale'];
}
protected updateValidators(emitEvent: boolean, trigger?: string) {
const showTitle: boolean = this.toggleButtonWidgetConfigForm.get('showTitle').value;
const showIcon: boolean = this.toggleButtonWidgetConfigForm.get('showIcon').value;
const autoScale: boolean = this.toggleButtonWidgetConfigForm.get('autoScale').value;
if (showTitle) {
this.toggleButtonWidgetConfigForm.get('title').enable();
this.toggleButtonWidgetConfigForm.get('titleFont').enable();
this.toggleButtonWidgetConfigForm.get('titleColor').enable();
this.toggleButtonWidgetConfigForm.get('showIcon').enable({emitEvent: false});
if (showIcon) {
this.toggleButtonWidgetConfigForm.get('iconSize').enable();
this.toggleButtonWidgetConfigForm.get('iconSizeUnit').enable();
this.toggleButtonWidgetConfigForm.get('icon').enable();
this.toggleButtonWidgetConfigForm.get('iconColor').enable();
} else {
this.toggleButtonWidgetConfigForm.get('iconSize').disable();
this.toggleButtonWidgetConfigForm.get('iconSizeUnit').disable();
this.toggleButtonWidgetConfigForm.get('icon').disable();
this.toggleButtonWidgetConfigForm.get('iconColor').disable();
}
} else {
this.toggleButtonWidgetConfigForm.get('title').disable();
this.toggleButtonWidgetConfigForm.get('titleFont').disable();
this.toggleButtonWidgetConfigForm.get('titleColor').disable();
this.toggleButtonWidgetConfigForm.get('showIcon').disable({emitEvent: false});
this.toggleButtonWidgetConfigForm.get('iconSize').disable();
this.toggleButtonWidgetConfigForm.get('iconSizeUnit').disable();
this.toggleButtonWidgetConfigForm.get('icon').disable();
this.toggleButtonWidgetConfigForm.get('iconColor').disable();
}
if (autoScale) {
this.toggleButtonWidgetConfigForm.get('horizontalFill').disable();
this.toggleButtonWidgetConfigForm.get('verticalFill').disable();
} else {
this.toggleButtonWidgetConfigForm.get('horizontalFill').enable();
this.toggleButtonWidgetConfigForm.get('verticalFill').enable();
}
}
private getCardButtons(config: WidgetConfig): string[] {
const buttons: string[] = [];
if (isUndefined(config.enableFullscreen) || config.enableFullscreen) {
buttons.push('fullscreen');
}
return buttons;
}
private setCardButtons(buttons: string[], config: WidgetConfig) {
config.enableFullscreen = buttons.includes('fullscreen');
}
protected readonly TargetDeviceType = TargetDeviceType;
}

34
ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.component.html

@ -0,0 +1,34 @@
<!--
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-toggle-button-panel" [style]="backgroundStyle$ | async">
<div class="tb-toggle-button-overlay" [style]="overlayStyle"></div>
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container>
<div class="tb-toggle-button-container"
[class.auto-scale]="autoScale"
[class.horizontal-fill]="horizontalFill"
[class.vertical-fill]="verticalFill">
<tb-widget-button
[appearance]="appearance"
[autoScale]="autoScale"
[disabled]="disabled || (loading$ | async)"
[ctx]="ctx"
(clicked)="onClick($event)">
</tb-widget-button>
</div>
<mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar>
</div>

70
ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.component.scss

@ -0,0 +1,70 @@
/**
* 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-toggle-button-panel {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
padding: 20px 24px 24px 24px;
> div:not(.tb-toggle-button-overlay) {
z-index: 1;
}
.tb-toggle-button-overlay {
position: absolute;
top: 12px;
left: 12px;
bottom: 12px;
right: 12px;
}
div.tb-widget-title {
padding: 0;
}
.tb-toggle-button-container {
flex: 1;
min-width: 0;
min-height: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
&.auto-scale {
tb-widget-button {
flex: 1;
min-width: 0;
min-height: 0;
width: 100%;
height: 100%;
}
}
&.horizontal-fill {
tb-widget-button {
width: 100%;
}
}
&.vertical-fill {
tb-widget-button {
height: 100%;
}
}
}
}

142
ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.component.ts

@ -0,0 +1,142 @@
///
/// 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, ValueSetter } 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 { WidgetButtonAppearance } from '@shared/components/button/widget-button.models';
import {
toggleButtonDefaultSettings,
ToggleButtonWidgetSettings
} from '@home/components/widget/lib/button/toggle-button-widget.models';
import { Observable } from 'rxjs';
import { backgroundStyle, ComponentStyle, overlayStyle } from '@shared/models/widget-settings.models';
@Component({
selector: 'tb-toggle-button-widget',
templateUrl: './toggle-button-widget.component.html',
styleUrls: ['../action/action-widget.scss', './toggle-button-widget.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class ToggleButtonWidgetComponent extends
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy {
settings: ToggleButtonWidgetSettings;
backgroundStyle$: Observable<ComponentStyle>;
overlayStyle: ComponentStyle = {};
value = false;
disabled = false;
autoScale: boolean;
horizontalFill: boolean;
verticalFill: boolean;
appearance: WidgetButtonAppearance;
private checkValueSetter: ValueSetter<boolean>;
private uncheckValueSetter: ValueSetter<boolean>;
constructor(protected imagePipe: ImagePipe,
protected sanitizer: DomSanitizer,
protected cd: ChangeDetectorRef) {
super(cd);
}
ngOnInit(): void {
super.ngOnInit();
this.settings = {...toggleButtonDefaultSettings, ...this.ctx.settings};
this.autoScale = this.settings.autoScale;
this.horizontalFill = this.settings.horizontalFill;
this.verticalFill = this.settings.verticalFill;
this.backgroundStyle$ = backgroundStyle(this.settings.background, this.imagePipe, this.sanitizer);
this.overlayStyle = overlayStyle(this.settings.background.overlay);
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)
});
const checkStateSettings = {...this.settings.checkState,
actionLabel: this.ctx.translate.instant('widgets.toggle-button.check')};
this.checkValueSetter = this.createValueSetter(checkStateSettings);
const uncheckStateSettings = {...this.settings.uncheckState,
actionLabel: this.ctx.translate.instant('widgets.toggle-button.uncheck')};
this.uncheckValueSetter = this.createValueSetter(uncheckStateSettings);
this.appearance = this.value ? this.settings.checkedAppearance : this.settings.uncheckedAppearance;
}
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.value ? this.settings.checkedAppearance : this.settings.uncheckedAppearance;
this.cd.markForCheck();
}
}
private onDisabled(value: boolean): void {
const newDisabled = !!value;
if (this.disabled !== newDisabled) {
this.disabled = newDisabled;
this.cd.markForCheck();
}
}
public onClick(_$event: MouseEvent) {
if (!this.ctx.isEdit && !this.ctx.isPreview) {
this.onValue(!this.value);
const targetValue = this.value;
const targetSetter = targetValue ? this.checkValueSetter : this.uncheckValueSetter;
this.updateValue(targetSetter, targetValue, {
next: () => {
this.onValue(targetValue);
},
error: () => {
this.onValue(!targetValue);
}
});
}
}
}

143
ui-ngx/src/app/modules/home/components/widget/lib/button/toggle-button-widget.models.ts

@ -0,0 +1,143 @@
///
/// 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 {
WidgetButtonAppearance,
widgetButtonDefaultAppearance,
WidgetButtonType
} from '@shared/components/button/widget-button.models';
import {
DataToValueType,
GetValueAction,
GetValueSettings,
SetValueAction,
SetValueSettings,
ValueToDataType
} from '@shared/models/action-widget-settings.models';
import { BackgroundSettings, BackgroundType } from '@shared/models/widget-settings.models';
import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
export interface ToggleButtonWidgetSettings {
initialState: GetValueSettings<boolean>;
disabledState: GetValueSettings<boolean>;
checkState: SetValueSettings;
uncheckState: SetValueSettings;
autoScale: boolean;
horizontalFill: boolean;
verticalFill: boolean;
checkedAppearance: WidgetButtonAppearance;
uncheckedAppearance: WidgetButtonAppearance;
background: BackgroundSettings;
}
export const toggleButtonDefaultSettings: ToggleButtonWidgetSettings = {
initialState: {
action: GetValueAction.EXECUTE_RPC,
defaultValue: false,
executeRpc: {
method: 'getState',
requestTimeout: 5000,
requestPersistent: false,
persistentPollingInterval: 1000
},
getAttribute: {
key: 'state',
scope: null
},
getTimeSeries: {
key: 'state'
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
dataToValueFunction: '/* Should return boolean value */\nreturn data;'
}
},
disabledState: {
action: GetValueAction.DO_NOTHING,
defaultValue: false,
getAttribute: {
key: 'state',
scope: null
},
getTimeSeries: {
key: 'state'
},
dataToValue: {
type: DataToValueType.NONE,
compareToValue: true,
dataToValueFunction: '/* Should return boolean value */\nreturn data;'
}
},
checkState: {
action: SetValueAction.EXECUTE_RPC,
executeRpc: {
method: 'setState',
requestTimeout: 5000,
requestPersistent: false,
persistentPollingInterval: 1000
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
},
putTimeSeries: {
key: 'state'
},
valueToData: {
type: ValueToDataType.CONSTANT,
constantValue: true,
valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;'
}
},
uncheckState: {
action: SetValueAction.EXECUTE_RPC,
executeRpc: {
method: 'setState',
requestTimeout: 5000,
requestPersistent: false,
persistentPollingInterval: 1000
},
setAttribute: {
key: 'state',
scope: AttributeScope.SHARED_SCOPE
},
putTimeSeries: {
key: 'state'
},
valueToData: {
type: ValueToDataType.CONSTANT,
constantValue: false,
valueToDataFunction: '/* Convert input boolean value to RPC parameters or attribute/time-series value */ \n return value;'
}
},
autoScale: true,
horizontalFill: true,
verticalFill: false,
checkedAppearance: {...widgetButtonDefaultAppearance,
type: WidgetButtonType.outlined, mainColor: '#198038', label: 'Opened', icon: 'mdi:lock-open-variant', borderRadius: '4px'},
uncheckedAppearance: {...widgetButtonDefaultAppearance,
type: WidgetButtonType.filled, mainColor: '#D12730', label: 'Closed', icon: 'lock', borderRadius: '4px'},
background: {
type: BackgroundType.color,
color: '#fff',
overlay: {
enabled: false,
color: 'rgba(255,255,255,0.72)',
blur: 3
}
}
};

119
ui-ngx/src/app/modules/home/components/widget/lib/settings/button/toggle-button-widget-settings.component.html

@ -0,0 +1,119 @@
<!--
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]="toggleButtonWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.toggle-button.behavior</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.initial-state-hint' | translate}}" translate>widgets.rpc-state.initial-state</div>
<tb-get-value-action-settings fxFlex
panelTitle="widgets.rpc-state.initial-state"
[valueType]="valueType.BOOLEAN"
trueLabel="widgets.toggle-button.checked"
falseLabel="widgets.toggle-button.unchecked"
stateLabel="widgets.toggle-button.checked"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="initialState"></tb-get-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.toggle-button.check-hint' | translate}}" translate>widgets.toggle-button.check</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.toggle-button.check"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="checkState"></tb-set-value-action-settings>
</div>
<div class="tb-form-row space-between">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.toggle-button.uncheck-hint' | translate}}" translate>widgets.toggle-button.uncheck</div>
<tb-set-value-action-settings fxFlex
panelTitle="widgets.toggle-button.uncheck"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="uncheckState"></tb-set-value-action-settings>
</div>
<div class="tb-form-row">
<div class="fixed-title-width" tb-hint-tooltip-icon="{{'widgets.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div>
<tb-get-value-action-settings fxFlex
panelTitle="widgets.rpc-state.disabled-state"
[valueType]="valueType.BOOLEAN"
stateLabel="widgets.rpc-state.disabled"
[aliasController]="aliasController"
[targetDevice]="targetDevice"
[widgetType]="widgetType"
formControlName="disabledState"></tb-get-value-action-settings>
</div>
</div>
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widget-config.appearance</div>
<div class="tb-form-panel stroked tb-slide-toggle">
<mat-expansion-panel class="tb-settings" [expanded]="!toggleButtonWidgetSettingsForm.get('autoScale').value" disabled>
<mat-expansion-panel-header fxLayout="row wrap">
<mat-panel-title>
<mat-slide-toggle class="mat-slide" formControlName="autoScale" (click)="$event.stopPropagation()"
fxLayoutAlign="center">
{{ 'widgets.toggle-button.auto-scale' | translate }}
</mat-slide-toggle>
</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="horizontalFill">
{{ 'widgets.toggle-button.horizontal-fill' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="verticalFill">
{{ 'widgets.toggle-button.vertical-fill' | translate }}
</mat-slide-toggle>
</div>
</ng-template>
</mat-expansion-panel>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
</div>
<div class="tb-form-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-form-panel-title" translate>widgets.toggle-button.button-appearance</div>
<tb-toggle-select [(ngModel)]="buttonAppearanceType" [ngModelOptions]="{standalone: true}">
<tb-toggle-option value="checked">{{ 'widgets.toggle-button.checked' | translate }}</tb-toggle-option>
<tb-toggle-option value="unchecked">{{ 'widgets.toggle-button.unchecked' | translate }}</tb-toggle-option>
</tb-toggle-select>
</div>
<tb-widget-button-appearance
[fxShow]="buttonAppearanceType === 'checked'"
withBorderRadius
[withAutoScale]="false"
[autoScale]="toggleButtonWidgetSettingsForm.get('autoScale').value"
formControlName="checkedAppearance">
</tb-widget-button-appearance>
<tb-widget-button-appearance
[fxShow]="buttonAppearanceType === 'unchecked'"
withBorderRadius
[withAutoScale]="false"
[autoScale]="toggleButtonWidgetSettingsForm.get('autoScale').value"
formControlName="uncheckedAppearance">
</tb-widget-button-appearance>
</div>
</ng-container>

94
ui-ngx/src/app/modules/home/components/widget/lib/settings/button/toggle-button-widget-settings.component.ts

@ -0,0 +1,94 @@
///
/// 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 { toggleButtonDefaultSettings } from '@home/components/widget/lib/button/toggle-button-widget.models';
type ButtonAppearanceType = 'checked' | 'unchecked';
@Component({
selector: 'tb-toggle-button-widget-settings',
templateUrl: './toggle-button-widget-settings.component.html',
styleUrls: ['./../widget-settings.scss']
})
export class ToggleButtonWidgetSettingsComponent extends WidgetSettingsComponent {
get targetDevice(): TargetDevice {
return this.widgetConfig?.config?.targetDevice;
}
get widgetType(): widgetType {
return this.widgetConfig?.widgetType;
}
valueType = ValueType;
buttonAppearanceType: ButtonAppearanceType = 'checked';
toggleButtonWidgetSettingsForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.toggleButtonWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...toggleButtonDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.toggleButtonWidgetSettingsForm = this.fb.group({
initialState: [settings.initialState, []],
checkState: [settings.checkState, []],
uncheckState: [settings.uncheckState, []],
disabledState: [settings.disabledState, []],
autoScale: [settings.autoScale, []],
horizontalFill: [settings.horizontalFill, []],
verticalFill: [settings.verticalFill, []],
checkedAppearance: [settings.checkedAppearance, []],
uncheckedAppearance: [settings.uncheckedAppearance, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['autoScale'];
}
protected updateValidators(emitEvent: boolean) {
const autoScale: boolean = this.toggleButtonWidgetSettingsForm.get('autoScale').value;
if (autoScale) {
this.toggleButtonWidgetSettingsForm.get('horizontalFill').disable();
this.toggleButtonWidgetSettingsForm.get('verticalFill').disable();
} else {
this.toggleButtonWidgetSettingsForm.get('horizontalFill').enable();
this.toggleButtonWidgetSettingsForm.get('verticalFill').enable();
}
}
}

2
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/action/set-value-action-settings-panel.component.html

@ -97,7 +97,7 @@
<div class="fixed-title-width">{{ (setValueSettingsFormGroup.get('action').value === setValueAction.EXECUTE_RPC ?
'widgets.value-action.parameters' : 'widgets.value-action.value') | translate }}</div>
<tb-toggle-select fxFlex formControlName="type">
<tb-toggle-option [value]="valueToDataType.VALUE">{{ 'widgets.value-action.converter-value' | translate }}</tb-toggle-option>
<tb-toggle-option *ngIf="valueType !== ValueType.BOOLEAN" [value]="valueToDataType.VALUE">{{ 'widgets.value-action.converter-value' | translate }}</tb-toggle-option>
<tb-toggle-option *ngIf="valueType === ValueType.BOOLEAN" [value]="valueToDataType.CONSTANT">{{ 'widgets.value-action.converter-constant' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="valueToDataType.FUNCTION">{{ 'widgets.value-action.converter-function' | translate }}</tb-toggle-option>
<tb-toggle-option *ngIf="setValueSettingsFormGroup.get('action').value === setValueAction.EXECUTE_RPC"

7
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.html

@ -28,7 +28,7 @@
{{ widgetButtonTypeTranslationMap.get(type) | translate }}
</tb-image-cards-select-option>
</tb-image-cards-select>
<div class="tb-form-row">
<div *ngIf="withAutoScale" class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="autoScale">
{{ 'widgets.button.auto-scale' | translate }}
</mat-slide-toggle>
@ -56,6 +56,10 @@
</tb-material-icon-select>
</div>
</div>
<div *ngIf="withBorderRadius" class="tb-form-row">
<div class="fixed-title-width">{{ 'widgets.button.border-radius' | translate }}</div>
<tb-css-size-input flex formControlName="borderRadius"></tb-css-size-input>
</div>
<div class="tb-form-row space-between column-xs">
<div>{{ 'widgets.button.color-palette' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px">
@ -88,6 +92,7 @@
[state]="state"
[appearance]="this.appearanceFormGroup.value"
[borderRadius]="borderRadius"
[autoScale]="autoScale"
[formControlName]="state">
</tb-widget-button-custom-style>
</div>

19
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-appearance.component.ts

@ -24,6 +24,7 @@ import {
widgetButtonTypeTranslations
} from '@shared/components/button/widget-button.models';
import { merge } from 'rxjs';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-widget-button-appearance',
@ -46,6 +47,17 @@ export class WidgetButtonAppearanceComponent implements OnInit, ControlValueAcce
@Input()
borderRadius: string;
@Input()
autoScale: boolean;
@Input()
@coerceBoolean()
withAutoScale = true;
@Input()
@coerceBoolean()
withBorderRadius = false;
widgetButtonTypes = widgetButtonTypes;
widgetButtonTypeTranslationMap = widgetButtonTypeTranslations;
@ -65,7 +77,6 @@ export class WidgetButtonAppearanceComponent implements OnInit, ControlValueAcce
ngOnInit(): void {
this.appearanceFormGroup = this.fb.group({
type: [null, []],
autoScale: [null, []],
showLabel: [null, []],
label: [null, []],
showIcon: [null, []],
@ -75,6 +86,12 @@ export class WidgetButtonAppearanceComponent implements OnInit, ControlValueAcce
mainColor: [null, []],
backgroundColor: [null, []]
});
if (this.withAutoScale) {
this.appearanceFormGroup.addControl('autoScale', this.fb.control(null, []));
}
if (this.withBorderRadius) {
this.appearanceFormGroup.addControl('borderRadius', this.fb.control(null, []));
}
const customStyle = this.fb.group({});
for (const state of widgetButtonStates) {
customStyle.addControl(state, this.fb.control(null, []));

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.html

@ -51,6 +51,7 @@
#widgetButtonPreview
[appearance]="previewAppearance"
[borderRadius]="borderRadius"
[autoScale]="autoScale"
disableEvents
[hovered]="state === widgetButtonState.hovered"
[pressed]="state === widgetButtonState.pressed"

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style-panel.component.ts

@ -61,6 +61,9 @@ export class WidgetButtonCustomStylePanelComponent extends PageComponent impleme
@Input()
borderRadius: string;
@Input()
autoScale: boolean;
@Input()
state: WidgetButtonState;

1
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.html

@ -20,6 +20,7 @@
<tb-widget-button
[appearance]="previewAppearance"
[borderRadius]="borderRadius"
[autoScale]="autoScale"
disableEvents
[hovered]="state === widgetButtonState.hovered"
[pressed]="state === widgetButtonState.pressed"

4
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/button/widget-button-custom-style.component.ts

@ -63,6 +63,9 @@ export class WidgetButtonCustomStyleComponent implements OnInit, OnChanges, Cont
@Input()
borderRadius: string;
@Input()
autoScale: boolean;
@Input()
state: WidgetButtonState;
@ -124,6 +127,7 @@ export class WidgetButtonCustomStyleComponent implements OnInit, OnChanges, Cont
const ctx: any = {
appearance: this.appearance,
borderRadius: this.borderRadius,
autoScale: this.autoScale,
state: this.state,
customStyle: this.modelValue
};

5
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-size-input.component.html

@ -16,7 +16,7 @@
-->
<div [formGroup]="cssSizeFormGroup" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute number">
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="tb-inline-field tb-suffix-absolute number" [class.flex]="flex">
<input matInput [required]="required"
type="number" min="0" formControlName="size" placeholder="{{ 'widget-config.set' | translate }}">
<mat-icon matSuffix
@ -29,5 +29,6 @@
warning
</mat-icon>
</mat-form-field>
<tb-css-unit-select [allowEmpty]="allowEmptyUnit" width="" formControlName="unit"></tb-css-unit-select>
<tb-css-unit-select [allowEmpty]="allowEmptyUnit" [style.flex]="flex ? '1' : null"
[width]="flex ? '100%' : ''" formControlName="unit"></tb-css-unit-select>
</div>

16
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/css-size-input.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { Component, forwardRef, HostBinding, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
@ -48,6 +48,16 @@ import { isDefinedAndNotNull } from '@core/utils';
})
export class CssSizeInputComponent implements OnInit, ControlValueAccessor, Validator {
@HostBinding('style.width')
get hostWidth(): string {
return this.flex ? '100%' : null;
}
@HostBinding('style.flex')
get hostFlex(): string {
return this.flex ? '1' : null;
}
@Input()
disabled: boolean;
@ -62,6 +72,10 @@ export class CssSizeInputComponent implements OnInit, ControlValueAccessor, Vali
@coerceBoolean()
allowEmptyUnit = false;
@Input()
@coerceBoolean()
flex = false;
cssSizeFormGroup: UntypedFormGroup;
modelValue: string;

12
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -327,6 +327,9 @@ import {
import {
SliderWidgetSettingsComponent
} from '@home/components/widget/lib/settings/control/slider-widget-settings.component';
import {
ToggleButtonWidgetSettingsComponent
} from '@home/components/widget/lib/settings/button/toggle-button-widget-settings.component';
@NgModule({
declarations: [
@ -444,7 +447,8 @@ import {
ActionButtonWidgetSettingsComponent,
CommandButtonWidgetSettingsComponent,
PowerButtonWidgetSettingsComponent,
SliderWidgetSettingsComponent
SliderWidgetSettingsComponent,
ToggleButtonWidgetSettingsComponent
],
imports: [
CommonModule,
@ -567,7 +571,8 @@ import {
ActionButtonWidgetSettingsComponent,
CommandButtonWidgetSettingsComponent,
PowerButtonWidgetSettingsComponent,
SliderWidgetSettingsComponent
SliderWidgetSettingsComponent,
ToggleButtonWidgetSettingsComponent
]
})
export class WidgetSettingsModule {
@ -657,5 +662,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-action-button-widget-settings': ActionButtonWidgetSettingsComponent,
'tb-command-button-widget-settings': CommandButtonWidgetSettingsComponent,
'tb-power-button-widget-settings': PowerButtonWidgetSettingsComponent,
'tb-slider-widget-settings': SliderWidgetSettingsComponent
'tb-slider-widget-settings': SliderWidgetSettingsComponent,
'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent
};

7
ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts

@ -75,6 +75,7 @@ import { ActionButtonWidgetComponent } from '@home/components/widget/lib/button/
import { CommandButtonWidgetComponent } from '@home/components/widget/lib/button/command-button-widget.component';
import { PowerButtonWidgetComponent } from '@home/components/widget/lib/rpc/power-button-widget.component';
import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-widget.component';
import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/toggle-button-widget.component';
@NgModule({
declarations:
@ -122,7 +123,8 @@ import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-wi
ActionButtonWidgetComponent,
CommandButtonWidgetComponent,
PowerButtonWidgetComponent,
SliderWidgetComponent
SliderWidgetComponent,
ToggleButtonWidgetComponent
],
imports: [
CommonModule,
@ -174,7 +176,8 @@ import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-wi
ActionButtonWidgetComponent,
CommandButtonWidgetComponent,
PowerButtonWidgetComponent,
SliderWidgetComponent
SliderWidgetComponent,
ToggleButtonWidgetComponent
],
providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }

3
ui-ngx/src/app/shared/components/button/widget-button.component.html

@ -25,12 +25,13 @@
(mouseleave)="mousePressed = false"
[disabled]="disabled"
[class]="'tb-'+appearance.type"
[class.tb-icon-only]="appearance.showIcon && !appearance.showLabel"
[class.tb-pressed]="mousePressed"
[class.tb-pressed-state]="pressed"
[class.tb-hover-state]="hovered"
[class.tb-active-state]="activated"
[class.tb-disabled-state]="disabled"
[style.border-radius]="borderRadius"
[style.border-radius]="computedBorderRadius"
[style.pointer-events]="disableEvents ? 'none' : ''">
<div #widgetButtonContent class="tb-widget-button-content" *ngIf="appearance.showIcon || appearance.showLabel">
<tb-icon matButtonIcon *ngIf="appearance.showIcon" [style]="iconStyle">{{ appearance.icon }}</tb-icon>

4
ui-ngx/src/app/shared/components/button/widget-button.component.scss

@ -70,7 +70,11 @@ $boxShadowColorDisabled: var(--tb-widget-button-box-shadow-color-disabled, $defa
.mat-mdc-button.mat-mdc-button-base.tb-widget-button {
width: 100%;
height: 100%;
min-width: 0;
padding: 8px 12px;
&.tb-icon-only {
padding: 8px;
}
.mdc-button__label {
width: 100%;
height: 100%;

25
ui-ngx/src/app/shared/components/button/widget-button.component.ts

@ -34,11 +34,12 @@ import {
widgetButtonDefaultAppearance
} from '@shared/components/button/widget-button.models';
import { coerceBoolean } from '@shared/decorators/coercion';
import { ComponentStyle, iconStyle } from '@shared/models/widget-settings.models';
import { ComponentStyle, iconStyle, validateCssSize } from '@shared/models/widget-settings.models';
import { UtilsService } from '@core/services/utils.service';
import { ResizeObserver } from '@juggle/resize-observer';
import { Observable, of } from 'rxjs';
import { WidgetContext } from '@home/models/widget-component.models';
import { isDefinedAndNotNull, isNotEmptyStr } from '@core/utils';
const initialButtonHeight = 60;
const horizontalLayoutPadding = 24;
@ -64,6 +65,9 @@ export class WidgetButtonComponent implements OnInit, AfterViewInit, OnDestroy,
@Input()
borderRadius = '4px';
@Input()
autoScale: boolean;
@Input()
@coerceBoolean()
disabled = false;
@ -94,6 +98,8 @@ export class WidgetButtonComponent implements OnInit, AfterViewInit, OnDestroy,
iconStyle: ComponentStyle = {};
computedBorderRadius: string;
mousePressed = false;
private buttonResize$: ResizeObserver;
@ -114,6 +120,10 @@ export class WidgetButtonComponent implements OnInit, AfterViewInit, OnDestroy,
if (!change.firstChange) {
if (propName === 'appearance') {
this.updateAppearance();
} else if (propName === 'borderRadius') {
this.updateBorderRadius();
} else if (propName === 'autoScale') {
this.updateAutoScale();
}
}
}
@ -144,12 +154,22 @@ export class WidgetButtonComponent implements OnInit, AfterViewInit, OnDestroy,
if (this.appearance.showLabel) {
this.label$ = this.ctx ? this.ctx.registerLabelPattern(this.appearance.label, this.label$) : of(this.appearance.label);
}
this.updateBorderRadius();
const appearanceCss = generateWidgetButtonAppearanceCss(this.appearance);
this.appearanceCssClass = this.utils.applyCssToElement(this.renderer, this.elementRef.nativeElement,
'tb-widget-button', appearanceCss);
this.updateAutoScale();
}
private updateBorderRadius(): void {
const validatedBorderRadius = validateCssSize(this.appearance.borderRadius);
if (validatedBorderRadius) {
this.computedBorderRadius = validatedBorderRadius;
} else {
this.computedBorderRadius = this.borderRadius;
}
}
private clearAppearanceCss(): void {
if (this.appearanceCssClass) {
this.utils.clearCssElement(this.renderer, this.appearanceCssClass, this.elementRef?.nativeElement);
@ -162,7 +182,8 @@ export class WidgetButtonComponent implements OnInit, AfterViewInit, OnDestroy,
this.buttonResize$.disconnect();
}
if (this.widgetButton && this.widgetButtonContent) {
if (this.appearance.autoScale) {
const autoScale = isDefinedAndNotNull(this.autoScale) ? this.autoScale : this.appearance.autoScale;
if (autoScale) {
this.buttonResize$ = new ResizeObserver(() => {
this.onResize();
});

1
ui-ngx/src/app/shared/components/button/widget-button.models.ts

@ -98,6 +98,7 @@ export interface WidgetButtonAppearance {
icon: string;
iconSize: number;
iconSizeUnit: cssUnit;
borderRadius?: string;
mainColor: string;
backgroundColor: string;
customStyle: WidgetButtonCustomStyles;

12
ui-ngx/src/app/shared/models/widget-settings.models.ts

@ -21,7 +21,8 @@ import {
Datasource,
DatasourceData,
DatasourceType,
TargetDevice, TargetDeviceType
TargetDevice,
TargetDeviceType
} from '@shared/models/widget.models';
import { Injector } from '@angular/core';
import { DatePipe } from '@angular/common';
@ -232,6 +233,15 @@ export const resolveCssSize = (strSize?: string): [number, cssUnit] => {
return [numericSize, resolvedUnit];
};
export const validateCssSize = (strSize?: string): string | undefined => {
const resolved = resolveCssSize(strSize);
if (!!resolved[0] && !!resolved[1]) {
return cssSizeToStrSize(resolved[0], resolved[1]);
} else {
return undefined;
}
};
type ValueColorFunction = (value: any) => string;
export abstract class ColorProcessor {

14
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -5231,6 +5231,19 @@
"disabled-colors": "Disabled colors",
"button": "Button"
},
"toggle-button": {
"behavior": "Behavior",
"checked": "Checked",
"unchecked": "Unchecked",
"check": "Check",
"check-hint": "Action performed to check the component.",
"uncheck": "Uncheck",
"uncheck-hint": "Action performed to uncheck the component.",
"auto-scale": "Auto scale",
"horizontal-fill": "Horizontal fill",
"vertical-fill": "Vertical fill",
"button-appearance": "Button appearance"
},
"button": {
"layout": "Layout",
"outlined": "Outlined",
@ -5240,6 +5253,7 @@
"auto-scale": "Auto scale",
"label": "Label",
"icon": "Icon",
"border-radius": "Border radius",
"color-palette": "Color palette",
"main": "Main",
"background": "Background",

3
ui-ngx/src/form.scss

@ -651,7 +651,8 @@
&.mdc-evolution-chip--with-primary-graphic {
.mdc-evolution-chip__action--primary {
width: 100%;
padding-right: 0;
padding-left: 12px;
padding-right: 12px;
gap: 0;
.mdc-evolution-chip__graphic {
padding: 0;

Loading…
Cancel
Save