17 changed files with 1191 additions and 8 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,241 @@ |
|||||
|
///
|
||||
|
/// 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.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); |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
Reference in new issue