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 { DataKeyConfigDialogComponent } from '@home/components/widget/data-key-config-dialog.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 { ManageWidgetActionsComponent } from '@home/components/widget/action/manage-widget-actions.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,
DataKeyConfigComponent,
DataKeyConfigDialogComponent,
LegendConfigPanelComponent,
LegendConfigComponent,
ManageWidgetActionsComponent,
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.
-->
<button cdkOverlayOrigin #legendConfigPanelOrigin="cdkOverlayOrigin" [disabled]="disabled"
type="button"
mat-button mat-raised-button color="primary" (click)="openEditMode()">
<mat-icon class="material-icons">toc</mat-icon>
<span translate>legend.settings</span>
</button>
<form [formGroup]="legendConfigForm">
<div fxLayout.xs="column" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"
fxLayoutGap="8px">
<mat-form-field fxFlex>
<mat-label translate>legend.direction</mat-label>
<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.
///
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 {
Component,
forwardRef,
Inject,
Injector,
Input,
OnDestroy,
OnInit,
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';
LegendConfig,
LegendDirection,
legendDirectionTranslationMap,
LegendPosition,
legendPositionTranslationMap
} from '@shared/models/widget.models';
import { Subscription } from 'rxjs';
// @dynamic
@Component({
@ -58,105 +43,60 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
@Input() disabled: boolean;
@ViewChild('legendConfigPanelOrigin') legendConfigPanelOrigin: CdkOverlayOrigin;
innerValue: LegendConfig;
legendConfigForm: FormGroup;
legendDirection = LegendDirection;
legendDirections = Object.keys(LegendDirection);
legendDirectionTranslations = legendDirectionTranslationMap;
legendPosition = LegendPosition;
legendPositions = Object.keys(LegendPosition);
legendPositionTranslations = legendPositionTranslationMap;
private legendSettingsFormChanges$: Subscription;
private legendSettingsFormDirectionChanges$: Subscription;
private propagateChange = (_: any) => {};
constructor(private overlay: Overlay,
public viewContainerRef: ViewContainerRef,
public breakpointObserver: BreakpointObserver,
@Inject(DOCUMENT) private document: Document,
@Inject(WINDOW) private window: Window) {
constructor(private fb: FormBuilder) {
}
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 {
}
openEditMode() {
if (this.disabled) {
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';
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;
}
if (leftX + panelWidth > wWidth) {
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%');
this.legendConfigForm.patchValue({position}, {emitEvent: false}
);
}
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 {
const providers: StaticProvider[] = [
{provide: LEGEND_CONFIG_PANEL_DATA, useValue: data},
{provide: OverlayRef, useValue: overlayRef}
];
return Injector.create({parent: this.viewContainerRef.injector, providers});
ngOnDestroy(): void {
if (this.legendSettingsFormDirectionChanges$) {
this.legendSettingsFormDirectionChanges$.unsubscribe();
this.legendSettingsFormDirectionChanges$ = null;
}
if (this.legendSettingsFormChanges$) {
this.legendSettingsFormChanges$.unsubscribe();
this.legendSettingsFormChanges$ = null;
}
}
registerOnChange(fn: any): void {
@ -168,14 +108,29 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.legendConfigForm.disable({emitEvent: false});
} else {
this.legendConfigForm.enable({emitEvent: false});
}
}
writeValue(obj: LegendConfig): void {
this.innerValue = obj;
writeValue(legendConfig: LegendConfig): void {
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) {
this.innerValue = legendConfig;
this.propagateChange(this.innerValue);
private legendConfigUpdated() {
this.propagateChange(this.legendConfigForm.value);
}
}

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

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

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

@ -17,9 +17,6 @@
:host {
.tb-widget-config {
.tb-advanced-widget-config {
height: 100%;
}
.tb-advanced-widget-config {
height: 100%;
}
@ -69,6 +66,28 @@
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;
}
.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 {
&.mat-expanded {
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, []],
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) => {
if (value) {
this.widgetSettings.get('titleIcon').enable({emitEvent: false});
this.widgetSettings.get('iconColor').enable({emitEvent: false});
this.widgetSettings.get('iconSize').enable({emitEvent: false});
} else {
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) => {
@ -236,6 +253,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
});
}
ngOnDestroy(): void {
this.removeChangeSubscriptions();
}
private removeChangeSubscriptions() {
if (this.dataSettingsChangesSubscription) {
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)',
iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
showTitle: config.showTitle,
showTitle: isDefined(config.showTitle) ? config.showTitle : false,
dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
backgroundColor: config.backgroundColor,
@ -396,11 +417,25 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
},
{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;
if (showTitleIcon) {
this.widgetSettings.get('titleIcon').enable({emitEvent: false});
this.widgetSettings.get('iconColor').enable({emitEvent: false});
this.widgetSettings.get('iconSize').enable({emitEvent: false});
} else {
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;
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() {
this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe(
(icon) => {
if (icon) {
this.materialIconFormGroup.patchValue(
{icon}, {emitEvent: true}
);
if (!this.disabled) {
this.dialogs.materialIconPicker(this.materialIconFormGroup.get('icon').value).subscribe(
(icon) => {
if (icon) {
this.materialIconFormGroup.patchValue(
{icon}, {emitEvent: true}
);
}
}
}
);
);
}
}
}

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

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

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

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

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

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

Loading…
Cancel
Save