committed by
GitHub
20 changed files with 1362 additions and 11 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,101 @@ |
|||
<!-- |
|||
|
|||
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]="statusWidgetConfigForm"> |
|||
<tb-target-device formControlName="targetDevice"></tb-target-device> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.status-widget.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.rpc-state.on" |
|||
falseLabel="widgets.rpc-state.off" |
|||
stateLabel="widgets.rpc-state.on" |
|||
[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.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div> |
|||
<tb-get-value-action-settings fxFlex |
|||
panelTitle="widgets.rpc-state.disabled-state" |
|||
[valueType]="valueType.BOOLEAN" |
|||
stateLabel="widgets.rpc-state.disabled" |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="disabledState"></tb-get-value-action-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
|||
<tb-image-cards-select rowHeight="1:1" |
|||
[cols]="{columns: 3, |
|||
breakpoints: { |
|||
'lt-sm': 1, |
|||
'lt-md': 2 |
|||
}}" |
|||
label="{{ 'widgets.status-widget.layout' | translate }}" formControlName="layout"> |
|||
<tb-image-cards-select-option *ngFor="let layout of statusWidgetLayouts" |
|||
[value]="layout" |
|||
[image]="statusWidgetLayoutImageMap.get(layout)"> |
|||
{{ statusWidgetLayoutTranslationMap.get(layout) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div class="tb-form-panel-title" translate>widget-config.card-style</div> |
|||
<tb-toggle-select [(ngModel)]="cardStyleMode" |
|||
[ngModelOptions]="{ standalone: true }"> |
|||
<tb-toggle-option value="on">{{ 'widgets.status-widget.on' | translate }}</tb-toggle-option> |
|||
<tb-toggle-option value="off">{{ 'widgets.status-widget.off' | translate }}</tb-toggle-option> |
|||
</tb-toggle-select> |
|||
</div> |
|||
<tb-status-widget-state-settings |
|||
*ngIf="cardStyleMode === 'on'" |
|||
[layout]="statusWidgetConfigForm.get('layout').value" |
|||
formControlName="onState"> |
|||
</tb-status-widget-state-settings> |
|||
<tb-status-widget-state-settings |
|||
*ngIf="cardStyleMode === 'off'" |
|||
[layout]="statusWidgetConfigForm.get('layout').value" |
|||
formControlName="offState"> |
|||
</tb-status-widget-state-settings> |
|||
</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 column-lt-md"> |
|||
<div translate>widget-config.show-card-buttons</div> |
|||
<mat-chip-listbox multiple formControlName="cardButtons"> |
|||
<mat-chip-option value="fullscreen">{{ 'fullscreen.fullscreen' | translate }}</mat-chip-option> |
|||
</mat-chip-listbox> |
|||
</div> |
|||
<div class="tb-form-row space-between"> |
|||
<div>{{ 'widget-config.card-border-radius' | translate }}</div> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<input matInput formControlName="borderRadius" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
</div> |
|||
</div> |
|||
<tb-widget-actions-panel |
|||
formControlName="actions"> |
|||
</tb-widget-actions-panel> |
|||
</ng-container> |
|||
@ -0,0 +1,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.
|
|||
///
|
|||
|
|||
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 { TargetDevice, 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 { |
|||
statusWidgetDefaultSettings, |
|||
statusWidgetLayoutImages, |
|||
statusWidgetLayouts, |
|||
statusWidgetLayoutTranslations, |
|||
StatusWidgetSettings |
|||
} from '@home/components/widget/lib/indicator/status-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-status-widget-basic-config', |
|||
templateUrl: './status-widget-basic-config.component.html', |
|||
styleUrls: ['../basic-config.scss'] |
|||
}) |
|||
export class StatusWidgetBasicConfigComponent extends BasicWidgetConfigComponent { |
|||
|
|||
get targetDevice(): TargetDevice { |
|||
return this.statusWidgetConfigForm.get('targetDevice').value; |
|||
} |
|||
|
|||
statusWidgetLayouts = statusWidgetLayouts; |
|||
|
|||
statusWidgetLayoutTranslationMap = statusWidgetLayoutTranslations; |
|||
statusWidgetLayoutImageMap = statusWidgetLayoutImages; |
|||
|
|||
valueType = ValueType; |
|||
|
|||
statusWidgetConfigForm: UntypedFormGroup; |
|||
|
|||
cardStyleMode = 'on'; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected widgetConfigComponent: WidgetConfigComponent, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store, widgetConfigComponent); |
|||
} |
|||
|
|||
protected configForm(): UntypedFormGroup { |
|||
return this.statusWidgetConfigForm; |
|||
} |
|||
|
|||
protected onConfigSet(configData: WidgetConfigComponentData) { |
|||
const settings: StatusWidgetSettings = {...statusWidgetDefaultSettings, ...(configData.config.settings || {})}; |
|||
this.statusWidgetConfigForm = this.fb.group({ |
|||
targetDevice: [configData.config.targetDevice, []], |
|||
|
|||
initialState: [settings.initialState, []], |
|||
disabledState: [settings.disabledState, []], |
|||
|
|||
layout: [settings.layout, []], |
|||
|
|||
onState: [settings.onState, []], |
|||
offState: [settings.offState, []], |
|||
|
|||
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.settings = this.widgetConfig.config.settings || {}; |
|||
|
|||
this.widgetConfig.config.settings.initialState = config.initialState; |
|||
this.widgetConfig.config.settings.disabledState = config.disabledState; |
|||
|
|||
this.widgetConfig.config.settings.layout = config.layout; |
|||
|
|||
this.widgetConfig.config.settings.onState = config.onState; |
|||
this.widgetConfig.config.settings.offState = config.offState; |
|||
|
|||
this.setCardButtons(config.cardButtons, this.widgetConfig.config); |
|||
this.widgetConfig.config.borderRadius = config.borderRadius; |
|||
|
|||
this.widgetConfig.config.actions = config.actions; |
|||
return this.widgetConfig; |
|||
} |
|||
|
|||
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'); |
|||
} |
|||
|
|||
} |
|||
@ -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 #statusWidgetPanel class="tb-status-widget-panel" [style]="backgroundStyle$ | async"> |
|||
<div class="tb-status-widget-overlay" [style]="overlayStyle" [style.inset]="overlayInset"></div> |
|||
<div class="tb-status-widget-title-panel"> |
|||
<ng-container *ngTemplateOutlet="widgetTitlePanel"></ng-container> |
|||
</div> |
|||
<div #statusWidgetContent class="tb-status-widget-content" [class]="this.layout"> |
|||
<div class="tb-status-widget-icon-container"> |
|||
<tb-icon [style]="iconStyle">{{ icon }}</tb-icon> |
|||
</div> |
|||
<div class="tb-status-widget-labels-container"> |
|||
<div *ngIf="showLabel" class="tb-status-widget-label" [style]="labelStyle">{{ label$ | async }}</div> |
|||
<div *ngIf="showStatus" class="tb-status-widget-status" [style]="statusStyle">{{ status$ | async }}</div> |
|||
</div> |
|||
</div> |
|||
<mat-progress-bar class="tb-action-widget-progress" style="height: 4px;" color="accent" mode="indeterminate" *ngIf="loading$ | async"></mat-progress-bar> |
|||
</div> |
|||
@ -0,0 +1,99 @@ |
|||
/** |
|||
* 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-status-widget-panel { |
|||
width: 100%; |
|||
height: 100%; |
|||
position: relative; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 0; |
|||
> div:not(.tb-status-widget-overlay), > tb-icon { |
|||
z-index: 1; |
|||
} |
|||
.tb-status-widget-overlay { |
|||
position: absolute; |
|||
inset: 12px; |
|||
} |
|||
> div.tb-status-widget-title-panel { |
|||
position: absolute; |
|||
top: 12px; |
|||
left: 12px; |
|||
right: 12px; |
|||
z-index: 2; |
|||
} |
|||
.tb-status-widget-content { |
|||
width: 100%; |
|||
height: 100%; |
|||
padding: 16px; |
|||
position: relative; |
|||
display: flex; |
|||
flex-direction: column; |
|||
.tb-status-widget-icon-container { |
|||
display: flex; |
|||
width: 100%; |
|||
flex-direction: column; |
|||
} |
|||
.tb-status-widget-labels-container { |
|||
display: flex; |
|||
width: 100%; |
|||
flex-direction: column; |
|||
.tb-status-widget-label { |
|||
display: -webkit-box; |
|||
-webkit-line-clamp: 2; |
|||
-webkit-box-orient: vertical; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
.tb-status-widget-status { |
|||
text-transform: uppercase; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
} |
|||
&.default { |
|||
place-content: flex-start space-between; |
|||
align-items: flex-start; |
|||
} |
|||
&.center { |
|||
place-content: center flex-start; |
|||
align-items: center; |
|||
.tb-status-widget-icon-container { |
|||
flex: 1; |
|||
place-content: center; |
|||
align-items: center; |
|||
} |
|||
.tb-status-widget-labels-container { |
|||
flex-direction: column-reverse; |
|||
place-content: center flex-start; |
|||
align-items: center; |
|||
} |
|||
} |
|||
&.icon { |
|||
place-content: center; |
|||
align-items: center; |
|||
.tb-status-widget-icon-container { |
|||
flex: 1; |
|||
place-content: center; |
|||
align-items: center; |
|||
} |
|||
.tb-status-widget-labels-container { |
|||
display: none; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,242 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|||
///
|
|||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|||
/// you may not use this file except in compliance with the License.
|
|||
/// You may obtain a copy of the License at
|
|||
///
|
|||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|||
///
|
|||
/// Unless required by applicable law or agreed to in writing, software
|
|||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
/// See the License for the specific language governing permissions and
|
|||
/// limitations under the License.
|
|||
///
|
|||
|
|||
import { |
|||
AfterViewInit, |
|||
ChangeDetectorRef, |
|||
Component, |
|||
ElementRef, |
|||
OnDestroy, |
|||
OnInit, |
|||
Renderer2, ViewChild, |
|||
ViewEncapsulation |
|||
} from '@angular/core'; |
|||
import { BasicActionWidgetComponent } from '@home/components/widget/lib/action/action-widget.models'; |
|||
import { |
|||
statusWidgetDefaultSettings, |
|||
StatusWidgetLayout, |
|||
StatusWidgetSettings, StatusWidgetStateSettings |
|||
} from '@home/components/widget/lib/indicator/status-widget.models'; |
|||
import { Observable } from 'rxjs'; |
|||
import { |
|||
backgroundStyle, |
|||
ComponentStyle, |
|||
iconStyle, |
|||
overlayStyle, |
|||
textStyle |
|||
} from '@shared/models/widget-settings.models'; |
|||
import { ResizeObserver } from '@juggle/resize-observer'; |
|||
import { ImagePipe } from '@shared/pipe/image.pipe'; |
|||
import { DomSanitizer } from '@angular/platform-browser'; |
|||
import { UtilsService } from '@core/services/utils.service'; |
|||
import { ValueType } from '@shared/models/constants'; |
|||
|
|||
const initialStatusWidgetSize = 147; |
|||
|
|||
@Component({ |
|||
selector: 'tb-status-widget', |
|||
templateUrl: './status-widget.component.html', |
|||
styleUrls: ['../action/action-widget.scss', './status-widget.component.scss'], |
|||
encapsulation: ViewEncapsulation.None |
|||
}) |
|||
export class StatusWidgetComponent extends |
|||
BasicActionWidgetComponent implements OnInit, AfterViewInit, OnDestroy { |
|||
|
|||
@ViewChild('statusWidgetPanel', {static: false}) |
|||
statusWidgetPanel: ElementRef<HTMLElement>; |
|||
|
|||
@ViewChild('statusWidgetContent', {static: false}) |
|||
statusWidgetContent: ElementRef<HTMLElement>; |
|||
|
|||
settings: StatusWidgetSettings; |
|||
|
|||
backgroundStyle$: Observable<ComponentStyle>; |
|||
overlayStyle: ComponentStyle = {}; |
|||
|
|||
overlayInset = '12px'; |
|||
borderRadius = ''; |
|||
|
|||
layout: StatusWidgetLayout; |
|||
|
|||
showLabel = true; |
|||
label$: Observable<string>; |
|||
labelStyle: ComponentStyle = {}; |
|||
|
|||
showStatus = true; |
|||
status$: Observable<string>; |
|||
statusStyle: ComponentStyle = {}; |
|||
|
|||
icon = ''; |
|||
iconStyle: ComponentStyle = {}; |
|||
|
|||
private panelResize$: ResizeObserver; |
|||
|
|||
private onLabel$: Observable<string>; |
|||
private onStatus$: Observable<string>; |
|||
private onBackground$: Observable<ComponentStyle>; |
|||
private onBackgroundDisabled$: Observable<ComponentStyle>; |
|||
|
|||
private offLabel$: Observable<string>; |
|||
private offStatus$: Observable<string>; |
|||
private offBackground$: Observable<ComponentStyle>; |
|||
private offBackgroundDisabled$: Observable<ComponentStyle>; |
|||
|
|||
private state = false; |
|||
private disabled = false; |
|||
private disabledState = false; |
|||
|
|||
constructor(protected imagePipe: ImagePipe, |
|||
protected sanitizer: DomSanitizer, |
|||
private renderer: Renderer2, |
|||
private utils: UtilsService, |
|||
protected cd: ChangeDetectorRef, |
|||
private elementRef: ElementRef) { |
|||
super(cd); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
super.ngOnInit(); |
|||
this.settings = {...statusWidgetDefaultSettings, ...this.ctx.settings}; |
|||
this.layout = this.settings.layout; |
|||
|
|||
this.onLabel$ = this.ctx.registerLabelPattern(this.settings.onState.label, this.onLabel$); |
|||
this.onStatus$ = this.ctx.registerLabelPattern(this.settings.onState.status, this.onStatus$); |
|||
this.onBackground$ = backgroundStyle(this.settings.onState.background, this.imagePipe, this.sanitizer); |
|||
this.onBackgroundDisabled$ = backgroundStyle(this.settings.onState.backgroundDisabled, this.imagePipe, this.sanitizer); |
|||
|
|||
this.offLabel$ = this.ctx.registerLabelPattern(this.settings.offState.label, this.offLabel$); |
|||
this.offStatus$ = this.ctx.registerLabelPattern(this.settings.offState.status, this.offStatus$); |
|||
this.offBackground$ = backgroundStyle(this.settings.offState.background, this.imagePipe, this.sanitizer); |
|||
this.offBackgroundDisabled$ = backgroundStyle(this.settings.offState.backgroundDisabled, this.imagePipe, this.sanitizer); |
|||
|
|||
const getInitialStateSettings = |
|||
{...this.settings.initialState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.initial-state')}; |
|||
this.createValueGetter(getInitialStateSettings, ValueType.BOOLEAN, { |
|||
next: (value) => this.onState(value) |
|||
}); |
|||
|
|||
const disabledStateSettings = |
|||
{...this.settings.disabledState, actionLabel: this.ctx.translate.instant('widgets.rpc-state.disabled-state')}; |
|||
this.createValueGetter(disabledStateSettings, ValueType.BOOLEAN, { |
|||
next: (value) => this.onDisabled(value) |
|||
}); |
|||
|
|||
this.loading$.subscribe((loading) => { |
|||
this.updateDisabledState(loading || this.disabled); |
|||
}); |
|||
|
|||
this.updateStyle(this.state, this.disabled); |
|||
} |
|||
|
|||
ngAfterViewInit(): void { |
|||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'overflow', 'visible'); |
|||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'position', 'absolute'); |
|||
this.panelResize$ = new ResizeObserver(() => { |
|||
this.onResize(); |
|||
}); |
|||
this.panelResize$.observe(this.statusWidgetPanel.nativeElement); |
|||
if (this.showLabel) { |
|||
this.panelResize$.observe(this.statusWidgetPanel.nativeElement); |
|||
} |
|||
this.onResize(); |
|||
super.ngAfterViewInit(); |
|||
} |
|||
|
|||
ngOnDestroy() { |
|||
if (this.panelResize$) { |
|||
this.panelResize$.disconnect(); |
|||
} |
|||
super.ngOnDestroy(); |
|||
} |
|||
|
|||
public onInit() { |
|||
super.onInit(); |
|||
this.borderRadius = this.ctx.$widgetElement.css('borderRadius'); |
|||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius: this.borderRadius}}; |
|||
this.cd.detectChanges(); |
|||
} |
|||
|
|||
private onState(value: boolean): void { |
|||
const newState = !!value; |
|||
if (this.state !== newState) { |
|||
this.state = newState; |
|||
this.updateStyle(this.state, this.disabled || this.disabledState); |
|||
} |
|||
} |
|||
|
|||
private onDisabled(value: boolean): void { |
|||
const newDisabled = !!value; |
|||
if (this.disabled !== newDisabled) { |
|||
this.disabled = newDisabled; |
|||
this.updateDisabledState(this.disabled); |
|||
} |
|||
} |
|||
|
|||
private updateDisabledState(disabled: boolean) { |
|||
this.disabledState = disabled; |
|||
this.updateStyle(this.state, this.disabledState); |
|||
} |
|||
|
|||
private onResize() { |
|||
const panelWidth = this.statusWidgetPanel.nativeElement.getBoundingClientRect().width; |
|||
const panelHeight = this.statusWidgetPanel.nativeElement.getBoundingClientRect().height; |
|||
const targetSize = Math.min(panelWidth, panelHeight); |
|||
const scale = targetSize / initialStatusWidgetSize; |
|||
const width = initialStatusWidgetSize; |
|||
const height = initialStatusWidgetSize; |
|||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'width', width + 'px'); |
|||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'height', height + 'px'); |
|||
this.renderer.setStyle(this.statusWidgetContent.nativeElement, 'transform', `scale(${scale})`); |
|||
this.overlayInset = (Math.floor(12 * scale * 100) / 100) + 'px'; |
|||
this.cd.markForCheck(); |
|||
} |
|||
|
|||
private updateStyle(state: boolean, disabled: boolean) { |
|||
let stateSettings: StatusWidgetStateSettings; |
|||
if (state) { |
|||
this.label$ = this.onLabel$; |
|||
this.status$ = this.onStatus$; |
|||
this.backgroundStyle$ = disabled ? this.onBackgroundDisabled$ : this.onBackground$; |
|||
stateSettings = this.settings.onState; |
|||
} else { |
|||
this.label$ = this.offLabel$; |
|||
this.status$ = this.offStatus$; |
|||
this.backgroundStyle$ = disabled ? this.offBackgroundDisabled$ : this.offBackground$; |
|||
stateSettings = this.settings.offState; |
|||
} |
|||
this.showLabel = stateSettings.showLabel && this.layout !== StatusWidgetLayout.icon; |
|||
this.showStatus = stateSettings.showStatus && this.layout !== StatusWidgetLayout.icon; |
|||
this.icon = stateSettings.icon; |
|||
|
|||
const primaryColor = disabled ? stateSettings.primaryColorDisabled : stateSettings.primaryColor; |
|||
const secondaryColor = disabled ? stateSettings.secondaryColorDisabled : stateSettings.secondaryColor; |
|||
|
|||
this.labelStyle = textStyle(stateSettings.labelFont); |
|||
this.labelStyle.color = primaryColor; |
|||
|
|||
this.statusStyle = textStyle(stateSettings.statusFont); |
|||
this.statusStyle.color = secondaryColor; |
|||
|
|||
this.iconStyle = iconStyle(stateSettings.iconSize, stateSettings.iconSizeUnit); |
|||
this.iconStyle.color = primaryColor; |
|||
|
|||
this.overlayStyle = overlayStyle(disabled ? stateSettings.backgroundDisabled.overlay : stateSettings.background.overlay); |
|||
if (this.borderRadius) { |
|||
this.overlayStyle = {...this.overlayStyle, ...{borderRadius: this.borderRadius}}; |
|||
} |
|||
this.cd.detectChanges(); |
|||
} |
|||
} |
|||
@ -0,0 +1,204 @@ |
|||
///
|
|||
/// 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 { BackgroundSettings, BackgroundType, cssUnit, Font } from '@shared/models/widget-settings.models'; |
|||
|
|||
export enum StatusWidgetLayout { |
|||
default = 'default', |
|||
center = 'center', |
|||
icon = 'icon' |
|||
} |
|||
|
|||
export const statusWidgetLayouts = Object.keys(StatusWidgetLayout) as StatusWidgetLayout[]; |
|||
|
|||
export const statusWidgetLayoutTranslations = new Map<StatusWidgetLayout, string>( |
|||
[ |
|||
[StatusWidgetLayout.default, 'widgets.status-widget.layout-default'], |
|||
[StatusWidgetLayout.center, 'widgets.status-widget.layout-center'], |
|||
[StatusWidgetLayout.icon, 'widgets.status-widget.layout-icon'] |
|||
] |
|||
); |
|||
|
|||
export const statusWidgetLayoutImages = new Map<StatusWidgetLayout, string>( |
|||
[ |
|||
[StatusWidgetLayout.default, 'assets/widget/status-widget/default-layout.svg'], |
|||
[StatusWidgetLayout.center, 'assets/widget/status-widget/center-layout.svg'], |
|||
[StatusWidgetLayout.icon, 'assets/widget/status-widget/icon-layout.svg'] |
|||
] |
|||
); |
|||
|
|||
export interface StatusWidgetStateSettings { |
|||
showLabel: boolean; |
|||
label: string; |
|||
labelFont: Font; |
|||
showStatus: boolean; |
|||
status: string; |
|||
statusFont: Font; |
|||
icon: string; |
|||
iconSize: number; |
|||
iconSizeUnit: cssUnit; |
|||
primaryColor: string; |
|||
secondaryColor: string; |
|||
background: BackgroundSettings; |
|||
primaryColorDisabled: string; |
|||
secondaryColorDisabled: string; |
|||
backgroundDisabled: BackgroundSettings; |
|||
} |
|||
|
|||
export interface StatusWidgetSettings { |
|||
initialState: GetValueSettings<boolean>; |
|||
disabledState: GetValueSettings<boolean>; |
|||
layout: StatusWidgetLayout; |
|||
onState: StatusWidgetStateSettings; |
|||
offState: StatusWidgetStateSettings; |
|||
} |
|||
|
|||
export const statusWidgetDefaultSettings: StatusWidgetSettings = { |
|||
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;' |
|||
} |
|||
}, |
|||
layout: StatusWidgetLayout.default, |
|||
onState: { |
|||
showLabel: true, |
|||
label: 'Window left corner', |
|||
labelFont: { |
|||
family: 'Roboto', |
|||
size: 12, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '16px' |
|||
}, |
|||
showStatus: true, |
|||
status: 'Opened', |
|||
statusFont: { |
|||
family: 'Roboto', |
|||
size: 10, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '20px' |
|||
}, |
|||
icon: 'mdi:curtains', |
|||
iconSize: 32, |
|||
iconSizeUnit: 'px', |
|||
primaryColor: '#fff', |
|||
secondaryColor: 'rgba(255, 255, 255, 0.80)', |
|||
background: { |
|||
type: BackgroundType.color, |
|||
color: '#3F52DD', |
|||
overlay: { |
|||
enabled: false, |
|||
color: 'rgba(255,255,255,0.72)', |
|||
blur: 3 |
|||
} |
|||
}, |
|||
primaryColorDisabled: 'rgba(0, 0, 0, 0.38)', |
|||
secondaryColorDisabled: 'rgba(0, 0, 0, 0.38)', |
|||
backgroundDisabled: { |
|||
type: BackgroundType.color, |
|||
color: '#CACACA', |
|||
overlay: { |
|||
enabled: false, |
|||
color: 'rgba(255,255,255,0.72)', |
|||
blur: 3 |
|||
} |
|||
} |
|||
}, |
|||
offState: { |
|||
showLabel: true, |
|||
label: 'Window left corner', |
|||
labelFont: { |
|||
family: 'Roboto', |
|||
size: 12, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '16px' |
|||
}, |
|||
showStatus: true, |
|||
status: 'Closed', |
|||
statusFont: { |
|||
family: 'Roboto', |
|||
size: 10, |
|||
sizeUnit: 'px', |
|||
style: 'normal', |
|||
weight: '500', |
|||
lineHeight: '20px' |
|||
}, |
|||
icon: 'mdi:curtains-closed', |
|||
iconSize: 32, |
|||
iconSizeUnit: 'px', |
|||
primaryColor: 'rgba(0, 0, 0, 0.87)', |
|||
secondaryColor: 'rgba(0, 0, 0, 0.54)', |
|||
background: { |
|||
type: BackgroundType.color, |
|||
color: '#FFF', |
|||
overlay: { |
|||
enabled: false, |
|||
color: 'rgba(255,255,255,0.72)', |
|||
blur: 3 |
|||
} |
|||
}, |
|||
primaryColorDisabled: 'rgba(0, 0, 0, 0.38)', |
|||
secondaryColorDisabled: 'rgba(0, 0, 0, 0.38)', |
|||
backgroundDisabled: { |
|||
type: BackgroundType.color, |
|||
color: '#CACACA', |
|||
overlay: { |
|||
enabled: false, |
|||
color: 'rgba(255,255,255,0.72)', |
|||
blur: 3 |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
@ -0,0 +1,111 @@ |
|||
<!-- |
|||
|
|||
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]="stateSettingsFormGroup"> |
|||
<div *ngIf="layout !== StatusWidgetLayout.icon" class="tb-form-row column-xs"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showLabel"> |
|||
{{ 'widgets.status-widget.label' | 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="label" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-font-settings formControlName="labelFont" |
|||
[previewText]="stateSettingsFormGroup.get('label').value"> |
|||
</tb-font-settings> |
|||
</div> |
|||
</div> |
|||
<div *ngIf="layout !== StatusWidgetLayout.icon" class="tb-form-row column-xs"> |
|||
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showStatus"> |
|||
{{ 'widgets.status-widget.status' | 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="status" placeholder="{{ 'widget-config.set' | translate }}"> |
|||
</mat-form-field> |
|||
<tb-font-settings formControlName="statusFont" |
|||
[previewText]="stateSettingsFormGroup.get('status').value"> |
|||
</tb-font-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row"> |
|||
<div class="fixed-title-width"> |
|||
{{ 'widgets.status-widget.icon' | translate }} |
|||
</div> |
|||
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<mat-form-field appearance="outline" class="flex number" subscriptSizing="dynamic"> |
|||
<input matInput type="number" min="0" formControlName="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 |
|||
formControlName="icon"> |
|||
</tb-material-icon-select> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between" [class]="{'column-xs': layout === StatusWidgetLayout.icon, 'column-lt-md': layout !== StatusWidgetLayout.icon}"> |
|||
<div>{{ 'widgets.status-widget.color-palette' | translate }}</div> |
|||
<div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutAlign.lt-sm="space-between center" |
|||
[fxLayoutAlign.lt-md]="layout !== StatusWidgetLayout.icon ? 'space-between center': 'start center'" |
|||
style="gap: 12px;"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.primary-color-hint' | translate}}" translate>widgets.status-widget.primary</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="primaryColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider *ngIf="layout !== StatusWidgetLayout.icon" vertical fxHide.lt-md></mat-divider> |
|||
<div *ngIf="layout !== StatusWidgetLayout.icon" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.secondary-color-hint' | translate}}" translate>widgets.status-widget.secondary</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="secondaryColor"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical fxHide.lt-sm [fxHide.lt-md]="layout !== StatusWidgetLayout.icon"></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.status-widget.background</div> |
|||
<tb-background-settings formControlName="background"> |
|||
</tb-background-settings> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-row space-between" [class]="{'column-xs': layout === StatusWidgetLayout.icon, 'column-lt-md': layout !== StatusWidgetLayout.icon}"> |
|||
<div>{{ 'widgets.status-widget.disabled-color-palette' | translate }}</div> |
|||
<div fxLayout="row wrap" fxLayoutAlign="start center" fxLayoutAlign.lt-sm="space-between center" |
|||
[fxLayoutAlign.lt-md]="layout !== StatusWidgetLayout.icon ? 'space-between center': 'start center'" |
|||
style="gap: 12px;"> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.primary-color-hint' | translate}}" translate>widgets.status-widget.primary</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="primaryColorDisabled"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider *ngIf="layout !== StatusWidgetLayout.icon" vertical fxHide.lt-md></mat-divider> |
|||
<div *ngIf="layout !== StatusWidgetLayout.icon" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div tb-hint-tooltip-icon="{{'widgets.status-widget.secondary-color-hint' | translate}}" translate>widgets.status-widget.secondary</div> |
|||
<tb-color-input asBoxInput |
|||
formControlName="secondaryColorDisabled"> |
|||
</tb-color-input> |
|||
</div> |
|||
<mat-divider vertical fxHide.lt-sm [fxHide.lt-md]="layout !== StatusWidgetLayout.icon"></mat-divider> |
|||
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px"> |
|||
<div translate>widgets.status-widget.background</div> |
|||
<tb-background-settings formControlName="backgroundDisabled"> |
|||
</tb-background-settings> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</ng-container> |
|||
@ -0,0 +1,158 @@ |
|||
///
|
|||
/// 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, forwardRef, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; |
|||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; |
|||
import { merge } from 'rxjs'; |
|||
import { |
|||
StatusWidgetLayout, |
|||
StatusWidgetStateSettings |
|||
} from '@home/components/widget/lib/indicator/status-widget.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-status-widget-state-settings', |
|||
templateUrl: './status-widget-state-settings.component.html', |
|||
styleUrls: ['./../../widget-settings.scss'], |
|||
providers: [ |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => StatusWidgetStateSettingsComponent), |
|||
multi: true |
|||
} |
|||
] |
|||
}) |
|||
export class StatusWidgetStateSettingsComponent implements OnInit, OnChanges, ControlValueAccessor { |
|||
|
|||
StatusWidgetLayout = StatusWidgetLayout; |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@Input() |
|||
layout: StatusWidgetLayout; |
|||
|
|||
private modelValue: StatusWidgetStateSettings; |
|||
|
|||
private propagateChange = null; |
|||
|
|||
public stateSettingsFormGroup: UntypedFormGroup; |
|||
|
|||
constructor(private fb: UntypedFormBuilder) { |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.stateSettingsFormGroup = this.fb.group({ |
|||
showLabel: [null, []], |
|||
label: [null, []], |
|||
labelFont: [null, []], |
|||
showStatus: [null, []], |
|||
status: [null, []], |
|||
statusFont: [null, []], |
|||
icon: [null, []], |
|||
iconSize: [null, []], |
|||
iconSizeUnit: [null, []], |
|||
primaryColor: [null, []], |
|||
secondaryColor: [null, []], |
|||
background: [null, []], |
|||
primaryColorDisabled: [null, []], |
|||
secondaryColorDisabled: [null, []], |
|||
backgroundDisabled: [null, []] |
|||
}); |
|||
this.stateSettingsFormGroup.valueChanges.subscribe(() => { |
|||
this.updateModel(); |
|||
}); |
|||
merge(this.stateSettingsFormGroup.get('showLabel').valueChanges, |
|||
this.stateSettingsFormGroup.get('showStatus').valueChanges) |
|||
.subscribe(() => { |
|||
this.updateValidators(); |
|||
}); |
|||
} |
|||
|
|||
ngOnChanges(changes: SimpleChanges): void { |
|||
for (const propName of Object.keys(changes)) { |
|||
const change = changes[propName]; |
|||
if (!change.firstChange && change.currentValue !== change.previousValue) { |
|||
if (['layout'].includes(propName)) { |
|||
this.updateValidators(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(_fn: any): void { |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (isDisabled) { |
|||
this.stateSettingsFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.stateSettingsFormGroup.enable({emitEvent: false}); |
|||
this.updateValidators(); |
|||
} |
|||
} |
|||
|
|||
writeValue(value: StatusWidgetStateSettings): void { |
|||
this.modelValue = value; |
|||
this.stateSettingsFormGroup.patchValue( |
|||
value, {emitEvent: false} |
|||
); |
|||
this.updateValidators(); |
|||
} |
|||
|
|||
private updateValidators() { |
|||
if (this.layout === StatusWidgetLayout.icon) { |
|||
this.stateSettingsFormGroup.get('showLabel').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('label').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('labelFont').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('showStatus').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('status').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('statusFont').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('secondaryColor').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('secondaryColorDisabled').disable({emitEvent: false}); |
|||
} else { |
|||
this.stateSettingsFormGroup.get('showLabel').enable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('showStatus').enable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('secondaryColor').enable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('secondaryColorDisabled').enable({emitEvent: false}); |
|||
const showLabel: boolean = this.stateSettingsFormGroup.get('showLabel').value; |
|||
const showStatus: boolean = this.stateSettingsFormGroup.get('showStatus').value; |
|||
if (showLabel) { |
|||
this.stateSettingsFormGroup.get('label').enable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('labelFont').enable({emitEvent: false}); |
|||
} else { |
|||
this.stateSettingsFormGroup.get('label').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('labelFont').disable({emitEvent: false}); |
|||
} |
|||
if (showStatus) { |
|||
this.stateSettingsFormGroup.get('status').enable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('statusFont').enable({emitEvent: false}); |
|||
} else { |
|||
this.stateSettingsFormGroup.get('status').disable({emitEvent: false}); |
|||
this.stateSettingsFormGroup.get('statusFont').disable({emitEvent: false}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private updateModel() { |
|||
this.modelValue = this.stateSettingsFormGroup.getRawValue(); |
|||
this.propagateChange(this.modelValue); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
<!-- |
|||
|
|||
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]="statusWidgetSettingsForm"> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widgets.status-widget.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.rpc-state.on" |
|||
falseLabel="widgets.rpc-state.off" |
|||
stateLabel="widgets.rpc-state.on" |
|||
[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.rpc-state.disabled-state-hint' | translate}}" translate>widgets.rpc-state.disabled-state</div> |
|||
<tb-get-value-action-settings fxFlex |
|||
panelTitle="widgets.rpc-state.disabled-state" |
|||
[valueType]="valueType.BOOLEAN" |
|||
stateLabel="widgets.rpc-state.disabled" |
|||
[aliasController]="aliasController" |
|||
[targetDevice]="targetDevice" |
|||
[widgetType]="widgetType" |
|||
formControlName="disabledState"></tb-get-value-action-settings> |
|||
</div> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div class="tb-form-panel-title" translate>widget-config.appearance</div> |
|||
<tb-image-cards-select rowHeight="1:1" |
|||
[cols]="{columns: 3, |
|||
breakpoints: { |
|||
'lt-sm': 1, |
|||
'lt-md': 2 |
|||
}}" |
|||
label="{{ 'widgets.status-widget.layout' | translate }}" formControlName="layout"> |
|||
<tb-image-cards-select-option *ngFor="let layout of statusWidgetLayouts" |
|||
[value]="layout" |
|||
[image]="statusWidgetLayoutImageMap.get(layout)"> |
|||
{{ statusWidgetLayoutTranslationMap.get(layout) | translate }} |
|||
</tb-image-cards-select-option> |
|||
</tb-image-cards-select> |
|||
</div> |
|||
<div class="tb-form-panel"> |
|||
<div fxLayout="row" fxLayoutAlign="space-between center"> |
|||
<div class="tb-form-panel-title" translate>widget-config.card-style</div> |
|||
<tb-toggle-select [(ngModel)]="cardStyleMode" |
|||
[ngModelOptions]="{ standalone: true }"> |
|||
<tb-toggle-option value="on">{{ 'widgets.status-widget.on' | translate }}</tb-toggle-option> |
|||
<tb-toggle-option value="off">{{ 'widgets.status-widget.off' | translate }}</tb-toggle-option> |
|||
</tb-toggle-select> |
|||
</div> |
|||
<tb-status-widget-state-settings |
|||
*ngIf="cardStyleMode === 'on'" |
|||
[layout]="statusWidgetSettingsForm.get('layout').value" |
|||
formControlName="onState"> |
|||
</tb-status-widget-state-settings> |
|||
<tb-status-widget-state-settings |
|||
*ngIf="cardStyleMode === 'off'" |
|||
[layout]="statusWidgetSettingsForm.get('layout').value" |
|||
formControlName="offState"> |
|||
</tb-status-widget-state-settings> |
|||
</div> |
|||
</ng-container> |
|||
@ -0,0 +1,79 @@ |
|||
///
|
|||
/// 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, Injector } 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 { |
|||
statusWidgetDefaultSettings, |
|||
statusWidgetLayoutImages, |
|||
statusWidgetLayouts, |
|||
statusWidgetLayoutTranslations |
|||
} from '@home/components/widget/lib/indicator/status-widget.models'; |
|||
import { ValueType } from '@shared/models/constants'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-status-widget-settings', |
|||
templateUrl: './status-widget-settings.component.html', |
|||
styleUrls: ['./../widget-settings.scss'], |
|||
}) |
|||
export class StatusWidgetSettingsComponent extends WidgetSettingsComponent { |
|||
|
|||
get targetDevice(): TargetDevice { |
|||
return this.widgetConfig?.config?.targetDevice; |
|||
} |
|||
|
|||
get widgetType(): widgetType { |
|||
return this.widgetConfig?.widgetType; |
|||
} |
|||
|
|||
statusWidgetLayouts = statusWidgetLayouts; |
|||
|
|||
statusWidgetLayoutTranslationMap = statusWidgetLayoutTranslations; |
|||
statusWidgetLayoutImageMap = statusWidgetLayoutImages; |
|||
|
|||
valueType = ValueType; |
|||
|
|||
statusWidgetSettingsForm: UntypedFormGroup; |
|||
|
|||
cardStyleMode = 'on'; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
private $injector: Injector, |
|||
private fb: UntypedFormBuilder) { |
|||
super(store); |
|||
} |
|||
|
|||
protected settingsForm(): UntypedFormGroup { |
|||
return this.statusWidgetSettingsForm; |
|||
} |
|||
|
|||
protected defaultSettings(): WidgetSettings { |
|||
return {...statusWidgetDefaultSettings}; |
|||
} |
|||
|
|||
protected onSettingsSet(settings: WidgetSettings) { |
|||
this.statusWidgetSettingsForm = this.fb.group({ |
|||
initialState: [settings.initialState, []], |
|||
disabledState: [settings.disabledState, []], |
|||
layout: [settings.layout, []], |
|||
onState: [settings.onState, []], |
|||
offState: [settings.offState, []] |
|||
}); |
|||
} |
|||
} |
|||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
Reference in new issue