Browse Source

UI: Widget config improvements

pull/8708/head
Igor Kulikov 3 years ago
parent
commit
600d6cf27c
  1. 22
      application/src/main/data/json/system/widget_bundles/charts.json
  2. 30
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  3. 8
      ui-ngx/src/app/modules/common/modules-map.ts
  4. 37
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html
  5. 7
      ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts
  6. 13
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html
  7. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  8. 32
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html
  9. 35
      ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts
  10. 9
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  11. 32
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.html
  12. 151
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.ts
  13. 17
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts
  14. 4
      ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts
  15. 3
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts
  16. 0
      ui-ngx/src/app/modules/home/components/widget/lib/legend.component.html
  17. 0
      ui-ngx/src/app/modules/home/components/widget/lib/legend.component.scss
  18. 0
      ui-ngx/src/app/modules/home/components/widget/lib/legend.component.ts
  19. 21
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html
  20. 22
      ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts
  21. 0
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html
  22. 0
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts
  23. 3
      ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts
  24. 10
      ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts
  25. 337
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  26. 23
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss
  27. 79
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  28. 32
      ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html
  29. 49
      ui-ngx/src/app/modules/home/components/widget/widget-preview.component.scss
  30. 64
      ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts
  31. 16
      ui-ngx/src/app/modules/home/components/widget/widget.component.html
  32. 87
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  33. 1
      ui-ngx/src/app/shared/components/public-api.ts
  34. 3
      ui-ngx/src/app/shared/components/toggle-header.component.html
  35. 14
      ui-ngx/src/app/shared/components/toggle-header.component.scss
  36. 9
      ui-ngx/src/app/shared/components/toggle-header.component.ts
  37. 36
      ui-ngx/src/app/shared/models/widget.models.ts
  38. 7
      ui-ngx/src/app/shared/shared.module.ts
  39. 7
      ui-ngx/src/assets/locale/locale.constant-en_US.json

22
application/src/main/data/json/system/widget_bundles/charts.json

File diff suppressed because one or more lines are too long

30
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -29,8 +29,15 @@ import {
GridSettings, GridSettings,
WidgetLayout WidgetLayout
} from '@shared/models/dashboard.models'; } from '@shared/models/dashboard.models';
import { isDefined, isString, isUndefined } from '@core/utils'; import { isDefined, isDefinedAndNotNull, isString, isUndefined } from '@core/utils';
import { Datasource, DatasourceType, Widget, WidgetConfig, widgetType } from '@app/shared/models/widget.models'; import {
Datasource,
DatasourceType,
defaultLegendConfig,
Widget,
WidgetConfig,
widgetType
} from '@app/shared/models/widget.models';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models'; import { AliasFilterType, EntityAlias, EntityAliasFilter } from '@app/shared/models/alias.models';
import { EntityId } from '@app/shared/models/id/entity-id'; import { EntityId } from '@app/shared/models/id/entity-id';
@ -204,9 +211,26 @@ export class DashboardUtilsService {
public validateAndUpdateWidget(widget: Widget): Widget { public validateAndUpdateWidget(widget: Widget): Widget {
widget.config = this.validateAndUpdateWidgetConfig(widget.config, widget.type); widget.config = this.validateAndUpdateWidgetConfig(widget.config, widget.type);
// Temp workaround // Temp workaround
if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') {
widget.typeAlias = 'basic_timeseries'; widget.typeAlias = 'basic_timeseries';
} }
if (widget.isSystemType && widget.bundleAlias === 'charts' &&
['state_chart', 'basic_timeseries', 'timeseries_bars_flot'].includes(widget.typeAlias)) {
const widgetConfig = widget.config;
const widgetSettings = widget.config.settings;
if (isDefinedAndNotNull(widgetConfig.showLegend)) {
widgetSettings.showLegend = widgetConfig.showLegend;
delete widgetConfig.showLegend;
} else if (isUndefined(widgetSettings.showLegend)) {
widgetSettings.showLegend = true;
}
if (isDefinedAndNotNull(widgetConfig.legendConfig)) {
widgetSettings.legendConfig = widgetConfig.legendConfig;
delete widgetConfig.legendConfig;
} else if (isUndefined(widgetSettings.legendConfig)) {
widgetSettings.legendConfig = defaultLegendConfig(widget.type);
}
}
return widget; return widget;
} }

8
ui-ngx/src/app/modules/common/modules-map.ts

@ -196,7 +196,7 @@ import * as AddAttributeDialogComponent from '@home/components/attribute/add-att
import * as EditAttributeValuePanelComponent from '@home/components/attribute/edit-attribute-value-panel.component'; import * as EditAttributeValuePanelComponent from '@home/components/attribute/edit-attribute-value-panel.component';
import * as DashboardComponent from '@home/components/dashboard/dashboard.component'; import * as DashboardComponent from '@home/components/dashboard/dashboard.component';
import * as WidgetComponent from '@home/components/widget/widget.component'; import * as WidgetComponent from '@home/components/widget/widget.component';
import * as LegendComponent from '@home/components/widget/legend.component'; import * as LegendComponent from '@home/components/widget/lib/legend.component';
import * as AliasesEntitySelectPanelComponent from '@home/components/alias/aliases-entity-select-panel.component'; import * as AliasesEntitySelectPanelComponent from '@home/components/alias/aliases-entity-select-panel.component';
import * as AliasesEntitySelectComponent from '@home/components/alias/aliases-entity-select.component'; import * as AliasesEntitySelectComponent from '@home/components/alias/aliases-entity-select.component';
import * as WidgetConfigComponent from '@home/components/widget/widget-config.component'; import * as WidgetConfigComponent from '@home/components/widget/widget-config.component';
@ -209,7 +209,7 @@ import * as EntityAliasSelectComponent from '@home/components/alias/entity-alias
import * as DataKeysComponent from '@home/components/widget/data-keys.component'; import * as DataKeysComponent from '@home/components/widget/data-keys.component';
import * as DataKeyConfigDialogComponent from '@home/components/widget/data-key-config-dialog.component'; import * as DataKeyConfigDialogComponent from '@home/components/widget/data-key-config-dialog.component';
import * as DataKeyConfigComponent from '@home/components/widget/data-key-config.component'; import * as DataKeyConfigComponent from '@home/components/widget/data-key-config.component';
import * as LegendConfigComponent from '@home/components/widget/legend-config.component'; import * as LegendConfigComponent from '@home/components/widget/lib/settings/common/legend-config.component';
import * as ManageWidgetActionsComponent from '@home/components/widget/action/manage-widget-actions.component'; import * as ManageWidgetActionsComponent from '@home/components/widget/action/manage-widget-actions.component';
import * as WidgetActionDialogComponent from '@home/components/widget/action/widget-action-dialog.component'; import * as WidgetActionDialogComponent from '@home/components/widget/action/widget-action-dialog.component';
import * as CustomActionPrettyResourcesTabsComponent from '@home/components/widget/action/custom-action-pretty-resources-tabs.component'; import * as CustomActionPrettyResourcesTabsComponent from '@home/components/widget/action/custom-action-pretty-resources-tabs.component';
@ -491,7 +491,7 @@ class ModulesMap implements IModulesMap {
'@home/components/attribute/edit-attribute-value-panel.component': EditAttributeValuePanelComponent, '@home/components/attribute/edit-attribute-value-panel.component': EditAttributeValuePanelComponent,
'@home/components/dashboard/dashboard.component': DashboardComponent, '@home/components/dashboard/dashboard.component': DashboardComponent,
'@home/components/widget/widget.component': WidgetComponent, '@home/components/widget/widget.component': WidgetComponent,
'@home/components/widget/legend.component': LegendComponent, '@home/components/widget/lib/legend.component': LegendComponent,
'@home/components/alias/aliases-entity-select-panel.component': AliasesEntitySelectPanelComponent, '@home/components/alias/aliases-entity-select-panel.component': AliasesEntitySelectPanelComponent,
'@home/components/alias/aliases-entity-select.component': AliasesEntitySelectComponent, '@home/components/alias/aliases-entity-select.component': AliasesEntitySelectComponent,
'@home/components/widget/widget-config.component': WidgetConfigComponent, '@home/components/widget/widget-config.component': WidgetConfigComponent,
@ -504,7 +504,7 @@ class ModulesMap implements IModulesMap {
'@home/components/widget/data-keys.component': DataKeysComponent, '@home/components/widget/data-keys.component': DataKeysComponent,
'@home/components/widget/data-key-config-dialog.component': DataKeyConfigDialogComponent, '@home/components/widget/data-key-config-dialog.component': DataKeyConfigDialogComponent,
'@home/components/widget/data-key-config.component': DataKeyConfigComponent, '@home/components/widget/data-key-config.component': DataKeyConfigComponent,
'@home/components/widget/legend-config.component': LegendConfigComponent, '@home/components/widget/lib/settings/common/legend-config.component': LegendConfigComponent,
'@home/components/widget/action/manage-widget-actions.component': ManageWidgetActionsComponent, '@home/components/widget/action/manage-widget-actions.component': ManageWidgetActionsComponent,
'@home/components/widget/action/widget-action-dialog.component': WidgetActionDialogComponent, '@home/components/widget/action/widget-action-dialog.component': WidgetActionDialogComponent,
'@home/components/widget/action/custom-action-pretty-resources-tabs.component': CustomActionPrettyResourcesTabsComponent, '@home/components/widget/action/custom-action-pretty-resources-tabs.component': CustomActionPrettyResourcesTabsComponent,

37
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<form [formGroup]="widgetFormGroup" (ngSubmit)="add()" style="width: 1200px;"> <form [formGroup]="widgetFormGroup" style="width: 1200px;">
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 translate>widget.add</h2> <h2 translate>widget.add</h2>
<span fxFlex>: {{data.widgetInfo.widgetName}}</span> <span fxFlex>: {{data.widgetInfo.widgetName}}</span>
@ -37,9 +37,15 @@
[widget]="widget" [widget]="widget"
formControlName="widgetConfig"> formControlName="widgetConfig">
</tb-widget-config> </tb-widget-config>
<tb-widget-preview *ngIf="previewMode" class="tb-absolute-fill"
[aliasController]="aliasController"
[stateController]="stateController"
[widget]="widget"
[widgetConfig]="widgetFormGroup.get('widgetConfig').value.config">
</tb-widget-preview>
</fieldset> </fieldset>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="space-between center">
<button mat-button color="primary" <button mat-button color="primary"
type="button" type="button"
[disabled]="(isLoading$ | async)" [disabled]="(isLoading$ | async)"
@ -47,10 +53,27 @@
cdkFocusInitial> cdkFocusInitial>
{{ 'action.cancel' | translate }} {{ 'action.cancel' | translate }}
</button> </button>
<button mat-raised-button color="primary" <div fxLayout="row" fxLayoutGap="8px">
type="submit" <button *ngIf="!previewMode"
[disabled]="(isLoading$ | async) || widgetFormGroup.invalid"> mat-button color="primary"
{{ 'action.add' | translate }} type="button"
</button> [disabled]="(isLoading$ | async) || widgetFormGroup.invalid"
(click)="previewMode = true"
cdkFocusInitial>
{{ 'widget-config.preview' | translate }}
</button>
<button *ngIf="previewMode"
mat-button color="primary"
type="button"
(click)="previewMode = false"
cdkFocusInitial>
{{ 'action.back' | translate }}
</button>
<button mat-raised-button color="primary"
(click)="add()"
[disabled]="(isLoading$ | async) || widgetFormGroup.invalid">
{{ 'action.add' | translate }}
</button>
</div>
</div> </div>
</form> </form>

7
ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts

@ -24,13 +24,14 @@ import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component'; import { DialogComponent } from '@app/shared/components/dialog.component';
import { Widget, widgetTypesData } from '@shared/models/widget.models'; import { Widget, widgetTypesData } from '@shared/models/widget.models';
import { Dashboard } from '@app/shared/models/dashboard.models'; import { Dashboard } from '@app/shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController, IStateController } from '@core/api/widget-api.models';
import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-component.models'; import { WidgetConfigComponentData, WidgetInfo } from '@home/models/widget-component.models';
import { isDefined, isString } from '@core/utils'; import { isDefined, isString } from '@core/utils';
export interface AddWidgetDialogData { export interface AddWidgetDialogData {
dashboard: Dashboard; dashboard: Dashboard;
aliasController: IAliasController; aliasController: IAliasController;
stateController: IStateController;
widget: Widget; widget: Widget;
widgetInfo: WidgetInfo; widgetInfo: WidgetInfo;
} }
@ -48,8 +49,11 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
dashboard: Dashboard; dashboard: Dashboard;
aliasController: IAliasController; aliasController: IAliasController;
stateController: IStateController;
widget: Widget; widget: Widget;
previewMode = false;
submitted = false; submitted = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
@ -62,6 +66,7 @@ export class AddWidgetDialogComponent extends DialogComponent<AddWidgetDialogCom
this.dashboard = this.data.dashboard; this.dashboard = this.data.dashboard;
this.aliasController = this.data.aliasController; this.aliasController = this.data.aliasController;
this.stateController = this.data.stateController;
this.widget = this.data.widget; this.widget = this.data.widget;
const widgetInfo = this.data.widgetInfo; const widgetInfo = this.data.widgetInfo;

13
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html

@ -357,21 +357,20 @@
<tb-details-panel *ngIf="!isEditingWidgetClosed" fxFlex <tb-details-panel *ngIf="!isEditingWidgetClosed" fxFlex
headerTitle="{{editingWidget?.config.title}}" headerTitle="{{editingWidget?.config.title}}"
headerSubtitle="{{ editingWidgetSubtitle }}" headerSubtitle="{{ editingWidgetSubtitle }}"
[isReadOnly]="false" [isReadOnly]="true"
[isAlwaysEdit]="true" (closeDetails)="onEditWidgetClosed()">
(closeDetails)="onEditWidgetClosed()"
(toggleDetailsEditMode)="onRevertWidgetEdit()"
(applyDetails)="saveWidget()"
[theForm]="tbEditWidget.widgetFormGroup">
<div class="details-buttons"> <div class="details-buttons">
<div [tb-help]="helpLinkIdForWidgetType()"></div> <div [tb-help]="helpLinkIdForWidgetType()"></div>
</div> </div>
<tb-edit-widget #tbEditWidget <tb-edit-widget #tbEditWidget
[dashboard]="dashboard" [dashboard]="dashboard"
[aliasController]="dashboardCtx.aliasController" [aliasController]="dashboardCtx.aliasController"
[stateController]="dashboardCtx.stateController"
[widgetEditMode]="widgetEditMode" [widgetEditMode]="widgetEditMode"
[widget]="editingWidget" [widget]="editingWidget"
[widgetLayout]="editingWidgetLayout"> [widgetLayout]="editingWidgetLayout"
(revertWidgetConfig)="onRevertWidgetEdit()"
(applyWidgetConfig)="saveWidget()">
</tb-edit-widget> </tb-edit-widget>
</tb-details-panel> </tb-details-panel>
<tb-details-panel *ngIf="!isAddingWidgetClosed && !widgetEditMode" fxFlex <tb-details-panel *ngIf="!isAddingWidgetClosed && !widgetEditMode" fxFlex

1
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -1176,6 +1176,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
data: { data: {
dashboard: this.dashboard, dashboard: this.dashboard,
aliasController: this.dashboardCtx.aliasController, aliasController: this.dashboardCtx.aliasController,
stateController: this.dashboardCtx.stateController,
widget: newWidget, widget: newWidget,
widgetInfo: widgetTypeInfo widgetInfo: widgetTypeInfo
} }

32
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.html

@ -23,6 +23,38 @@
[dashboard]="dashboard" [dashboard]="dashboard"
[widget]="widget" [widget]="widget"
formControlName="widgetConfig"> formControlName="widgetConfig">
<div class="tb-widget-config-header-suffix" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="12px">
<button [disabled]="(isLoading$ | async) || widgetFormGroup.invalid"
mat-button color="primary"
(click)="previewMode = true">
<mat-icon>visibility</mat-icon>
{{ 'widget-config.preview' | translate }}
</button>
<button [disabled]="(isLoading$ | async) || !widgetFormGroup.dirty"
mat-stroked-button color="primary"
(click)="onRevertWidgetConfig()">
<mat-icon>close</mat-icon>
{{ 'action.decline' | translate }}
</button>
<button [disabled]="(isLoading$ | async) || widgetFormGroup.invalid || !widgetFormGroup.dirty"
mat-flat-button color="primary"
(click)="onApplyWidgetConfig()">
<mat-icon>done</mat-icon>
{{ 'action.apply' | translate }}
</button>
</div>
</tb-widget-config> </tb-widget-config>
<tb-widget-preview *ngIf="previewMode" class="tb-absolute-fill"
[aliasController]="aliasController"
[stateController]="stateController"
[widget]="widget"
[widgetConfig]="widgetFormGroup.get('widgetConfig').value.config">
<div class="tb-preview-panel-content">
<button mat-button color="primary"
(click)="previewMode = false">
{{ 'action.back' | translate }}
</button>
</div>
</tb-widget-preview>
</fieldset> </fieldset>
</form> </form>

35
ui-ngx/src/app/modules/home/components/dashboard-page/edit-widget.component.ts

@ -14,13 +14,22 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges
} from '@angular/core';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Dashboard, WidgetLayout } from '@shared/models/dashboard.models'; import { Dashboard, WidgetLayout } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController, IStateController } from '@core/api/widget-api.models';
import { Widget } from '@shared/models/widget.models'; import { Widget } from '@shared/models/widget.models';
import { WidgetComponentService } from '@home/components/widget/widget-component.service'; import { WidgetComponentService } from '@home/components/widget/widget-component.service';
import { WidgetConfigComponentData } from '../../models/widget-component.models'; import { WidgetConfigComponentData } from '../../models/widget-component.models';
@ -40,6 +49,9 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
@Input() @Input()
aliasController: IAliasController; aliasController: IAliasController;
@Input()
stateController: IStateController;
@Input() @Input()
widgetEditMode: boolean; widgetEditMode: boolean;
@ -49,10 +61,18 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
@Input() @Input()
widgetLayout: WidgetLayout; widgetLayout: WidgetLayout;
@Output()
applyWidgetConfig = new EventEmitter<void>();
@Output()
revertWidgetConfig = new EventEmitter<void>();
widgetFormGroup: UntypedFormGroup; widgetFormGroup: UntypedFormGroup;
widgetConfig: WidgetConfigComponentData; widgetConfig: WidgetConfigComponentData;
previewMode = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
private dialog: MatDialog, private dialog: MatDialog,
private fb: UntypedFormBuilder, private fb: UntypedFormBuilder,
@ -78,10 +98,21 @@ export class EditWidgetComponent extends PageComponent implements OnInit, OnChan
} }
} }
if (reloadConfig) { if (reloadConfig) {
this.previewMode = false;
this.loadWidgetConfig(); this.loadWidgetConfig();
} }
} }
onApplyWidgetConfig() {
if (this.widgetFormGroup.valid) {
this.applyWidgetConfig.emit();
}
}
onRevertWidgetConfig() {
this.revertWidgetConfig.emit();
}
private loadWidgetConfig() { private loadWidgetConfig() {
if (!this.widget) { if (!this.widget) {
return; return;

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

@ -36,7 +36,6 @@ import { EditAttributeValuePanelComponent } from '@home/components/attribute/edi
import { DashboardComponent } from '@home/components/dashboard/dashboard.component'; import { DashboardComponent } from '@home/components/dashboard/dashboard.component';
import { WidgetComponent } from '@home/components/widget/widget.component'; import { WidgetComponent } from '@home/components/widget/widget.component';
import { WidgetComponentService } from '@home/components/widget/widget-component.service'; import { WidgetComponentService } from '@home/components/widget/widget-component.service';
import { LegendComponent } from '@home/components/widget/legend.component';
import { AliasesEntitySelectPanelComponent } from '@home/components/alias/aliases-entity-select-panel.component'; import { AliasesEntitySelectPanelComponent } from '@home/components/alias/aliases-entity-select-panel.component';
import { AliasesEntitySelectComponent } from '@home/components/alias/aliases-entity-select.component'; import { AliasesEntitySelectComponent } from '@home/components/alias/aliases-entity-select.component';
import { WidgetConfigComponent } from '@home/components/widget/widget-config.component'; import { WidgetConfigComponent } from '@home/components/widget/widget-config.component';
@ -49,7 +48,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 { 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';
import { CustomActionPrettyResourcesTabsComponent } from '@home/components/widget/action/custom-action-pretty-resources-tabs.component'; import { CustomActionPrettyResourcesTabsComponent } from '@home/components/widget/action/custom-action-pretty-resources-tabs.component';
@ -182,6 +180,7 @@ import { AlarmFilterConfigComponent } from '@home/components/alarm/alarm-filter-
import { AlarmAssigneeSelectPanelComponent } from '@home/components/alarm/alarm-assignee-select-panel.component'; import { AlarmAssigneeSelectPanelComponent } from '@home/components/alarm/alarm-assignee-select-panel.component';
import { AlarmAssigneeSelectComponent } from '@home/components/alarm/alarm-assignee-select.component'; import { AlarmAssigneeSelectComponent } from '@home/components/alarm/alarm-assignee-select.component';
import { DeviceInfoFilterComponent } from '@home/components/device/device-info-filter.component'; import { DeviceInfoFilterComponent } from '@home/components/device/device-info-filter.component';
import { WidgetPreviewComponent } from '@home/components/widget/widget-preview.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -220,16 +219,15 @@ import { DeviceInfoFilterComponent } from '@home/components/device/device-info-f
DashboardComponent, DashboardComponent,
WidgetContainerComponent, WidgetContainerComponent,
WidgetComponent, WidgetComponent,
LegendComponent,
WidgetSettingsComponent, WidgetSettingsComponent,
WidgetConfigComponent, WidgetConfigComponent,
WidgetPreviewComponent,
EntityFilterViewComponent, EntityFilterViewComponent,
EntityFilterComponent, EntityFilterComponent,
EntityAliasSelectComponent, EntityAliasSelectComponent,
DataKeysComponent, DataKeysComponent,
DataKeyConfigComponent, DataKeyConfigComponent,
DataKeyConfigDialogComponent, DataKeyConfigDialogComponent,
LegendConfigComponent,
ManageWidgetActionsComponent, ManageWidgetActionsComponent,
WidgetActionDialogComponent, WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,
@ -371,16 +369,15 @@ import { DeviceInfoFilterComponent } from '@home/components/device/device-info-f
DashboardComponent, DashboardComponent,
WidgetContainerComponent, WidgetContainerComponent,
WidgetComponent, WidgetComponent,
LegendComponent,
WidgetSettingsComponent, WidgetSettingsComponent,
WidgetConfigComponent, WidgetConfigComponent,
WidgetPreviewComponent,
EntityFilterViewComponent, EntityFilterViewComponent,
EntityFilterComponent, EntityFilterComponent,
EntityAliasSelectComponent, EntityAliasSelectComponent,
DataKeysComponent, DataKeysComponent,
DataKeyConfigComponent, DataKeyConfigComponent,
DataKeyConfigDialogComponent, DataKeyConfigDialogComponent,
LegendConfigComponent,
ManageWidgetActionsComponent, ManageWidgetActionsComponent,
WidgetActionDialogComponent, WidgetActionDialogComponent,
CustomActionPrettyResourcesTabsComponent, CustomActionPrettyResourcesTabsComponent,

32
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.html

@ -0,0 +1,32 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-absolute-fill" [fxLayout]="legendContainerLayoutType">
<tb-legend *ngIf="displayLegend && isLegendFirst"
[ngStyle]="legendStyle"
[legendConfig]="legendConfig"
[legendData]="legendData"
(legendKeyHiddenChange)="onLegendKeyHiddenChange($event)">
</tb-legend>
<div fxFlex #flotElement></div>
<tb-legend *ngIf="displayLegend && !isLegendFirst"
[ngStyle]="legendStyle"
[legendConfig]="legendConfig"
[legendData]="legendData"
(legendKeyHiddenChange)="onLegendKeyHiddenChange($event)">
</tb-legend>
</div>

151
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.component.ts

@ -0,0 +1,151 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { WidgetContext } from '@home/models/widget-component.models';
import { ChartType, TbFlotSettings } from '@home/components/widget/lib/flot-widget.models';
import { TbFlot } from '@home/components/widget/lib/flot-widget';
import {
defaultLegendConfig,
LegendConfig,
LegendData,
LegendPosition,
widgetType
} from '@shared/models/widget.models';
import { isDefinedAndNotNull } from '@core/utils';
@Component({
selector: 'tb-flot-widget',
templateUrl: './flot-widget.component.html',
styleUrls: []
})
export class FlotWidgetComponent implements OnInit {
@ViewChild('flotElement', {static: true}) flotElement: ElementRef;
@Input()
ctx: WidgetContext;
@Input()
chartType: ChartType;
displayLegend: boolean;
legendConfig: LegendConfig;
legendData: LegendData;
isLegendFirst: boolean;
legendContainerLayoutType: string;
legendStyle: {[klass: string]: any};
public settings: TbFlotSettings;
private flot: TbFlot;
constructor() {
}
ngOnInit(): void {
this.ctx.$scope.flotWidget = this;
this.settings = this.ctx.settings;
this.chartType = this.chartType || 'line';
this.configureLegend();
this.flot = new TbFlot(this.ctx, this.chartType, $(this.flotElement.nativeElement));
}
private configureLegend(): void {
this.displayLegend = isDefinedAndNotNull(this.settings.showLegend) ? this.settings.showLegend
: false;
this.legendContainerLayoutType = 'column';
if (this.displayLegend) {
this.legendConfig = this.settings.legendConfig || defaultLegendConfig(widgetType.timeseries);
if (this.ctx.defaultSubscription) {
this.legendData = this.ctx.defaultSubscription.legendData;
} else {
this.legendData = {
keys: [],
data: []
};
}
if (this.legendConfig.position === LegendPosition.top ||
this.legendConfig.position === LegendPosition.bottom) {
this.legendContainerLayoutType = 'column';
this.isLegendFirst = this.legendConfig.position === LegendPosition.top;
} else {
this.legendContainerLayoutType = 'row';
this.isLegendFirst = this.legendConfig.position === LegendPosition.left;
}
switch (this.legendConfig.position) {
case LegendPosition.top:
this.legendStyle = {
paddingBottom: '8px',
maxHeight: '50%',
overflowY: 'auto'
};
break;
case LegendPosition.bottom:
this.legendStyle = {
paddingTop: '8px',
maxHeight: '50%',
overflowY: 'auto'
};
break;
case LegendPosition.left:
this.legendStyle = {
paddingRight: '0px',
maxWidth: '50%',
overflowY: 'auto'
};
break;
case LegendPosition.right:
this.legendStyle = {
paddingLeft: '0px',
maxWidth: '50%',
overflowY: 'auto'
};
break;
}
}
}
public onLegendKeyHiddenChange(index: number) {
for (const id of Object.keys(this.ctx.subscriptions)) {
const subscription = this.ctx.subscriptions[id];
subscription.updateDataVisibility(index);
}
}
public onDataUpdated() {
this.flot.update();
}
public onLatestDataUpdated() {
this.flot.latestDataUpdate();
}
public onResize() {
this.flot.resize();
}
public onEditModeChanged() {
this.flot.checkMouseEvents();
}
public onDestroy() {
this.flot.destroy();
}
}

17
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts

@ -17,13 +17,21 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference // eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../../../../../../../src/typings/jquery.flot.typings.d.ts" /> /// <reference path="../../../../../../../src/typings/jquery.flot.typings.d.ts" />
import { DataKey, Datasource, DatasourceData, FormattedData, JsonSettingsSchema } from '@shared/models/widget.models'; import {
DataKey,
Datasource,
DatasourceData,
FormattedData,
JsonSettingsSchema,
LegendConfig
} from '@shared/models/widget.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { ComparisonDuration } from '@shared/models/time/time.models'; import { ComparisonDuration } from '@shared/models/time/time.models';
export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph'; export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph';
export declare type TbFlotSettings = TbFlotBaseSettings & TbFlotGraphSettings & TbFlotBarSettings & TbFlotPieSettings; export declare type TbFlotSettings = TbFlotBaseSettings & TbFlotLegendSettings &
TbFlotGraphSettings & TbFlotBarSettings & TbFlotPieSettings;
export declare type TooltipValueFormatFunction = (value: any, latestData: FormattedData) => string; export declare type TooltipValueFormatFunction = (value: any, latestData: FormattedData) => string;
@ -140,6 +148,11 @@ export interface TbFlotBaseSettings {
yaxis: TbFlotYAxisSettings; yaxis: TbFlotYAxisSettings;
} }
export interface TbFlotLegendSettings {
showLegend?: boolean;
legendConfig?: LegendConfig;
}
export interface TbFlotComparisonSettings { export interface TbFlotComparisonSettings {
comparisonEnabled: boolean; comparisonEnabled: boolean;
timeForComparison: ComparisonDuration; timeForComparison: ComparisonDuration;

4
ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts

@ -127,7 +127,7 @@ export class TbFlot {
private pieAnimationLastTime: number; private pieAnimationLastTime: number;
private pieAnimationCaf: CancelAnimationFrame; private pieAnimationCaf: CancelAnimationFrame;
constructor(private ctx: WidgetContext, private readonly chartType: ChartType) { constructor(private ctx: WidgetContext, private readonly chartType: ChartType, private $flotElement?: JQuery<any>) {
this.chartType = this.chartType || 'line'; this.chartType = this.chartType || 'line';
this.settings = ctx.settings as TbFlotSettings; this.settings = ctx.settings as TbFlotSettings;
this.utils = this.ctx.$injector.get(UtilsService); this.utils = this.ctx.$injector.get(UtilsService);
@ -334,7 +334,7 @@ export class TbFlot {
} }
if (this.ctx.defaultSubscription) { if (this.ctx.defaultSubscription) {
this.init(this.ctx.$container, this.ctx.defaultSubscription); this.init(this.$flotElement || this.ctx.$container, this.ctx.defaultSubscription);
} }
} }

3
ui-ngx/src/app/modules/home/components/widget/lib/home-page/home-page-widgets.module.ts

@ -28,7 +28,6 @@ import { GettingStartedWidgetComponent } from '@home/components/widget/lib/home-
import { import {
GettingStartedCompletedDialogComponent GettingStartedCompletedDialogComponent
} from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component'; } from '@home/components/widget/lib/home-page/getting-started-completed-dialog.component';
import { ToggleHeaderComponent } from '@home/components/widget/lib/home-page/toggle-header.component';
import { UsageInfoWidgetComponent } from '@home/components/widget/lib/home-page/usage-info-widget.component'; import { UsageInfoWidgetComponent } from '@home/components/widget/lib/home-page/usage-info-widget.component';
import { QuickLinksWidgetComponent } from '@home/components/widget/lib/home-page/quick-links-widget.component'; import { QuickLinksWidgetComponent } from '@home/components/widget/lib/home-page/quick-links-widget.component';
import { QuickLinkComponent } from '@home/components/widget/lib/home-page/quick-link.component'; import { QuickLinkComponent } from '@home/components/widget/lib/home-page/quick-link.component';
@ -49,7 +48,6 @@ import {
EditLinksDialogComponent, EditLinksDialogComponent,
GettingStartedWidgetComponent, GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent, GettingStartedCompletedDialogComponent,
ToggleHeaderComponent,
UsageInfoWidgetComponent, UsageInfoWidgetComponent,
QuickLinksWidgetComponent, QuickLinksWidgetComponent,
QuickLinkComponent, QuickLinkComponent,
@ -70,7 +68,6 @@ import {
EditLinksDialogComponent, EditLinksDialogComponent,
GettingStartedWidgetComponent, GettingStartedWidgetComponent,
GettingStartedCompletedDialogComponent, GettingStartedCompletedDialogComponent,
ToggleHeaderComponent,
UsageInfoWidgetComponent, UsageInfoWidgetComponent,
QuickLinksWidgetComponent, QuickLinksWidgetComponent,
QuickLinkComponent, QuickLinkComponent,

0
ui-ngx/src/app/modules/home/components/widget/legend.component.html → ui-ngx/src/app/modules/home/components/widget/lib/legend.component.html

0
ui-ngx/src/app/modules/home/components/widget/legend.component.scss → ui-ngx/src/app/modules/home/components/widget/lib/legend.component.scss

0
ui-ngx/src/app/modules/home/components/widget/legend.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/legend.component.ts

21
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.html

@ -68,6 +68,25 @@
<input matInput type="number" min="0" formControlName="thresholdsLineWidth"> <input matInput type="number" min="0" formControlName="thresholdsLineWidth">
</mat-form-field> </mat-form-field>
</fieldset> </fieldset>
<fieldset *ngIf="chartType === 'graph' || chartType === 'bar'" class="fields-group fields-group-slider">
<legend class="group-title" translate>widget-config.legend</legend>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('showLegend').value">
<mat-expansion-panel-header fxLayout="row" style="min-height: 48px;">
<mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="showLegend" (click)="$event.stopPropagation()"
fxLayoutAlign="center" style="height: 100%;">
{{ 'widget-config.display-legend' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxFlex="40" style="white-space: nowrap;" 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 class="fields-group fields-group-slider"> <fieldset class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.tooltip-settings</legend> <legend class="group-title" translate>widgets.chart.tooltip-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('showTooltip').value"> <mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('showTooltip').value">
@ -290,7 +309,7 @@
<fieldset *ngIf="chartType === 'graph' || chartType === 'bar'" class="fields-group fields-group-slider"> <fieldset *ngIf="chartType === 'graph' || chartType === 'bar'" class="fields-group fields-group-slider">
<legend class="group-title" translate>widgets.chart.custom-legend-settings</legend> <legend class="group-title" translate>widgets.chart.custom-legend-settings</legend>
<mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('customLegendEnabled').value"> <mat-expansion-panel class="tb-settings" [expanded]="flotSettingsFormGroup.get('customLegendEnabled').value">
<mat-expansion-panel-header fxLayout="row" style="height: 100%; min-height: 48px;"> <mat-expansion-panel-header fxLayout="row" style="min-height: 48px;">
<mat-panel-title fxFlex="60"> <mat-panel-title fxFlex="60">
<mat-slide-toggle formControlName="customLegendEnabled" (click)="$event.stopPropagation()" <mat-slide-toggle formControlName="customLegendEnabled" (click)="$event.stopPropagation()"
fxLayoutAlign="center" style="height: 100%;"> fxLayoutAlign="center" style="height: 100%;">

22
ui-ngx/src/app/modules/home/components/widget/lib/settings/chart/flot-widget-settings.component.ts

@ -40,8 +40,9 @@ import {
labelDataKeyValidator labelDataKeyValidator
} from '@home/components/widget/lib/settings/chart/label-data-key.component'; } from '@home/components/widget/lib/settings/chart/label-data-key.component';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { defaultLegendConfig, widgetType } from '@shared/models/widget.models';
export function flotDefaultSettings(chartType: ChartType): Partial<TbFlotSettings> { export const flotDefaultSettings = (chartType: ChartType): Partial<TbFlotSettings> => {
const settings: Partial<TbFlotSettings> = { const settings: Partial<TbFlotSettings> = {
stack: false, stack: false,
fontColor: '#545454', fontColor: '#545454',
@ -95,9 +96,11 @@ export function flotDefaultSettings(chartType: ChartType): Partial<TbFlotSetting
}; };
settings.customLegendEnabled = false; settings.customLegendEnabled = false;
settings.dataKeysListForLabels = []; settings.dataKeysListForLabels = [];
settings.showLegend = true;
settings.legendConfig = defaultLegendConfig(widgetType.timeseries);
} }
return settings; return settings;
} };
@Component({ @Component({
selector: 'tb-flot-widget-settings', selector: 'tb-flot-widget-settings',
@ -221,6 +224,11 @@ export class FlotWidgetSettingsComponent extends PageComponent implements OnInit
showLabels: [true, []] showLabels: [true, []]
})); }));
// Legend settings
this.flotSettingsFormGroup.addControl('showLegend', this.fb.control(false, []));
this.flotSettingsFormGroup.addControl('legendConfig', this.fb.control(null, []));
// Custom legend settings // Custom legend settings
this.flotSettingsFormGroup.addControl('customLegendEnabled', this.fb.control(false, [])); this.flotSettingsFormGroup.addControl('customLegendEnabled', this.fb.control(false, []));
@ -240,6 +248,9 @@ export class FlotWidgetSettingsComponent extends PageComponent implements OnInit
}); });
if (this.chartType === 'graph' || this.chartType === 'bar') { if (this.chartType === 'graph' || this.chartType === 'bar') {
this.flotSettingsFormGroup.get('showLegend').valueChanges.subscribe(() => {
this.updateValidators(true);
});
this.flotSettingsFormGroup.get('comparisonEnabled').valueChanges.subscribe(() => { this.flotSettingsFormGroup.get('comparisonEnabled').valueChanges.subscribe(() => {
this.updateValidators(true); this.updateValidators(true);
}); });
@ -350,9 +361,15 @@ export class FlotWidgetSettingsComponent extends PageComponent implements OnInit
this.flotSettingsFormGroup.get('yaxis.ticksFormatter').updateValueAndValidity({emitEvent: false}); this.flotSettingsFormGroup.get('yaxis.ticksFormatter').updateValueAndValidity({emitEvent: false});
if (this.chartType === 'graph' || this.chartType === 'bar') { if (this.chartType === 'graph' || this.chartType === 'bar') {
const showLegend: boolean = this.flotSettingsFormGroup.get('showLegend').value;
const comparisonEnabled: boolean = this.flotSettingsFormGroup.get('comparisonEnabled').value; const comparisonEnabled: boolean = this.flotSettingsFormGroup.get('comparisonEnabled').value;
const timeForComparison: ComparisonDuration = this.flotSettingsFormGroup.get('timeForComparison').value; const timeForComparison: ComparisonDuration = this.flotSettingsFormGroup.get('timeForComparison').value;
const customLegendEnabled: boolean = this.flotSettingsFormGroup.get('customLegendEnabled').value; const customLegendEnabled: boolean = this.flotSettingsFormGroup.get('customLegendEnabled').value;
if (showLegend) {
this.flotSettingsFormGroup.get('legendConfig').enable({emitEvent});
} else {
this.flotSettingsFormGroup.get('legendConfig').disable({emitEvent});
}
if (comparisonEnabled) { if (comparisonEnabled) {
this.flotSettingsFormGroup.get('timeForComparison').enable({emitEvent: false}); this.flotSettingsFormGroup.get('timeForComparison').enable({emitEvent: false});
if (timeForComparison === 'customInterval') { if (timeForComparison === 'customInterval') {
@ -370,6 +387,7 @@ export class FlotWidgetSettingsComponent extends PageComponent implements OnInit
this.flotSettingsFormGroup.get('dataKeysListForLabels').disable({emitEvent}); this.flotSettingsFormGroup.get('dataKeysListForLabels').disable({emitEvent});
} }
this.flotSettingsFormGroup.get('legendConfig').updateValueAndValidity({emitEvent: false});
this.flotSettingsFormGroup.get('timeForComparison').updateValueAndValidity({emitEvent: false}); this.flotSettingsFormGroup.get('timeForComparison').updateValueAndValidity({emitEvent: false});
this.flotSettingsFormGroup.get('comparisonCustomIntervalValue').updateValueAndValidity({emitEvent: false}); this.flotSettingsFormGroup.get('comparisonCustomIntervalValue').updateValueAndValidity({emitEvent: false});
this.flotSettingsFormGroup.get('dataKeysListForLabels').updateValueAndValidity({emitEvent: false}); this.flotSettingsFormGroup.get('dataKeysListForLabels').updateValueAndValidity({emitEvent: false});

0
ui-ngx/src/app/modules/home/components/widget/legend-config.component.html → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.html

0
ui-ngx/src/app/modules/home/components/widget/legend-config.component.ts → ui-ngx/src/app/modules/home/components/widget/lib/settings/common/legend-config.component.ts

3
ui-ngx/src/app/modules/home/components/widget/lib/settings/widget-settings.module.ts

@ -265,6 +265,7 @@ import {
import { import {
QuickLinksWidgetSettingsComponent QuickLinksWidgetSettingsComponent
} from '@home/components/widget/lib/settings/home-page/quick-links-widget-settings.component'; } from '@home/components/widget/lib/settings/home-page/quick-links-widget-settings.component';
import { LegendConfigComponent } from '@home/components/widget/lib/settings/common/legend-config.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -290,6 +291,7 @@ import {
AnalogueCompassWidgetSettingsComponent, AnalogueCompassWidgetSettingsComponent,
DigitalGaugeWidgetSettingsComponent, DigitalGaugeWidgetSettingsComponent,
ValueSourceComponent, ValueSourceComponent,
LegendConfigComponent,
FixedColorLevelComponent, FixedColorLevelComponent,
TickValueComponent, TickValueComponent,
FlotWidgetSettingsComponent, FlotWidgetSettingsComponent,
@ -395,6 +397,7 @@ import {
AnalogueCompassWidgetSettingsComponent, AnalogueCompassWidgetSettingsComponent,
DigitalGaugeWidgetSettingsComponent, DigitalGaugeWidgetSettingsComponent,
ValueSourceComponent, ValueSourceComponent,
LegendConfigComponent,
FixedColorLevelComponent, FixedColorLevelComponent,
TickValueComponent, TickValueComponent,
FlotWidgetSettingsComponent, FlotWidgetSettingsComponent,

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

@ -41,6 +41,8 @@ import { MarkdownWidgetComponent } from '@home/components/widget/lib/markdown-wi
import { SelectEntityDialogComponent } from '@home/components/widget/lib/maps/dialogs/select-entity-dialog.component'; import { SelectEntityDialogComponent } from '@home/components/widget/lib/maps/dialogs/select-entity-dialog.component';
import { HomePageWidgetsModule } from '@home/components/widget/lib/home-page/home-page-widgets.module'; import { HomePageWidgetsModule } from '@home/components/widget/lib/home-page/home-page-widgets.module';
import { WIDGET_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; import { WIDGET_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
import { FlotWidgetComponent } from '@home/components/widget/lib/flot-widget.component';
import { LegendComponent } from '@home/components/widget/lib/legend.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -62,7 +64,9 @@ import { WIDGET_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
NavigationCardWidgetComponent, NavigationCardWidgetComponent,
QrCodeWidgetComponent, QrCodeWidgetComponent,
MarkdownWidgetComponent, MarkdownWidgetComponent,
SelectEntityDialogComponent SelectEntityDialogComponent,
LegendComponent,
FlotWidgetComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -88,7 +92,9 @@ import { WIDGET_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens';
NavigationCardsWidgetComponent, NavigationCardsWidgetComponent,
NavigationCardWidgetComponent, NavigationCardWidgetComponent,
QrCodeWidgetComponent, QrCodeWidgetComponent,
MarkdownWidgetComponent MarkdownWidgetComponent,
LegendComponent,
FlotWidgetComponent
], ],
providers: [ providers: [
{provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule } {provide: WIDGET_COMPONENTS_MODULE_TOKEN, useValue: WidgetComponentsModule }

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

@ -15,9 +15,15 @@
limitations under the License. limitations under the License.
--> -->
<mat-tab-group mat-stretch-tabs="false" class="tb-widget-config tb-absolute-fill" [(selectedIndex)]="selectedTab"> <div class="tb-widget-config tb-absolute-fill">
<mat-tab label="{{ 'widget-config.data' | translate }}" *ngIf="widgetType !== widgetTypes.static"> <div class="tb-widget-config-header">
<div [formGroup]="dataSettings" class="mat-content mat-padding" fxLayout="column" fxLayoutGap="8px"> <tb-toggle-header (valueChange)="selectedOption = $event" ignoreMdLgSize="true"
appearance="fill" [options]="headerOptions" [value]="selectedOption" name="widgetConfigHeader" useSelectOnMdLg="false">
</tb-toggle-header>
<ng-content select=".tb-widget-config-header-suffix"></ng-content>
</div>
<div class="tb-widget-config-content">
<div *ngIf="widgetType !== widgetTypes.static" [fxShow]="selectedOption === 'data'" [formGroup]="dataSettings" class="mat-content" fxLayout="column" fxLayoutGap="8px">
<div *ngIf="displayTimewindowConfig()" fxFlex="100" <div *ngIf="displayTimewindowConfig()" fxFlex="100"
fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center"> fxLayout.xs="column" fxLayoutGap.gt-xs="8px" fxLayoutAlign.xs="center" fxLayout="row" fxLayoutAlign="start center">
<div fxLayout="column" fxFlex.gt-xs> <div fxLayout="column" fxFlex.gt-xs>
@ -55,7 +61,7 @@
<div *ngIf="modelValue?.typeParameters && modelValue?.typeParameters.maxDatasources > -1" <div *ngIf="modelValue?.typeParameters && modelValue?.typeParameters.maxDatasources > -1"
class="tb-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div> class="tb-panel-hint">{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}</div>
</div> </div>
<mat-icon *ngIf = '(datasourceError.length > 0 || !datasourcesFormArray().valid) && !openExtensionPanel' <mat-icon *ngIf = '(datasourceError.length > 0 || !datasourcesFormArray()?.valid) && !openExtensionPanel'
style = "color: red; font-size: 18px; margin-left: 5px;"> style = "color: red; font-size: 18px; margin-left: 5px;">
report_gmailerrorred report_gmailerrorred
</mat-icon> </mat-icon>
@ -64,7 +70,7 @@
<mat-error >{{ 'widget-config.timeseries-key-error' | translate }}</mat-error> <mat-error >{{ 'widget-config.timeseries-key-error' | translate }}</mat-error>
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div *ngIf="datasourcesFormArray().length === 0; else datasourcesTemplate"> <div *ngIf="datasourcesFormArray()?.length === 0; else datasourcesTemplate">
<span translate fxLayoutAlign="center center" <span translate fxLayoutAlign="center center"
class="tb-prompt">datasource.add-datasource-prompt</span> class="tb-prompt">datasource.add-datasource-prompt</span>
</div> </div>
@ -86,7 +92,7 @@
(cdkDropListDropped)="onDatasourceDrop($event)" (cdkDropListDropped)="onDatasourceDrop($event)"
[cdkDropListDisabled]="disabled" [cdkDropListDisabled]="disabled"
formArrayName="datasources"> formArrayName="datasources">
<mat-list-item *ngFor="let datasourceControl of datasourcesFormArray().controls; let $index = index;" <mat-list-item *ngFor="let datasourceControl of datasourcesFormArray()?.controls; let $index = index;"
class="tb-datasource-list-item tb-draggable" cdkDrag class="tb-datasource-list-item tb-draggable" cdkDrag
[cdkDragDisabled]="disabled"> [cdkDragDisabled]="disabled">
<div fxFlex fxLayout="row" fxLayoutAlign="start center"> <div fxFlex fxLayout="row" fxLayoutAlign="start center">
@ -205,7 +211,7 @@
type="button" type="button"
mat-raised-button color="primary" mat-raised-button color="primary"
[fxShow]="modelValue?.typeParameters && [fxShow]="modelValue?.typeParameters &&
(modelValue?.typeParameters.maxDatasources === -1 || datasourcesFormArray().controls.length < modelValue?.typeParameters.maxDatasources)" (modelValue?.typeParameters.maxDatasources === -1 || datasourcesFormArray()?.controls?.length < modelValue?.typeParameters.maxDatasources)"
(click)="addDatasource()" (click)="addDatasource()"
matTooltip="{{ 'widget-config.add-datasource' | translate }}" matTooltip="{{ 'widget-config.add-datasource' | translate }}"
matTooltipPosition="above"> matTooltipPosition="above">
@ -223,11 +229,11 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div [formGroup]="targetDeviceSettings" style="padding: 0 5px;"> <div [formGroup]="targetDeviceSettings" style="padding: 0 5px;">
<tb-entity-alias-select fxFlex <tb-entity-alias-select fxFlex
[tbRequired]="!widgetEditMode" [tbRequired]="!widgetEditMode"
[aliasController]="aliasController" [aliasController]="aliasController"
[allowedEntityTypes]="[entityTypes.DEVICE]" [allowedEntityTypes]="[entityTypes.DEVICE]"
[callbacks]="widgetConfigCallbacks" [callbacks]="widgetConfigCallbacks"
formControlName="targetDeviceAliasId"> formControlName="targetDeviceAliasId">
</tb-entity-alias-select> </tb-entity-alias-select>
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>
@ -312,183 +318,148 @@
</mat-expansion-panel> </mat-expansion-panel>
</mat-accordion> </mat-accordion>
</div> </div>
</mat-tab> <div *ngIf="displayAdvanced()" [fxShow]="selectedOption === 'appearance'" [formGroup]="advancedSettings" class="mat-content tb-advanced-widget-config"
<mat-tab label="{{ 'widget-config.settings' | translate }}"> fxLayout="column">
<div class="mat-content mat-padding" fxLayout="column"> <tb-widget-settings
<div [formGroup]="widgetSettings" fxLayout="column"> [aliasController]="aliasController"
<fieldset class="fields-group"> [dashboard]="dashboard"
<legend class="group-title" translate>widget-config.title</legend> [widget]="widget"
<mat-slide-toggle class="mat-slide" formControlName="showTitle"> formControlName="settings">
{{ 'widget-config.display-title' | translate }} </tb-widget-settings>
</div>
<div [fxShow]="selectedOption === 'card'" [formGroup]="widgetSettings" class="mat-content" fxLayout="column">
<fieldset class="fields-group">
<legend class="group-title" translate>widget-config.title</legend>
<mat-slide-toggle class="mat-slide" formControlName="showTitle">
{{ 'widget-config.display-title' | translate }}
</mat-slide-toggle>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px">
<mat-form-field fxFlex>
<mat-label translate>widget-config.title</mat-label>
<input matInput formControlName="title">
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>widget-config.title-tooltip</mat-label>
<input matInput formControlName="titleTooltip">
</mat-form-field>
</div>
<fieldset class="fields-group" fxLayoutGap.gt-xs="8px" style="margin: 0">
<legend class="group-title" translate>widget-config.title-icon</legend>
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.display-icon' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px"> <div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="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-form-field fxFlex>
<mat-label translate>widget-config.title</mat-label> <mat-label translate>widget-config.icon-size</mat-label>
<input matInput formControlName="title"> <input matInput formControlName="iconSize">
</mat-form-field>
<mat-form-field fxFlex>
<mat-label translate>widget-config.title-tooltip</mat-label>
<input matInput formControlName="titleTooltip">
</mat-form-field> </mat-form-field>
</div> </div>
<fieldset class="fields-group" fxLayoutGap.gt-xs="8px" style="margin: 0">
<legend class="group-title" translate>widget-config.title-icon</legend>
<mat-slide-toggle class="mat-slide" formControlName="showTitleIcon">
{{ 'widget-config.display-icon' | translate }}
</mat-slide-toggle>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="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>
<fieldset class="fields-group"> <mat-expansion-panel class="tb-settings">
<legend class="group-title" translate>widget-config.widget-style</legend> <mat-expansion-panel-header>
<div fxLayout="column" fxLayout.gt-md="row wrap" fxFlex fxLayoutGap.gt-xs="8px" class="tb-widget-style"> <mat-panel-description fxLayoutAlign="end" translate>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex> widget-config.advanced-settings
<tb-color-input fxFlex </mat-panel-description>
label="{{'widget-config.background-color' | translate}}" </mat-expansion-panel-header>
icon="format_color_fill" <ng-template matExpansionPanelContent>
openOnInput <tb-json-object-edit
formControlName="backgroundColor"> [editorStyle]="{minHeight: '100px'}"
</tb-color-input> required
<tb-color-input fxFlex label="{{ 'widget-config.title-style' | translate }}"
label="{{'widget-config.text-color' | translate}}" formControlName="titleStyle"
icon="format_color_fill" ></tb-json-object-edit>
openOnInput </ng-template>
formControlName="color"> </mat-expansion-panel>
</tb-color-input> </fieldset>
</div> <fieldset class="fields-group">
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex> <legend class="group-title" translate>widget-config.widget-style</legend>
<mat-form-field fxFlex> <div fxLayout="column" fxLayout.gt-md="row wrap" fxFlex fxLayoutGap.gt-xs="8px" class="tb-widget-style">
<mat-label translate>widget-config.padding</mat-label> <div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex>
<input matInput formControlName="padding"> <tb-color-input fxFlex
</mat-form-field> label="{{'widget-config.background-color' | translate}}"
<mat-form-field fxFlex> icon="format_color_fill"
<mat-label translate>widget-config.margin</mat-label> openOnInput
<input matInput formControlName="margin"> formControlName="backgroundColor">
</mat-form-field> </tb-color-input>
</div> <tb-color-input fxFlex
label="{{'widget-config.text-color' | translate}}"
icon="format_color_fill"
openOnInput
formControlName="color">
</tb-color-input>
</div> </div>
<mat-slide-toggle class="slide-block" formControlName="dropShadow"> <div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex>
{{ 'widget-config.drop-shadow' | translate }} <mat-form-field fxFlex>
</mat-slide-toggle> <mat-label translate>widget-config.padding</mat-label>
<mat-slide-toggle class="slide-block" formControlName="enableFullscreen"> <input matInput formControlName="padding">
{{ 'widget-config.enable-fullscreen' | translate }} </mat-form-field>
</mat-slide-toggle> <mat-form-field fxFlex>
<mat-expansion-panel class="tb-settings"> <mat-label translate>widget-config.margin</mat-label>
<mat-expansion-panel-header> <input matInput formControlName="margin">
<mat-panel-description fxLayoutAlign="end" translate> </mat-form-field>
widget-config.advanced-settings </div>
</mat-panel-description> </div>
</mat-expansion-panel-header> <mat-slide-toggle class="slide-block" formControlName="dropShadow">
<ng-template matExpansionPanelContent> {{ 'widget-config.drop-shadow' | translate }}
<tb-json-object-edit </mat-slide-toggle>
[editorStyle]="{minHeight: '100px'}" <mat-slide-toggle class="slide-block" formControlName="enableFullscreen">
required {{ 'widget-config.enable-fullscreen' | translate }}
label="{{ 'widget-config.widget-style' | translate }}" </mat-slide-toggle>
formControlName="widgetStyle" <mat-expansion-panel class="tb-settings">
></tb-json-object-edit> <mat-expansion-panel-header>
<tb-css <mat-panel-description fxLayoutAlign="end" translate>
label="{{ 'widget-config.widget-css' | translate }}" widget-config.advanced-settings
formControlName="widgetCss" </mat-panel-description>
></tb-css> </mat-expansion-panel-header>
</ng-template> <ng-template matExpansionPanelContent>
</mat-expansion-panel> <tb-json-object-edit
</fieldset> [editorStyle]="{minHeight: '100px'}"
<fieldset class="fields-group fields-group-slider" *ngIf="showLegendFieldset"> required
<legend class="group-title" translate>widget-config.legend</legend> label="{{ 'widget-config.widget-style' | translate }}"
<mat-expansion-panel class="tb-settings"> formControlName="widgetStyle"
<mat-expansion-panel-header fxLayout="row wrap"> ></tb-json-object-edit>
<mat-panel-title> <tb-css
<mat-slide-toggle formControlName="showLegend" (click)="$event.stopPropagation()" fxLayoutAlign="center"> label="{{ 'widget-config.widget-css' | translate }}"
{{ 'widget-config.display-legend' | translate }} formControlName="widgetCss"
</mat-slide-toggle> ></tb-css>
</mat-panel-title> </ng-template>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate> </mat-expansion-panel>
widget-config.advanced-settings </fieldset>
</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">
<legend class="group-title" translate>widget-config.mobile-mode-settings</legend>
<mat-expansion-panel class="tb-settings">
<mat-expansion-panel-header style="height: fit-content;">
<mat-panel-title fxLayout.xs="column" fxLayoutAlign.xs="center start" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex="70">
<mat-slide-toggle formControlName="mobileHide" (click)="$event.stopPropagation()" fxLayoutAlign="center">
{{ 'widget-config.mobile-hide' | translate }}
</mat-slide-toggle>
<mat-slide-toggle formControlName="desktopHide" (click)="$event.stopPropagation()" fxLayoutAlign="center">
{{ 'widget-config.desktop-hide' | translate }}
</mat-slide-toggle>
</mat-panel-title>
<mat-panel-description fxLayoutAlign="end center" fxHide.xs translate fxFlex="30">
widget-config.advanced-settings
</mat-panel-description>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex>
<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" step="1">
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>
</div>
</div> </div>
</mat-tab> <div [fxShow]="selectedOption === 'actions'" [formGroup]="actionsSettings" class="mat-content" fxLayout="column" style="height: 100%;">
<mat-tab *ngIf="displayAdvanced()" label="{{ 'widget-config.advanced' | translate }}"> <tb-manage-widget-actions
<div [formGroup]="advancedSettings" class="mat-content mat-padding tb-advanced-widget-config" [callbacks]="widgetConfigCallbacks"
fxLayout="column"> [widgetType] = "modelValue.widgetType"
<tb-widget-settings formControlName="actionsData">
[aliasController]="aliasController" </tb-manage-widget-actions>
[dashboard]="dashboard"
[widget]="widget"
formControlName="settings">
</tb-widget-settings>
</div> </div>
</mat-tab> <div [fxShow]="selectedOption === 'mobile'" [formGroup]="layoutSettings" class="mat-content" fxLayout="column" fxLayoutGap="16px">
<mat-tab label="{{ 'widget-config.actions' | translate }}" [formGroup]="actionsSettings"> <mat-slide-toggle formControlName="mobileHide">
<tb-manage-widget-actions {{ 'widget-config.mobile-hide' | translate }}
[callbacks]="widgetConfigCallbacks" </mat-slide-toggle>
[widgetType] = "modelValue.widgetType" <mat-slide-toggle formControlName="desktopHide">
formControlName="actionsData"> {{ 'widget-config.desktop-hide' | translate }}
</tb-manage-widget-actions> </mat-slide-toggle>
</mat-tab> <div fxLayout.xs="column" fxLayout="row" fxLayoutGap.gt-xs="8px" fxFlex>
</mat-tab-group> <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" step="1">
</mat-form-field>
</div>
</div>
</div>
</div>

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

@ -16,6 +16,24 @@
@import '../../../../../scss/constants'; @import '../../../../../scss/constants';
.tb-widget-config { .tb-widget-config {
display: flex;
flex-direction: column;
gap: 16px;
.tb-widget-config-header {
padding: 24px 24px 8px;
height: 56px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.tb-widget-config-content {
flex: 1;
overflow: auto;
& > div {
padding: 16px;
}
}
.tb-panel-hint { .tb-panel-hint {
font-size: 12px; font-size: 12px;
color: #808080; color: #808080;
@ -120,6 +138,11 @@
:host ::ng-deep { :host ::ng-deep {
.tb-widget-config { .tb-widget-config {
.tb-widget-config-header {
.mat-button-toggle-appearance-standard .mat-button-toggle-label-content {
padding: 0 20px;
}
}
tb-alarm-filter-config { tb-alarm-filter-config {
.mdc-button { .mdc-button {
width: 100%; width: 100%;

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

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core';
import { PageComponent } from '@shared/components/page.component'; import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
@ -25,7 +25,6 @@ import {
datasourcesHasOnlyComparisonAggregation, datasourcesHasOnlyComparisonAggregation,
DatasourceType, DatasourceType,
datasourceTypeTranslationMap, datasourceTypeTranslationMap,
defaultLegendConfig,
GroupInfo, GroupInfo,
JsonSchema, JsonSchema,
JsonSettingsSchema, JsonSettingsSchema,
@ -68,6 +67,7 @@ import { entityFields } from '@shared/models/entity.models';
import { Filter } from '@shared/models/query/query.models'; import { Filter } from '@shared/models/query/query.models';
import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component'; import { FilterDialogComponent, FilterDialogData } from '@home/components/filter/filter-dialog.component';
import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { CdkDragDrop } from '@angular/cdk/drag-drop';
import { ToggleHeaderOption } from '@shared/components/toggle-header.component';
const emptySettingsSchema: JsonSchema = { const emptySettingsSchema: JsonSchema = {
type: 'object', type: 'object',
@ -95,7 +95,7 @@ const defaultSettingsForm = [
} }
] ]
}) })
export class WidgetConfigComponent extends PageComponent implements OnInit, ControlValueAccessor, Validator { export class WidgetConfigComponent extends PageComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
widgetTypes = widgetType; widgetTypes = widgetType;
@ -134,14 +134,13 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
widgetEditMode = this.utils.widgetEditMode; widgetEditMode = this.utils.widgetEditMode;
selectedTab: number;
modelValue: WidgetConfigComponentData; modelValue: WidgetConfigComponentData;
showLegendFieldset = true;
private propagateChange = null; private propagateChange = null;
headerOptions: ToggleHeaderOption[] = [];
selectedOption: string;
public dataSettings: UntypedFormGroup; public dataSettings: UntypedFormGroup;
public targetDeviceSettings: UntypedFormGroup; public targetDeviceSettings: UntypedFormGroup;
public alarmSourceSettings: UntypedFormGroup; public alarmSourceSettings: UntypedFormGroup;
@ -196,9 +195,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
pageSize: [1024, [Validators.min(1), Validators.pattern(/^\d*$/)]], pageSize: [1024, [Validators.min(1), Validators.pattern(/^\d*$/)]],
units: [null, []], units: [null, []],
decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]], decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]],
noDataDisplayMessage: [null, []], noDataDisplayMessage: [null, []]
showLegend: [null, []],
legendConfig: [null, []]
}); });
this.widgetSettings.get('showTitle').valueChanges.subscribe((value: boolean) => { this.widgetSettings.get('showTitle').valueChanges.subscribe((value: boolean) => {
if (value) { if (value) {
@ -224,13 +221,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
this.widgetSettings.get('iconSize').disable({emitEvent: false}); this.widgetSettings.get('iconSize').disable({emitEvent: false});
} }
}); });
this.widgetSettings.get('showLegend').valueChanges.subscribe((value: boolean) => {
if (value) {
this.widgetSettings.get('legendConfig').enable({emitEvent: false});
} else {
this.widgetSettings.get('legendConfig').disable({emitEvent: false});
}
});
this.layoutSettings = this.fb.group({ this.layoutSettings = this.fb.group({
mobileOrder: [null, [Validators.pattern(/^-?[0-9]+$/)]], mobileOrder: [null, [Validators.pattern(/^-?[0-9]+$/)]],
mobileHeight: [null, [Validators.min(1), Validators.pattern(/^\d*$/)]], mobileHeight: [null, [Validators.min(1), Validators.pattern(/^\d*$/)]],
@ -370,15 +360,49 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
this.modelValue = value; this.modelValue = value;
this.removeChangeSubscriptions(); this.removeChangeSubscriptions();
if (this.modelValue) { if (this.modelValue) {
this.headerOptions.length = 0;
if (this.modelValue.widgetType !== widgetType.static) {
this.headerOptions.push(
{
name: this.translate.instant('widget-config.data'),
value: 'data'
}
);
}
if (this.displayAdvanced()) {
this.headerOptions.push(
{
name: this.translate.instant('widget-config.appearance'),
value: 'appearance'
}
);
}
this.headerOptions.push(
{
name: this.translate.instant('widget-config.widget-card'),
value: 'card'
}
);
this.headerOptions.push(
{
name: this.translate.instant('widget-config.actions'),
value: 'actions'
}
);
this.headerOptions.push(
{
name: this.translate.instant('widget-config.mobile'),
value: 'mobile'
}
);
this.selectedOption = this.headerOptions[0].value;
if (this.widgetType !== this.modelValue.widgetType) { if (this.widgetType !== this.modelValue.widgetType) {
this.widgetType = this.modelValue.widgetType; this.widgetType = this.modelValue.widgetType;
this.showLegendFieldset = (this.widgetType === widgetType.timeseries || this.widgetType === widgetType.latest);
this.buildForms(); this.buildForms();
} }
const config = this.modelValue.config; const config = this.modelValue.config;
const layout = this.modelValue.layout; const layout = this.modelValue.layout;
if (config) { if (config) {
this.selectedTab = 0;
const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false; const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false;
this.widgetSettings.patchValue({ this.widgetSettings.patchValue({
title: config.title, title: config.title,
@ -403,10 +427,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
pageSize: isDefined(config.pageSize) ? config.pageSize : 1024, pageSize: isDefined(config.pageSize) ? config.pageSize : 1024,
units: config.units, units: config.units,
decimals: config.decimals, decimals: config.decimals,
noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : '', noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : ''
showLegend: isDefined(config.showLegend) ? config.showLegend :
this.widgetType === widgetType.timeseries,
legendConfig: config.legendConfig || defaultLegendConfig(this.widgetType)
}, },
{emitEvent: false} {emitEvent: false}
); );
@ -430,12 +451,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
this.widgetSettings.get('iconColor').disable({emitEvent: false}); this.widgetSettings.get('iconColor').disable({emitEvent: false});
this.widgetSettings.get('iconSize').disable({emitEvent: false}); this.widgetSettings.get('iconSize').disable({emitEvent: false});
} }
const showLegend: boolean = this.widgetSettings.get('showLegend').value;
if (showLegend) {
this.widgetSettings.get('legendConfig').enable({emitEvent: false});
} else {
this.widgetSettings.get('legendConfig').disable({emitEvent: false});
}
const actionsData: WidgetActionsData = { const actionsData: WidgetActionsData = {
actionsMap: config.actions || {}, actionsMap: config.actions || {},
actionSources: this.modelValue.actionSources || {} actionSources: this.modelValue.actionSources || {}
@ -812,15 +827,13 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
private fetchEntityKeys(entityAliasId: string, dataKeyTypes: Array<DataKeyType>): Observable<Array<DataKey>> { private fetchEntityKeys(entityAliasId: string, dataKeyTypes: Array<DataKeyType>): Observable<Array<DataKey>> {
return this.aliasController.getAliasInfo(entityAliasId).pipe( return this.aliasController.getAliasInfo(entityAliasId).pipe(
mergeMap((aliasInfo) => { mergeMap((aliasInfo) => this.entityService.getEntityKeysByEntityFilter(
return this.entityService.getEntityKeysByEntityFilter(
aliasInfo.entityFilter, aliasInfo.entityFilter,
dataKeyTypes, dataKeyTypes,
{ignoreLoading: true, ignoreErrors: true} {ignoreLoading: true, ignoreErrors: true}
).pipe( ).pipe(
catchError(() => of([])) catchError(() => of([]))
); )),
}),
catchError(() => of([] as Array<DataKey>)) catchError(() => of([] as Array<DataKey>))
); );
} }

32
ui-ngx/src/app/modules/home/components/widget/widget-preview.component.html

@ -0,0 +1,32 @@
<!--
Copyright © 2016-2023 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<tb-dashboard class="tb-preview-dashboard"
[aliasController]="aliasController"
[stateController]="stateController"
[widgets]="widgets"
[autofillHeight]="true"
[columns]="20"
[isEdit]="false"
[isMobileDisabled]="true"
[isEditActionEnabled]="false"
[isRemoveActionEnabled]="false">
</tb-dashboard>
<span class="tb-preview-title" translate>widget-config.preview</span>
<div class="tb-preview-panel">
<ng-content select=".tb-preview-panel-content"></ng-content>
</div>

49
ui-ngx/src/app/modules/home/components/widget/widget-preview.component.scss

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host {
z-index: 10;
background: #F3F6FA;
.tb-preview-dashboard {
position: absolute;
top: 15%;
bottom: 15%;
left: 0;
right: 0;
}
.tb-preview-title {
position: absolute;
top: 16px;
left: 16px;
font-weight: 500;
font-size: 13px;
line-height: 16px;
letter-spacing: normal;
color: rgba(0, 0, 0, 0.38);
}
.tb-preview-panel {
position: absolute;
top: 16px;
right: 24px;
}
}
:host ::ng-deep {
tb-dashboard.tb-preview-dashboard {
.tb-dashboard-content {
background-color: transparent !important;
}
}
}

64
ui-ngx/src/app/modules/home/components/widget/widget-preview.component.ts

@ -0,0 +1,64 @@
///
/// Copyright © 2016-2023 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms';
import { PageComponent } from '@shared/components/page.component';
import { IAliasController, IStateController } from '@core/api/widget-api.models';
import { Widget, WidgetConfig } from '@shared/models/widget.models';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { deepClone } from '@core/utils';
@Component({
selector: 'tb-widget-preview',
templateUrl: './widget-preview.component.html',
styleUrls: ['./widget-preview.component.scss']
})
export class WidgetPreviewComponent extends PageComponent implements OnInit {
@Input()
aliasController: IAliasController;
@Input()
stateController: IStateController;
@Input()
widget: Widget;
@Input()
widgetConfig: WidgetConfig;
widgets: Widget[];
constructor(protected store: Store<AppState>) {
super(store);
}
ngOnInit(): void {
const sizeX = this.widget.sizeX * 2;
const sizeY = this.widget.sizeY * 2;
const col = Math.floor(Math.max(0, (20 - sizeX) / 2));
const widget = deepClone(this.widget);
widget.sizeX = sizeX;
widget.sizeY = sizeY;
widget.row = 0;
widget.col = col;
widget.config = this.widgetConfig;
this.widgets = [widget];
}
}

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

@ -15,23 +15,11 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-absolute-fill" [fxLayout]="legendContainerLayoutType" tb-toast <div class="tb-absolute-fill" fxLayout="column" tb-toast
toastTarget="{{ toastTargetId }}"> toastTarget="{{ toastTargetId }}">
<tb-legend *ngIf="displayLegend && isLegendFirst"
[ngStyle]="legendStyle"
[legendConfig]="legendConfig"
[legendData]="legendData"
(legendKeyHiddenChange)="onLegendKeyHiddenChange($event)">
</tb-legend>
<div fxFlex id="widget-container"> <div fxFlex id="widget-container">
<ng-container #widgetContent></ng-container> <ng-container #widgetContent></ng-container>
</div> </div>
<tb-legend *ngIf="displayLegend && !isLegendFirst"
[ngStyle]="legendStyle"
[legendConfig]="legendConfig"
[legendData]="legendData"
(legendKeyHiddenChange)="onLegendKeyHiddenChange($event)">
</tb-legend>
</div> </div>
<div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData"> <div class="tb-absolute-fill tb-widget-error" *ngIf="widgetErrorData">
<span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span> <span>Widget Error: {{ widgetErrorData.name + ": " + widgetErrorData.message}}</span>
@ -42,5 +30,5 @@
class="tb-absolute-fill">{{ noDataDisplayMessageText }}</span> class="tb-absolute-fill">{{ noDataDisplayMessageText }}</span>
</div> </div>
<div class="tb-absolute-fill tb-widget-loading" [fxShow]="loadingData" fxLayout="column" fxLayoutAlign="center center"> <div class="tb-absolute-fill tb-widget-loading" [fxShow]="loadingData" fxLayout="column" fxLayoutAlign="center center">
<mat-spinner color="accent" md-mode="indeterminate" diameter="40"></mat-spinner> <mat-spinner color="accent" mode="indeterminate" diameter="40"></mat-spinner>
</div> </div>

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

@ -39,10 +39,6 @@ import {
} from '@angular/core'; } from '@angular/core';
import { DashboardWidget } from '@home/models/dashboard-component.models'; import { DashboardWidget } from '@home/models/dashboard-component.models';
import { import {
defaultLegendConfig,
LegendConfig,
LegendData,
LegendPosition,
Widget, Widget,
WidgetActionDescriptor, WidgetActionDescriptor,
widgetActionSources, widgetActionSources,
@ -152,13 +148,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
displayNoData = false; displayNoData = false;
noDataDisplayMessageText: string; noDataDisplayMessageText: string;
displayLegend: boolean;
legendConfig: LegendConfig;
legendData: LegendData;
isLegendFirst: boolean;
legendContainerLayoutType: string;
legendStyle: {[klass: string]: any};
dynamicWidgetComponentRef: ComponentRef<IDynamicWidgetComponent>; dynamicWidgetComponentRef: ComponentRef<IDynamicWidgetComponent>;
dynamicWidgetComponent: IDynamicWidgetComponent; dynamicWidgetComponent: IDynamicWidgetComponent;
@ -220,57 +209,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.widget = this.dashboardWidget.widget; this.widget = this.dashboardWidget.widget;
this.displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend
: this.widget.type === widgetType.timeseries;
this.legendContainerLayoutType = 'column';
if (this.displayLegend) {
this.legendConfig = this.widget.config.legendConfig || defaultLegendConfig(this.widget.type);
this.legendData = {
keys: [],
data: []
};
if (this.legendConfig.position === LegendPosition.top ||
this.legendConfig.position === LegendPosition.bottom) {
this.legendContainerLayoutType = 'column';
this.isLegendFirst = this.legendConfig.position === LegendPosition.top;
} else {
this.legendContainerLayoutType = 'row';
this.isLegendFirst = this.legendConfig.position === LegendPosition.left;
}
switch (this.legendConfig.position) {
case LegendPosition.top:
this.legendStyle = {
paddingBottom: '8px',
maxHeight: '50%',
overflowY: 'auto'
};
break;
case LegendPosition.bottom:
this.legendStyle = {
paddingTop: '8px',
maxHeight: '50%',
overflowY: 'auto'
};
break;
case LegendPosition.left:
this.legendStyle = {
paddingRight: '0px',
maxWidth: '50%',
overflowY: 'auto'
};
break;
case LegendPosition.right:
this.legendStyle = {
paddingLeft: '0px',
maxWidth: '50%',
overflowY: 'auto'
};
break;
}
}
const actionDescriptorsBySourceId: {[actionSourceId: string]: Array<WidgetActionDescriptor>} = {}; const actionDescriptorsBySourceId: {[actionSourceId: string]: Array<WidgetActionDescriptor>} = {};
if (this.widget.config.actions) { if (this.widget.config.actions) {
for (const actionSourceId of Object.keys(this.widget.config.actions)) { for (const actionSourceId of Object.keys(this.widget.config.actions)) {
@ -463,13 +401,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
} }
} }
public onLegendKeyHiddenChange(index: number) {
for (const id of Object.keys(this.widgetContext.subscriptions)) {
const subscription = this.widgetContext.subscriptions[id];
subscription.updateDataVisibility(index);
}
}
private loadFromWidgetInfo() { private loadFromWidgetInfo() {
this.widgetContext.widgetNamespace = this.widgetContext.widgetNamespace =
`widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`; `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`;
@ -872,9 +803,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
} }
this.createSubscription(options, subscribe).subscribe( this.createSubscription(options, subscribe).subscribe(
(subscription) => { (subscription) => {
if (useDefaultComponents) {
this.defaultSubscriptionOptions(subscription, options);
}
createSubscriptionSubject.next(subscription); createSubscriptionSubject.next(subscription);
createSubscriptionSubject.complete(); createSubscriptionSubject.complete();
}, },
@ -892,8 +820,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
? this.widget.config.displayTimewindow : !options.useDashboardTimewindow; ? this.widget.config.displayTimewindow : !options.useDashboardTimewindow;
options.timeWindowConfig = options.useDashboardTimewindow ? this.widgetContext.dashboardTimewindow : this.widget.config.timewindow; options.timeWindowConfig = options.useDashboardTimewindow ? this.widgetContext.dashboardTimewindow : this.widget.config.timewindow;
options.legendConfig = null; options.legendConfig = null;
if (this.displayLegend) { if (this.widget.config.settings.showLegend === true) {
options.legendConfig = this.legendConfig; options.legendConfig = this.widget.config.settings.legendConfig;
} }
options.decimals = this.widgetContext.decimals; options.decimals = this.widgetContext.decimals;
options.units = this.widgetContext.units; options.units = this.widgetContext.units;
@ -962,13 +890,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}); });
} }
}; };
}
private defaultSubscriptionOptions(subscription: IWidgetSubscription, options: WidgetSubscriptionOptions) {
if (this.displayLegend) {
this.legendData = subscription.legendData;
}
} }
private createDefaultSubscription(): Observable<any> { private createDefaultSubscription(): Observable<any> {
@ -999,7 +920,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.createSubscription(options).subscribe( this.createSubscription(options).subscribe(
(subscription) => { (subscription) => {
this.defaultSubscriptionOptions(subscription, options);
// backward compatibility // backward compatibility
this.widgetContext.datasources = subscription.datasources; this.widgetContext.datasources = subscription.datasources;
@ -1479,7 +1399,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
} }
} }
private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array<WidgetResource>, actionDescriptor: WidgetActionDescriptor): Observable<any> { private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array<WidgetResource>,
actionDescriptor: WidgetActionDescriptor): Observable<any> {
const resourceTasks: Observable<string>[] = []; const resourceTasks: Observable<string>[] = [];
const modulesTasks: Observable<ModulesWithFactories | string>[] = []; const modulesTasks: Observable<ModulesWithFactories | string>[] = [];

1
ui-ngx/src/app/shared/components/public-api.ts

@ -22,3 +22,4 @@ export * from './js-func.component';
export * from './script-lang.component'; export * from './script-lang.component';
export * from './slack-conversation-autocomplete.component'; export * from './slack-conversation-autocomplete.component';
export * from './notification/template-autocomplete.component'; export * from './notification/template-autocomplete.component';
export * from './toggle-header.component';

3
ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.html → ui-ngx/src/app/shared/components/toggle-header.component.html

@ -15,7 +15,8 @@
limitations under the License. limitations under the License.
--> -->
<mat-button-toggle-group *ngIf="!isMdLg || !useSelectOnMdLg; else select" class="tb-toggle-header" [name]="name" [(ngModel)]="value" <mat-button-toggle-group *ngIf="!isMdLg || !useSelectOnMdLg; else select" class="tb-toggle-header"
[ngClass]="{'tb-fill': appearance === 'fill', 'tb-ignore-md-lg': ignoreMdLgSize}" [name]="name" [(ngModel)]="value"
(ngModelChange)="valueChange.emit(value)"> (ngModelChange)="valueChange.emit(value)">
<mat-button-toggle *ngFor="let option of options" [value]="option.value">{{ option.name }}</mat-button-toggle> <mat-button-toggle *ngFor="let option of options" [value]="option.value">{{ option.name }}</mat-button-toggle>
</mat-button-toggle-group> </mat-button-toggle-group>

14
ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.scss → ui-ngx/src/app/shared/components/toggle-header.component.scss

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
@import "../../../../../../../scss/constants"; @import "../../../scss/constants";
:host ::ng-deep { :host ::ng-deep {
.mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header {
@ -58,9 +58,19 @@
} }
} }
} }
&.tb-fill {
.mat-button-toggle.mat-button-toggle-appearance-standard {
&.mat-button-toggle-checked {
.mat-button-toggle-button {
background: #305680;
color: #FFFFFF;
}
}
}
}
} }
@media #{$mat-md-lg} { @media #{$mat-md-lg} {
.mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header { .mat-button-toggle-group.mat-button-toggle-group-appearance-standard.tb-toggle-header:not(.tb-ignore-md-lg) {
height: 24px; height: 24px;
.mat-button-toggle.mat-button-toggle-appearance-standard { .mat-button-toggle.mat-button-toggle-appearance-standard {
.mat-button-toggle-button { .mat-button-toggle-button {

9
ui-ngx/src/app/modules/home/components/widget/lib/home-page/toggle-header.component.ts → ui-ngx/src/app/shared/components/toggle-header.component.ts

@ -44,6 +44,8 @@ export interface ToggleHeaderOption {
value: any; value: any;
} }
export type ToggleHeaderAppearance = 'fill' | 'stroked';
@Component({ @Component({
selector: 'tb-toggle-header', selector: 'tb-toggle-header',
templateUrl: './toggle-header.component.html', templateUrl: './toggle-header.component.html',
@ -67,6 +69,13 @@ export class ToggleHeaderComponent extends PageComponent implements OnInit {
@coerceBoolean() @coerceBoolean()
useSelectOnMdLg = true; useSelectOnMdLg = true;
@Input()
@coerceBoolean()
ignoreMdLgSize = false;
@Input()
appearance: ToggleHeaderAppearance = 'stroked';
isMdLg: boolean; isMdLg: boolean;
private observeBreakpointSubscription: Subscription; private observeBreakpointSubscription: Subscription;

36
ui-ngx/src/app/shared/models/widget.models.ts

@ -252,18 +252,16 @@ export interface LegendConfig {
showLatest: boolean; showLatest: boolean;
} }
export function defaultLegendConfig(wType: widgetType): LegendConfig { export const defaultLegendConfig = (wType: widgetType): LegendConfig => ({
return { direction: LegendDirection.column,
direction: LegendDirection.column, position: LegendPosition.bottom,
position: LegendPosition.bottom, sortDataKeys: false,
sortDataKeys: false, showMin: false,
showMin: false, showMax: false,
showMax: false, showAvg: wType === widgetType.timeseries,
showAvg: wType === widgetType.timeseries, showTotal: false,
showTotal: false, showLatest: false
showLatest: false });
};
}
export enum ComparisonResultType { export enum ComparisonResultType {
PREVIOUS_VALUE = 'PREVIOUS_VALUE', PREVIOUS_VALUE = 'PREVIOUS_VALUE',
@ -363,7 +361,7 @@ export interface Datasource {
[key: string]: any; [key: string]: any;
} }
export function datasourcesHasAggregation(datasources?: Array<Datasource>): boolean { export const datasourcesHasAggregation = (datasources?: Array<Datasource>): boolean => {
if (datasources) { if (datasources) {
const foundDatasource = datasources.find(datasource => { const foundDatasource = datasources.find(datasource => {
const found = datasource.dataKeys && datasource.dataKeys.find(key => key.type === DataKeyType.timeseries && const found = datasource.dataKeys && datasource.dataKeys.find(key => key.type === DataKeyType.timeseries &&
@ -375,9 +373,9 @@ export function datasourcesHasAggregation(datasources?: Array<Datasource>): bool
} }
} }
return false; return false;
} };
export function datasourcesHasOnlyComparisonAggregation(datasources?: Array<Datasource>): boolean { export const datasourcesHasOnlyComparisonAggregation = (datasources?: Array<Datasource>): boolean => {
if (!datasourcesHasAggregation(datasources)) { if (!datasourcesHasAggregation(datasources)) {
return false; return false;
} }
@ -392,7 +390,7 @@ export function datasourcesHasOnlyComparisonAggregation(datasources?: Array<Data
} }
} }
return true; return true;
} };
export interface FormattedData { export interface FormattedData {
$datasource: Datasource; $datasource: Datasource;
@ -614,8 +612,6 @@ export interface WidgetConfig {
enableFullscreen?: boolean; enableFullscreen?: boolean;
useDashboardTimewindow?: boolean; useDashboardTimewindow?: boolean;
displayTimewindow?: boolean; displayTimewindow?: boolean;
showLegend?: boolean;
legendConfig?: LegendConfig;
timewindow?: Timewindow; timewindow?: Timewindow;
desktopHide?: boolean; desktopHide?: boolean;
mobileHide?: boolean; mobileHide?: boolean;
@ -700,7 +696,7 @@ export interface IWidgetSettingsComponent {
[key: string]: any; [key: string]: any;
} }
function removeEmptyWidgetSettings(settings: WidgetSettings): WidgetSettings { const removeEmptyWidgetSettings = (settings: WidgetSettings): WidgetSettings => {
if (settings) { if (settings) {
const keys = Object.keys(settings); const keys = Object.keys(settings);
for (const key of keys) { for (const key of keys) {
@ -711,7 +707,7 @@ function removeEmptyWidgetSettings(settings: WidgetSettings): WidgetSettings {
} }
} }
return settings; return settings;
} };
@Directive() @Directive()
// eslint-disable-next-line @angular-eslint/directive-class-suffix // eslint-disable-next-line @angular-eslint/directive-class-suffix

7
ui-ngx/src/app/shared/shared.module.ts

@ -189,6 +189,7 @@ import {
} from '@shared/layout/layout.directives'; } from '@shared/layout/layout.directives';
import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component'; import { ColorPickerComponent } from '@shared/components/color-picker/color-picker.component';
import { ShortNumberPipe } from '@shared/pipe/short-number.pipe'; import { ShortNumberPipe } from '@shared/pipe/short-number.pipe';
import { ToggleHeaderComponent } from '@shared/components/toggle-header.component';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService; return markedOptionsService;
@ -356,7 +357,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
GtMdLgLayoutAlignDirective, GtMdLgLayoutAlignDirective,
GtMdLgLayoutGapDirective, GtMdLgLayoutGapDirective,
GtMdLgShowHideDirective, GtMdLgShowHideDirective,
ColorPickerComponent ColorPickerComponent,
ToggleHeaderComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -580,7 +582,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
GtMdLgLayoutAlignDirective, GtMdLgLayoutAlignDirective,
GtMdLgLayoutGapDirective, GtMdLgLayoutGapDirective,
GtMdLgShowHideDirective, GtMdLgShowHideDirective,
ColorPickerComponent ColorPickerComponent,
ToggleHeaderComponent
] ]
}) })
export class SharedModule { } export class SharedModule { }

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

@ -35,6 +35,7 @@
"edit-mode": "Edit mode", "edit-mode": "Edit mode",
"enter-edit-mode": "Enter edit mode", "enter-edit-mode": "Enter edit mode",
"decline-changes": "Decline changes", "decline-changes": "Decline changes",
"decline": "Decline",
"close": "Close", "close": "Close",
"back": "Back", "back": "Back",
"run": "Run", "run": "Run",
@ -4091,6 +4092,9 @@
"data": "Data", "data": "Data",
"settings": "Settings", "settings": "Settings",
"advanced": "Advanced", "advanced": "Advanced",
"appearance": "Appearance",
"widget-card": "Widget card",
"mobile": "Mobile",
"title": "Title", "title": "Title",
"title-tooltip": "Title Tooltip", "title-tooltip": "Title Tooltip",
"general-settings": "General settings", "general-settings": "General settings",
@ -4151,7 +4155,8 @@
"data-settings": "Data settings", "data-settings": "Data settings",
"no-data-display-message": "\"No data to display\" alternative message", "no-data-display-message": "\"No data to display\" alternative message",
"data-page-size": "Maximum entities per datasource", "data-page-size": "Maximum entities per datasource",
"settings-component-not-found": "Settings form component not found for selector '{{selector}}'" "settings-component-not-found": "Settings form component not found for selector '{{selector}}'",
"preview": "Preview"
}, },
"widget-type": { "widget-type": {
"import": "Import widget type", "import": "Import widget type",

Loading…
Cancel
Save