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 49 import { DataKeysComponent } from '@home/components/widget/data-keys.component';
50 50 import { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.component';
51 51 import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component';
52   -import { LegendConfigPanelComponent } from '@home/components/widget/legend-config-panel.component';
53 52 import { LegendConfigComponent } from '@home/components/widget/legend-config.component';
54 53 import { ManageWidgetActionsComponent } from '@home/components/widget/action/manage-widget-actions.component';
55 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 181 DataKeysComponent,
183 182 DataKeyConfigComponent,
184 183 DataKeyConfigDialogComponent,
185   - LegendConfigPanelComponent,
186 184 LegendConfigComponent,
187 185 ManageWidgetActionsComponent,
188 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 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 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 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 29 // @dynamic
45 30 @Component({
... ... @@ -58,105 +43,60 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
58 43
59 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 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 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 102 registerOnChange(fn: any): void {
... ... @@ -168,14 +108,29 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
168 108
169 109 setDisabledState(isDisabled: boolean): void {
170 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 82 </mat-checkbox>
83 83 </div>
84 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 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 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 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 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 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 312 </div>
293 313 </mat-tab>
294 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 325 <mat-label translate>widget-config.title</mat-label>
302 326 <input matInput formControlName="title">
303 327 </mat-form-field>
... ... @@ -306,130 +330,143 @@
306 330 <input matInput formControlName="titleTooltip">
307 331 </mat-form-field>
308 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 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 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 470 </div>
434 471 </div>
435 472 </mat-tab>
... ...
... ... @@ -20,9 +20,6 @@
20 20 .tb-advanced-widget-config {
21 21 height: 100%;
22 22 }
23   - .tb-advanced-widget-config {
24   - height: 100%;
25   - }
26 23 .tb-datasources {
27 24
28 25 .handle {
... ... @@ -69,6 +66,28 @@
69 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 113 white-space: normal;
95 114 }
96 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 146 &.tb-datasources {
98 147 &.mat-expanded {
99 148 overflow: visible;
... ... @@ -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 212 showLegend: [null, []],
213 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 228 this.widgetSettings.get('showTitleIcon').valueChanges.subscribe((value: boolean) => {
216 229 if (value) {
217 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 233 } else {
219 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 239 this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => {
... ... @@ -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 260 private removeChangeSubscriptions() {
240 261 if (this.dataSettingsChangesSubscription) {
241 262 this.dataSettingsChangesSubscription.unsubscribe();
... ... @@ -376,7 +397,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
376 397 iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
377 398 iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
378 399 titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
379   - showTitle: config.showTitle,
  400 + showTitle: isDefined(config.showTitle) ? config.showTitle : false,
380 401 dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
381 402 enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
382 403 backgroundColor: config.backgroundColor,
... ... @@ -396,11 +417,25 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
396 417 },
397 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 430 const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value;
400 431 if (showTitleIcon) {
401 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 435 } else {
403 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 440 const showLegend: boolean = this.widgetSettings.get('showLegend').value;
406 441 if (showLegend) {
... ...
... ... @@ -92,14 +92,16 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
92 92 }
93 93
94 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 3030 "title": "Title",
3031 3031 "title-tooltip": "Title Tooltip",
3032 3032 "general-settings": "General settings",
3033   - "display-title": "Display title",
  3033 + "display-title": "Display widget title",
3034 3034 "drop-shadow": "Drop shadow",
3035 3035 "enable-fullscreen": "Enable fullscreen",
3036 3036 "background-color": "Background color",
... ... @@ -3039,7 +3039,7 @@
3039 3039 "margin": "Margin",
3040 3040 "widget-style": "Widget style",
3041 3041 "title-style": "Title style",
3042   - "mobile-mode-settings": "Mobile mode settings",
  3042 + "mobile-mode-settings": "Mobile mode",
3043 3043 "order": "Order",
3044 3044 "height": "Height",
3045 3045 "mobile-hide": "Hide widget in mobile mode",
... ... @@ -3048,6 +3048,7 @@
3048 3048 "timewindow": "Timewindow",
3049 3049 "use-dashboard-timewindow": "Use dashboard timewindow",
3050 3050 "display-timewindow": "Display timewindow",
  3051 + "legend": "Legend",
3051 3052 "display-legend": "Display legend",
3052 3053 "datasources": "Datasources",
3053 3054 "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }",
... ... @@ -3075,9 +3076,12 @@
3075 3076 "delete-action": "Delete action",
3076 3077 "delete-action-title": "Delete widget action",
3077 3078 "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?",
  3079 + "title-icon": "Title icon",
3078 3080 "display-icon": "Display title icon",
3079 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 3086 "widget-type": {
3083 3087 "import": "Import widget type",
... ...
... ... @@ -1631,7 +1631,7 @@
1631 1631 "advanced": "Дополнительно",
1632 1632 "title": "Название",
1633 1633 "general-settings": "Общие настройки",
1634   - "display-title": "Показать название",
  1634 + "display-title": "Показать название на виджете",
1635 1635 "drop-shadow": "Тень",
1636 1636 "enable-fullscreen": "Во весь экран",
1637 1637 "background-color": "Цвет фона",
... ... @@ -1640,7 +1640,7 @@
1640 1640 "margin": "Margin",
1641 1641 "widget-style": "Стиль виджета",
1642 1642 "title-style": "Стиль названия",
1643   - "mobile-mode-settings": "Настройки мобильного режима",
  1643 + "mobile-mode-settings": "Мобильный режим",
1644 1644 "order": "Порядок",
1645 1645 "height": "Высота",
1646 1646 "units": "Специальный символ после значения",
... ... @@ -1648,6 +1648,7 @@
1648 1648 "timewindow": "Временное окно",
1649 1649 "use-dashboard-timewindow": "Использовать временное окно дашборда",
1650 1650 "display-timewindow": "Показывать временное окно",
  1651 + "legend": "Легенда",
1651 1652 "display-legend": "Показать легенду",
1652 1653 "datasources": "Источники данных",
1653 1654 "maximum-datasources": "Максимальной количество источников данных равно {{count}}",
... ... @@ -1673,9 +1674,12 @@
1673 1674 "delete-action": "Удалить действие",
1674 1675 "delete-action-title": "Удалить действие виджета",
1675 1676 "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?",
1676   - "display-icon": "Показывать иконку в названии",
  1677 + "title-icon": "Иконка в названии виджета",
  1678 + "display-icon": "Показывать иконку в названии виджета",
1677 1679 "icon-color": "Цвет иконки",
1678   - "icon-size": "Размер иконки"
  1680 + "icon-size": "Размер иконки",
  1681 + "advanced-settings": "Расширенные настройки",
  1682 + "data-settings": "Настройки данных"
1679 1683 },
1680 1684 "widget-type": {
1681 1685 "import": "Импортировать тип виджета",
... ...
... ... @@ -2202,7 +2202,7 @@
2202 2202 "advanced": "Додатково",
2203 2203 "title": "Назва",
2204 2204 "general-settings": "Загальні налаштування",
2205   - "display-title": "Відобразити назву",
  2205 + "display-title": "Відобразити назву у віджеті",
2206 2206 "drop-shadow": "Тінь",
2207 2207 "enable-fullscreen": "Увімкнути повноекранний режим",
2208 2208 "enable-data-export": "Увімкнути експорт даних",
... ... @@ -2212,7 +2212,7 @@
2212 2212 "margin": "Границі",
2213 2213 "widget-style": "Стиль віджетів",
2214 2214 "title-style": "Стиль заголовка",
2215   - "mobile-mode-settings": "Налаштування мобільного режиму",
  2215 + "mobile-mode-settings": "мобільний режим",
2216 2216 "order": "Порядок",
2217 2217 "height": "Висота",
2218 2218 "units": "Спеціальний символ після значення",
... ... @@ -2220,6 +2220,7 @@
2220 2220 "timewindow": "Вікно часу",
2221 2221 "use-dashboard-timewindow": "Використати вікно часу на панелі візуалізації",
2222 2222 "display-timewindow": "Показувати вікно часу",
  2223 + "legend": "Легенда",
2223 2224 "display-legend": "Показати легенду",
2224 2225 "datasources": "Джерела даних",
2225 2226 "maximum-datasources": "Максимально { count, plural, 1 {1 дозволене джерело даних.} other {# дозволені джерела даних } }",
... ... @@ -2245,9 +2246,12 @@
2245 2246 "delete-action": "Видалити дію",
2246 2247 "delete-action-title": "Видалити дію віджета",
2247 2248 "delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?",
2248   - "display-icon": "Показувати іконку у назві",
  2249 + "title-icon": "Іконка у назві віджету",
  2250 + "display-icon": "Показувати іконку у назві віджету",
2249 2251 "icon-color": "Колір іконки",
2250   - "icon-size": "Розмір іконки"
  2252 + "icon-size": "Розмір іконки",
  2253 + "advanced-settings": "Розширені налаштування",
  2254 + "data-settings": "Налаштування даних"
2251 2255 },
2252 2256 "widget-type": {
2253 2257 "import": "Імпортувати тип віджета",
... ...