Merge pull request #10611 from thingsboard/feature/status-widget
Status widget
This commit is contained in:
commit
e79751d3d2
@ -2,15 +2,15 @@
|
|||||||
"widgetsBundle": {
|
"widgetsBundle": {
|
||||||
"alias": "status_indicators",
|
"alias": "status_indicators",
|
||||||
"title": "Status indicators",
|
"title": "Status indicators",
|
||||||
"image": "data:image/png;base64,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",
|
"image": "tb-image:c3RhdHVzX2luZGljYXRvcnNfc3lzdGVtX2J1bmRsZV9pbWFnZS5wbmc=:IlN0YXR1cyBpbmRpY2F0b3JzIiBzeXN0ZW0gYnVuZGxlIGltYWdl;data:image/png;base64,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",
|
||||||
"description": "Contains widgets displaying battery level and signal strength.",
|
"description": "Contains widgets displaying battery level and signal strength.",
|
||||||
"order": 9000,
|
"order": 9000,
|
||||||
"externalId": null,
|
|
||||||
"name": "Status indicators"
|
"name": "Status indicators"
|
||||||
},
|
},
|
||||||
"widgetTypeFqns": [
|
"widgetTypeFqns": [
|
||||||
"battery_level",
|
"battery_level",
|
||||||
"signal_strength",
|
"signal_strength",
|
||||||
"progress_bar"
|
"progress_bar",
|
||||||
|
"status_widget"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
File diff suppressed because one or more lines are too long
@ -114,6 +114,9 @@ import { ComparisonKeyRowComponent } from '@home/components/widget/config/basic/
|
|||||||
import {
|
import {
|
||||||
ComparisonKeysTableComponent
|
ComparisonKeysTableComponent
|
||||||
} from '@home/components/widget/config/basic/chart/comparison-keys-table.component';
|
} from '@home/components/widget/config/basic/chart/comparison-keys-table.component';
|
||||||
|
import {
|
||||||
|
StatusWidgetBasicConfigComponent
|
||||||
|
} from '@home/components/widget/config/basic/indicator/status-widget-basic-config.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -151,7 +154,8 @@ import {
|
|||||||
ToggleButtonBasicConfigComponent,
|
ToggleButtonBasicConfigComponent,
|
||||||
TimeSeriesChartBasicConfigComponent,
|
TimeSeriesChartBasicConfigComponent,
|
||||||
ComparisonKeyRowComponent,
|
ComparisonKeyRowComponent,
|
||||||
ComparisonKeysTableComponent
|
ComparisonKeysTableComponent,
|
||||||
|
StatusWidgetBasicConfigComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -191,7 +195,8 @@ import {
|
|||||||
PowerButtonBasicConfigComponent,
|
PowerButtonBasicConfigComponent,
|
||||||
SliderBasicConfigComponent,
|
SliderBasicConfigComponent,
|
||||||
ToggleButtonBasicConfigComponent,
|
ToggleButtonBasicConfigComponent,
|
||||||
TimeSeriesChartBasicConfigComponent
|
TimeSeriesChartBasicConfigComponent,
|
||||||
|
StatusWidgetBasicConfigComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class BasicWidgetConfigModule {
|
export class BasicWidgetConfigModule {
|
||||||
@ -225,5 +230,6 @@ export const basicWidgetConfigComponentsMap: {[key: string]: Type<IBasicWidgetCo
|
|||||||
'tb-power-button-basic-config': PowerButtonBasicConfigComponent,
|
'tb-power-button-basic-config': PowerButtonBasicConfigComponent,
|
||||||
'tb-slider-basic-config': SliderBasicConfigComponent,
|
'tb-slider-basic-config': SliderBasicConfigComponent,
|
||||||
'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent,
|
'tb-toggle-button-basic-config': ToggleButtonBasicConfigComponent,
|
||||||
'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent
|
'tb-time-series-chart-basic-config': TimeSeriesChartBasicConfigComponent,
|
||||||
|
'tb-status-widget-basic-config': StatusWidgetBasicConfigComponent
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -145,6 +145,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
TimeSeriesChartGridSettingsComponent
|
TimeSeriesChartGridSettingsComponent
|
||||||
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-grid-settings.component';
|
} from '@home/components/widget/lib/settings/common/chart/time-series-chart-grid-settings.component';
|
||||||
|
import {
|
||||||
|
StatusWidgetStateSettingsComponent
|
||||||
|
} from '@home/components/widget/lib/settings/common/indicator/status-widget-state-settings.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -198,6 +201,7 @@ import {
|
|||||||
TimeSeriesChartStatesPanelComponent,
|
TimeSeriesChartStatesPanelComponent,
|
||||||
TimeSeriesChartStateRowComponent,
|
TimeSeriesChartStateRowComponent,
|
||||||
TimeSeriesChartGridSettingsComponent,
|
TimeSeriesChartGridSettingsComponent,
|
||||||
|
StatusWidgetStateSettingsComponent,
|
||||||
DataKeyInputComponent,
|
DataKeyInputComponent,
|
||||||
EntityAliasInputComponent
|
EntityAliasInputComponent
|
||||||
],
|
],
|
||||||
@ -257,6 +261,7 @@ import {
|
|||||||
TimeSeriesChartStatesPanelComponent,
|
TimeSeriesChartStatesPanelComponent,
|
||||||
TimeSeriesChartStateRowComponent,
|
TimeSeriesChartStateRowComponent,
|
||||||
TimeSeriesChartGridSettingsComponent,
|
TimeSeriesChartGridSettingsComponent,
|
||||||
|
StatusWidgetStateSettingsComponent,
|
||||||
DataKeyInputComponent,
|
DataKeyInputComponent,
|
||||||
EntityAliasInputComponent
|
EntityAliasInputComponent
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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, []]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -342,6 +342,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
TimeSeriesChartWidgetSettingsComponent
|
TimeSeriesChartWidgetSettingsComponent
|
||||||
} from '@home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component';
|
} from '@home/components/widget/lib/settings/chart/time-series-chart-widget-settings.component';
|
||||||
|
import {
|
||||||
|
StatusWidgetSettingsComponent
|
||||||
|
} from '@home/components/widget/lib/settings/indicator/status-widget-settings.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -464,7 +467,8 @@ import {
|
|||||||
TimeSeriesChartKeySettingsComponent,
|
TimeSeriesChartKeySettingsComponent,
|
||||||
TimeSeriesChartLineSettingsComponent,
|
TimeSeriesChartLineSettingsComponent,
|
||||||
TimeSeriesChartBarSettingsComponent,
|
TimeSeriesChartBarSettingsComponent,
|
||||||
TimeSeriesChartWidgetSettingsComponent
|
TimeSeriesChartWidgetSettingsComponent,
|
||||||
|
StatusWidgetSettingsComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -592,7 +596,8 @@ import {
|
|||||||
TimeSeriesChartKeySettingsComponent,
|
TimeSeriesChartKeySettingsComponent,
|
||||||
TimeSeriesChartLineSettingsComponent,
|
TimeSeriesChartLineSettingsComponent,
|
||||||
TimeSeriesChartBarSettingsComponent,
|
TimeSeriesChartBarSettingsComponent,
|
||||||
TimeSeriesChartWidgetSettingsComponent
|
TimeSeriesChartWidgetSettingsComponent,
|
||||||
|
StatusWidgetSettingsComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class WidgetSettingsModule {
|
export class WidgetSettingsModule {
|
||||||
@ -685,5 +690,6 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
|
|||||||
'tb-slider-widget-settings': SliderWidgetSettingsComponent,
|
'tb-slider-widget-settings': SliderWidgetSettingsComponent,
|
||||||
'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent,
|
'tb-toggle-button-widget-settings': ToggleButtonWidgetSettingsComponent,
|
||||||
'tb-time-series-chart-key-settings': TimeSeriesChartKeySettingsComponent,
|
'tb-time-series-chart-key-settings': TimeSeriesChartKeySettingsComponent,
|
||||||
'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent
|
'tb-time-series-chart-widget-settings': TimeSeriesChartWidgetSettingsComponent,
|
||||||
|
'tb-status-widget-settings': StatusWidgetSettingsComponent
|
||||||
};
|
};
|
||||||
|
|||||||
@ -86,6 +86,7 @@ import { PowerButtonWidgetComponent } from '@home/components/widget/lib/rpc/powe
|
|||||||
import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-widget.component';
|
import { SliderWidgetComponent } from '@home/components/widget/lib/rpc/slider-widget.component';
|
||||||
import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/toggle-button-widget.component';
|
import { ToggleButtonWidgetComponent } from '@home/components/widget/lib/button/toggle-button-widget.component';
|
||||||
import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/chart/time-series-chart-widget.component';
|
import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/chart/time-series-chart-widget.component';
|
||||||
|
import { StatusWidgetComponent } from '@home/components/widget/lib/indicator/status-widget.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations:
|
declarations:
|
||||||
@ -138,7 +139,8 @@ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/char
|
|||||||
PowerButtonWidgetComponent,
|
PowerButtonWidgetComponent,
|
||||||
SliderWidgetComponent,
|
SliderWidgetComponent,
|
||||||
ToggleButtonWidgetComponent,
|
ToggleButtonWidgetComponent,
|
||||||
TimeSeriesChartWidgetComponent
|
TimeSeriesChartWidgetComponent,
|
||||||
|
StatusWidgetComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -195,7 +197,8 @@ import { TimeSeriesChartWidgetComponent } from '@home/components/widget/lib/char
|
|||||||
PowerButtonWidgetComponent,
|
PowerButtonWidgetComponent,
|
||||||
SliderWidgetComponent,
|
SliderWidgetComponent,
|
||||||
ToggleButtonWidgetComponent,
|
ToggleButtonWidgetComponent,
|
||||||
TimeSeriesChartWidgetComponent
|
TimeSeriesChartWidgetComponent,
|
||||||
|
StatusWidgetComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
|
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }
|
||||||
|
|||||||
@ -5461,6 +5461,25 @@
|
|||||||
"signal-strength-card-style": "Signal strength card style",
|
"signal-strength-card-style": "Signal strength card style",
|
||||||
"no-signal-rssi-value": "\"No signal\" rssi value"
|
"no-signal-rssi-value": "\"No signal\" rssi value"
|
||||||
},
|
},
|
||||||
|
"status-widget": {
|
||||||
|
"behavior": "Behavior",
|
||||||
|
"layout": "Layout",
|
||||||
|
"layout-default": "Default",
|
||||||
|
"layout-center": "Center",
|
||||||
|
"layout-icon": "Icon",
|
||||||
|
"on": "On",
|
||||||
|
"off": "Off",
|
||||||
|
"label": "Label",
|
||||||
|
"status": "Status",
|
||||||
|
"icon": "Icon",
|
||||||
|
"color-palette": "Color palette",
|
||||||
|
"disabled-color-palette": "Disabled color palette",
|
||||||
|
"primary": "Primary",
|
||||||
|
"primary-color-hint": "Color of icon and label",
|
||||||
|
"secondary": "Secondary",
|
||||||
|
"secondary-color-hint": "Color of status",
|
||||||
|
"background": "Background"
|
||||||
|
},
|
||||||
"chart": {
|
"chart": {
|
||||||
"common-settings": "Common settings",
|
"common-settings": "Common settings",
|
||||||
"enable-stacking-mode": "Enable stacking mode",
|
"enable-stacking-mode": "Enable stacking mode",
|
||||||
|
|||||||
20
ui-ngx/src/assets/widget/status-widget/center-layout.svg
Normal file
20
ui-ngx/src/assets/widget/status-widget/center-layout.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
20
ui-ngx/src/assets/widget/status-widget/default-layout.svg
Normal file
20
ui-ngx/src/assets/widget/status-widget/default-layout.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 19 KiB |
18
ui-ngx/src/assets/widget/status-widget/icon-layout.svg
Normal file
18
ui-ngx/src/assets/widget/status-widget/icon-layout.svg
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<svg width="141" height="140" viewBox="0 0 141 140" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g filter="url(#filter0_d_5952_143941)">
|
||||||
|
<rect x="8.66675" y="4" width="124" height="124" rx="4" fill="#3F52DD"/>
|
||||||
|
<path d="M83.0387 55.8776H58.2949V53.6282H83.0387V55.8776ZM59.4196 77.2472H63.9185C63.9185 73.8731 61.6691 71.6236 61.6691 71.6236C68.4174 67.1248 69.5421 57.0023 69.5421 57.0023H59.4196V77.2472ZM81.914 57.0023H71.7915C71.7915 57.0023 72.9162 67.1248 79.6645 71.6236C79.6645 71.6236 77.4151 73.8731 77.4151 77.2472H81.914V57.0023Z" fill="white"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<filter id="filter0_d_5952_143941" x="0.666748" y="0" width="140" height="140" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||||
|
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||||
|
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||||
|
<feOffset dy="4"/>
|
||||||
|
<feGaussianBlur stdDeviation="4"/>
|
||||||
|
<feComposite in2="hardAlpha" operator="out"/>
|
||||||
|
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
|
||||||
|
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5952_143941"/>
|
||||||
|
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5952_143941" result="shape"/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
Loading…
x
Reference in New Issue
Block a user