Commit 72cfe8e411313e55fd5b88935aa4d0559494c04b

Authored by Igor Kulikov
Committed by GitHub
2 parents 0c1d5b9c c1823ff1

Merge pull request #5452 from ArtemDzhereleiko/imp/new-widget-settings

[3.3.2] UI: New widget settings layout
@@ -49,7 +49,6 @@ import { EntityAliasSelectComponent } from '@home/components/alias/entity-alias- @@ -49,7 +49,6 @@ import { EntityAliasSelectComponent } from '@home/components/alias/entity-alias-
49 import { DataKeysComponent } from '@home/components/widget/data-keys.component'; 49 import { DataKeysComponent } from '@home/components/widget/data-keys.component';
50 import { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.component'; 50 import { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.component';
51 import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component'; 51 import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component';
52 -import { LegendConfigPanelComponent } from '@home/components/widget/legend-config-panel.component';  
53 import { LegendConfigComponent } from '@home/components/widget/legend-config.component'; 52 import { LegendConfigComponent } from '@home/components/widget/legend-config.component';
54 import { ManageWidgetActionsComponent } from '@home/components/widget/action/manage-widget-actions.component'; 53 import { ManageWidgetActionsComponent } from '@home/components/widget/action/manage-widget-actions.component';
55 import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component'; 54 import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component';
@@ -182,7 +181,6 @@ import { DeviceProfileCommonModule } from '@home/components/profile/device/commo @@ -182,7 +181,6 @@ import { DeviceProfileCommonModule } from '@home/components/profile/device/commo
182 DataKeysComponent, 181 DataKeysComponent,
183 DataKeyConfigComponent, 182 DataKeyConfigComponent,
184 DataKeyConfigDialogComponent, 183 DataKeyConfigDialogComponent,
185 - LegendConfigPanelComponent,  
186 LegendConfigComponent, 184 LegendConfigComponent,
187 ManageWidgetActionsComponent, 185 ManageWidgetActionsComponent,
188 WidgetActionDialogComponent, 186 WidgetActionDialogComponent,
1 -<!--  
2 -  
3 - Copyright © 2016-2021 The Thingsboard Authors  
4 -  
5 - Licensed under the Apache License, Version 2.0 (the "License");  
6 - you may not use this file except in compliance with the License.  
7 - You may obtain a copy of the License at  
8 -  
9 - http://www.apache.org/licenses/LICENSE-2.0  
10 -  
11 - Unless required by applicable law or agreed to in writing, software  
12 - distributed under the License is distributed on an "AS IS" BASIS,  
13 - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
14 - See the License for the specific language governing permissions and  
15 - limitations under the License.  
16 -  
17 --->  
18 -<form [formGroup]="legendConfigForm">  
19 - <fieldset [disabled]="(isLoading$ | async)">  
20 - <div class="mat-content" style="height: 100%;">  
21 - <div class="mat-padding">  
22 - <section fxLayout="column">  
23 - <mat-form-field>  
24 - <mat-label translate>legend.direction</mat-label>  
25 - <mat-select matInput formControlName="direction" style="min-width: 150px;">  
26 - <mat-option *ngFor="let direction of legendDirections" [value]="direction">  
27 - {{ legendDirectionTranslations.get(legendDirection[direction]) | translate }}  
28 - </mat-option>  
29 - </mat-select>  
30 - </mat-form-field>  
31 - <mat-form-field>  
32 - <mat-label translate>legend.position</mat-label>  
33 - <mat-select matInput formControlName="position" style="min-width: 150px;">  
34 - <mat-option *ngFor="let pos of legendPositions" [value]="pos"  
35 - [disabled]="legendConfigForm.get('direction').value === legendDirection.row &&  
36 - (pos === legendPosition.left || pos === legendPosition.right)">  
37 - {{ legendPositionTranslations.get(legendPosition[pos]) | translate }}  
38 - </mat-option>  
39 - </mat-select>  
40 - </mat-form-field>  
41 - <mat-checkbox formControlName="sortDataKeys">  
42 - {{ 'legend.sort-legend' | translate }}  
43 - </mat-checkbox>  
44 - <mat-checkbox formControlName="showMin">  
45 - {{ 'legend.show-min' | translate }}  
46 - </mat-checkbox>  
47 - <mat-checkbox formControlName="showMax">  
48 - {{ 'legend.show-max' | translate }}  
49 - </mat-checkbox>  
50 - <mat-checkbox formControlName="showAvg">  
51 - {{ 'legend.show-avg' | translate }}  
52 - </mat-checkbox>  
53 - <mat-checkbox formControlName="showTotal">  
54 - {{ 'legend.show-total' | translate }}  
55 - </mat-checkbox>  
56 - </section>  
57 - </div>  
58 - </div>  
59 - </fieldset>  
60 -</form>  
1 -/**  
2 - * Copyright © 2016-2021 The Thingsboard Authors  
3 - *  
4 - * Licensed under the Apache License, Version 2.0 (the "License");  
5 - * you may not use this file except in compliance with the License.  
6 - * You may obtain a copy of the License at  
7 - *  
8 - * http://www.apache.org/licenses/LICENSE-2.0  
9 - *  
10 - * Unless required by applicable law or agreed to in writing, software  
11 - * distributed under the License is distributed on an "AS IS" BASIS,  
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 - * See the License for the specific language governing permissions and  
14 - * limitations under the License.  
15 - */  
16 -:host {  
17 - width: 100%;  
18 - height: 100%;  
19 - form,  
20 - fieldset {  
21 - height: 100%;  
22 - }  
23 -  
24 - .mat-content {  
25 - overflow: hidden;  
26 - background-color: #fff;  
27 - }  
28 -  
29 - .mat-padding {  
30 - padding: 16px;  
31 - }  
32 -}  
1 -///  
2 -/// Copyright © 2016-2021 The Thingsboard Authors  
3 -///  
4 -/// Licensed under the Apache License, Version 2.0 (the "License");  
5 -/// you may not use this file except in compliance with the License.  
6 -/// You may obtain a copy of the License at  
7 -///  
8 -/// http://www.apache.org/licenses/LICENSE-2.0  
9 -///  
10 -/// Unless required by applicable law or agreed to in writing, software  
11 -/// distributed under the License is distributed on an "AS IS" BASIS,  
12 -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
13 -/// See the License for the specific language governing permissions and  
14 -/// limitations under the License.  
15 -///  
16 -  
17 -import { Component, Inject, InjectionToken, OnInit, ViewContainerRef } from '@angular/core';  
18 -import { OverlayRef } from '@angular/cdk/overlay';  
19 -import { PageComponent } from '@shared/components/page.component';  
20 -import { Store } from '@ngrx/store';  
21 -import { AppState } from '@core/core.state';  
22 -import { FormBuilder, FormGroup } from '@angular/forms';  
23 -import {  
24 - LegendConfig,  
25 - LegendDirection,  
26 - legendDirectionTranslationMap,  
27 - LegendPosition,  
28 - legendPositionTranslationMap  
29 -} from '@shared/models/widget.models';  
30 -  
31 -export const LEGEND_CONFIG_PANEL_DATA = new InjectionToken<any>('LegendConfigPanelData');  
32 -  
33 -export interface LegendConfigPanelData {  
34 - legendConfig: LegendConfig;  
35 - legendConfigUpdated: (legendConfig: LegendConfig) => void;  
36 -}  
37 -  
38 -@Component({  
39 - selector: 'tb-legend-config-panel',  
40 - templateUrl: './legend-config-panel.component.html',  
41 - styleUrls: ['./legend-config-panel.component.scss']  
42 -})  
43 -export class LegendConfigPanelComponent extends PageComponent implements OnInit {  
44 -  
45 - legendConfigForm: FormGroup;  
46 -  
47 - legendDirection = LegendDirection;  
48 -  
49 - legendDirections = Object.keys(LegendDirection);  
50 -  
51 - legendDirectionTranslations = legendDirectionTranslationMap;  
52 -  
53 - legendPosition = LegendPosition;  
54 -  
55 - legendPositions = Object.keys(LegendPosition);  
56 -  
57 - legendPositionTranslations = legendPositionTranslationMap;  
58 -  
59 - constructor(@Inject(LEGEND_CONFIG_PANEL_DATA) public data: LegendConfigPanelData,  
60 - public overlayRef: OverlayRef,  
61 - protected store: Store<AppState>,  
62 - public fb: FormBuilder,  
63 - public viewContainerRef: ViewContainerRef) {  
64 - super(store);  
65 - }  
66 -  
67 - ngOnInit(): void {  
68 - this.legendConfigForm = this.fb.group({  
69 - direction: [this.data.legendConfig.direction, []],  
70 - position: [this.data.legendConfig.position, []],  
71 - sortDataKeys: [this.data.legendConfig.sortDataKeys, []],  
72 - showMin: [this.data.legendConfig.showMin, []],  
73 - showMax: [this.data.legendConfig.showMax, []],  
74 - showAvg: [this.data.legendConfig.showAvg, []],  
75 - showTotal: [this.data.legendConfig.showTotal, []]  
76 - });  
77 - this.legendConfigForm.get('direction').valueChanges.subscribe((direction: LegendDirection) => {  
78 - this.onDirectionChanged(direction);  
79 - });  
80 - this.onDirectionChanged(this.data.legendConfig.direction);  
81 - this.legendConfigForm.valueChanges.subscribe(() => {  
82 - this.update();  
83 - });  
84 - }  
85 -  
86 - private onDirectionChanged(direction: LegendDirection) {  
87 - if (direction === LegendDirection.row) {  
88 - let position: LegendPosition = this.legendConfigForm.get('position').value;  
89 - if (position !== LegendPosition.bottom && position !== LegendPosition.top) {  
90 - position = LegendPosition.bottom;  
91 - }  
92 - this.legendConfigForm.patchValue(  
93 - {  
94 - position  
95 - }, {emitEvent: false}  
96 - );  
97 - }  
98 - }  
99 -  
100 - update() {  
101 - const newLegendConfig: LegendConfig = this.legendConfigForm.value;  
102 - this.data.legendConfigUpdated(newLegendConfig);  
103 - }  
104 -  
105 -}  
@@ -15,9 +15,43 @@ @@ -15,9 +15,43 @@
15 limitations under the License. 15 limitations under the License.
16 16
17 --> 17 -->
18 -<button cdkOverlayOrigin #legendConfigPanelOrigin="cdkOverlayOrigin" [disabled]="disabled"  
19 - type="button"  
20 - mat-button mat-raised-button color="primary" (click)="openEditMode()">  
21 - <mat-icon class="material-icons">toc</mat-icon>  
22 - <span translate>legend.settings</span>  
23 -</button> 18 +<form [formGroup]="legendConfigForm">
  19 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
  20 + fxLayoutGap="8px">
  21 + <mat-form-field fxFlex>
  22 + <mat-label translate>legend.direction</mat-label>
  23 + <mat-select matInput formControlName="direction">
  24 + <mat-option *ngFor="let direction of legendDirections" [value]="direction">
  25 + {{ legendDirectionTranslations.get(legendDirection[direction]) | translate }}
  26 + </mat-option>
  27 + </mat-select>
  28 + </mat-form-field>
  29 + <mat-form-field fxFlex>
  30 + <mat-label translate>legend.position</mat-label>
  31 + <mat-select matInput formControlName="position">
  32 + <mat-option *ngFor="let pos of legendPositions" [value]="pos"
  33 + [disabled]="legendConfigForm.get('direction').value === legendDirection.row &&
  34 + (pos === legendPosition.left || pos === legendPosition.right)">
  35 + {{ legendPositionTranslations.get(legendPosition[pos]) | translate }}
  36 + </mat-option>
  37 + </mat-select>
  38 + </mat-form-field>
  39 + </div>
  40 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row wrap" fxLayoutAlign="space-between center" fxLayoutGap="8px">
  41 + <mat-checkbox formControlName="showMin" fxFlex="48">
  42 + {{ 'legend.show-min' | translate }}
  43 + </mat-checkbox>
  44 + <mat-checkbox formControlName="showMax" fxFlex="48">
  45 + {{ 'legend.show-max' | translate }}
  46 + </mat-checkbox>
  47 + <mat-checkbox formControlName="showAvg" fxFlex="48">
  48 + {{ 'legend.show-avg' | translate }}
  49 + </mat-checkbox>
  50 + <mat-checkbox formControlName="showTotal" fxFlex="48">
  51 + {{ 'legend.show-total' | translate }}
  52 + </mat-checkbox>
  53 + <mat-checkbox formControlName="sortDataKeys" fxFlex="48">
  54 + {{ 'legend.sort-legend' | translate }}
  55 + </mat-checkbox>
  56 + </div>
  57 +</form>
@@ -14,32 +14,17 @@ @@ -14,32 +14,17 @@
14 /// limitations under the License. 14 /// limitations under the License.
15 /// 15 ///
16 16
  17 +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
  19 +import { isDefined } from '@core/utils';
17 import { 20 import {
18 - Component,  
19 - forwardRef,  
20 - Inject,  
21 - Injector,  
22 - Input,  
23 - OnDestroy,  
24 - OnInit,  
25 - StaticProvider,  
26 - ViewChild,  
27 - ViewContainerRef  
28 -} from '@angular/core';  
29 -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';  
30 -import { DOCUMENT } from '@angular/common';  
31 -import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';  
32 -import { ComponentPortal } from '@angular/cdk/portal';  
33 -import { MediaBreakpoints } from '@shared/models/constants';  
34 -import { BreakpointObserver } from '@angular/cdk/layout';  
35 -import { WINDOW } from '@core/services/window.service';  
36 -import { deepClone } from '@core/utils';  
37 -import { LegendConfig } from '@shared/models/widget.models';  
38 -import {  
39 - LEGEND_CONFIG_PANEL_DATA,  
40 - LegendConfigPanelComponent,  
41 - LegendConfigPanelData  
42 -} from '@home/components/widget/legend-config-panel.component'; 21 + LegendConfig,
  22 + LegendDirection,
  23 + legendDirectionTranslationMap,
  24 + LegendPosition,
  25 + legendPositionTranslationMap
  26 +} from '@shared/models/widget.models';
  27 +import { Subscription } from 'rxjs';
43 28
44 // @dynamic 29 // @dynamic
45 @Component({ 30 @Component({
@@ -58,105 +43,60 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc @@ -58,105 +43,60 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
58 43
59 @Input() disabled: boolean; 44 @Input() disabled: boolean;
60 45
61 - @ViewChild('legendConfigPanelOrigin') legendConfigPanelOrigin: CdkOverlayOrigin;  
62 -  
63 - innerValue: LegendConfig; 46 + legendConfigForm: FormGroup;
  47 + legendDirection = LegendDirection;
  48 + legendDirections = Object.keys(LegendDirection);
  49 + legendDirectionTranslations = legendDirectionTranslationMap;
  50 + legendPosition = LegendPosition;
  51 + legendPositions = Object.keys(LegendPosition);
  52 + legendPositionTranslations = legendPositionTranslationMap;
64 53
  54 + private legendSettingsFormChanges$: Subscription;
  55 + private legendSettingsFormDirectionChanges$: Subscription;
65 private propagateChange = (_: any) => {}; 56 private propagateChange = (_: any) => {};
66 57
67 - constructor(private overlay: Overlay,  
68 - public viewContainerRef: ViewContainerRef,  
69 - public breakpointObserver: BreakpointObserver,  
70 - @Inject(DOCUMENT) private document: Document,  
71 - @Inject(WINDOW) private window: Window) { 58 + constructor(private fb: FormBuilder) {
72 } 59 }
73 60
74 ngOnInit(): void { 61 ngOnInit(): void {
  62 + this.legendConfigForm = this.fb.group({
  63 + direction: [null, []],
  64 + position: [null, []],
  65 + sortDataKeys: [null, []],
  66 + showMin: [null, []],
  67 + showMax: [null, []],
  68 + showAvg: [null, []],
  69 + showTotal: [null, []]
  70 + });
  71 + this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges
  72 + .subscribe((direction: LegendDirection) => {
  73 + this.onDirectionChanged(direction);
  74 + });
  75 + this.legendSettingsFormChanges$ = this.legendConfigForm.valueChanges.subscribe(
  76 + () => this.legendConfigUpdated()
  77 + );
75 } 78 }
76 79
77 - ngOnDestroy(): void {  
78 - }  
79 -  
80 - openEditMode() {  
81 - if (this.disabled) {  
82 - return;  
83 - }  
84 - const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']);  
85 - const position = this.overlay.position();  
86 - const config = new OverlayConfig({  
87 - panelClass: 'tb-legend-config-panel',  
88 - backdropClass: 'cdk-overlay-transparent-backdrop',  
89 - hasBackdrop: isGtSm,  
90 - });  
91 - if (isGtSm) {  
92 - config.minWidth = '220px';  
93 - config.maxHeight = '300px';  
94 - const panelHeight = 220;  
95 - const panelWidth = 220;  
96 - const el = this.legendConfigPanelOrigin.elementRef.nativeElement;  
97 - const offset = el.getBoundingClientRect();  
98 - const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0;  
99 - const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0;  
100 - const bottomY = offset.bottom - scrollTop;  
101 - const leftX = offset.left - scrollLeft;  
102 - let originX;  
103 - let originY;  
104 - let overlayX;  
105 - let overlayY;  
106 - const wHeight = this.document.documentElement.clientHeight;  
107 - const wWidth = this.document.documentElement.clientWidth;  
108 - if (bottomY + panelHeight > wHeight) {  
109 - originY = 'top';  
110 - overlayY = 'bottom';  
111 - } else {  
112 - originY = 'bottom';  
113 - overlayY = 'top'; 80 + private onDirectionChanged(direction: LegendDirection) {
  81 + if (direction === LegendDirection.row) {
  82 + let position: LegendPosition = this.legendConfigForm.get('position').value;
  83 + if (position !== LegendPosition.bottom && position !== LegendPosition.top) {
  84 + position = LegendPosition.bottom;
114 } 85 }
115 - if (leftX + panelWidth > wWidth) {  
116 - originX = 'end';  
117 - overlayX = 'end';  
118 - } else {  
119 - originX = 'start';  
120 - overlayX = 'start';  
121 - }  
122 - const connectedPosition: ConnectedPosition = {  
123 - originX,  
124 - originY,  
125 - overlayX,  
126 - overlayY  
127 - };  
128 - config.positionStrategy = position.flexibleConnectedTo(this.legendConfigPanelOrigin.elementRef)  
129 - .withPositions([connectedPosition]);  
130 - } else {  
131 - config.minWidth = '100%';  
132 - config.minHeight = '100%';  
133 - config.positionStrategy = position.global().top('0%').left('0%')  
134 - .right('0%').bottom('0%'); 86 + this.legendConfigForm.patchValue({position}, {emitEvent: false}
  87 + );
135 } 88 }
136 -  
137 - const overlayRef = this.overlay.create(config);  
138 -  
139 - overlayRef.backdropClick().subscribe(() => {  
140 - overlayRef.dispose();  
141 - });  
142 -  
143 - const injector = this._createLegendConfigPanelInjector(  
144 - overlayRef,  
145 - {  
146 - legendConfig: deepClone(this.innerValue),  
147 - legendConfigUpdated: this.legendConfigUpdated.bind(this)  
148 - }  
149 - );  
150 -  
151 - overlayRef.attach(new ComponentPortal(LegendConfigPanelComponent, this.viewContainerRef, injector));  
152 } 89 }
153 90
154 - private _createLegendConfigPanelInjector(overlayRef: OverlayRef, data: LegendConfigPanelData): Injector {  
155 - const providers: StaticProvider[] = [  
156 - {provide: LEGEND_CONFIG_PANEL_DATA, useValue: data},  
157 - {provide: OverlayRef, useValue: overlayRef}  
158 - ];  
159 - return Injector.create({parent: this.viewContainerRef.injector, providers}); 91 + ngOnDestroy(): void {
  92 + if (this.legendSettingsFormDirectionChanges$) {
  93 + this.legendSettingsFormDirectionChanges$.unsubscribe();
  94 + this.legendSettingsFormDirectionChanges$ = null;
  95 + }
  96 + if (this.legendSettingsFormChanges$) {
  97 + this.legendSettingsFormChanges$.unsubscribe();
  98 + this.legendSettingsFormChanges$ = null;
  99 + }
160 } 100 }
161 101
162 registerOnChange(fn: any): void { 102 registerOnChange(fn: any): void {
@@ -168,14 +108,29 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc @@ -168,14 +108,29 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
168 108
169 setDisabledState(isDisabled: boolean): void { 109 setDisabledState(isDisabled: boolean): void {
170 this.disabled = isDisabled; 110 this.disabled = isDisabled;
  111 + if (this.disabled) {
  112 + this.legendConfigForm.disable({emitEvent: false});
  113 + } else {
  114 + this.legendConfigForm.enable({emitEvent: false});
  115 + }
171 } 116 }
172 117
173 - writeValue(obj: LegendConfig): void {  
174 - this.innerValue = obj; 118 + writeValue(legendConfig: LegendConfig): void {
  119 + if (legendConfig) {
  120 + this.legendConfigForm.patchValue({
  121 + direction: legendConfig.direction,
  122 + position: legendConfig.position,
  123 + sortDataKeys: isDefined(legendConfig.sortDataKeys) ? legendConfig.sortDataKeys : false,
  124 + showMin: isDefined(legendConfig.showMin) ? legendConfig.showMin : false,
  125 + showMax: isDefined(legendConfig.showMax) ? legendConfig.showMax : false,
  126 + showAvg: isDefined(legendConfig.showAvg) ? legendConfig.showAvg : false,
  127 + showTotal: isDefined(legendConfig.showTotal) ? legendConfig.showTotal : false
  128 + }, {emitEvent: false});
  129 + }
  130 + this.onDirectionChanged(legendConfig.direction);
175 } 131 }
176 132
177 - private legendConfigUpdated(legendConfig: LegendConfig) {  
178 - this.innerValue = legendConfig;  
179 - this.propagateChange(this.innerValue); 133 + private legendConfigUpdated() {
  134 + this.propagateChange(this.legendConfigForm.value);
180 } 135 }
181 } 136 }
@@ -82,222 +82,246 @@ @@ -82,222 +82,246 @@
82 </mat-checkbox> 82 </mat-checkbox>
83 </div> 83 </div>
84 </div> 84 </div>
85 - <mat-expansion-panel class="tb-datasources" *ngIf="widgetType !== widgetTypes.rpc &&  
86 - widgetType !== widgetTypes.alarm &&  
87 - modelValue?.isDataEnabled" [expanded]="true">  
88 - <mat-expansion-panel-header>  
89 - <mat-panel-title fxLayout="column">  
90 - <div class="tb-panel-title" translate>widget-config.datasources</div>  
91 - <div *ngIf="modelValue?.typeParameters && modelValue?.typeParameters.maxDatasources > -1"  
92 - class="tb-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div>  
93 - </mat-panel-title>  
94 - </mat-expansion-panel-header>  
95 - <div *ngIf="datasourcesFormArray().length === 0; else datasourcesTemplate">  
96 - <span translate fxLayoutAlign="center center"  
97 - class="tb-prompt">datasource.add-datasource-prompt</span>  
98 - </div>  
99 - <ng-template #datasourcesTemplate>  
100 - <div fxFlex fxLayout="row" fxLayoutAlign="start center">  
101 - <span style="width: 60px;"></span>  
102 - <div fxFlex fxLayout="row" fxLayoutAlign="start center"  
103 - style="padding: 0 0 0 10px; margin: 5px;">  
104 - <span translate style="min-width: 120px;">widget-config.datasource-type</span>  
105 - <span fxHide fxShow.gt-sm translate fxFlex  
106 - style="padding-left: 10px;">widget-config.datasource-parameters</span>  
107 - <span style="min-width: 40px;"></span>  
108 - </div> 85 + <mat-accordion multi>
  86 + <mat-expansion-panel class="tb-datasources" *ngIf="widgetType !== widgetTypes.rpc &&
  87 + widgetType !== widgetTypes.alarm &&
  88 + modelValue?.isDataEnabled" [expanded]="true">
  89 + <mat-expansion-panel-header>
  90 + <mat-panel-title fxLayout="column">
  91 + <div class="tb-panel-title" translate>widget-config.datasources</div>
  92 + <div *ngIf="modelValue?.typeParameters && modelValue?.typeParameters.maxDatasources > -1"
  93 + class="tb-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div>
  94 + </mat-panel-title>
  95 + </mat-expansion-panel-header>
  96 + <div *ngIf="datasourcesFormArray().length === 0; else datasourcesTemplate">
  97 + <span translate fxLayoutAlign="center center"
  98 + class="tb-prompt">datasource.add-datasource-prompt</span>
109 </div> 99 </div>
110 - <div style="overflow: auto; padding-bottom: 15px;">  
111 - <mat-list dndDropzone dndEffectAllowed="move"  
112 - (dndDrop)="onDatasourceDrop($event)"  
113 - [dndDisableIf]="disabled" formArrayName="datasources">  
114 - <mat-list-item dndPlaceholderRef  
115 - class="dndPlaceholder">  
116 - </mat-list-item>  
117 - <mat-list-item *ngFor="let datasourceControl of datasourcesFormArray().controls; let $index = index;"  
118 - [dndDraggable]="datasourceControl.value"  
119 - (dndMoved)="dndDatasourceMoved($index)"  
120 - [dndDisableIf]="disabled"  
121 - dndEffectAllowed="move">  
122 - <div fxFlex fxLayout="row" fxLayoutAlign="start center">  
123 - <div style="width: 60px;">  
124 - <button *ngIf="!disabled" mat-icon-button color="primary"  
125 - class="handle"  
126 - style="min-width: 40px; margin: 0"  
127 - dndHandle  
128 - matTooltip="{{ 'action.drag' | translate }}"  
129 - matTooltipPosition="above">  
130 - <mat-icon>drag_handle</mat-icon>  
131 - </button>  
132 - <span>{{$index + 1}}.</span>  
133 - </div>  
134 - <div class="mat-elevation-z4" fxFlex  
135 - fxLayout="row"  
136 - fxLayoutAlign="start center"  
137 - style="padding: 0 0 0 10px; margin: 5px;">  
138 - <section fxFlex  
139 - fxLayout="column"  
140 - fxLayoutAlign="center"  
141 - fxLayout.gt-sm="row"  
142 - fxLayoutAlign.gt-sm="start center">  
143 - <mat-form-field class="tb-datasource-type">  
144 - <mat-select [formControl]="datasourceControl.get('type')">  
145 - <mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType">  
146 - {{ datasourceTypesTranslations.get(datasourceType) | translate }}  
147 - </mat-option>  
148 - </mat-select>  
149 - </mat-form-field>  
150 - <section fxLayout="column" class="tb-datasource" [ngSwitch]="datasourceControl.get('type').value">  
151 - <ng-template [ngSwitchCase]="datasourceType.function">  
152 - <mat-form-field floatLabel="always"  
153 - class="tb-datasource-name" style="min-width: 200px;">  
154 - <mat-label></mat-label>  
155 - <input matInput  
156 - placeholder="{{ 'datasource.label' | translate }}"  
157 - [formControl]="datasourceControl.get('name')">  
158 - </mat-form-field>  
159 - </ng-template>  
160 - <ng-template [ngSwitchCase]="datasourceControl.get('type').value === datasourceType.entity ||  
161 - datasourceControl.get('type').value === datasourceType.entityCount ? datasourceControl.get('type').value : ''">  
162 - <tb-entity-alias-select  
163 - [showLabel]="true"  
164 - [tbRequired]="true"  
165 - [aliasController]="aliasController"  
166 - [formControl]="datasourceControl.get('entityAliasId')"  
167 - [callbacks]="widgetConfigCallbacks">  
168 - </tb-entity-alias-select>  
169 - <tb-filter-select  
170 - [showLabel]="true"  
171 - [aliasController]="aliasController"  
172 - [formControl]="datasourceControl.get('filterId')"  
173 - [callbacks]="widgetConfigCallbacks">  
174 - </tb-filter-select>  
175 - <mat-form-field *ngIf="datasourceControl.get('type').value === datasourceType.entityCount"  
176 - floatLabel="always"  
177 - class="tb-datasource-name no-border-top" style="min-width: 200px;">  
178 - <mat-label></mat-label>  
179 - <input matInput  
180 - placeholder="{{ 'datasource.label' | translate }}"  
181 - [formControl]="datasourceControl.get('name')">  
182 - </mat-form-field>  
183 - </ng-template> 100 + <ng-template #datasourcesTemplate>
  101 + <div fxFlex fxLayout="row" fxLayoutAlign="start center">
  102 + <span style="width: 60px;"></span>
  103 + <div fxFlex fxLayout="row" fxLayoutAlign="start center"
  104 + style="padding: 0 0 0 10px; margin: 5px;">
  105 + <span translate style="min-width: 120px;">widget-config.datasource-type</span>
  106 + <span fxHide fxShow.gt-sm translate fxFlex
  107 + style="padding-left: 10px;">widget-config.datasource-parameters</span>
  108 + <span style="min-width: 40px;"></span>
  109 + </div>
  110 + </div>
  111 + <div style="overflow: auto; padding-bottom: 15px;">
  112 + <mat-list dndDropzone dndEffectAllowed="move"
  113 + (dndDrop)="onDatasourceDrop($event)"
  114 + [dndDisableIf]="disabled" formArrayName="datasources">
  115 + <mat-list-item dndPlaceholderRef
  116 + class="dndPlaceholder">
  117 + </mat-list-item>
  118 + <mat-list-item *ngFor="let datasourceControl of datasourcesFormArray().controls; let $index = index;"
  119 + [dndDraggable]="datasourceControl.value"
  120 + (dndMoved)="dndDatasourceMoved($index)"
  121 + [dndDisableIf]="disabled"
  122 + dndEffectAllowed="move">
  123 + <div fxFlex fxLayout="row" fxLayoutAlign="start center">
  124 + <div style="width: 60px;">
  125 + <button *ngIf="!disabled" mat-icon-button color="primary"
  126 + class="handle"
  127 + style="min-width: 40px; margin: 0"
  128 + dndHandle
  129 + matTooltip="{{ 'action.drag' | translate }}"
  130 + matTooltipPosition="above">
  131 + <mat-icon>drag_handle</mat-icon>
  132 + </button>
  133 + <span>{{$index + 1}}.</span>
  134 + </div>
  135 + <div class="mat-elevation-z4" fxFlex
  136 + fxLayout="row"
  137 + fxLayoutAlign="start center"
  138 + style="padding: 0 0 0 10px; margin: 5px;">
  139 + <section fxFlex
  140 + fxLayout="column"
  141 + fxLayoutAlign="center"
  142 + fxLayout.gt-sm="row"
  143 + fxLayoutAlign.gt-sm="start center">
  144 + <mat-form-field class="tb-datasource-type">
  145 + <mat-select [formControl]="datasourceControl.get('type')">
  146 + <mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType">
  147 + {{ datasourceTypesTranslations.get(datasourceType) | translate }}
  148 + </mat-option>
  149 + </mat-select>
  150 + </mat-form-field>
  151 + <section fxLayout="column" class="tb-datasource" [ngSwitch]="datasourceControl.get('type').value">
  152 + <ng-template [ngSwitchCase]="datasourceType.function">
  153 + <mat-form-field floatLabel="always"
  154 + class="tb-datasource-name" style="min-width: 200px;">
  155 + <mat-label></mat-label>
  156 + <input matInput
  157 + placeholder="{{ 'datasource.label' | translate }}"
  158 + [formControl]="datasourceControl.get('name')">
  159 + </mat-form-field>
  160 + </ng-template>
  161 + <ng-template [ngSwitchCase]="datasourceControl.get('type').value === datasourceType.entity ||
  162 + datasourceControl.get('type').value === datasourceType.entityCount ? datasourceControl.get('type').value : ''">
  163 + <tb-entity-alias-select
  164 + [showLabel]="true"
  165 + [tbRequired]="true"
  166 + [aliasController]="aliasController"
  167 + [formControl]="datasourceControl.get('entityAliasId')"
  168 + [callbacks]="widgetConfigCallbacks">
  169 + </tb-entity-alias-select>
  170 + <tb-filter-select
  171 + [showLabel]="true"
  172 + [aliasController]="aliasController"
  173 + [formControl]="datasourceControl.get('filterId')"
  174 + [callbacks]="widgetConfigCallbacks">
  175 + </tb-filter-select>
  176 + <mat-form-field *ngIf="datasourceControl.get('type').value === datasourceType.entityCount"
  177 + floatLabel="always"
  178 + class="tb-datasource-name no-border-top" style="min-width: 200px;">
  179 + <mat-label></mat-label>
  180 + <input matInput
  181 + placeholder="{{ 'datasource.label' | translate }}"
  182 + [formControl]="datasourceControl.get('name')">
  183 + </mat-form-field>
  184 + </ng-template>
  185 + </section>
  186 + <tb-data-keys class="tb-data-keys" fxFlex
  187 + [widgetType]="widgetType"
  188 + [datasourceType]="datasourceControl.get('type').value"
  189 + [maxDataKeys]="modelValue?.typeParameters?.maxDataKeys"
  190 + [optDataKeys]="modelValue?.typeParameters?.dataKeysOptional"
  191 + [aliasController]="aliasController"
  192 + [datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"
  193 + [callbacks]="widgetConfigCallbacks"
  194 + [entityAliasId]="datasourceControl.get('entityAliasId').value"
  195 + [formControl]="datasourceControl.get('dataKeys')">
  196 + </tb-data-keys>
184 </section> 197 </section>
185 - <tb-data-keys class="tb-data-keys" fxFlex  
186 - [widgetType]="widgetType"  
187 - [datasourceType]="datasourceControl.get('type').value"  
188 - [maxDataKeys]="modelValue?.typeParameters?.maxDataKeys"  
189 - [optDataKeys]="modelValue?.typeParameters?.dataKeysOptional"  
190 - [aliasController]="aliasController"  
191 - [datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"  
192 - [callbacks]="widgetConfigCallbacks"  
193 - [entityAliasId]="datasourceControl.get('entityAliasId').value"  
194 - [formControl]="datasourceControl.get('dataKeys')">  
195 - </tb-data-keys>  
196 - </section>  
197 - <button [disabled]="isLoading$ | async"  
198 - type="button"  
199 - mat-icon-button color="primary"  
200 - style="min-width: 40px;"  
201 - (click)="removeDatasource($index)"  
202 - matTooltip="{{ 'widget-config.remove-datasource' | translate }}"  
203 - matTooltipPosition="above">  
204 - <mat-icon>close</mat-icon>  
205 - </button> 198 + <button [disabled]="isLoading$ | async"
  199 + type="button"
  200 + mat-icon-button color="primary"
  201 + style="min-width: 40px;"
  202 + (click)="removeDatasource($index)"
  203 + matTooltip="{{ 'widget-config.remove-datasource' | translate }}"
  204 + matTooltipPosition="above">
  205 + <mat-icon>close</mat-icon>
  206 + </button>
  207 + </div>
206 </div> 208 </div>
207 - </div>  
208 - </mat-list-item>  
209 - </mat-list> 209 + </mat-list-item>
  210 + </mat-list>
  211 + </div>
  212 + </ng-template>
  213 + <div fxFlex fxLayout="row" fxLayoutAlign="start center">
  214 + <button [disabled]="isLoading$ | async"
  215 + type="button"
  216 + mat-raised-button color="primary"
  217 + [fxShow]="modelValue?.typeParameters &&
  218 + (modelValue?.typeParameters.maxDatasources == -1 || datasourcesFormArray().controls.length < modelValue?.typeParameters.maxDatasources)"
  219 + (click)="addDatasource()"
  220 + matTooltip="{{ 'widget-config.add-datasource' | translate }}"
  221 + matTooltipPosition="above">
  222 + <mat-icon>add</mat-icon>
  223 + <span translate>action.add</span>
  224 + </button>
210 </div> 225 </div>
211 - </ng-template>  
212 - <div fxFlex fxLayout="row" fxLayoutAlign="start center">  
213 - <button [disabled]="isLoading$ | async"  
214 - type="button"  
215 - mat-raised-button color="primary"  
216 - [fxShow]="modelValue?.typeParameters &&  
217 - (modelValue?.typeParameters.maxDatasources == -1 || datasourcesFormArray().controls.length < modelValue?.typeParameters.maxDatasources)"  
218 - (click)="addDatasource()"  
219 - matTooltip="{{ 'widget-config.add-datasource' | translate }}"  
220 - matTooltipPosition="above">  
221 - <mat-icon>add</mat-icon>  
222 - <span translate>action.add</span>  
223 - </button>  
224 - </div>  
225 - </mat-expansion-panel>  
226 - <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.rpc &&  
227 - modelValue?.isDataEnabled" [expanded]="true">  
228 - <mat-expansion-panel-header>  
229 - <mat-panel-title>  
230 - {{ 'widget-config.target-device' | translate }}  
231 - </mat-panel-title>  
232 - </mat-expansion-panel-header>  
233 - <div [formGroup]="targetDeviceSettings" style="padding: 0 5px;">  
234 - <tb-entity-alias-select fxFlex  
235 - [tbRequired]="!widgetEditMode"  
236 - [aliasController]="aliasController"  
237 - [allowedEntityTypes]="[entityTypes.DEVICE]"  
238 - [callbacks]="widgetConfigCallbacks"  
239 - formControlName="targetDeviceAliasId">  
240 - </tb-entity-alias-select>  
241 - </div>  
242 - </mat-expansion-panel>  
243 - <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.alarm &&  
244 - modelValue?.isDataEnabled" [expanded]="true">  
245 - <mat-expansion-panel-header>  
246 - <mat-panel-title>  
247 - {{ 'widget-config.alarm-source' | translate }}  
248 - </mat-panel-title>  
249 - </mat-expansion-panel-header>  
250 - <div [formGroup]="alarmSourceSettings" style="padding: 0 5px;">  
251 - <section fxFlex  
252 - fxLayout="column"  
253 - fxLayoutAlign="center"  
254 - fxLayout.gt-sm="row"  
255 - fxLayoutAlign.gt-sm="start center">  
256 - <mat-form-field class="tb-datasource-type">  
257 - <mat-select formControlName="type">  
258 - <mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType">  
259 - {{ datasourceTypesTranslations.get(datasourceType) | translate }}  
260 - </mat-option>  
261 - </mat-select>  
262 - </mat-form-field>  
263 - <section class="tb-datasource" [ngSwitch]="alarmSourceSettings.get('type').value">  
264 - <ng-template [ngSwitchCase]="datasourceType.entity">  
265 - <tb-entity-alias-select  
266 - [showLabel]="true"  
267 - [tbRequired]="alarmSourceSettings.get('type').value === datasourceType.entity"  
268 - [aliasController]="aliasController"  
269 - formControlName="entityAliasId"  
270 - [callbacks]="widgetConfigCallbacks">  
271 - </tb-entity-alias-select>  
272 - <tb-filter-select  
273 - [showLabel]="true"  
274 - [aliasController]="aliasController"  
275 - formControlName="filterId"  
276 - [callbacks]="widgetConfigCallbacks">  
277 - </tb-filter-select>  
278 - </ng-template> 226 + </mat-expansion-panel>
  227 + <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.rpc &&
  228 + modelValue?.isDataEnabled" [expanded]="true">
  229 + <mat-expansion-panel-header>
  230 + <mat-panel-title>
  231 + {{ 'widget-config.target-device' | translate }}
  232 + </mat-panel-title>
  233 + </mat-expansion-panel-header>
  234 + <div [formGroup]="targetDeviceSettings" style="padding: 0 5px;">
  235 + <tb-entity-alias-select fxFlex
  236 + [tbRequired]="!widgetEditMode"
  237 + [aliasController]="aliasController"
  238 + [allowedEntityTypes]="[entityTypes.DEVICE]"
  239 + [callbacks]="widgetConfigCallbacks"
  240 + formControlName="targetDeviceAliasId">
  241 + </tb-entity-alias-select>
  242 + </div>
  243 + </mat-expansion-panel>
  244 + <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.alarm &&
  245 + modelValue?.isDataEnabled" [expanded]="true">
  246 + <mat-expansion-panel-header>
  247 + <mat-panel-title>
  248 + {{ 'widget-config.alarm-source' | translate }}
  249 + </mat-panel-title>
  250 + </mat-expansion-panel-header>
  251 + <div [formGroup]="alarmSourceSettings" style="padding: 0 5px;">
  252 + <section fxFlex
  253 + fxLayout="column"
  254 + fxLayoutAlign="center"
  255 + fxLayout.gt-sm="row"
  256 + fxLayoutAlign.gt-sm="start center">
  257 + <mat-form-field class="tb-datasource-type">
  258 + <mat-select formControlName="type">
  259 + <mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType">
  260 + {{ datasourceTypesTranslations.get(datasourceType) | translate }}
  261 + </mat-option>
  262 + </mat-select>
  263 + </mat-form-field>
  264 + <section class="tb-datasource" [ngSwitch]="alarmSourceSettings.get('type').value">
  265 + <ng-template [ngSwitchCase]="datasourceType.entity">
  266 + <tb-entity-alias-select
  267 + [showLabel]="true"
  268 + [tbRequired]="alarmSourceSettings.get('type').value === datasourceType.entity"
  269 + [aliasController]="aliasController"
  270 + formControlName="entityAliasId"
  271 + [callbacks]="widgetConfigCallbacks">
  272 + </tb-entity-alias-select>
  273 + <tb-filter-select
  274 + [showLabel]="true"
  275 + [aliasController]="aliasController"
  276 + formControlName="filterId"
  277 + [callbacks]="widgetConfigCallbacks">
  278 + </tb-filter-select>
  279 + </ng-template>
  280 + </section>
  281 + <tb-data-keys class="tb-data-keys" fxFlex
  282 + [widgetType]="widgetType"
  283 + [datasourceType]="alarmSourceSettings.get('type').value"
  284 + [aliasController]="aliasController"
  285 + [datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"
  286 + [callbacks]="widgetConfigCallbacks"
  287 + [entityAliasId]="alarmSourceSettings.get('entityAliasId').value"
  288 + formControlName="dataKeys">
  289 + </tb-data-keys>
279 </section> 290 </section>
280 - <tb-data-keys class="tb-data-keys" fxFlex  
281 - [widgetType]="widgetType"  
282 - [datasourceType]="alarmSourceSettings.get('type').value"  
283 - [aliasController]="aliasController"  
284 - [datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"  
285 - [callbacks]="widgetConfigCallbacks"  
286 - [entityAliasId]="alarmSourceSettings.get('entityAliasId').value"  
287 - formControlName="dataKeys">  
288 - </tb-data-keys>  
289 - </section>  
290 - </div>  
291 - </mat-expansion-panel> 291 + </div>
  292 + </mat-expansion-panel>
  293 + <mat-expansion-panel [formGroup]="widgetSettings">
  294 + <mat-expansion-panel-header>
  295 + <mat-panel-title translate>widget-config.data-settings</mat-panel-title>
  296 + </mat-expansion-panel-header>
  297 + <ng-template matExpansionPanelContent>
  298 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
  299 + fxLayoutGap="8px">
  300 + <mat-form-field fxFlex>
  301 + <mat-label translate>widget-config.units</mat-label>
  302 + <input matInput formControlName="units">
  303 + </mat-form-field>
  304 + <mat-form-field fxFlex>
  305 + <mat-label translate>widget-config.decimals</mat-label>
  306 + <input matInput formControlName="decimals" type="number" min="0" max="15" step="1">
  307 + </mat-form-field>
  308 + </div>
  309 + </ng-template>
  310 + </mat-expansion-panel>
  311 + </mat-accordion>
292 </div> 312 </div>
293 </mat-tab> 313 </mat-tab>
294 <mat-tab label="{{ 'widget-config.settings' | translate }}"> 314 <mat-tab label="{{ 'widget-config.settings' | translate }}">
295 - <div class="mat-content mat-padding" fxLayout="column" fxLayoutGap="8px">  
296 - <div [formGroup]="widgetSettings" fxLayout="column" fxLayoutGap="8px">  
297 - <span translate>widget-config.general-settings</span>  
298 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center">  
299 - <div fxLayout="column" fxLayoutAlign="center" fxFlex.sm="40%" fxFlex.gt-sm="30%">  
300 - <mat-form-field fxFlex class="mat-block"> 315 + <div class="mat-content mat-padding" fxLayout="column">
  316 + <div [formGroup]="widgetSettings" fxLayout="column">
  317 + <fieldset class="fields-group" fxLayout="column">
  318 + <legend class="group-title" translate>widget-config.title</legend>
  319 + <mat-slide-toggle formControlName="showTitle" style="margin: 8px 0">
  320 + {{ 'widget-config.display-title' | translate }}
  321 + </mat-slide-toggle>
  322 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
  323 + fxLayoutGap="8px">
  324 + <mat-form-field fxFlex>
301 <mat-label translate>widget-config.title</mat-label> 325 <mat-label translate>widget-config.title</mat-label>
302 <input matInput formControlName="title"> 326 <input matInput formControlName="title">
303 </mat-form-field> 327 </mat-form-field>
@@ -306,130 +330,143 @@ @@ -306,130 +330,143 @@
306 <input matInput formControlName="titleTooltip"> 330 <input matInput formControlName="titleTooltip">
307 </mat-form-field> 331 </mat-form-field>
308 </div> 332 </div>
309 - <div fxFlex [fxShow]="widgetSettings.get('showTitle').value">  
310 - <tb-json-object-edit  
311 - [editorStyle]="{minHeight: '100px'}"  
312 - required  
313 - label="{{ 'widget-config.title-style' | translate }}"  
314 - formControlName="titleStyle"  
315 - ></tb-json-object-edit>  
316 - </div>  
317 - </div>  
318 - <div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-md="row" fxLayoutAlign.gt-md="start center" fxFlex="100%"  
319 - fxLayoutGap="8px">  
320 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
321 - fxLayoutGap="8px" fxFlex.gt-md>  
322 - <mat-checkbox fxFlex formControlName="showTitleIcon"> 333 + <fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px" style="margin: 0">
  334 + <legend class="group-title" translate>widget-config.title-icon</legend>
  335 + <mat-slide-toggle formControlName="showTitleIcon">
323 {{ 'widget-config.display-icon' | translate }} 336 {{ 'widget-config.display-icon' | translate }}
324 - </mat-checkbox>  
325 - <tb-material-icon-select fxFlex  
326 - formControlName="titleIcon">  
327 - </tb-material-icon-select>  
328 - </div>  
329 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
330 - fxLayoutGap="8px" fxFlex.gt-md>  
331 - <tb-color-input fxFlex  
332 - label="{{'widget-config.icon-color' | translate}}"  
333 - icon="format_color_fill"  
334 - openOnInput  
335 - formControlName="iconColor">  
336 - </tb-color-input>  
337 - <mat-form-field fxFlex>  
338 - <mat-label translate>widget-config.icon-size</mat-label>  
339 - <input matInput formControlName="iconSize">  
340 - </mat-form-field>  
341 - </div>  
342 - </div>  
343 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
344 - fxLayoutGap="8px">  
345 - <div fxLayout="column" fxLayoutAlign="center" fxLayoutGap="8px" fxFlex.sm="40%" fxFlex.gt-sm="30%">  
346 - <mat-checkbox formControlName="showTitle">  
347 - {{ 'widget-config.display-title' | translate }}  
348 - </mat-checkbox>  
349 - <mat-checkbox formControlName="dropShadow">  
350 - {{ 'widget-config.drop-shadow' | translate }}  
351 - </mat-checkbox>  
352 - <mat-checkbox formControlName="enableFullscreen">  
353 - {{ 'widget-config.enable-fullscreen' | translate }}  
354 - </mat-checkbox>  
355 - </div>  
356 - <div fxFlex>  
357 - <tb-json-object-edit  
358 - [editorStyle]="{minHeight: '100px'}"  
359 - required  
360 - label="{{ 'widget-config.widget-style' | translate }}"  
361 - formControlName="widgetStyle"  
362 - ></tb-json-object-edit>  
363 - </div>  
364 - </div>  
365 - <div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-md="row" fxLayoutAlign.gt-md="start center"  
366 - fxFlex="100%" fxLayoutGap="8px">  
367 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
368 - fxLayoutGap="8px" fxFlex.gt-md>  
369 - <tb-color-input fxFlex  
370 - label="{{'widget-config.background-color' | translate}}"  
371 - icon="format_color_fill"  
372 - openOnInput  
373 - formControlName="backgroundColor">  
374 - </tb-color-input>  
375 - <tb-color-input fxFlex  
376 - label="{{'widget-config.text-color' | translate}}"  
377 - icon="format_color_fill"  
378 - openOnInput  
379 - formControlName="color">  
380 - </tb-color-input>  
381 - </div>  
382 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
383 - fxLayoutGap="8px" fxFlex.gt-md>  
384 - <mat-form-field fxFlex>  
385 - <mat-label translate>widget-config.padding</mat-label>  
386 - <input matInput formControlName="padding">  
387 - </mat-form-field>  
388 - <mat-form-field fxFlex>  
389 - <mat-label translate>widget-config.margin</mat-label>  
390 - <input matInput formControlName="margin">  
391 - </mat-form-field> 337 + </mat-slide-toggle>
  338 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row wrap" fxLayoutAlign="start center"
  339 + fxLayoutGap="8px">
  340 + <tb-material-icon-select fxFlex
  341 + formControlName="titleIcon">
  342 + </tb-material-icon-select>
  343 + <tb-color-input fxFlex
  344 + label="{{'widget-config.icon-color' | translate}}"
  345 + icon="format_color_fill"
  346 + openOnInput
  347 + formControlName="iconColor">
  348 + </tb-color-input>
  349 + <mat-form-field fxFlex>
  350 + <mat-label translate>widget-config.icon-size</mat-label>
  351 + <input matInput formControlName="iconSize">
  352 + </mat-form-field>
  353 + </div>
  354 + </fieldset>
  355 + <mat-expansion-panel class="tb-settings">
  356 + <mat-expansion-panel-header>
  357 + <mat-panel-description fxLayoutAlign="end" translate>
  358 + widget-config.advanced-settings
  359 + </mat-panel-description>
  360 + </mat-expansion-panel-header>
  361 + <ng-template matExpansionPanelContent>
  362 + <tb-json-object-edit
  363 + [editorStyle]="{minHeight: '100px'}"
  364 + required
  365 + label="{{ 'widget-config.title-style' | translate }}"
  366 + formControlName="titleStyle"
  367 + ></tb-json-object-edit>
  368 + </ng-template>
  369 + </mat-expansion-panel>
  370 + </fieldset>
  371 + <fieldset class="fields-group" fxLayout="column">
  372 + <legend class="group-title" translate>widget-config.widget-style</legend>
  373 + <div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-md="row" fxLayoutAlign.gt-md="start center"
  374 + fxFlex="100%" fxLayoutGap="8px" class="tb-widget-style">
  375 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
  376 + fxLayoutGap="8px" fxFlex.gt-md>
  377 + <tb-color-input fxFlex
  378 + label="{{'widget-config.background-color' | translate}}"
  379 + icon="format_color_fill"
  380 + openOnInput
  381 + formControlName="backgroundColor">
  382 + </tb-color-input>
  383 + <tb-color-input fxFlex
  384 + label="{{'widget-config.text-color' | translate}}"
  385 + icon="format_color_fill"
  386 + openOnInput
  387 + formControlName="color">
  388 + </tb-color-input>
  389 + </div>
  390 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
  391 + fxLayoutGap="8px" fxFlex.gt-md>
  392 + <mat-form-field fxFlex>
  393 + <mat-label translate>widget-config.padding</mat-label>
  394 + <input matInput formControlName="padding">
  395 + </mat-form-field>
  396 + <mat-form-field fxFlex>
  397 + <mat-label translate>widget-config.margin</mat-label>
  398 + <input matInput formControlName="margin">
  399 + </mat-form-field>
  400 + </div>
392 </div> 401 </div>
393 - </div>  
394 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
395 - fxLayoutGap="8px">  
396 - <mat-form-field fxFlex>  
397 - <mat-label translate>widget-config.units</mat-label>  
398 - <input matInput formControlName="units">  
399 - </mat-form-field>  
400 - <mat-form-field fxFlex>  
401 - <mat-label translate>widget-config.decimals</mat-label>  
402 - <input matInput formControlName="decimals" type="number" min="0" max="15" step="1">  
403 - </mat-form-field>  
404 - </div>  
405 - <div [fxShow]="widgetType === widgetTypes.timeseries || widgetType === widgetTypes.latest"  
406 - fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
407 - fxLayoutGap="8px" fxFlex="100%">  
408 - <mat-checkbox fxFlex.gt-xs formControlName="showLegend">  
409 - {{ 'widget-config.display-legend' | translate }}  
410 - </mat-checkbox>  
411 - <section fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 16px;">  
412 - <tb-legend-config formControlName="legendConfig">  
413 - </tb-legend-config>  
414 - </section>  
415 - </div>  
416 - </div>  
417 - <div [formGroup]="layoutSettings" fxLayout="column" fxLayoutGap="8px">  
418 - <span translate>widget-config.mobile-mode-settings</span>  
419 - <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"  
420 - fxLayoutGap="8px">  
421 - <mat-checkbox formControlName="mobileHide">  
422 - {{ 'widget-config.mobile-hide' | translate }}  
423 - </mat-checkbox>  
424 - <mat-form-field fxFlex>  
425 - <mat-label translate>widget-config.order</mat-label>  
426 - <input matInput formControlName="mobileOrder" type="number" step="1">  
427 - </mat-form-field>  
428 - <mat-form-field fxFlex>  
429 - <mat-label translate>widget-config.height</mat-label>  
430 - <input matInput formControlName="mobileHeight" type="number" min="1" max="10" step="1">  
431 - </mat-form-field>  
432 - </div> 402 + <mat-slide-toggle formControlName="dropShadow" style="margin-bottom: 8px">
  403 + {{ 'widget-config.drop-shadow' | translate }}
  404 + </mat-slide-toggle>
  405 + <mat-slide-toggle formControlName="enableFullscreen">
  406 + {{ 'widget-config.enable-fullscreen' | translate }}
  407 + </mat-slide-toggle>
  408 + <mat-expansion-panel class="tb-settings">
  409 + <mat-expansion-panel-header>
  410 + <mat-panel-description fxLayoutAlign="end" translate>
  411 + widget-config.advanced-settings
  412 + </mat-panel-description>
  413 + </mat-expansion-panel-header>
  414 + <ng-template matExpansionPanelContent>
  415 + <tb-json-object-edit
  416 + [editorStyle]="{minHeight: '100px'}"
  417 + required
  418 + label="{{ 'widget-config.widget-style' | translate }}"
  419 + formControlName="widgetStyle"
  420 + ></tb-json-object-edit>
  421 + </ng-template>
  422 + </mat-expansion-panel>
  423 + </fieldset>
  424 + <fieldset class="fields-group fields-group-slider" fxLayout="column">
  425 + <legend class="group-title" translate>widget-config.legend</legend>
  426 + <mat-expansion-panel class="tb-settings">
  427 + <mat-expansion-panel-header fxLayout="row wrap">
  428 + <mat-panel-title>
  429 + <mat-slide-toggle formControlName="showLegend" (click)="$event.stopPropagation()" fxLayoutAlign="center">
  430 + {{ 'widget-config.display-legend' | translate }}
  431 + </mat-slide-toggle>
  432 + </mat-panel-title>
  433 + <mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
  434 + widget-config.advanced-settings
  435 + </mat-panel-description>
  436 + </mat-expansion-panel-header>
  437 + <ng-template matExpansionPanelContent>
  438 + <tb-legend-config formControlName="legendConfig"></tb-legend-config>
  439 + </ng-template>
  440 + </mat-expansion-panel>
  441 + </fieldset>
  442 + <fieldset [formGroup]="layoutSettings" class="fields-group fields-group-slider" fxLayout="column">
  443 + <legend class="group-title" translate>widget-config.mobile-mode-settings</legend>
  444 + <mat-expansion-panel class="tb-settings">
  445 + <mat-expansion-panel-header>
  446 + <mat-panel-title>
  447 + <mat-slide-toggle formControlName="mobileHide" (click)="$event.stopPropagation()" fxLayoutAlign="center">
  448 + {{ 'widget-config.mobile-hide' | translate }}
  449 + </mat-slide-toggle>
  450 + </mat-panel-title>
  451 + <mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
  452 + widget-config.advanced-settings
  453 + </mat-panel-description>
  454 + </mat-expansion-panel-header>
  455 + <ng-template matExpansionPanelContent>
  456 + <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
  457 + fxLayoutGap="8px">
  458 + <mat-form-field fxFlex>
  459 + <mat-label translate>widget-config.order</mat-label>
  460 + <input matInput formControlName="mobileOrder" type="number" step="1">
  461 + </mat-form-field>
  462 + <mat-form-field fxFlex>
  463 + <mat-label translate>widget-config.height</mat-label>
  464 + <input matInput formControlName="mobileHeight" type="number" min="1" max="10" step="1">
  465 + </mat-form-field>
  466 + </div>
  467 + </ng-template>
  468 + </mat-expansion-panel>
  469 + </fieldset>
433 </div> 470 </div>
434 </div> 471 </div>
435 </mat-tab> 472 </mat-tab>
@@ -20,9 +20,6 @@ @@ -20,9 +20,6 @@
20 .tb-advanced-widget-config { 20 .tb-advanced-widget-config {
21 height: 100%; 21 height: 100%;
22 } 22 }
23 - .tb-advanced-widget-config {  
24 - height: 100%;  
25 - }  
26 .tb-datasources { 23 .tb-datasources {
27 24
28 .handle { 25 .handle {
@@ -69,6 +66,28 @@ @@ -69,6 +66,28 @@
69 padding-left: 8px; 66 padding-left: 8px;
70 } 67 }
71 } 68 }
  69 + .fields-group {
  70 + padding: 0 16px 8px;
  71 + margin-bottom: 10px;
  72 + border: 1px groove rgba(0, 0, 0, .25);
  73 + border-radius: 4px;
  74 + legend {
  75 + color: rgba(0, 0, 0, .7);
  76 + width: fit-content;
  77 + }
  78 + }
  79 + .fields-group-slider {
  80 + padding: 0;
  81 + legend {
  82 + margin-left: 16px;
  83 + }
  84 + .tb-settings {
  85 + padding: 0 16px 8px;
  86 + }
  87 + }
  88 + .tb-widget-style {
  89 + margin-top: 16px;
  90 + }
72 } 91 }
73 } 92 }
74 93
@@ -94,6 +113,36 @@ @@ -94,6 +113,36 @@
94 white-space: normal; 113 white-space: normal;
95 } 114 }
96 .mat-expansion-panel { 115 .mat-expansion-panel {
  116 + &.tb-settings {
  117 + box-shadow: none;
  118 + .mat-content {
  119 + overflow: visible;
  120 + }
  121 + .mat-expansion-panel-header {
  122 + padding: 0;
  123 + &:hover {
  124 + background: none;
  125 + }
  126 + .mat-expansion-indicator {
  127 + padding: 2px;
  128 + }
  129 + }
  130 + .mat-expansion-panel-header-description {
  131 + align-items: center;
  132 + }
  133 + .mat-expansion-panel-body{
  134 + padding: 0;
  135 + }
  136 + .tb-json-object-panel {
  137 + margin: 0 0 8px;
  138 + }
  139 + .mat-checkbox-layout {
  140 + margin: 5px 0;
  141 + }
  142 + .mat-checkbox-inner-container {
  143 + margin-right: 12px;
  144 + }
  145 + }
97 &.tb-datasources { 146 &.tb-datasources {
98 &.mat-expanded { 147 &.mat-expanded {
99 overflow: visible; 148 overflow: visible;
@@ -152,5 +201,8 @@ @@ -152,5 +201,8 @@
152 } 201 }
153 } 202 }
154 } 203 }
  204 + .mat-slide-toggle-content {
  205 + white-space: normal;
  206 + }
155 } 207 }
156 } 208 }
@@ -212,11 +212,28 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont @@ -212,11 +212,28 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
212 showLegend: [null, []], 212 showLegend: [null, []],
213 legendConfig: [null, []] 213 legendConfig: [null, []]
214 }); 214 });
  215 + this.widgetSettings.get('showTitle').valueChanges.subscribe((value: boolean) => {
  216 + if (value) {
  217 + this.widgetSettings.get('titleStyle').enable({emitEvent: false});
  218 + this.widgetSettings.get('titleTooltip').enable({emitEvent: false});
  219 + this.widgetSettings.get('showTitleIcon').enable({emitEvent: false});
  220 + } else {
  221 + this.widgetSettings.get('titleStyle').disable({emitEvent: false});
  222 + this.widgetSettings.get('titleTooltip').disable({emitEvent: false});
  223 + this.widgetSettings.get('showTitleIcon').patchValue(false);
  224 + this.widgetSettings.get('showTitleIcon').disable({emitEvent: false});
  225 + }
  226 + });
  227 +
215 this.widgetSettings.get('showTitleIcon').valueChanges.subscribe((value: boolean) => { 228 this.widgetSettings.get('showTitleIcon').valueChanges.subscribe((value: boolean) => {
216 if (value) { 229 if (value) {
217 this.widgetSettings.get('titleIcon').enable({emitEvent: false}); 230 this.widgetSettings.get('titleIcon').enable({emitEvent: false});
  231 + this.widgetSettings.get('iconColor').enable({emitEvent: false});
  232 + this.widgetSettings.get('iconSize').enable({emitEvent: false});
218 } else { 233 } else {
219 this.widgetSettings.get('titleIcon').disable({emitEvent: false}); 234 this.widgetSettings.get('titleIcon').disable({emitEvent: false});
  235 + this.widgetSettings.get('iconColor').disable({emitEvent: false});
  236 + this.widgetSettings.get('iconSize').disable({emitEvent: false});
220 } 237 }
221 }); 238 });
222 this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => { 239 this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => {
@@ -236,6 +253,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont @@ -236,6 +253,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
236 }); 253 });
237 } 254 }
238 255
  256 + ngOnDestroy(): void {
  257 + this.removeChangeSubscriptions();
  258 + }
  259 +
239 private removeChangeSubscriptions() { 260 private removeChangeSubscriptions() {
240 if (this.dataSettingsChangesSubscription) { 261 if (this.dataSettingsChangesSubscription) {
241 this.dataSettingsChangesSubscription.unsubscribe(); 262 this.dataSettingsChangesSubscription.unsubscribe();
@@ -376,7 +397,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont @@ -376,7 +397,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
376 iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)', 397 iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
377 iconSize: isDefined(config.iconSize) ? config.iconSize : '24px', 398 iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
378 titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '', 399 titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
379 - showTitle: config.showTitle, 400 + showTitle: isDefined(config.showTitle) ? config.showTitle : false,
380 dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true, 401 dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
381 enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true, 402 enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
382 backgroundColor: config.backgroundColor, 403 backgroundColor: config.backgroundColor,
@@ -396,11 +417,25 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont @@ -396,11 +417,25 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
396 }, 417 },
397 {emitEvent: false} 418 {emitEvent: false}
398 ); 419 );
  420 + const showTitle: boolean = this.widgetSettings.get('showTitle').value;
  421 + if (showTitle) {
  422 + this.widgetSettings.get('titleTooltip').enable({emitEvent: false});
  423 + this.widgetSettings.get('titleStyle').enable({emitEvent: false});
  424 + this.widgetSettings.get('showTitleIcon').enable({emitEvent: false});
  425 + } else {
  426 + this.widgetSettings.get('titleTooltip').disable({emitEvent: false});
  427 + this.widgetSettings.get('titleStyle').disable({emitEvent: false});
  428 + this.widgetSettings.get('showTitleIcon').disable({emitEvent: false});
  429 + }
399 const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value; 430 const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value;
400 if (showTitleIcon) { 431 if (showTitleIcon) {
401 this.widgetSettings.get('titleIcon').enable({emitEvent: false}); 432 this.widgetSettings.get('titleIcon').enable({emitEvent: false});
  433 + this.widgetSettings.get('iconColor').enable({emitEvent: false});
  434 + this.widgetSettings.get('iconSize').enable({emitEvent: false});
402 } else { 435 } else {
403 this.widgetSettings.get('titleIcon').disable({emitEvent: false}); 436 this.widgetSettings.get('titleIcon').disable({emitEvent: false});
  437 + this.widgetSettings.get('iconColor').disable({emitEvent: false});
  438 + this.widgetSettings.get('iconSize').disable({emitEvent: false});
404 } 439 }
405 const showLegend: boolean = this.widgetSettings.get('showLegend').value; 440 const showLegend: boolean = this.widgetSettings.get('showLegend').value;
406 if (showLegend) { 441 if (showLegend) {
@@ -92,14 +92,16 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit @@ -92,14 +92,16 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
92 } 92 }
93 93
94 openIconDialog() { 94 openIconDialog() {
95 - this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe(  
96 - (icon) => {  
97 - if (icon) {  
98 - this.materialIconFormGroup.patchValue(  
99 - {icon}, {emitEvent: true}  
100 - ); 95 + if (!this.disabled) {
  96 + this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe(
  97 + (icon) => {
  98 + if (icon) {
  99 + this.materialIconFormGroup.patchValue(
  100 + {icon}, {emitEvent: true}
  101 + );
  102 + }
101 } 103 }
102 - }  
103 - ); 104 + );
  105 + }
104 } 106 }
105 } 107 }
@@ -3030,7 +3030,7 @@ @@ -3030,7 +3030,7 @@
3030 "title": "Title", 3030 "title": "Title",
3031 "title-tooltip": "Title Tooltip", 3031 "title-tooltip": "Title Tooltip",
3032 "general-settings": "General settings", 3032 "general-settings": "General settings",
3033 - "display-title": "Display title", 3033 + "display-title": "Display widget title",
3034 "drop-shadow": "Drop shadow", 3034 "drop-shadow": "Drop shadow",
3035 "enable-fullscreen": "Enable fullscreen", 3035 "enable-fullscreen": "Enable fullscreen",
3036 "background-color": "Background color", 3036 "background-color": "Background color",
@@ -3039,7 +3039,7 @@ @@ -3039,7 +3039,7 @@
3039 "margin": "Margin", 3039 "margin": "Margin",
3040 "widget-style": "Widget style", 3040 "widget-style": "Widget style",
3041 "title-style": "Title style", 3041 "title-style": "Title style",
3042 - "mobile-mode-settings": "Mobile mode settings", 3042 + "mobile-mode-settings": "Mobile mode",
3043 "order": "Order", 3043 "order": "Order",
3044 "height": "Height", 3044 "height": "Height",
3045 "mobile-hide": "Hide widget in mobile mode", 3045 "mobile-hide": "Hide widget in mobile mode",
@@ -3048,6 +3048,7 @@ @@ -3048,6 +3048,7 @@
3048 "timewindow": "Timewindow", 3048 "timewindow": "Timewindow",
3049 "use-dashboard-timewindow": "Use dashboard timewindow", 3049 "use-dashboard-timewindow": "Use dashboard timewindow",
3050 "display-timewindow": "Display timewindow", 3050 "display-timewindow": "Display timewindow",
  3051 + "legend": "Legend",
3051 "display-legend": "Display legend", 3052 "display-legend": "Display legend",
3052 "datasources": "Datasources", 3053 "datasources": "Datasources",
3053 "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", 3054 "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }",
@@ -3075,9 +3076,12 @@ @@ -3075,9 +3076,12 @@
3075 "delete-action": "Delete action", 3076 "delete-action": "Delete action",
3076 "delete-action-title": "Delete widget action", 3077 "delete-action-title": "Delete widget action",
3077 "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?", 3078 "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?",
  3079 + "title-icon": "Title icon",
3078 "display-icon": "Display title icon", 3080 "display-icon": "Display title icon",
3079 "icon-color": "Icon color", 3081 "icon-color": "Icon color",
3080 - "icon-size": "Icon size" 3082 + "icon-size": "Icon size",
  3083 + "advanced-settings": "Advanced settings",
  3084 + "data-settings": "Data settings"
3081 }, 3085 },
3082 "widget-type": { 3086 "widget-type": {
3083 "import": "Import widget type", 3087 "import": "Import widget type",
@@ -1631,7 +1631,7 @@ @@ -1631,7 +1631,7 @@
1631 "advanced": "Дополнительно", 1631 "advanced": "Дополнительно",
1632 "title": "Название", 1632 "title": "Название",
1633 "general-settings": "Общие настройки", 1633 "general-settings": "Общие настройки",
1634 - "display-title": "Показать название", 1634 + "display-title": "Показать название на виджете",
1635 "drop-shadow": "Тень", 1635 "drop-shadow": "Тень",
1636 "enable-fullscreen": "Во весь экран", 1636 "enable-fullscreen": "Во весь экран",
1637 "background-color": "Цвет фона", 1637 "background-color": "Цвет фона",
@@ -1640,7 +1640,7 @@ @@ -1640,7 +1640,7 @@
1640 "margin": "Margin", 1640 "margin": "Margin",
1641 "widget-style": "Стиль виджета", 1641 "widget-style": "Стиль виджета",
1642 "title-style": "Стиль названия", 1642 "title-style": "Стиль названия",
1643 - "mobile-mode-settings": "Настройки мобильного режима", 1643 + "mobile-mode-settings": "Мобильный режим",
1644 "order": "Порядок", 1644 "order": "Порядок",
1645 "height": "Высота", 1645 "height": "Высота",
1646 "units": "Специальный символ после значения", 1646 "units": "Специальный символ после значения",
@@ -1648,6 +1648,7 @@ @@ -1648,6 +1648,7 @@
1648 "timewindow": "Временное окно", 1648 "timewindow": "Временное окно",
1649 "use-dashboard-timewindow": "Использовать временное окно дашборда", 1649 "use-dashboard-timewindow": "Использовать временное окно дашборда",
1650 "display-timewindow": "Показывать временное окно", 1650 "display-timewindow": "Показывать временное окно",
  1651 + "legend": "Легенда",
1651 "display-legend": "Показать легенду", 1652 "display-legend": "Показать легенду",
1652 "datasources": "Источники данных", 1653 "datasources": "Источники данных",
1653 "maximum-datasources": "Максимальной количество источников данных равно {{count}}", 1654 "maximum-datasources": "Максимальной количество источников данных равно {{count}}",
@@ -1673,9 +1674,12 @@ @@ -1673,9 +1674,12 @@
1673 "delete-action": "Удалить действие", 1674 "delete-action": "Удалить действие",
1674 "delete-action-title": "Удалить действие виджета", 1675 "delete-action-title": "Удалить действие виджета",
1675 "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?", 1676 "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?",
1676 - "display-icon": "Показывать иконку в названии", 1677 + "title-icon": "Иконка в названии виджета",
  1678 + "display-icon": "Показывать иконку в названии виджета",
1677 "icon-color": "Цвет иконки", 1679 "icon-color": "Цвет иконки",
1678 - "icon-size": "Размер иконки" 1680 + "icon-size": "Размер иконки",
  1681 + "advanced-settings": "Расширенные настройки",
  1682 + "data-settings": "Настройки данных"
1679 }, 1683 },
1680 "widget-type": { 1684 "widget-type": {
1681 "import": "Импортировать тип виджета", 1685 "import": "Импортировать тип виджета",
@@ -2202,7 +2202,7 @@ @@ -2202,7 +2202,7 @@
2202 "advanced": "Додатково", 2202 "advanced": "Додатково",
2203 "title": "Назва", 2203 "title": "Назва",
2204 "general-settings": "Загальні налаштування", 2204 "general-settings": "Загальні налаштування",
2205 - "display-title": "Відобразити назву", 2205 + "display-title": "Відобразити назву у віджеті",
2206 "drop-shadow": "Тінь", 2206 "drop-shadow": "Тінь",
2207 "enable-fullscreen": "Увімкнути повноекранний режим", 2207 "enable-fullscreen": "Увімкнути повноекранний режим",
2208 "enable-data-export": "Увімкнути експорт даних", 2208 "enable-data-export": "Увімкнути експорт даних",
@@ -2212,7 +2212,7 @@ @@ -2212,7 +2212,7 @@
2212 "margin": "Границі", 2212 "margin": "Границі",
2213 "widget-style": "Стиль віджетів", 2213 "widget-style": "Стиль віджетів",
2214 "title-style": "Стиль заголовка", 2214 "title-style": "Стиль заголовка",
2215 - "mobile-mode-settings": "Налаштування мобільного режиму", 2215 + "mobile-mode-settings": "мобільний режим",
2216 "order": "Порядок", 2216 "order": "Порядок",
2217 "height": "Висота", 2217 "height": "Висота",
2218 "units": "Спеціальний символ після значення", 2218 "units": "Спеціальний символ після значення",
@@ -2220,6 +2220,7 @@ @@ -2220,6 +2220,7 @@
2220 "timewindow": "Вікно часу", 2220 "timewindow": "Вікно часу",
2221 "use-dashboard-timewindow": "Використати вікно часу на панелі візуалізації", 2221 "use-dashboard-timewindow": "Використати вікно часу на панелі візуалізації",
2222 "display-timewindow": "Показувати вікно часу", 2222 "display-timewindow": "Показувати вікно часу",
  2223 + "legend": "Легенда",
2223 "display-legend": "Показати легенду", 2224 "display-legend": "Показати легенду",
2224 "datasources": "Джерела даних", 2225 "datasources": "Джерела даних",
2225 "maximum-datasources": "Максимально { count, plural, 1 {1 дозволене джерело даних.} other {# дозволені джерела даних } }", 2226 "maximum-datasources": "Максимально { count, plural, 1 {1 дозволене джерело даних.} other {# дозволені джерела даних } }",
@@ -2245,9 +2246,12 @@ @@ -2245,9 +2246,12 @@
2245 "delete-action": "Видалити дію", 2246 "delete-action": "Видалити дію",
2246 "delete-action-title": "Видалити дію віджета", 2247 "delete-action-title": "Видалити дію віджета",
2247 "delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?", 2248 "delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?",
2248 - "display-icon": "Показувати іконку у назві", 2249 + "title-icon": "Іконка у назві віджету",
  2250 + "display-icon": "Показувати іконку у назві віджету",
2249 "icon-color": "Колір іконки", 2251 "icon-color": "Колір іконки",
2250 - "icon-size": "Розмір іконки" 2252 + "icon-size": "Розмір іконки",
  2253 + "advanced-settings": "Розширені налаштування",
  2254 + "data-settings": "Налаштування даних"
2251 }, 2255 },
2252 "widget-type": { 2256 "widget-type": {
2253 "import": "Імпортувати тип віджета", 2257 "import": "Імпортувати тип віджета",