UI: Implement widgets config basic mode. Widgets config minor refactoring.

This commit is contained in:
Igor Kulikov 2023-06-02 19:37:16 +03:00
parent 4ab93c8c52
commit 1fe00ac46a
36 changed files with 464 additions and 303 deletions

View File

@ -1,42 +0,0 @@
<!--
Copyright © 2016-2023 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]="simpleCardWidgetConfigForm" >
<tb-datasources
[configMode]="basicMode"
formControlName="datasources">
</tb-datasources>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.appearance</div>
<div class="tb-widget-config-row space-between">
<div translate>widgets.simple-card.label-position</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="labelPosition">
<mat-option [value]="'left'">
{{ 'widgets.simple-card.label-position-left' | translate }}
</mat-option>
<mat-option [value]="'top'">
{{ 'widgets.simple-card.label-position-top' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

View File

@ -1,58 +0,0 @@
///
/// Copyright © 2016-2023 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/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
@Component({
selector: 'tb-simple-card-basic-config',
templateUrl: './simple-card-basic-config.component.html',
styleUrls: ['../basic-config.scss', '../../widget-config.scss']
})
export class SimpleCardBasicConfigComponent extends BasicWidgetConfigComponent {
simpleCardWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected configForm(): UntypedFormGroup {
return this.simpleCardWidgetConfigForm;
}
protected onConfigSet(configData: WidgetConfigComponentData) {
this.simpleCardWidgetConfigForm = this.fb.group({
datasources: [configData.config.datasources, []],
labelPosition: [configData.config.settings?.labelPosition, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.datasources = this.simpleCardWidgetConfigForm.value.datasources;
this.widgetConfig.config.actions = this.simpleCardWidgetConfigForm.value.actions;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.labelPosition = this.simpleCardWidgetConfigForm.value.labelPosition;
return this.widgetConfig;
}
}

View File

@ -24,7 +24,7 @@ import {
} from '@home/components/widget/basic-config/cards/simple-card-basic-config.component';
import {
WidgetActionsPanelComponent
} from '@home/components/widget/basic-config/action/widget-actions-panel.component';
} from '@home/components/widget/basic-config/common/widget-actions-panel.component';
@NgModule({
declarations: [

View File

@ -0,0 +1,86 @@
<!--
Copyright © 2016-2023 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]="simpleCardWidgetConfigForm">
<tb-timewindow-config-panel *ngIf="displayTimewindowConfig"
[onlyHistoryTimewindow]="onlyHistoryTimewindow()"
formControlName="timewindowConfig">
</tb-timewindow-config-panel>
<tb-datasources
[configMode]="basicMode"
hideDataKeyLabel
hideDataKeyColor
hideDataKeyUnits
hideDataKeyDecimals
formControlName="datasources">
</tb-datasources>
<div class="tb-widget-config-panel">
<div class="tb-widget-config-panel-title" translate>widget-config.appearance</div>
<div class="tb-widget-config-row">
<div translate>widgets.simple-card.label</div>
<mat-form-field fxFlex appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="label" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div translate>widgets.simple-card.label-position</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-select formControlName="labelPosition">
<mat-option [value]="'left'">
{{ 'widgets.simple-card.label-position-left' | translate }}
</mat-option>
<mat-option [value]="'top'">
{{ 'widgets.simple-card.label-position-top' | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div translate>widget-config.units-short</div>
<mat-form-field appearance="outline" class="center" subscriptSizing="dynamic">
<input matInput formControlName="units" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between">
<div translate>widget-config.decimals-short</div>
<mat-form-field appearance="outline" class="center number" subscriptSizing="dynamic">
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div>{{ 'widget-config.text-color' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="color">
</tb-color-input>
</div>
</div>
<div class="tb-widget-config-row space-between same-padding">
<div>{{ 'widget-config.background' | translate }}</div>
<div fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="16px">
<mat-divider vertical></mat-divider>
<tb-color-input asBoxInput
formControlName="backgroundColor">
</tb-color-input>
</div>
</div>
</div>
<tb-widget-actions-panel
formControlName="actions">
</tb-widget-actions-panel>
</ng-container>

View File

@ -0,0 +1,110 @@
///
/// Copyright © 2016-2023 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/widget-config.component.models';
import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import {
Datasource,
datasourcesHasAggregation,
datasourcesHasOnlyComparisonAggregation
} from '@shared/models/widget.models';
@Component({
selector: 'tb-simple-card-basic-config',
templateUrl: './simple-card-basic-config.component.html',
styleUrls: ['../basic-config.scss', '../../widget-config.scss']
})
export class SimpleCardBasicConfigComponent extends BasicWidgetConfigComponent {
public get displayTimewindowConfig(): boolean {
const datasources = this.simpleCardWidgetConfigForm.get('datasources').value;
return datasourcesHasAggregation(datasources);
}
public onlyHistoryTimewindow(): boolean {
const datasources = this.simpleCardWidgetConfigForm.get('datasources').value;
return datasourcesHasOnlyComparisonAggregation(datasources);
}
simpleCardWidgetConfigForm: UntypedFormGroup;
constructor(protected store: Store<AppState>,
private fb: UntypedFormBuilder) {
super(store);
}
protected configForm(): UntypedFormGroup {
return this.simpleCardWidgetConfigForm;
}
protected onConfigSet(configData: WidgetConfigComponentData) {
this.simpleCardWidgetConfigForm = this.fb.group({
timewindowConfig: [{
useDashboardTimewindow: configData.config.useDashboardTimewindow,
displayTimewindow: configData.config.useDashboardTimewindow,
timewindow: configData.config.timewindow
}, []],
datasources: [configData.config.datasources, []],
label: [this.getDataKeyLabel(configData.config.datasources), []],
labelPosition: [configData.config.settings?.labelPosition, []],
units: [configData.config.units, []],
decimals: [configData.config.decimals, []],
color: [configData.config.color, []],
backgroundColor: [configData.config.backgroundColor, []],
actions: [configData.config.actions || {}, []]
});
}
protected prepareOutputConfig(config: any): WidgetConfigComponentData {
this.widgetConfig.config.useDashboardTimewindow = config.timewindowConfig.useDashboardTimewindow;
this.widgetConfig.config.displayTimewindow = config.timewindowConfig.displayTimewindow;
this.widgetConfig.config.timewindow = config.timewindowConfig.timewindow;
this.widgetConfig.config.datasources = config.datasources;
this.setDataKeyLabel(config.label, this.widgetConfig.config.datasources);
this.widgetConfig.config.actions = config.actions;
this.widgetConfig.config.units = config.units;
this.widgetConfig.config.decimals = config.decimals;
this.widgetConfig.config.color = config.color;
this.widgetConfig.config.backgroundColor = config.backgroundColor;
this.widgetConfig.config.settings = this.widgetConfig.config.settings || {};
this.widgetConfig.config.settings.labelPosition = config.labelPosition;
return this.widgetConfig;
}
private getDataKeyLabel(datasources?: Datasource[]): string {
if (datasources && datasources.length) {
const dataKeys = datasources[0].dataKeys;
if (dataKeys && dataKeys.length) {
return dataKeys[0].label;
}
}
return '';
}
private setDataKeyLabel(label: string, datasources?: Datasource[]) {
if (datasources && datasources.length) {
const dataKeys = datasources[0].dataKeys;
if (dataKeys && dataKeys.length) {
dataKeys[0].label = label;
}
}
}
}

View File

@ -40,6 +40,10 @@
[widgetType]="data.widgetType"
[showPostProcessing]="data.showPostProcessing"
[callbacks]="data.callbacks"
[hideDataKeyLabel]="data.hideDataKeyLabel"
[hideDataKeyColor]="data.hideDataKeyColor"
[hideDataKeyUnits]="data.hideDataKeyUnits"
[hideDataKeyDecimals]="data.hideDataKeyDecimals"
formControlName="dataKey">
</tb-data-key-config>
</div>

View File

@ -40,6 +40,10 @@ export interface DataKeyConfigDialogData {
entityAliasId?: string;
showPostProcessing?: boolean;
callbacks?: DataKeysCallbacks;
hideDataKeyLabel: boolean;
hideDataKeyColor: boolean;
hideDataKeyUnits: boolean;
hideDataKeyDecimals: boolean;
}
@Component({

View File

@ -40,11 +40,11 @@
</mat-autocomplete>
</mat-form-field>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start center" fxLayoutAlign.xs fxLayoutGap="8px">
<mat-form-field fxFlex class="mat-block">
<mat-form-field *ngIf="!hideDataKeyLabel" fxFlex class="mat-block">
<mat-label translate>datakey.label</mat-label>
<input matInput formControlName="label" required>
</mat-form-field>
<tb-color-input fxFlex
<tb-color-input *ngIf="!hideDataKeyColor" fxFlex
required
label="{{'datakey.color' | translate}}"
icon="format_color_fill"
@ -53,11 +53,11 @@
</tb-color-input>
</div>
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="start center" fxLayoutAlign.xs fxLayoutGap="8px" *ngIf="modelValue.type !== dataKeyTypes.alarm">
<mat-form-field fxFlex>
<mat-form-field *ngIf="!hideDataKeyUnits" fxFlex>
<mat-label translate>datakey.units</mat-label>
<input matInput formControlName="units">
</mat-form-field>
<mat-form-field fxFlex>
<mat-form-field *ngIf="!hideDataKeyDecimals" fxFlex>
<mat-label translate>datakey.decimals</mat-label>
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1">
</mat-form-field>

View File

@ -52,6 +52,7 @@ import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models';
import { aggregationTranslations, AggregationType, ComparisonDuration } from '@shared/models/time/time.models';
import { genNextLabel } from '@core/utils';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-data-key-config',
@ -120,6 +121,22 @@ export class DataKeyConfigComponent extends PageComponent implements OnInit, Con
@Input()
showPostProcessing = true;
@Input()
@coerceBoolean()
hideDataKeyLabel = false;
@Input()
@coerceBoolean()
hideDataKeyColor = false;
@Input()
@coerceBoolean()
hideDataKeyUnits = false;
@Input()
@coerceBoolean()
hideDataKeyDecimals = false;
@ViewChild('keyInput') keyInput: ElementRef;
@ViewChild('funcBodyEdit') funcBodyEdit: JsFuncComponent;

View File

@ -62,16 +62,16 @@
matTooltip="{{'datakey.timeseries' | translate }}"
matTooltipPosition="above">timeline</mat-icon>
</ng-container>
<span *ngIf="key.label !== key.name">{{key.label}}</span>
<span *ngIf="!hideDataKeyLabel && key.label !== key.name">{{key.label}}</span>
</div>
<div *ngIf="key.label !== key.name" class="tb-chip-separator">: </div>
<div *ngIf="!hideDataKeyLabel && key.label !== key.name" class="tb-chip-separator">: </div>
<div class="tb-chip-label">
<strong>
<ng-container *ngTemplateOutlet="keyName; context:{key: key}"></ng-container>
</strong>
</div>
</div>
<div style="padding: 3px;">
<div *ngIf="!hideDataKeyColor" style="padding: 3px;">
<div class="tb-color-preview small box" (click)="showColorPicker(key)">
<div class="tb-color-result" [ngStyle]="{background: key.color}"></div>
</div>

View File

@ -68,6 +68,7 @@ import { AggregationType } from '@shared/models/time/time.models';
import { DndDropEvent } from 'ngx-drag-drop/lib/dnd-dropzone.directive';
import { moveItemInArray } from '@angular/cdk/drag-drop';
import { coerceBoolean } from '@shared/decorators/coercion';
import { DatasourceComponent } from '@home/components/widget/datasource.component';
@Component({
selector: 'tb-data-keys',
@ -88,6 +89,22 @@ import { coerceBoolean } from '@shared/decorators/coercion';
})
export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChanges, ErrorStateMatcher {
public get hideDataKeyLabel(): boolean {
return this.datasourceComponent.hideDataKeyLabel;
}
public get hideDataKeyColor(): boolean {
return this.datasourceComponent.hideDataKeyColor;
}
public get hideDataKeyUnits(): boolean {
return this.datasourceComponent.hideDataKeyUnits;
}
public get hideDataKeyDecimals(): boolean {
return this.datasourceComponent.hideDataKeyDecimals;
}
datasourceTypes = DatasourceType;
widgetTypes = widgetType;
dataKeyTypes = DataKeyType;
@ -188,6 +205,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
constructor(private store: Store<AppState>,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
private datasourceComponent: DatasourceComponent,
public translate: TranslateService,
private utils: UtilsService,
private dialogs: DialogService,
@ -480,7 +498,11 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange
deviceId: this.deviceId,
entityAliasId: this.entityAliasId,
showPostProcessing: this.widgetType !== widgetType.alarm,
callbacks: this.callbacks
callbacks: this.callbacks,
hideDataKeyLabel: this.hideDataKeyLabel,
hideDataKeyColor: this.hideDataKeyColor,
hideDataKeyUnits: this.hideDataKeyUnits,
hideDataKeyDecimals: this.hideDataKeyDecimals
}
}).afterClosed().subscribe((updatedDataKey) => {
if (updatedDataKey) {

View File

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { Component, forwardRef, Input, OnInit, Optional } from '@angular/core';
import {
ControlValueAccessor,
NG_VALIDATORS,
@ -41,6 +41,7 @@ import { EntityAliasSelectCallbacks } from '@home/components/alias/entity-alias-
import { FilterSelectCallbacks } from '@home/components/filter/filter-select.component.models';
import { DataKeysCallbacks } from '@home/components/widget/data-keys.component.models';
import { EntityType } from '@shared/models/entity-type.models';
import { DatasourcesComponent } from '@home/components/widget/datasources.component';
@Component({
selector: 'tb-datasource',
@ -122,6 +123,22 @@ export class DatasourceComponent implements ControlValueAccessor, OnInit, Valida
return this.widgetConfigComponent.widget;
}
public get hideDataKeyLabel(): boolean {
return this.datasourcesComponent?.hideDataKeyLabel;
}
public get hideDataKeyColor(): boolean {
return this.datasourcesComponent?.hideDataKeyColor;
}
public get hideDataKeyUnits(): boolean {
return this.datasourcesComponent?.hideDataKeyUnits;
}
public get hideDataKeyDecimals(): boolean {
return this.datasourcesComponent?.hideDataKeyDecimals;
}
@Input()
disabled: boolean;
@ -138,6 +155,8 @@ export class DatasourceComponent implements ControlValueAccessor, OnInit, Valida
private propagateChange = (_val: any) => {};
constructor(private fb: UntypedFormBuilder,
@Optional()
private datasourcesComponent: DatasourcesComponent,
private widgetConfigComponent: WidgetConfigComponent) {
}

View File

@ -41,6 +41,7 @@ import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { UtilsService } from '@core/services/utils.service';
import { DataKeysCallbacks } from '@home/components/widget/data-keys.component.models';
import { TranslateService } from '@ngx-translate/core';
import { coerceBoolean } from '@shared/decorators/coercion';
@Component({
selector: 'tb-datasources',
@ -87,6 +88,22 @@ export class DatasourcesComponent implements ControlValueAccessor, OnInit, Valid
@Input()
disabled: boolean;
@Input()
@coerceBoolean()
hideDataKeyLabel = false;
@Input()
@coerceBoolean()
hideDataKeyColor = false;
@Input()
@coerceBoolean()
hideDataKeyUnits = false;
@Input()
@coerceBoolean()
hideDataKeyDecimals = false;
@Input()
configMode: WidgetConfigMode;

View File

@ -18,6 +18,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@app/shared/shared.module';
import { AlarmFilterConfigComponent } from '@home/components/alarm/alarm-filter-config.component';
import { AlarmAssigneeSelectComponent } from '@home/components/alarm/alarm-assignee-select.component';
import { DataKeysComponent } from '@home/components/widget/data-keys.component';
import { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.component';
import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component';
@ -27,10 +28,12 @@ import { EntityAliasSelectComponent } from '@home/components/alias/entity-alias-
import { FilterSelectComponent } from '@home/components/filter/filter-select.component';
import { WidgetSettingsModule } from '@home/components/widget/lib/settings/widget-settings.module';
import { WidgetSettingsComponent } from '@home/components/widget/widget-settings.component';
import { TimewindowConfigPanelComponent } from '@home/components/widget/timewindow-config-panel.component';
@NgModule({
declarations:
[
AlarmAssigneeSelectComponent,
AlarmFilterConfigComponent,
DataKeysComponent,
DataKeyConfigDialogComponent,
@ -39,6 +42,7 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
DatasourcesComponent,
EntityAliasSelectComponent,
FilterSelectComponent,
TimewindowConfigPanelComponent,
WidgetSettingsComponent
],
imports: [
@ -47,6 +51,7 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
WidgetSettingsModule
],
exports: [
AlarmAssigneeSelectComponent,
AlarmFilterConfigComponent,
DataKeysComponent,
DataKeyConfigDialogComponent,
@ -55,6 +60,7 @@ import { WidgetSettingsComponent } from '@home/components/widget/widget-settings
DatasourcesComponent,
EntityAliasSelectComponent,
FilterSelectComponent,
TimewindowConfigPanelComponent,
WidgetSettingsComponent
]
})

View File

@ -80,6 +80,30 @@
}
}
.mat-mdc-form-field {
&.center {
.mat-mdc-text-field-wrapper.mdc-text-field--outlined {
.mat-mdc-form-field-infix {
.mdc-text-field__input {
text-align: center;
}
}
}
}
&.number {
.mat-mdc-text-field-wrapper.mdc-text-field--outlined {
padding-right: 4px;
.mat-mdc-form-field-infix {
width: 80px;
input.mdc-text-field__input[type=number]::-webkit-inner-spin-button,
input.mdc-text-field__input[type=number]::-webkit-outer-spin-button {
opacity: 1;
}
}
}
}
}
.tb-widget-config-row {
.mat-mdc-form-field {
.mat-mdc-text-field-wrapper.mdc-text-field--outlined {
@ -97,27 +121,6 @@
width: 72px;
}
}
&.center {
.mat-mdc-text-field-wrapper.mdc-text-field--outlined {
.mat-mdc-form-field-infix {
.mdc-text-field__input {
text-align: center;
}
}
}
}
&.number {
.mat-mdc-text-field-wrapper.mdc-text-field--outlined {
padding-right: 4px;
.mat-mdc-form-field-infix {
width: 80px;
input.mdc-text-field__input[type=number]::-webkit-inner-spin-button,
input.mdc-text-field__input[type=number]::-webkit-outer-spin-button {
opacity: 1;
}
}
}
}
}
}

View File

@ -16,28 +16,27 @@
import {
AfterViewInit,
Component, ComponentFactoryResolver,
Component,
ComponentFactoryResolver,
ComponentRef,
forwardRef,
Input, OnChanges,
Input,
OnChanges,
OnDestroy,
OnInit, SimpleChanges,
OnInit,
SimpleChanges,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
IRuleNodeConfigurationComponent,
RuleNodeConfiguration,
RuleNodeDefinition
} from '@shared/models/rule-node.models';
ControlValueAccessor,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
Validators
} from '@angular/forms';
import { Subscription } from 'rxjs';
import { RuleChainService } from '@core/http/rule-chain.service';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { TranslateService } from '@ngx-translate/core';
import { JsonObjectEditComponent } from '@shared/components/json-object-edit.component';
import { deepClone } from '@core/utils';
import { RuleChainType } from '@shared/models/rule-chain.models';
import { JsonFormComponent } from '@shared/components/json-form/json-form.component';
import { JsonFormComponentData } from '@shared/components/json-form/json-form-component.models';
import { IWidgetSettingsComponent, Widget, WidgetSettings } from '@shared/models/widget.models';

View File

@ -199,31 +199,12 @@
</div>
</ng-template>
<ng-template #data>
<div *ngIf="displayTimewindowConfig" [formGroup]="dataSettings" class="tb-widget-config-panel">
<div fxLayout="row" fxLayoutAlign="space-between center">
<div class="tb-widget-config-panel-title" translate>timewindow.timewindow</div>
<tb-toggle-header (valueChange)="dataSettings.get('useDashboardTimewindow').patchValue($event)" ignoreMdLgSize="true"
[options]="[
{ name: translate.instant('widget-config.use-dashboard-timewindow'), value: true},
{ name: translate.instant('widget-config.use-widget-timewindow'), value: false}
]"
[value]="dataSettings.get('useDashboardTimewindow').value" name="useDashboardTimewindow" useSelectOnMdLg="false">
</tb-toggle-header>
</div>
<div class="tb-widget-config-row">
<tb-timewindow asButton="true"
strokedButton
isEdit="true"
alwaysDisplayTypePrefix
[historyOnly]="onlyHistoryTimewindow()"
quickIntervalOnly="{{ widgetType === widgetTypes.latest }}"
aggregation="{{ widgetType === widgetTypes.timeseries }}"
formControlName="timewindow"></tb-timewindow>
<mat-slide-toggle class="mat-slide" formControlName="displayTimewindow">
{{ 'widget-config.display-timewindow' | translate }}
</mat-slide-toggle>
</div>
</div>
<ng-container *ngIf="displayTimewindowConfig" [formGroup]="dataSettings">
<tb-timewindow-config-panel
[onlyHistoryTimewindow]="onlyHistoryTimewindow()"
formControlName="timewindowConfig">
</tb-timewindow-config-panel>
</ng-container>
<div *ngIf="widgetType === widgetTypes.alarm" [formGroup]="dataSettings" class="tb-widget-config-panel" fxLayout="column" fxLayoutAlign="center">
<tb-alarm-filter-config buttonMode="false" formControlName="alarmFilterConfig"></tb-alarm-filter-config>
</div>

View File

@ -65,7 +65,7 @@ import { Observable, of, Subscription } from 'rxjs';
import {
IBasicWidgetConfigComponent,
WidgetConfigCallbacks
} from '@home/components/widget/widget-config.component.models';
} from '@home/components/widget/config/widget-config.component.models';
import {
EntityAliasDialogComponent,
EntityAliasDialogData
@ -80,8 +80,9 @@ import { Filter, singleEntityFilterFromDeviceId } from '@shared/models/query/que
import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
import { coerceBoolean } from '@shared/decorators/coercion';
import { basicWidgetConfigComponentsMap } from '@home/components/widget/basic-config/basic-widget-config.module';
import { basicWidgetConfigComponentsMap } from '@home/components/widget/config/basic/basic-widget-config.module';
import Timeout = NodeJS.Timeout;
import { TimewindowConfigData } from '@home/components/widget/timewindow-config-panel.component';
const emptySettingsSchema: JsonSchema = {
type: 'object',
@ -188,6 +189,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
private advancedSettingsSubscription: Subscription;
private actionsSettingsSubscription: Subscription;
private defaultConfigFormsType: widgetType;
constructor(protected store: Store<AppState>,
private utils: UtilsService,
private entityService: EntityService,
@ -356,12 +359,11 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
this.targetDeviceSettings = this.fb.group({});
this.advancedSettings = this.fb.group({});
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
this.dataSettings.addControl('useDashboardTimewindow', this.fb.control(true));
this.dataSettings.addControl('displayTimewindow', this.fb.control({value: true, disabled: true}));
this.dataSettings.addControl('timewindow', this.fb.control({value: null, disabled: true}));
this.dataSettings.get('useDashboardTimewindow').valueChanges.subscribe(() => {
this.updateDataSettingsEnabledState();
});
this.dataSettings.addControl('timewindowConfig', this.fb.control({
useDashboardTimewindow: true,
displayTimewindow: true,
timewindow: null
}));
if (this.widgetType === widgetType.alarm) {
this.dataSettings.addControl('alarmFilterConfig', this.fb.control(null));
}
@ -396,16 +398,19 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
writeValue(value: WidgetConfigComponentData): void {
this.modelValue = value;
this.widgetType = this.modelValue?.widgetType;
this.setupConfig();
}
private setupConfig() {
this.destroyBasicModeComponent();
this.removeChangeSubscriptions();
if (this.hasBasicModeDirective && this.widgetConfigMode === WidgetConfigMode.basic) {
this.setupBasicModeConfig();
} else {
this.setupDefaultConfig();
if (this.modelValue) {
this.destroyBasicModeComponent();
this.removeChangeSubscriptions();
if (this.hasBasicModeDirective && this.widgetConfigMode === WidgetConfigMode.basic) {
this.setupBasicModeConfig();
} else {
this.setupDefaultConfig();
}
}
}
@ -451,123 +456,116 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
private setupDefaultConfig() {
if (this.modelValue) {
if (this.widgetType !== this.modelValue.widgetType) {
this.widgetType = this.modelValue.widgetType;
this.buildForms();
if (this.defaultConfigFormsType !== this.widgetType) {
this.defaultConfigFormsType = this.widgetType;
this.buildForms();
}
this.buildHeader();
const config = this.modelValue.config;
const layout = this.modelValue.layout;
if (config) {
const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false;
this.widgetSettings.patchValue({
title: config.title,
showTitleIcon: isDefined(config.showTitleIcon) && displayWidgetTitle ? config.showTitleIcon : false,
titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '',
iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
showTitle: displayWidgetTitle,
dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
backgroundColor: config.backgroundColor,
color: config.color,
padding: config.padding,
margin: config.margin,
widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {},
widgetCss: isDefined(config.widgetCss) ? config.widgetCss : '',
titleStyle: isDefined(config.titleStyle) ? config.titleStyle : {
fontSize: '16px',
fontWeight: 400
},
pageSize: isDefined(config.pageSize) ? config.pageSize : 1024,
units: config.units,
decimals: config.decimals,
noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : ''
},
{emitEvent: false}
);
this.updateWidgetSettingsEnabledState();
this.actionsSettings.patchValue(
{
actions: config.actions || {}
},
{emitEvent: false}
);
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ?
config.useDashboardTimewindow : true;
this.dataSettings.get('timewindowConfig').patchValue({
useDashboardTimewindow,
displayTimewindow: isDefined(config.displayTimewindow) ?
config.displayTimewindow : true,
timewindow: config.timewindow
}, {emitEvent: false});
}
this.buildHeader();
const config = this.modelValue.config;
const layout = this.modelValue.layout;
if (config) {
const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false;
this.widgetSettings.patchValue({
title: config.title,
showTitleIcon: isDefined(config.showTitleIcon) && displayWidgetTitle ? config.showTitleIcon : false,
titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '',
iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
showTitle: displayWidgetTitle,
dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
backgroundColor: config.backgroundColor,
color: config.color,
padding: config.padding,
margin: config.margin,
widgetStyle: isDefined(config.widgetStyle) ? config.widgetStyle : {},
widgetCss: isDefined(config.widgetCss) ? config.widgetCss : '',
titleStyle: isDefined(config.titleStyle) ? config.titleStyle : {
fontSize: '16px',
fontWeight: 400
},
pageSize: isDefined(config.pageSize) ? config.pageSize : 1024,
units: config.units,
decimals: config.decimals,
noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : ''
},
{emitEvent: false}
);
this.updateWidgetSettingsEnabledState();
this.actionsSettings.patchValue(
{
actions: config.actions || {}
},
{emitEvent: false}
);
if (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.alarm || this.widgetType === widgetType.latest) {
const useDashboardTimewindow = isDefined(config.useDashboardTimewindow) ?
config.useDashboardTimewindow : true;
this.dataSettings.patchValue(
{ useDashboardTimewindow }, {emitEvent: false}
);
this.dataSettings.patchValue(
{ displayTimewindow: isDefined(config.displayTimewindow) ?
config.displayTimewindow : true }, {emitEvent: false}
);
this.dataSettings.patchValue(
{ timewindow: config.timewindow }, {emitEvent: false}
);
this.updateDataSettingsEnabledState();
}
if (this.modelValue.isDataEnabled) {
if (this.widgetType !== widgetType.rpc &&
this.widgetType !== widgetType.alarm &&
this.widgetType !== widgetType.static) {
this.dataSettings.patchValue({ datasources: config.datasources},
{emitEvent: false});
} else if (this.widgetType === widgetType.rpc) {
let targetDeviceAliasId: string;
if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
const aliasId = config.targetDeviceAliasIds[0];
const entityAliases = this.aliasController.getEntityAliases();
if (entityAliases[aliasId]) {
targetDeviceAliasId = entityAliases[aliasId].id;
} else {
targetDeviceAliasId = null;
}
if (this.modelValue.isDataEnabled) {
if (this.widgetType !== widgetType.rpc &&
this.widgetType !== widgetType.alarm &&
this.widgetType !== widgetType.static) {
this.dataSettings.patchValue({ datasources: config.datasources},
{emitEvent: false});
} else if (this.widgetType === widgetType.rpc) {
let targetDeviceAliasId: string;
if (config.targetDeviceAliasIds && config.targetDeviceAliasIds.length > 0) {
const aliasId = config.targetDeviceAliasIds[0];
const entityAliases = this.aliasController.getEntityAliases();
if (entityAliases[aliasId]) {
targetDeviceAliasId = entityAliases[aliasId].id;
} else {
targetDeviceAliasId = null;
}
this.targetDeviceSettings.patchValue({
targetDeviceAliasId
}, {emitEvent: false});
} else if (this.widgetType === widgetType.alarm) {
this.dataSettings.patchValue(
{ alarmFilterConfig: isDefined(config.alarmFilterConfig) ?
config.alarmFilterConfig :
{ statusList: [AlarmSearchStatus.ACTIVE], searchPropagatedAlarms: true },
alarmSource: config.alarmSource }, {emitEvent: false}
);
} else {
targetDeviceAliasId = null;
}
}
this.updateSchemaForm(config.settings);
if (layout) {
this.layoutSettings.patchValue(
{
mobileOrder: layout.mobileOrder,
mobileHeight: layout.mobileHeight,
mobileHide: layout.mobileHide,
desktopHide: layout.desktopHide
},
{emitEvent: false}
);
} else {
this.layoutSettings.patchValue(
{
mobileOrder: null,
mobileHeight: null,
mobileHide: false,
desktopHide: false
},
{emitEvent: false}
this.targetDeviceSettings.patchValue({
targetDeviceAliasId
}, {emitEvent: false});
} else if (this.widgetType === widgetType.alarm) {
this.dataSettings.patchValue(
{ alarmFilterConfig: isDefined(config.alarmFilterConfig) ?
config.alarmFilterConfig :
{ statusList: [AlarmSearchStatus.ACTIVE], searchPropagatedAlarms: true },
alarmSource: config.alarmSource }, {emitEvent: false}
);
}
}
this.createChangeSubscriptions();
this.updateSchemaForm(config.settings);
if (layout) {
this.layoutSettings.patchValue(
{
mobileOrder: layout.mobileOrder,
mobileHeight: layout.mobileHeight,
mobileHide: layout.mobileHide,
desktopHide: layout.desktopHide
},
{emitEvent: false}
);
} else {
this.layoutSettings.patchValue(
{
mobileOrder: null,
mobileHeight: null,
mobileHide: false,
desktopHide: false
},
{emitEvent: false}
);
}
}
this.createChangeSubscriptions();
}
private updateWidgetSettingsEnabledState() {
@ -595,17 +593,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}
}
private updateDataSettingsEnabledState() {
const useDashboardTimewindow: boolean = this.dataSettings.get('useDashboardTimewindow').value;
if (useDashboardTimewindow) {
this.dataSettings.get('displayTimewindow').disable({emitEvent: false});
this.dataSettings.get('timewindow').disable({emitEvent: false});
} else {
this.dataSettings.get('displayTimewindow').enable({emitEvent: false});
this.dataSettings.get('timewindow').enable({emitEvent: false});
}
}
private updateSchemaForm(settings?: any) {
const widgetSettingsFormData: JsonFormComponentData = {};
if (this.modelValue.settingsSchema && this.modelValue.settingsSchema.schema) {
@ -626,7 +613,13 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
private updateDataSettings() {
if (this.modelValue) {
if (this.modelValue.config) {
Object.assign(this.modelValue.config, this.dataSettings.value);
let data = this.dataSettings.value;
if (data.timewindowConfig) {
const timewindowConfig: TimewindowConfigData = data.timewindowConfig;
data = {...data, ...timewindowConfig};
delete data.timewindowConfig;
}
Object.assign(this.modelValue.config, data);
}
this.propagateChange(this.modelValue);
}