Browse Source

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

[3.3.2] UI: New widget settings layout
pull/5496/head
Igor Kulikov 5 years ago
committed by GitHub
parent
commit
72cfe8e411
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  2. 60
      ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html
  3. 32
      ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.scss
  4. 105
      ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts
  5. 46
      ui-ngx/src/app/modules/home/components/widget/legend-config.component.html
  6. 189
      ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts
  7. 697
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  8. 58
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss
  9. 37
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  10. 18
      ui-ngx/src/app/shared/components/material-icon-select.component.ts
  11. 10
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  12. 12
      ui-ngx/src/assets/locale/locale.constant-ru_RU.json
  13. 12
      ui-ngx/src/assets/locale/locale.constant-uk_UA.json

2
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -49,7 +49,6 @@ import { EntityAliasSelectComponent } from '@home/components/alias/entity-alias-
import { DataKeysComponent } from '@home/components/widget/data-keys.component'; import { DataKeysComponent } from '@home/components/widget/data-keys.component';
import { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.component'; import { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.component';
import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component'; import { DataKeyConfigComponent } from '@home/components/widget/data-key-config.component';
import { LegendConfigPanelComponent } from '@home/components/widget/legend-config-panel.component';
import { LegendConfigComponent } from '@home/components/widget/legend-config.component'; import { LegendConfigComponent } from '@home/components/widget/legend-config.component';
import { ManageWidgetActionsComponent } from '@home/components/widget/action/manage-widget-actions.component'; import { ManageWidgetActionsComponent } from '@home/components/widget/action/manage-widget-actions.component';
import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component'; import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component';
@ -182,7 +181,6 @@ import { DeviceProfileCommonModule } from '@home/components/profile/device/commo
DataKeysComponent, DataKeysComponent,
DataKeyConfigComponent, DataKeyConfigComponent,
DataKeyConfigDialogComponent, DataKeyConfigDialogComponent,
LegendConfigPanelComponent,
LegendConfigComponent, LegendConfigComponent,
ManageWidgetActionsComponent, ManageWidgetActionsComponent,
WidgetActionDialogComponent, WidgetActionDialogComponent,

60
ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html

@ -1,60 +0,0 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<form [formGroup]="legendConfigForm">
<fieldset [disabled]="(isLoading$ | async)">
<div class="mat-content" style="height: 100%;">
<div class="mat-padding">
<section fxLayout="column">
<mat-form-field>
<mat-label translate>legend.direction</mat-label>
<mat-select matInput formControlName="direction" style="min-width: 150px;">
<mat-option *ngFor="let direction of legendDirections" [value]="direction">
{{ legendDirectionTranslations.get(legendDirection[direction]) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-label translate>legend.position</mat-label>
<mat-select matInput formControlName="position" style="min-width: 150px;">
<mat-option *ngFor="let pos of legendPositions" [value]="pos"
[disabled]="legendConfigForm.get('direction').value === legendDirection.row &&
(pos === legendPosition.left || pos === legendPosition.right)">
{{ legendPositionTranslations.get(legendPosition[pos]) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox formControlName="sortDataKeys">
{{ 'legend.sort-legend' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showMin">
{{ 'legend.show-min' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showMax">
{{ 'legend.show-max' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showAvg">
{{ 'legend.show-avg' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showTotal">
{{ 'legend.show-total' | translate }}
</mat-checkbox>
</section>
</div>
</div>
</fieldset>
</form>

32
ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.scss

@ -1,32 +0,0 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
width: 100%;
height: 100%;
form,
fieldset {
height: 100%;
}
.mat-content {
overflow: hidden;
background-color: #fff;
}
.mat-padding {
padding: 16px;
}
}

105
ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts

@ -1,105 +0,0 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, InjectionToken, OnInit, ViewContainerRef } from '@angular/core';
import { OverlayRef } from '@angular/cdk/overlay';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup } from '@angular/forms';
import {
LegendConfig,
LegendDirection,
legendDirectionTranslationMap,
LegendPosition,
legendPositionTranslationMap
} from '@shared/models/widget.models';
export const LEGEND_CONFIG_PANEL_DATA = new InjectionToken<any>('LegendConfigPanelData');
export interface LegendConfigPanelData {
legendConfig: LegendConfig;
legendConfigUpdated: (legendConfig: LegendConfig) => void;
}
@Component({
selector: 'tb-legend-config-panel',
templateUrl: './legend-config-panel.component.html',
styleUrls: ['./legend-config-panel.component.scss']
})
export class LegendConfigPanelComponent extends PageComponent implements OnInit {
legendConfigForm: FormGroup;
legendDirection = LegendDirection;
legendDirections = Object.keys(LegendDirection);
legendDirectionTranslations = legendDirectionTranslationMap;
legendPosition = LegendPosition;
legendPositions = Object.keys(LegendPosition);
legendPositionTranslations = legendPositionTranslationMap;
constructor(@Inject(LEGEND_CONFIG_PANEL_DATA) public data: LegendConfigPanelData,
public overlayRef: OverlayRef,
protected store: Store<AppState>,
public fb: FormBuilder,
public viewContainerRef: ViewContainerRef) {
super(store);
}
ngOnInit(): void {
this.legendConfigForm = this.fb.group({
direction: [this.data.legendConfig.direction, []],
position: [this.data.legendConfig.position, []],
sortDataKeys: [this.data.legendConfig.sortDataKeys, []],
showMin: [this.data.legendConfig.showMin, []],
showMax: [this.data.legendConfig.showMax, []],
showAvg: [this.data.legendConfig.showAvg, []],
showTotal: [this.data.legendConfig.showTotal, []]
});
this.legendConfigForm.get('direction').valueChanges.subscribe((direction: LegendDirection) => {
this.onDirectionChanged(direction);
});
this.onDirectionChanged(this.data.legendConfig.direction);
this.legendConfigForm.valueChanges.subscribe(() => {
this.update();
});
}
private onDirectionChanged(direction: LegendDirection) {
if (direction === LegendDirection.row) {
let position: LegendPosition = this.legendConfigForm.get('position').value;
if (position !== LegendPosition.bottom && position !== LegendPosition.top) {
position = LegendPosition.bottom;
}
this.legendConfigForm.patchValue(
{
position
}, {emitEvent: false}
);
}
}
update() {
const newLegendConfig: LegendConfig = this.legendConfigForm.value;
this.data.legendConfigUpdated(newLegendConfig);
}
}

46
ui-ngx/src/app/modules/home/components/widget/legend-config.component.html

@ -15,9 +15,43 @@
limitations under the License. limitations under the License.
--> -->
<button cdkOverlayOrigin #legendConfigPanelOrigin="cdkOverlayOrigin" [disabled]="disabled" <form [formGroup]="legendConfigForm">
type="button" <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
mat-button mat-raised-button color="primary" (click)="openEditMode()"> fxLayoutGap="8px">
<mat-icon class="material-icons">toc</mat-icon> <mat-form-field fxFlex>
<span translate>legend.settings</span> <mat-label translate>legend.direction</mat-label>
</button> <mat-select matInput formControlName="direction">
<mat-option *ngFor="let direction of legendDirections" [value]="direction">
{{ legendDirectionTranslations.get(legendDirection[direction]) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>legend.position</mat-label>
<mat-select matInput formControlName="position">
<mat-option *ngFor="let pos of legendPositions" [value]="pos"
[disabled]="legendConfigForm.get('direction').value === legendDirection.row &&
(pos === legendPosition.left || pos === legendPosition.right)">
{{ legendPositionTranslations.get(legendPosition[pos]) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row wrap" fxLayoutAlign="space-between center" fxLayoutGap="8px">
<mat-checkbox formControlName="showMin" fxFlex="48">
{{ 'legend.show-min' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showMax" fxFlex="48">
{{ 'legend.show-max' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showAvg" fxFlex="48">
{{ 'legend.show-avg' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="showTotal" fxFlex="48">
{{ 'legend.show-total' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="sortDataKeys" fxFlex="48">
{{ 'legend.sort-legend' | translate }}
</mat-checkbox>
</div>
</form>

189
ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts

@ -14,32 +14,17 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { isDefined } from '@core/utils';
import { import {
Component, LegendConfig,
forwardRef, LegendDirection,
Inject, legendDirectionTranslationMap,
Injector, LegendPosition,
Input, legendPositionTranslationMap
OnDestroy, } from '@shared/models/widget.models';
OnInit, import { Subscription } from 'rxjs';
StaticProvider,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { DOCUMENT } from '@angular/common';
import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { MediaBreakpoints } from '@shared/models/constants';
import { BreakpointObserver } from '@angular/cdk/layout';
import { WINDOW } from '@core/services/window.service';
import { deepClone } from '@core/utils';
import { LegendConfig } from '@shared/models/widget.models';
import {
LEGEND_CONFIG_PANEL_DATA,
LegendConfigPanelComponent,
LegendConfigPanelData
} from '@home/components/widget/legend-config-panel.component';
// @dynamic // @dynamic
@Component({ @Component({
@ -58,105 +43,60 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
@Input() disabled: boolean; @Input() disabled: boolean;
@ViewChild('legendConfigPanelOrigin') legendConfigPanelOrigin: CdkOverlayOrigin; legendConfigForm: FormGroup;
legendDirection = LegendDirection;
innerValue: LegendConfig; legendDirections = Object.keys(LegendDirection);
legendDirectionTranslations = legendDirectionTranslationMap;
legendPosition = LegendPosition;
legendPositions = Object.keys(LegendPosition);
legendPositionTranslations = legendPositionTranslationMap;
private legendSettingsFormChanges$: Subscription;
private legendSettingsFormDirectionChanges$: Subscription;
private propagateChange = (_: any) => {}; private propagateChange = (_: any) => {};
constructor(private overlay: Overlay, constructor(private fb: FormBuilder) {
public viewContainerRef: ViewContainerRef,
public breakpointObserver: BreakpointObserver,
@Inject(DOCUMENT) private document: Document,
@Inject(WINDOW) private window: Window) {
} }
ngOnInit(): void { ngOnInit(): void {
this.legendConfigForm = this.fb.group({
direction: [null, []],
position: [null, []],
sortDataKeys: [null, []],
showMin: [null, []],
showMax: [null, []],
showAvg: [null, []],
showTotal: [null, []]
});
this.legendSettingsFormDirectionChanges$ = this.legendConfigForm.get('direction').valueChanges
.subscribe((direction: LegendDirection) => {
this.onDirectionChanged(direction);
});
this.legendSettingsFormChanges$ = this.legendConfigForm.valueChanges.subscribe(
() => this.legendConfigUpdated()
);
} }
ngOnDestroy(): void { private onDirectionChanged(direction: LegendDirection) {
} if (direction === LegendDirection.row) {
let position: LegendPosition = this.legendConfigForm.get('position').value;
openEditMode() { if (position !== LegendPosition.bottom && position !== LegendPosition.top) {
if (this.disabled) { position = LegendPosition.bottom;
return;
}
const isGtSm = this.breakpointObserver.isMatched(MediaBreakpoints['gt-sm']);
const position = this.overlay.position();
const config = new OverlayConfig({
panelClass: 'tb-legend-config-panel',
backdropClass: 'cdk-overlay-transparent-backdrop',
hasBackdrop: isGtSm,
});
if (isGtSm) {
config.minWidth = '220px';
config.maxHeight = '300px';
const panelHeight = 220;
const panelWidth = 220;
const el = this.legendConfigPanelOrigin.elementRef.nativeElement;
const offset = el.getBoundingClientRect();
const scrollTop = this.window.pageYOffset || this.document.documentElement.scrollTop || this.document.body.scrollTop || 0;
const scrollLeft = this.window.pageXOffset || this.document.documentElement.scrollLeft || this.document.body.scrollLeft || 0;
const bottomY = offset.bottom - scrollTop;
const leftX = offset.left - scrollLeft;
let originX;
let originY;
let overlayX;
let overlayY;
const wHeight = this.document.documentElement.clientHeight;
const wWidth = this.document.documentElement.clientWidth;
if (bottomY + panelHeight > wHeight) {
originY = 'top';
overlayY = 'bottom';
} else {
originY = 'bottom';
overlayY = 'top';
} }
if (leftX + panelWidth > wWidth) { this.legendConfigForm.patchValue({position}, {emitEvent: false}
originX = 'end'; );
overlayX = 'end';
} else {
originX = 'start';
overlayX = 'start';
}
const connectedPosition: ConnectedPosition = {
originX,
originY,
overlayX,
overlayY
};
config.positionStrategy = position.flexibleConnectedTo(this.legendConfigPanelOrigin.elementRef)
.withPositions([connectedPosition]);
} else {
config.minWidth = '100%';
config.minHeight = '100%';
config.positionStrategy = position.global().top('0%').left('0%')
.right('0%').bottom('0%');
} }
const overlayRef = this.overlay.create(config);
overlayRef.backdropClick().subscribe(() => {
overlayRef.dispose();
});
const injector = this._createLegendConfigPanelInjector(
overlayRef,
{
legendConfig: deepClone(this.innerValue),
legendConfigUpdated: this.legendConfigUpdated.bind(this)
}
);
overlayRef.attach(new ComponentPortal(LegendConfigPanelComponent, this.viewContainerRef, injector));
} }
private _createLegendConfigPanelInjector(overlayRef: OverlayRef, data: LegendConfigPanelData): Injector { ngOnDestroy(): void {
const providers: StaticProvider[] = [ if (this.legendSettingsFormDirectionChanges$) {
{provide: LEGEND_CONFIG_PANEL_DATA, useValue: data}, this.legendSettingsFormDirectionChanges$.unsubscribe();
{provide: OverlayRef, useValue: overlayRef} this.legendSettingsFormDirectionChanges$ = null;
]; }
return Injector.create({parent: this.viewContainerRef.injector, providers}); if (this.legendSettingsFormChanges$) {
this.legendSettingsFormChanges$.unsubscribe();
this.legendSettingsFormChanges$ = null;
}
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
@ -168,14 +108,29 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) {
this.legendConfigForm.disable({emitEvent: false});
} else {
this.legendConfigForm.enable({emitEvent: false});
}
} }
writeValue(obj: LegendConfig): void { writeValue(legendConfig: LegendConfig): void {
this.innerValue = obj; if (legendConfig) {
this.legendConfigForm.patchValue({
direction: legendConfig.direction,
position: legendConfig.position,
sortDataKeys: isDefined(legendConfig.sortDataKeys) ? legendConfig.sortDataKeys : false,
showMin: isDefined(legendConfig.showMin) ? legendConfig.showMin : false,
showMax: isDefined(legendConfig.showMax) ? legendConfig.showMax : false,
showAvg: isDefined(legendConfig.showAvg) ? legendConfig.showAvg : false,
showTotal: isDefined(legendConfig.showTotal) ? legendConfig.showTotal : false
}, {emitEvent: false});
}
this.onDirectionChanged(legendConfig.direction);
} }
private legendConfigUpdated(legendConfig: LegendConfig) { private legendConfigUpdated() {
this.innerValue = legendConfig; this.propagateChange(this.legendConfigForm.value);
this.propagateChange(this.innerValue);
} }
} }

697
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -82,222 +82,246 @@
</mat-checkbox> </mat-checkbox>
</div> </div>
</div> </div>
<mat-expansion-panel class="tb-datasources" *ngIf="widgetType !== widgetTypes.rpc && <mat-accordion multi>
widgetType !== widgetTypes.alarm && <mat-expansion-panel class="tb-datasources" *ngIf="widgetType !== widgetTypes.rpc &&
modelValue?.isDataEnabled" [expanded]="true"> widgetType !== widgetTypes.alarm &&
<mat-expansion-panel-header> modelValue?.isDataEnabled" [expanded]="true">
<mat-panel-title fxLayout="column"> <mat-expansion-panel-header>
<div class="tb-panel-title" translate>widget-config.datasources</div> <mat-panel-title fxLayout="column">
<div *ngIf="modelValue?.typeParameters && modelValue?.typeParameters.maxDatasources > -1" <div class="tb-panel-title" translate>widget-config.datasources</div>
class="tb-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div> <div *ngIf="modelValue?.typeParameters && modelValue?.typeParameters.maxDatasources > -1"
</mat-panel-title> class="tb-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div>
</mat-expansion-panel-header> </mat-panel-title>
<div *ngIf="datasourcesFormArray().length === 0; else datasourcesTemplate"> </mat-expansion-panel-header>
<span translate fxLayoutAlign="center center" <div *ngIf="datasourcesFormArray().length === 0; else datasourcesTemplate">
class="tb-prompt">datasource.add-datasource-prompt</span> <span translate fxLayoutAlign="center center"
</div> class="tb-prompt">datasource.add-datasource-prompt</span>
<ng-template #datasourcesTemplate>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<span style="width: 60px;"></span>
<div fxFlex fxLayout="row" fxLayoutAlign="start center"
style="padding: 0 0 0 10px; margin: 5px;">
<span translate style="min-width: 120px;">widget-config.datasource-type</span>
<span fxHide fxShow.gt-sm translate fxFlex
style="padding-left: 10px;">widget-config.datasource-parameters</span>
<span style="min-width: 40px;"></span>
</div>
</div> </div>
<div style="overflow: auto; padding-bottom: 15px;"> <ng-template #datasourcesTemplate>
<mat-list dndDropzone dndEffectAllowed="move" <div fxFlex fxLayout="row" fxLayoutAlign="start center">
(dndDrop)="onDatasourceDrop($event)" <span style="width: 60px;"></span>
[dndDisableIf]="disabled" formArrayName="datasources"> <div fxFlex fxLayout="row" fxLayoutAlign="start center"
<mat-list-item dndPlaceholderRef style="padding: 0 0 0 10px; margin: 5px;">
class="dndPlaceholder"> <span translate style="min-width: 120px;">widget-config.datasource-type</span>
</mat-list-item> <span fxHide fxShow.gt-sm translate fxFlex
<mat-list-item *ngFor="let datasourceControl of datasourcesFormArray().controls; let $index = index;" style="padding-left: 10px;">widget-config.datasource-parameters</span>
[dndDraggable]="datasourceControl.value" <span style="min-width: 40px;"></span>
(dndMoved)="dndDatasourceMoved($index)" </div>
[dndDisableIf]="disabled" </div>
dndEffectAllowed="move"> <div style="overflow: auto; padding-bottom: 15px;">
<div fxFlex fxLayout="row" fxLayoutAlign="start center"> <mat-list dndDropzone dndEffectAllowed="move"
<div style="width: 60px;"> (dndDrop)="onDatasourceDrop($event)"
<button *ngIf="!disabled" mat-icon-button color="primary" [dndDisableIf]="disabled" formArrayName="datasources">
class="handle" <mat-list-item dndPlaceholderRef
style="min-width: 40px; margin: 0" class="dndPlaceholder">
dndHandle </mat-list-item>
matTooltip="{{ 'action.drag' | translate }}" <mat-list-item *ngFor="let datasourceControl of datasourcesFormArray().controls; let $index = index;"
matTooltipPosition="above"> [dndDraggable]="datasourceControl.value"
<mat-icon>drag_handle</mat-icon> (dndMoved)="dndDatasourceMoved($index)"
</button> [dndDisableIf]="disabled"
<span>{{$index + 1}}.</span> dndEffectAllowed="move">
</div> <div fxFlex fxLayout="row" fxLayoutAlign="start center">
<div class="mat-elevation-z4" fxFlex <div style="width: 60px;">
fxLayout="row" <button *ngIf="!disabled" mat-icon-button color="primary"
fxLayoutAlign="start center" class="handle"
style="padding: 0 0 0 10px; margin: 5px;"> style="min-width: 40px; margin: 0"
<section fxFlex dndHandle
fxLayout="column" matTooltip="{{ 'action.drag' | translate }}"
fxLayoutAlign="center" matTooltipPosition="above">
fxLayout.gt-sm="row" <mat-icon>drag_handle</mat-icon>
fxLayoutAlign.gt-sm="start center"> </button>
<mat-form-field class="tb-datasource-type"> <span>{{$index + 1}}.</span>
<mat-select [formControl]="datasourceControl.get('type')"> </div>
<mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType"> <div class="mat-elevation-z4" fxFlex
{{ datasourceTypesTranslations.get(datasourceType) | translate }} fxLayout="row"
</mat-option> fxLayoutAlign="start center"
</mat-select> style="padding: 0 0 0 10px; margin: 5px;">
</mat-form-field> <section fxFlex
<section fxLayout="column" class="tb-datasource" [ngSwitch]="datasourceControl.get('type').value"> fxLayout="column"
<ng-template [ngSwitchCase]="datasourceType.function"> fxLayoutAlign="center"
<mat-form-field floatLabel="always" fxLayout.gt-sm="row"
class="tb-datasource-name" style="min-width: 200px;"> fxLayoutAlign.gt-sm="start center">
<mat-label></mat-label> <mat-form-field class="tb-datasource-type">
<input matInput <mat-select [formControl]="datasourceControl.get('type')">
placeholder="{{ 'datasource.label' | translate }}" <mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType">
[formControl]="datasourceControl.get('name')"> {{ datasourceTypesTranslations.get(datasourceType) | translate }}
</mat-form-field> </mat-option>
</ng-template> </mat-select>
<ng-template [ngSwitchCase]="datasourceControl.get('type').value === datasourceType.entity || </mat-form-field>
datasourceControl.get('type').value === datasourceType.entityCount ? datasourceControl.get('type').value : ''"> <section fxLayout="column" class="tb-datasource" [ngSwitch]="datasourceControl.get('type').value">
<tb-entity-alias-select <ng-template [ngSwitchCase]="datasourceType.function">
[showLabel]="true" <mat-form-field floatLabel="always"
[tbRequired]="true" class="tb-datasource-name" style="min-width: 200px;">
[aliasController]="aliasController" <mat-label></mat-label>
[formControl]="datasourceControl.get('entityAliasId')" <input matInput
[callbacks]="widgetConfigCallbacks"> placeholder="{{ 'datasource.label' | translate }}"
</tb-entity-alias-select> [formControl]="datasourceControl.get('name')">
<tb-filter-select </mat-form-field>
[showLabel]="true" </ng-template>
[aliasController]="aliasController" <ng-template [ngSwitchCase]="datasourceControl.get('type').value === datasourceType.entity ||
[formControl]="datasourceControl.get('filterId')" datasourceControl.get('type').value === datasourceType.entityCount ? datasourceControl.get('type').value : ''">
[callbacks]="widgetConfigCallbacks"> <tb-entity-alias-select
</tb-filter-select> [showLabel]="true"
<mat-form-field *ngIf="datasourceControl.get('type').value === datasourceType.entityCount" [tbRequired]="true"
floatLabel="always" [aliasController]="aliasController"
class="tb-datasource-name no-border-top" style="min-width: 200px;"> [formControl]="datasourceControl.get('entityAliasId')"
<mat-label></mat-label> [callbacks]="widgetConfigCallbacks">
<input matInput </tb-entity-alias-select>
placeholder="{{ 'datasource.label' | translate }}" <tb-filter-select
[formControl]="datasourceControl.get('name')"> [showLabel]="true"
</mat-form-field> [aliasController]="aliasController"
</ng-template> [formControl]="datasourceControl.get('filterId')"
[callbacks]="widgetConfigCallbacks">
</tb-filter-select>
<mat-form-field *ngIf="datasourceControl.get('type').value === datasourceType.entityCount"
floatLabel="always"
class="tb-datasource-name no-border-top" style="min-width: 200px;">
<mat-label></mat-label>
<input matInput
placeholder="{{ 'datasource.label' | translate }}"
[formControl]="datasourceControl.get('name')">
</mat-form-field>
</ng-template>
</section>
<tb-data-keys class="tb-data-keys" fxFlex
[widgetType]="widgetType"
[datasourceType]="datasourceControl.get('type').value"
[maxDataKeys]="modelValue?.typeParameters?.maxDataKeys"
[optDataKeys]="modelValue?.typeParameters?.dataKeysOptional"
[aliasController]="aliasController"
[datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"
[callbacks]="widgetConfigCallbacks"
[entityAliasId]="datasourceControl.get('entityAliasId').value"
[formControl]="datasourceControl.get('dataKeys')">
</tb-data-keys>
</section> </section>
<tb-data-keys class="tb-data-keys" fxFlex <button [disabled]="isLoading$ | async"
[widgetType]="widgetType" type="button"
[datasourceType]="datasourceControl.get('type').value" mat-icon-button color="primary"
[maxDataKeys]="modelValue?.typeParameters?.maxDataKeys" style="min-width: 40px;"
[optDataKeys]="modelValue?.typeParameters?.dataKeysOptional" (click)="removeDatasource($index)"
[aliasController]="aliasController" matTooltip="{{ 'widget-config.remove-datasource' | translate }}"
[datakeySettingsSchema]="modelValue?.dataKeySettingsSchema" matTooltipPosition="above">
[callbacks]="widgetConfigCallbacks" <mat-icon>close</mat-icon>
[entityAliasId]="datasourceControl.get('entityAliasId').value" </button>
[formControl]="datasourceControl.get('dataKeys')"> </div>
</tb-data-keys>
</section>
<button [disabled]="isLoading$ | async"
type="button"
mat-icon-button color="primary"
style="min-width: 40px;"
(click)="removeDatasource($index)"
matTooltip="{{ 'widget-config.remove-datasource' | translate }}"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div> </div>
</div> </mat-list-item>
</mat-list-item> </mat-list>
</mat-list> </div>
</ng-template>
<div fxFlex fxLayout="row" fxLayoutAlign="start center">
<button [disabled]="isLoading$ | async"
type="button"
mat-raised-button color="primary"
[fxShow]="modelValue?.typeParameters &&
(modelValue?.typeParameters.maxDatasources == -1 || datasourcesFormArray().controls.length < modelValue?.typeParameters.maxDatasources)"
(click)="addDatasource()"
matTooltip="{{ 'widget-config.add-datasource' | translate }}"
matTooltipPosition="above">
<mat-icon>add</mat-icon>
<span translate>action.add</span>
</button>
</div> </div>
</ng-template> </mat-expansion-panel>
<div fxFlex fxLayout="row" fxLayoutAlign="start center"> <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.rpc &&
<button [disabled]="isLoading$ | async" modelValue?.isDataEnabled" [expanded]="true">
type="button" <mat-expansion-panel-header>
mat-raised-button color="primary" <mat-panel-title>
[fxShow]="modelValue?.typeParameters && {{ 'widget-config.target-device' | translate }}
(modelValue?.typeParameters.maxDatasources == -1 || datasourcesFormArray().controls.length < modelValue?.typeParameters.maxDatasources)" </mat-panel-title>
(click)="addDatasource()" </mat-expansion-panel-header>
matTooltip="{{ 'widget-config.add-datasource' | translate }}" <div [formGroup]="targetDeviceSettings" style="padding: 0 5px;">
matTooltipPosition="above"> <tb-entity-alias-select fxFlex
<mat-icon>add</mat-icon> [tbRequired]="!widgetEditMode"
<span translate>action.add</span> [aliasController]="aliasController"
</button> [allowedEntityTypes]="[entityTypes.DEVICE]"
</div> [callbacks]="widgetConfigCallbacks"
</mat-expansion-panel> formControlName="targetDeviceAliasId">
<mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.rpc && </tb-entity-alias-select>
modelValue?.isDataEnabled" [expanded]="true"> </div>
<mat-expansion-panel-header> </mat-expansion-panel>
<mat-panel-title> <mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.alarm &&
{{ 'widget-config.target-device' | translate }} modelValue?.isDataEnabled" [expanded]="true">
</mat-panel-title> <mat-expansion-panel-header>
</mat-expansion-panel-header> <mat-panel-title>
<div [formGroup]="targetDeviceSettings" style="padding: 0 5px;"> {{ 'widget-config.alarm-source' | translate }}
<tb-entity-alias-select fxFlex </mat-panel-title>
[tbRequired]="!widgetEditMode" </mat-expansion-panel-header>
[aliasController]="aliasController" <div [formGroup]="alarmSourceSettings" style="padding: 0 5px;">
[allowedEntityTypes]="[entityTypes.DEVICE]" <section fxFlex
[callbacks]="widgetConfigCallbacks" fxLayout="column"
formControlName="targetDeviceAliasId"> fxLayoutAlign="center"
</tb-entity-alias-select> fxLayout.gt-sm="row"
</div> fxLayoutAlign.gt-sm="start center">
</mat-expansion-panel> <mat-form-field class="tb-datasource-type">
<mat-expansion-panel class="tb-datasources" *ngIf="widgetType === widgetTypes.alarm && <mat-select formControlName="type">
modelValue?.isDataEnabled" [expanded]="true"> <mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType">
<mat-expansion-panel-header> {{ datasourceTypesTranslations.get(datasourceType) | translate }}
<mat-panel-title> </mat-option>
{{ 'widget-config.alarm-source' | translate }} </mat-select>
</mat-panel-title> </mat-form-field>
</mat-expansion-panel-header> <section class="tb-datasource" [ngSwitch]="alarmSourceSettings.get('type').value">
<div [formGroup]="alarmSourceSettings" style="padding: 0 5px;"> <ng-template [ngSwitchCase]="datasourceType.entity">
<section fxFlex <tb-entity-alias-select
fxLayout="column" [showLabel]="true"
fxLayoutAlign="center" [tbRequired]="alarmSourceSettings.get('type').value === datasourceType.entity"
fxLayout.gt-sm="row" [aliasController]="aliasController"
fxLayoutAlign.gt-sm="start center"> formControlName="entityAliasId"
<mat-form-field class="tb-datasource-type"> [callbacks]="widgetConfigCallbacks">
<mat-select formControlName="type"> </tb-entity-alias-select>
<mat-option *ngFor="let datasourceType of datasourceTypes" [value]="datasourceType"> <tb-filter-select
{{ datasourceTypesTranslations.get(datasourceType) | translate }} [showLabel]="true"
</mat-option> [aliasController]="aliasController"
</mat-select> formControlName="filterId"
</mat-form-field> [callbacks]="widgetConfigCallbacks">
<section class="tb-datasource" [ngSwitch]="alarmSourceSettings.get('type').value"> </tb-filter-select>
<ng-template [ngSwitchCase]="datasourceType.entity"> </ng-template>
<tb-entity-alias-select </section>
[showLabel]="true" <tb-data-keys class="tb-data-keys" fxFlex
[tbRequired]="alarmSourceSettings.get('type').value === datasourceType.entity" [widgetType]="widgetType"
[aliasController]="aliasController" [datasourceType]="alarmSourceSettings.get('type').value"
formControlName="entityAliasId" [aliasController]="aliasController"
[callbacks]="widgetConfigCallbacks"> [datakeySettingsSchema]="modelValue?.dataKeySettingsSchema"
</tb-entity-alias-select> [callbacks]="widgetConfigCallbacks"
<tb-filter-select [entityAliasId]="alarmSourceSettings.get('entityAliasId').value"
[showLabel]="true" formControlName="dataKeys">
[aliasController]="aliasController" </tb-data-keys>
formControlName="filterId"
[callbacks]="widgetConfigCallbacks">
</tb-filter-select>
</ng-template>
</section> </section>
<tb-data-keys class="tb-data-keys" fxFlex </div>
[widgetType]="widgetType" </mat-expansion-panel>
[datasourceType]="alarmSourceSettings.get('type').value" <mat-expansion-panel [formGroup]="widgetSettings">
[aliasController]="aliasController" <mat-expansion-panel-header>
[datakeySettingsSchema]="modelValue?.dataKeySettingsSchema" <mat-panel-title translate>widget-config.data-settings</mat-panel-title>
[callbacks]="widgetConfigCallbacks" </mat-expansion-panel-header>
[entityAliasId]="alarmSourceSettings.get('entityAliasId').value" <ng-template matExpansionPanelContent>
formControlName="dataKeys"> <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
</tb-data-keys> fxLayoutGap="8px">
</section> <mat-form-field fxFlex>
</div> <mat-label translate>widget-config.units</mat-label>
</mat-expansion-panel> <input matInput formControlName="units">
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>widget-config.decimals</mat-label>
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1">
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</mat-accordion>
</div> </div>
</mat-tab> </mat-tab>
<mat-tab label="{{ 'widget-config.settings' | translate }}"> <mat-tab label="{{ 'widget-config.settings' | translate }}">
<div class="mat-content mat-padding" fxLayout="column" fxLayoutGap="8px"> <div class="mat-content mat-padding" fxLayout="column">
<div [formGroup]="widgetSettings" fxLayout="column" fxLayoutGap="8px"> <div [formGroup]="widgetSettings" fxLayout="column">
<span translate>widget-config.general-settings</span> <fieldset class="fields-group" fxLayout="column">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"> <legend class="group-title" translate>widget-config.title</legend>
<div fxLayout="column" fxLayoutAlign="center" fxFlex.sm="40%" fxFlex.gt-sm="30%"> <mat-slide-toggle formControlName="showTitle" style="margin: 8px 0">
<mat-form-field fxFlex class="mat-block"> {{ 'widget-config.display-title' | translate }}
</mat-slide-toggle>
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="8px">
<mat-form-field fxFlex>
<mat-label translate>widget-config.title</mat-label> <mat-label translate>widget-config.title</mat-label>
<input matInput formControlName="title"> <input matInput formControlName="title">
</mat-form-field> </mat-form-field>
@ -306,130 +330,143 @@
<input matInput formControlName="titleTooltip"> <input matInput formControlName="titleTooltip">
</mat-form-field> </mat-form-field>
</div> </div>
<div fxFlex [fxShow]="widgetSettings.get('showTitle').value"> <fieldset class="fields-group" fxLayout="column" fxLayoutGap="8px" style="margin: 0">
<tb-json-object-edit <legend class="group-title" translate>widget-config.title-icon</legend>
[editorStyle]="{minHeight: '100px'}" <mat-slide-toggle formControlName="showTitleIcon">
required
label="{{ 'widget-config.title-style' | translate }}"
formControlName="titleStyle"
></tb-json-object-edit>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-md="row" fxLayoutAlign.gt-md="start center" fxFlex="100%"
fxLayoutGap="8px">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="8px" fxFlex.gt-md>
<mat-checkbox fxFlex formControlName="showTitleIcon">
{{ 'widget-config.display-icon' | translate }} {{ 'widget-config.display-icon' | translate }}
</mat-checkbox> </mat-slide-toggle>
<tb-material-icon-select fxFlex <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row wrap" fxLayoutAlign="start center"
formControlName="titleIcon"> fxLayoutGap="8px">
</tb-material-icon-select> <tb-material-icon-select fxFlex
</div> formControlName="titleIcon">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center" </tb-material-icon-select>
fxLayoutGap="8px" fxFlex.gt-md> <tb-color-input fxFlex
<tb-color-input fxFlex label="{{'widget-config.icon-color' | translate}}"
label="{{'widget-config.icon-color' | translate}}" icon="format_color_fill"
icon="format_color_fill" openOnInput
openOnInput formControlName="iconColor">
formControlName="iconColor"> </tb-color-input>
</tb-color-input> <mat-form-field fxFlex>
<mat-form-field fxFlex> <mat-label translate>widget-config.icon-size</mat-label>
<mat-label translate>widget-config.icon-size</mat-label> <input matInput formControlName="iconSize">
<input matInput formControlName="iconSize"> </mat-form-field>
</mat-form-field> </div>
</div> </fieldset>
</div> <mat-expansion-panel class="tb-settings">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center" <mat-expansion-panel-header>
fxLayoutGap="8px"> <mat-panel-description fxLayoutAlign="end" translate>
<div fxLayout="column" fxLayoutAlign="center" fxLayoutGap="8px" fxFlex.sm="40%" fxFlex.gt-sm="30%"> widget-config.advanced-settings
<mat-checkbox formControlName="showTitle"> </mat-panel-description>
{{ 'widget-config.display-title' | translate }} </mat-expansion-panel-header>
</mat-checkbox> <ng-template matExpansionPanelContent>
<mat-checkbox formControlName="dropShadow"> <tb-json-object-edit
{{ 'widget-config.drop-shadow' | translate }} [editorStyle]="{minHeight: '100px'}"
</mat-checkbox> required
<mat-checkbox formControlName="enableFullscreen"> label="{{ 'widget-config.title-style' | translate }}"
{{ 'widget-config.enable-fullscreen' | translate }} formControlName="titleStyle"
</mat-checkbox> ></tb-json-object-edit>
</div> </ng-template>
<div fxFlex> </mat-expansion-panel>
<tb-json-object-edit </fieldset>
[editorStyle]="{minHeight: '100px'}" <fieldset class="fields-group" fxLayout="column">
required <legend class="group-title" translate>widget-config.widget-style</legend>
label="{{ 'widget-config.widget-style' | translate }}" <div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-md="row" fxLayoutAlign.gt-md="start center"
formControlName="widgetStyle" fxFlex="100%" fxLayoutGap="8px" class="tb-widget-style">
></tb-json-object-edit> <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
</div> fxLayoutGap="8px" fxFlex.gt-md>
</div> <tb-color-input fxFlex
<div fxLayout="column" fxLayoutAlign="center" fxLayout.gt-md="row" fxLayoutAlign.gt-md="start center" label="{{'widget-config.background-color' | translate}}"
fxFlex="100%" fxLayoutGap="8px"> icon="format_color_fill"
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center" openOnInput
fxLayoutGap="8px" fxFlex.gt-md> formControlName="backgroundColor">
<tb-color-input fxFlex </tb-color-input>
label="{{'widget-config.background-color' | translate}}" <tb-color-input fxFlex
icon="format_color_fill" label="{{'widget-config.text-color' | translate}}"
openOnInput icon="format_color_fill"
formControlName="backgroundColor"> openOnInput
</tb-color-input> formControlName="color">
<tb-color-input fxFlex </tb-color-input>
label="{{'widget-config.text-color' | translate}}" </div>
icon="format_color_fill" <div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
openOnInput fxLayoutGap="8px" fxFlex.gt-md>
formControlName="color"> <mat-form-field fxFlex>
</tb-color-input> <mat-label translate>widget-config.padding</mat-label>
</div> <input matInput formControlName="padding">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center" </mat-form-field>
fxLayoutGap="8px" fxFlex.gt-md> <mat-form-field fxFlex>
<mat-form-field fxFlex> <mat-label translate>widget-config.margin</mat-label>
<mat-label translate>widget-config.padding</mat-label> <input matInput formControlName="margin">
<input matInput formControlName="padding"> </mat-form-field>
</mat-form-field> </div>
<mat-form-field fxFlex>
<mat-label translate>widget-config.margin</mat-label>
<input matInput formControlName="margin">
</mat-form-field>
</div> </div>
</div> <mat-slide-toggle formControlName="dropShadow" style="margin-bottom: 8px">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center" {{ 'widget-config.drop-shadow' | translate }}
fxLayoutGap="8px"> </mat-slide-toggle>
<mat-form-field fxFlex> <mat-slide-toggle formControlName="enableFullscreen">
<mat-label translate>widget-config.units</mat-label> {{ 'widget-config.enable-fullscreen' | translate }}
<input matInput formControlName="units"> </mat-slide-toggle>
</mat-form-field> <mat-expansion-panel class="tb-settings">
<mat-form-field fxFlex> <mat-expansion-panel-header>
<mat-label translate>widget-config.decimals</mat-label> <mat-panel-description fxLayoutAlign="end" translate>
<input matInput formControlName="decimals" type="number" min="0" max="15" step="1"> widget-config.advanced-settings
</mat-form-field> </mat-panel-description>
</div> </mat-expansion-panel-header>
<div [fxShow]="widgetType === widgetTypes.timeseries || widgetType === widgetTypes.latest" <ng-template matExpansionPanelContent>
fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center" <tb-json-object-edit
fxLayoutGap="8px" fxFlex="100%"> [editorStyle]="{minHeight: '100px'}"
<mat-checkbox fxFlex.gt-xs formControlName="showLegend"> required
{{ 'widget-config.display-legend' | translate }} label="{{ 'widget-config.widget-style' | translate }}"
</mat-checkbox> formControlName="widgetStyle"
<section fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" style="margin-bottom: 16px;"> ></tb-json-object-edit>
<tb-legend-config formControlName="legendConfig"> </ng-template>
</tb-legend-config> </mat-expansion-panel>
</section> </fieldset>
</div> <fieldset class="fields-group fields-group-slider" fxLayout="column">
</div> <legend class="group-title" translate>widget-config.legend</legend>
<div [formGroup]="layoutSettings" fxLayout="column" fxLayoutGap="8px"> <mat-expansion-panel class="tb-settings">
<span translate>widget-config.mobile-mode-settings</span> <mat-expansion-panel-header fxLayout="row wrap">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center" <mat-panel-title>
fxLayoutGap="8px"> <mat-slide-toggle formControlName="showLegend" (click)="$event.stopPropagation()" fxLayoutAlign="center">
<mat-checkbox formControlName="mobileHide"> {{ 'widget-config.display-legend' | translate }}
{{ 'widget-config.mobile-hide' | translate }} </mat-slide-toggle>
</mat-checkbox> </mat-panel-title>
<mat-form-field fxFlex> <mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
<mat-label translate>widget-config.order</mat-label> widget-config.advanced-settings
<input matInput formControlName="mobileOrder" type="number" step="1"> </mat-panel-description>
</mat-form-field> </mat-expansion-panel-header>
<mat-form-field fxFlex> <ng-template matExpansionPanelContent>
<mat-label translate>widget-config.height</mat-label> <tb-legend-config formControlName="legendConfig"></tb-legend-config>
<input matInput formControlName="mobileHeight" type="number" min="1" max="10" step="1"> </ng-template>
</mat-form-field> </mat-expansion-panel>
</div> </fieldset>
<fieldset [formGroup]="layoutSettings" class="fields-group fields-group-slider" fxLayout="column">
<legend class="group-title" translate>widget-config.mobile-mode-settings</legend>
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header>
<mat-panel-title>
<mat-slide-toggle formControlName="mobileHide" (click)="$event.stopPropagation()" fxLayoutAlign="center">
{{ 'widget-config.mobile-hide' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate>
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="8px">
<mat-form-field fxFlex>
<mat-label translate>widget-config.order</mat-label>
<input matInput formControlName="mobileOrder" type="number" step="1">
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>widget-config.height</mat-label>
<input matInput formControlName="mobileHeight" type="number" min="1" max="10" step="1">
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
</div> </div>
</div> </div>
</mat-tab> </mat-tab>

58
ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss

@ -17,9 +17,6 @@
:host { :host {
.tb-widget-config { .tb-widget-config {
.tb-advanced-widget-config {
height: 100%;
}
.tb-advanced-widget-config { .tb-advanced-widget-config {
height: 100%; height: 100%;
} }
@ -69,6 +66,28 @@
padding-left: 8px; padding-left: 8px;
} }
} }
.fields-group {
padding: 0 16px 8px;
margin-bottom: 10px;
border: 1px groove rgba(0, 0, 0, .25);
border-radius: 4px;
legend {
color: rgba(0, 0, 0, .7);
width: fit-content;
}
}
.fields-group-slider {
padding: 0;
legend {
margin-left: 16px;
}
.tb-settings {
padding: 0 16px 8px;
}
}
.tb-widget-style {
margin-top: 16px;
}
} }
} }
@ -94,6 +113,36 @@
white-space: normal; white-space: normal;
} }
.mat-expansion-panel { .mat-expansion-panel {
&.tb-settings {
box-shadow: none;
.mat-content {
overflow: visible;
}
.mat-expansion-panel-header {
padding: 0;
&:hover {
background: none;
}
.mat-expansion-indicator {
padding: 2px;
}
}
.mat-expansion-panel-header-description {
align-items: center;
}
.mat-expansion-panel-body{
padding: 0;
}
.tb-json-object-panel {
margin: 0 0 8px;
}
.mat-checkbox-layout {
margin: 5px 0;
}
.mat-checkbox-inner-container {
margin-right: 12px;
}
}
&.tb-datasources { &.tb-datasources {
&.mat-expanded { &.mat-expanded {
overflow: visible; overflow: visible;
@ -152,5 +201,8 @@
} }
} }
} }
.mat-slide-toggle-content {
white-space: normal;
}
} }
} }

37
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -212,11 +212,28 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
showLegend: [null, []], showLegend: [null, []],
legendConfig: [null, []] legendConfig: [null, []]
}); });
this.widgetSettings.get('showTitle').valueChanges.subscribe((value: boolean) => {
if (value) {
this.widgetSettings.get('titleStyle').enable({emitEvent: false});
this.widgetSettings.get('titleTooltip').enable({emitEvent: false});
this.widgetSettings.get('showTitleIcon').enable({emitEvent: false});
} else {
this.widgetSettings.get('titleStyle').disable({emitEvent: false});
this.widgetSettings.get('titleTooltip').disable({emitEvent: false});
this.widgetSettings.get('showTitleIcon').patchValue(false);
this.widgetSettings.get('showTitleIcon').disable({emitEvent: false});
}
});
this.widgetSettings.get('showTitleIcon').valueChanges.subscribe((value: boolean) => { this.widgetSettings.get('showTitleIcon').valueChanges.subscribe((value: boolean) => {
if (value) { if (value) {
this.widgetSettings.get('titleIcon').enable({emitEvent: false}); this.widgetSettings.get('titleIcon').enable({emitEvent: false});
this.widgetSettings.get('iconColor').enable({emitEvent: false});
this.widgetSettings.get('iconSize').enable({emitEvent: false});
} else { } else {
this.widgetSettings.get('titleIcon').disable({emitEvent: false}); this.widgetSettings.get('titleIcon').disable({emitEvent: false});
this.widgetSettings.get('iconColor').disable({emitEvent: false});
this.widgetSettings.get('iconSize').disable({emitEvent: false});
} }
}); });
this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => { this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => {
@ -236,6 +253,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}); });
} }
ngOnDestroy(): void {
this.removeChangeSubscriptions();
}
private removeChangeSubscriptions() { private removeChangeSubscriptions() {
if (this.dataSettingsChangesSubscription) { if (this.dataSettingsChangesSubscription) {
this.dataSettingsChangesSubscription.unsubscribe(); this.dataSettingsChangesSubscription.unsubscribe();
@ -376,7 +397,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)', iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
iconSize: isDefined(config.iconSize) ? config.iconSize : '24px', iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '', titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
showTitle: config.showTitle, showTitle: isDefined(config.showTitle) ? config.showTitle : false,
dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true, dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true, enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
backgroundColor: config.backgroundColor, backgroundColor: config.backgroundColor,
@ -396,11 +417,25 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
}, },
{emitEvent: false} {emitEvent: false}
); );
const showTitle: boolean = this.widgetSettings.get('showTitle').value;
if (showTitle) {
this.widgetSettings.get('titleTooltip').enable({emitEvent: false});
this.widgetSettings.get('titleStyle').enable({emitEvent: false});
this.widgetSettings.get('showTitleIcon').enable({emitEvent: false});
} else {
this.widgetSettings.get('titleTooltip').disable({emitEvent: false});
this.widgetSettings.get('titleStyle').disable({emitEvent: false});
this.widgetSettings.get('showTitleIcon').disable({emitEvent: false});
}
const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value; const showTitleIcon: boolean = this.widgetSettings.get('showTitleIcon').value;
if (showTitleIcon) { if (showTitleIcon) {
this.widgetSettings.get('titleIcon').enable({emitEvent: false}); this.widgetSettings.get('titleIcon').enable({emitEvent: false});
this.widgetSettings.get('iconColor').enable({emitEvent: false});
this.widgetSettings.get('iconSize').enable({emitEvent: false});
} else { } else {
this.widgetSettings.get('titleIcon').disable({emitEvent: false}); this.widgetSettings.get('titleIcon').disable({emitEvent: false});
this.widgetSettings.get('iconColor').disable({emitEvent: false});
this.widgetSettings.get('iconSize').disable({emitEvent: false});
} }
const showLegend: boolean = this.widgetSettings.get('showLegend').value; const showLegend: boolean = this.widgetSettings.get('showLegend').value;
if (showLegend) { if (showLegend) {

18
ui-ngx/src/app/shared/components/material-icon-select.component.ts

@ -92,14 +92,16 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit
} }
openIconDialog() { openIconDialog() {
this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe( if (!this.disabled) {
(icon) => { this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe(
if (icon) { (icon) => {
this.materialIconFormGroup.patchValue( if (icon) {
{icon}, {emitEvent: true} this.materialIconFormGroup.patchValue(
); {icon}, {emitEvent: true}
);
}
} }
} );
); }
} }
} }

10
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -3030,7 +3030,7 @@
"title": "Title", "title": "Title",
"title-tooltip": "Title Tooltip", "title-tooltip": "Title Tooltip",
"general-settings": "General settings", "general-settings": "General settings",
"display-title": "Display title", "display-title": "Display widget title",
"drop-shadow": "Drop shadow", "drop-shadow": "Drop shadow",
"enable-fullscreen": "Enable fullscreen", "enable-fullscreen": "Enable fullscreen",
"background-color": "Background color", "background-color": "Background color",
@ -3039,7 +3039,7 @@
"margin": "Margin", "margin": "Margin",
"widget-style": "Widget style", "widget-style": "Widget style",
"title-style": "Title style", "title-style": "Title style",
"mobile-mode-settings": "Mobile mode settings", "mobile-mode-settings": "Mobile mode",
"order": "Order", "order": "Order",
"height": "Height", "height": "Height",
"mobile-hide": "Hide widget in mobile mode", "mobile-hide": "Hide widget in mobile mode",
@ -3048,6 +3048,7 @@
"timewindow": "Timewindow", "timewindow": "Timewindow",
"use-dashboard-timewindow": "Use dashboard timewindow", "use-dashboard-timewindow": "Use dashboard timewindow",
"display-timewindow": "Display timewindow", "display-timewindow": "Display timewindow",
"legend": "Legend",
"display-legend": "Display legend", "display-legend": "Display legend",
"datasources": "Datasources", "datasources": "Datasources",
"maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }",
@ -3075,9 +3076,12 @@
"delete-action": "Delete action", "delete-action": "Delete action",
"delete-action-title": "Delete widget action", "delete-action-title": "Delete widget action",
"delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?", "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?",
"title-icon": "Title icon",
"display-icon": "Display title icon", "display-icon": "Display title icon",
"icon-color": "Icon color", "icon-color": "Icon color",
"icon-size": "Icon size" "icon-size": "Icon size",
"advanced-settings": "Advanced settings",
"data-settings": "Data settings"
}, },
"widget-type": { "widget-type": {
"import": "Import widget type", "import": "Import widget type",

12
ui-ngx/src/assets/locale/locale.constant-ru_RU.json

@ -1631,7 +1631,7 @@
"advanced": "Дополнительно", "advanced": "Дополнительно",
"title": "Название", "title": "Название",
"general-settings": "Общие настройки", "general-settings": "Общие настройки",
"display-title": "Показать название", "display-title": "Показать название на виджете",
"drop-shadow": "Тень", "drop-shadow": "Тень",
"enable-fullscreen": "Во весь экран", "enable-fullscreen": "Во весь экран",
"background-color": "Цвет фона", "background-color": "Цвет фона",
@ -1640,7 +1640,7 @@
"margin": "Margin", "margin": "Margin",
"widget-style": "Стиль виджета", "widget-style": "Стиль виджета",
"title-style": "Стиль названия", "title-style": "Стиль названия",
"mobile-mode-settings": "Настройки мобильного режима", "mobile-mode-settings": "Мобильный режим",
"order": "Порядок", "order": "Порядок",
"height": "Высота", "height": "Высота",
"units": "Специальный символ после значения", "units": "Специальный символ после значения",
@ -1648,6 +1648,7 @@
"timewindow": "Временное окно", "timewindow": "Временное окно",
"use-dashboard-timewindow": "Использовать временное окно дашборда", "use-dashboard-timewindow": "Использовать временное окно дашборда",
"display-timewindow": "Показывать временное окно", "display-timewindow": "Показывать временное окно",
"legend": "Легенда",
"display-legend": "Показать легенду", "display-legend": "Показать легенду",
"datasources": "Источники данных", "datasources": "Источники данных",
"maximum-datasources": "Максимальной количество источников данных равно {{count}}", "maximum-datasources": "Максимальной количество источников данных равно {{count}}",
@ -1673,9 +1674,12 @@
"delete-action": "Удалить действие", "delete-action": "Удалить действие",
"delete-action-title": "Удалить действие виджета", "delete-action-title": "Удалить действие виджета",
"delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?", "delete-action-text": "Вы точно хотите удалить действие виджета '{{actionName}}'?",
"display-icon": "Показывать иконку в названии", "title-icon": "Иконка в названии виджета",
"display-icon": "Показывать иконку в названии виджета",
"icon-color": "Цвет иконки", "icon-color": "Цвет иконки",
"icon-size": "Размер иконки" "icon-size": "Размер иконки",
"advanced-settings": "Расширенные настройки",
"data-settings": "Настройки данных"
}, },
"widget-type": { "widget-type": {
"import": "Импортировать тип виджета", "import": "Импортировать тип виджета",

12
ui-ngx/src/assets/locale/locale.constant-uk_UA.json

@ -2202,7 +2202,7 @@
"advanced": "Додатково", "advanced": "Додатково",
"title": "Назва", "title": "Назва",
"general-settings": "Загальні налаштування", "general-settings": "Загальні налаштування",
"display-title": "Відобразити назву", "display-title": "Відобразити назву у віджеті",
"drop-shadow": "Тінь", "drop-shadow": "Тінь",
"enable-fullscreen": "Увімкнути повноекранний режим", "enable-fullscreen": "Увімкнути повноекранний режим",
"enable-data-export": "Увімкнути експорт даних", "enable-data-export": "Увімкнути експорт даних",
@ -2212,7 +2212,7 @@
"margin": "Границі", "margin": "Границі",
"widget-style": "Стиль віджетів", "widget-style": "Стиль віджетів",
"title-style": "Стиль заголовка", "title-style": "Стиль заголовка",
"mobile-mode-settings": "Налаштування мобільного режиму", "mobile-mode-settings": "мобільний режим",
"order": "Порядок", "order": "Порядок",
"height": "Висота", "height": "Висота",
"units": "Спеціальний символ після значення", "units": "Спеціальний символ після значення",
@ -2220,6 +2220,7 @@
"timewindow": "Вікно часу", "timewindow": "Вікно часу",
"use-dashboard-timewindow": "Використати вікно часу на панелі візуалізації", "use-dashboard-timewindow": "Використати вікно часу на панелі візуалізації",
"display-timewindow": "Показувати вікно часу", "display-timewindow": "Показувати вікно часу",
"legend": "Легенда",
"display-legend": "Показати легенду", "display-legend": "Показати легенду",
"datasources": "Джерела даних", "datasources": "Джерела даних",
"maximum-datasources": "Максимально { count, plural, 1 {1 дозволене джерело даних.} other {# дозволені джерела даних } }", "maximum-datasources": "Максимально { count, plural, 1 {1 дозволене джерело даних.} other {# дозволені джерела даних } }",
@ -2245,9 +2246,12 @@
"delete-action": "Видалити дію", "delete-action": "Видалити дію",
"delete-action-title": "Видалити дію віджета", "delete-action-title": "Видалити дію віджета",
"delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?", "delete-action-text": "Ви впевнені, що хочете видалити дію віджета '{{actionName}}'?",
"display-icon": "Показувати іконку у назві", "title-icon": "Іконка у назві віджету",
"display-icon": "Показувати іконку у назві віджету",
"icon-color": "Колір іконки", "icon-color": "Колір іконки",
"icon-size": "Розмір іконки" "icon-size": "Розмір іконки",
"advanced-settings": "Розширені налаштування",
"data-settings": "Налаштування даних"
}, },
"widget-type": { "widget-type": {
"import": "Імпортувати тип віджета", "import": "Імпортувати тип віджета",

Loading…
Cancel
Save