UI: Auto-switch to edit mode for empty dashboard. Navigate to dashboard page right after adding dashboard. Implement aggregated value card advanced settings form.

This commit is contained in:
Igor Kulikov 2023-08-17 13:09:16 +03:00
parent 965b526b7c
commit b7b34da831
11 changed files with 216 additions and 32 deletions

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,6 @@ import {
} from '@shared/models/dashboard.models'; } from '@shared/models/dashboard.models';
import { deepClone, isDefined, isDefinedAndNotNull, isString, isUndefined } from '@core/utils'; import { deepClone, isDefined, isDefinedAndNotNull, isString, isUndefined } from '@core/utils';
import { import {
DataKey,
Datasource, Datasource,
datasourcesHasOnlyComparisonAggregation, datasourcesHasOnlyComparisonAggregation,
DatasourceType, DatasourceType,
@ -484,6 +483,14 @@ export class DashboardUtilsService {
return widgetsArray; return widgetsArray;
} }
public isEmptyDashboard(dashboard: Dashboard): boolean {
if (dashboard?.configuration?.widgets) {
return Object.keys(dashboard?.configuration?.widgets).length === 0;
} else {
return true;
}
}
public addWidgetToLayout(dashboard: Dashboard, public addWidgetToLayout(dashboard: Dashboard,
targetState: string, targetState: string,
targetLayout: DashboardLayoutId, targetLayout: DashboardLayoutId,

View File

@ -71,7 +71,6 @@ import { MediaBreakpoints } from '@shared/models/constants';
import { AuthUser } from '@shared/models/user.model'; import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { getCurrentAuthState } from '@core/auth/auth.selectors';
import { import {
DatasourceType,
Widget, Widget,
WidgetConfig, WidgetConfig,
WidgetInfo, WidgetInfo,
@ -415,6 +414,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
this.updateLayoutSizes(); this.updateLayoutSizes();
}); });
this.dashboardResize$.observe(this.dashboardContainer.nativeElement); this.dashboardResize$.observe(this.dashboardContainer.nativeElement);
if (!this.widgetEditMode && !this.readonly && this.dashboardUtils.isEmptyDashboard(this.dashboard)) {
this.setEditMode(true, false);
}
} }
private init(data: DashboardPageInitData) { private init(data: DashboardPageInitData) {

View File

@ -90,7 +90,7 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
protected setupDefaults(configData: WidgetConfigComponentData) { protected setupDefaults(configData: WidgetConfigComponentData) {
this.setupDefaultDatasource(configData, [ this.setupDefaultDatasource(configData, [
{ name: 'watermeter', label: 'Watermeter', type: DataKeyType.timeseries, units: 'm³', decimals: 0 } { name: 'watermeter', label: 'Watermeter', type: DataKeyType.timeseries, color: 'rgba(0, 0, 0, 0.87)', units: 'm³', decimals: 0 }
], ],
createDefaultAggregatedValueLatestDataKeys('watermeter', 'm³') createDefaultAggregatedValueLatestDataKeys('watermeter', 'm³')
); );
@ -143,7 +143,7 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
showChart: [settings.showChart, []], showChart: [settings.showChart, []],
chartUnits: [dataKey?.units, []], chartUnits: [dataKey?.units, []],
chartDecimals: [dataKey?.decimals, []], chartDecimals: [dataKey?.decimals, []],
chartColor: [settings.chartColor, []], chartColor: [dataKey?.color, []],
values: [this.getValues(configData.config.datasources, keyName), []], values: [this.getValues(configData.config.datasources, keyName), []],
@ -188,10 +188,9 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
if (dataKey) { if (dataKey) {
dataKey.units = config.chartUnits; dataKey.units = config.chartUnits;
dataKey.decimals = config.chartDecimals; dataKey.decimals = config.chartDecimals;
dataKey.color = config.chartColor;
} }
this.widgetConfig.config.settings.chartColor = config.chartColor;
this.setValues(config.values, this.widgetConfig.config.datasources); this.setValues(config.values, this.widgetConfig.config.datasources);
this.widgetConfig.config.settings.background = config.background; this.widgetConfig.config.settings.background = config.background;

View File

@ -79,7 +79,6 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
values: {[key: string]: AggregatedValueCardValue} = {}; values: {[key: string]: AggregatedValueCardValue} = {};
showChart = true; showChart = true;
chartColor: string;
showDate = true; showDate = true;
dateFormat: DateFormatProcessor; dateFormat: DateFormatProcessor;
@ -123,7 +122,6 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
} }
this.showChart = this.settings.showChart; this.showChart = this.settings.showChart;
this.chartColor = this.settings.chartColor;
if (this.showChart) { if (this.showChart) {
if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) { if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) {
this.flotDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0]; this.flotDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0];
@ -132,7 +130,6 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
showLines: true, showLines: true,
lineWidth: 2 lineWidth: 2
} as TbFlotKeySettings; } as TbFlotKeySettings;
this.flotDataKey.color = this.chartColor;
} }
} }

View File

@ -41,7 +41,6 @@ export interface AggregatedValueCardWidgetSettings {
dateFont: Font; dateFont: Font;
dateColor: string; dateColor: string;
showChart: boolean; showChart: boolean;
chartColor: string;
background: BackgroundSettings; background: BackgroundSettings;
} }
@ -136,7 +135,6 @@ export const aggregatedValueCardDefaultSettings: AggregatedValueCardWidgetSettin
}, },
dateColor: 'rgba(0, 0, 0, 0.38)', dateColor: 'rgba(0, 0, 0, 0.38)',
showChart: true, showChart: true,
chartColor: 'rgba(0, 0, 0, 0.87)',
background: { background: {
type: BackgroundType.color, type: BackgroundType.color,
color: '#fff', color: '#fff',
@ -164,7 +162,7 @@ export const aggregatedValueCardDefaultKeySettings: AggregatedValueCardKeySettin
export const createDefaultAggregatedValueLatestDataKeys = (keyName: string, units): DataKey[] => [ export const createDefaultAggregatedValueLatestDataKeys = (keyName: string, units): DataKey[] => [
{ {
name: keyName, label: keyName, type: DataKeyType.timeseries, units, decimals: 0, name: keyName, label: 'Latest', type: DataKeyType.timeseries, units, decimals: 0,
aggregationType: AggregationType.NONE, aggregationType: AggregationType.NONE,
settings: { settings: {
position: AggregatedValueCardKeyPosition.center, position: AggregatedValueCardKeyPosition.center,
@ -181,7 +179,7 @@ export const createDefaultAggregatedValueLatestDataKeys = (keyName: string, unit
} as AggregatedValueCardKeySettings } as AggregatedValueCardKeySettings
}, },
{ {
name: keyName, label: 'Delta percent ' + keyName, type: DataKeyType.timeseries, units: '%', decimals: 0, name: keyName, label: 'Delta percent', type: DataKeyType.timeseries, units: '%', decimals: 0,
aggregationType: AggregationType.AVG, aggregationType: AggregationType.AVG,
comparisonEnabled: true, comparisonEnabled: true,
timeForComparison: 'previousInterval', timeForComparison: 'previousInterval',
@ -210,7 +208,7 @@ export const createDefaultAggregatedValueLatestDataKeys = (keyName: string, unit
} as AggregatedValueCardKeySettings } as AggregatedValueCardKeySettings
}, },
{ {
name: keyName, label: 'Delta absolute ' + keyName, type: DataKeyType.timeseries, units, decimals: 1, name: keyName, label: 'Delta absolute', type: DataKeyType.timeseries, units, decimals: 1,
aggregationType: AggregationType.AVG, aggregationType: AggregationType.AVG,
comparisonEnabled: true, comparisonEnabled: true,
timeForComparison: 'previousInterval', timeForComparison: 'previousInterval',

View File

@ -0,0 +1,65 @@
<!--
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.
-->
<ng-container [formGroup]="aggregatedValueCardWidgetSettingsForm">
<div class="tb-form-panel">
<div class="tb-form-panel-title" translate>widgets.aggregated-value-card.aggregated-value-card-style</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showSubtitle">
{{ 'widgets.aggregated-value-card.subtitle' | translate }}
</mat-slide-toggle>
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<mat-form-field class="flex" appearance="outline" subscriptSizing="dynamic">
<input matInput formControlName="subtitle" placeholder="{{ 'widget-config.set' | translate }}">
</mat-form-field>
<tb-font-settings formControlName="subtitleFont"
clearButton
[previewText]="aggregatedValueCardWidgetSettingsForm.get('subtitle').value">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="subtitleColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row column-xs">
<mat-slide-toggle class="mat-slide fixed-title-width" formControlName="showDate">
{{ 'widgets.value-card.date' | translate }}
</mat-slide-toggle>
<div fxFlex.gt-xs fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<tb-date-format-select fxFlex formControlName="dateFormat"></tb-date-format-select>
<tb-font-settings formControlName="dateFont"
[previewText]="datePreviewFn">
</tb-font-settings>
<tb-color-input asBoxInput
colorClearButton
formControlName="dateColor">
</tb-color-input>
</div>
</div>
<div class="tb-form-row">
<mat-slide-toggle class="mat-slide" formControlName="showChart">
{{ 'widgets.aggregated-value-card.chart' | translate }}
</mat-slide-toggle>
</div>
<div class="tb-form-row space-between">
<div>{{ 'widgets.background.background' | translate }}</div>
<tb-background-settings formControlName="background">
</tb-background-settings>
</div>
</div>
</ng-container>

View File

@ -0,0 +1,111 @@
///
/// 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, Injector } from '@angular/core';
import { WidgetSettings, WidgetSettingsComponent } from '@shared/models/widget.models';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { DateFormatProcessor, DateFormatSettings } from '@shared/models/widget-settings.models';
import { aggregatedValueCardDefaultSettings } from '@home/components/widget/lib/cards/aggregated-value-card.models';
@Component({
selector: 'tb-aggregated-value-card-widget-settings',
templateUrl: './aggregated-value-card-widget-settings.component.html',
styleUrls: []
})
export class AggregatedValueCardWidgetSettingsComponent extends WidgetSettingsComponent {
aggregatedValueCardWidgetSettingsForm: UntypedFormGroup;
datePreviewFn = this._datePreviewFn.bind(this);
constructor(protected store: Store<AppState>,
private $injector: Injector,
private fb: UntypedFormBuilder) {
super(store);
}
protected settingsForm(): UntypedFormGroup {
return this.aggregatedValueCardWidgetSettingsForm;
}
protected defaultSettings(): WidgetSettings {
return {...aggregatedValueCardDefaultSettings};
}
protected onSettingsSet(settings: WidgetSettings) {
this.aggregatedValueCardWidgetSettingsForm = this.fb.group({
showSubtitle: [settings.showSubtitle, []],
subtitle: [settings.subtitle, []],
subtitleFont: [settings.subtitleFont, []],
subtitleColor: [settings.subtitleColor, []],
showDate: [settings.showDate, []],
dateFormat: [settings.dateFormat, []],
dateFont: [settings.dateFont, []],
dateColor: [settings.dateColor, []],
showChart: [settings.showChart, []],
background: [settings.background, []]
});
}
protected validatorTriggers(): string[] {
return ['showSubtitle', 'showDate'];
}
protected updateValidators(emitEvent: boolean) {
const showSubtitle: boolean = this.aggregatedValueCardWidgetSettingsForm.get('showSubtitle').value;
const showDate: boolean = this.aggregatedValueCardWidgetSettingsForm.get('showDate').value;
if (showSubtitle) {
this.aggregatedValueCardWidgetSettingsForm.get('subtitle').enable();
this.aggregatedValueCardWidgetSettingsForm.get('subtitleFont').enable();
this.aggregatedValueCardWidgetSettingsForm.get('subtitleColor').enable();
} else {
this.aggregatedValueCardWidgetSettingsForm.get('subtitle').disable();
this.aggregatedValueCardWidgetSettingsForm.get('subtitleFont').disable();
this.aggregatedValueCardWidgetSettingsForm.get('subtitleColor').disable();
}
if (showDate) {
this.aggregatedValueCardWidgetSettingsForm.get('dateFormat').enable();
this.aggregatedValueCardWidgetSettingsForm.get('dateFont').enable();
this.aggregatedValueCardWidgetSettingsForm.get('dateColor').enable();
} else {
this.aggregatedValueCardWidgetSettingsForm.get('dateFormat').disable();
this.aggregatedValueCardWidgetSettingsForm.get('dateFont').disable();
this.aggregatedValueCardWidgetSettingsForm.get('dateColor').disable();
}
this.aggregatedValueCardWidgetSettingsForm.get('subtitle').updateValueAndValidity({emitEvent});
this.aggregatedValueCardWidgetSettingsForm.get('subtitleFont').updateValueAndValidity({emitEvent});
this.aggregatedValueCardWidgetSettingsForm.get('subtitleColor').updateValueAndValidity({emitEvent});
this.aggregatedValueCardWidgetSettingsForm.get('dateFormat').updateValueAndValidity({emitEvent});
this.aggregatedValueCardWidgetSettingsForm.get('dateFont').updateValueAndValidity({emitEvent});
this.aggregatedValueCardWidgetSettingsForm.get('dateColor').updateValueAndValidity({emitEvent});
}
private _datePreviewFn(): string {
const dateFormat: DateFormatSettings = this.aggregatedValueCardWidgetSettingsForm.get('dateFormat').value;
const processor = DateFormatProcessor.fromSettings(this.$injector, dateFormat);
processor.update(Date.now());
return processor.formatted;
}
}

View File

@ -270,6 +270,9 @@ import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings
import { import {
AggregatedValueCardKeySettingsComponent AggregatedValueCardKeySettingsComponent
} from '@home/components/widget/lib/settings/cards/aggregated-value-card-key-settings.component'; } from '@home/components/widget/lib/settings/cards/aggregated-value-card-key-settings.component';
import {
AggregatedValueCardWidgetSettingsComponent
} from '@home/components/widget/lib/settings/cards/aggregated-value-card-widget-settings.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -370,7 +373,8 @@ import {
DocLinksWidgetSettingsComponent, DocLinksWidgetSettingsComponent,
QuickLinksWidgetSettingsComponent, QuickLinksWidgetSettingsComponent,
ValueCardWidgetSettingsComponent, ValueCardWidgetSettingsComponent,
AggregatedValueCardKeySettingsComponent AggregatedValueCardKeySettingsComponent,
AggregatedValueCardWidgetSettingsComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -476,7 +480,8 @@ import {
DocLinksWidgetSettingsComponent, DocLinksWidgetSettingsComponent,
QuickLinksWidgetSettingsComponent, QuickLinksWidgetSettingsComponent,
ValueCardWidgetSettingsComponent, ValueCardWidgetSettingsComponent,
AggregatedValueCardKeySettingsComponent AggregatedValueCardKeySettingsComponent,
AggregatedValueCardWidgetSettingsComponent
] ]
}) })
export class WidgetSettingsModule { export class WidgetSettingsModule {
@ -548,4 +553,5 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
'tb-quick-links-widget-settings': QuickLinksWidgetSettingsComponent, 'tb-quick-links-widget-settings': QuickLinksWidgetSettingsComponent,
'tb-value-card-widget-settings': ValueCardWidgetSettingsComponent, 'tb-value-card-widget-settings': ValueCardWidgetSettingsComponent,
'tb-aggregated-value-card-key-settings': AggregatedValueCardKeySettingsComponent, 'tb-aggregated-value-card-key-settings': AggregatedValueCardKeySettingsComponent,
'tb-aggregated-value-card-widget-settings': AggregatedValueCardWidgetSettingsComponent
}; };

View File

@ -103,9 +103,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text'); this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text');
this.config.loadEntity = id => this.dashboardService.getDashboard(id.id); this.config.loadEntity = id => this.dashboardService.getDashboard(id.id);
this.config.saveEntity = dashboard => { this.config.saveEntity = dashboard => this.dashboardService.saveDashboard(dashboard as Dashboard);
return this.dashboardService.saveDashboard(dashboard as Dashboard);
};
this.config.onEntityAction = action => this.onDashboardAction(action); this.config.onEntityAction = action => this.onDashboardAction(action);
this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' || this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' ||
this.config.componentsData.dashboardScope === 'edge_customer_user'); this.config.componentsData.dashboardScope === 'edge_customer_user');
@ -118,6 +116,10 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
} }
return true; return true;
}; };
this.config.entityAdded = dashboard => {
this.openDashboard(null, dashboard);
};
} }
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> { resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> {
@ -178,13 +180,9 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
if (dashboardScope === 'tenant') { if (dashboardScope === 'tenant') {
columns.push( columns.push(
new EntityTableColumn<DashboardInfo>('customersTitle', 'dashboard.assignedToCustomers', new EntityTableColumn<DashboardInfo>('customersTitle', 'dashboard.assignedToCustomers',
'50%', entity => { '50%', entity => getDashboardAssignedCustomersText(entity), () => ({}), false),
return getDashboardAssignedCustomersText(entity);
}, () => ({}), false),
new EntityTableColumn<DashboardInfo>('dashboardIsPublic', 'dashboard.public', '60px', new EntityTableColumn<DashboardInfo>('dashboardIsPublic', 'dashboard.public', '60px',
entity => { entity => checkBoxCell(isPublicDashboard(entity)), () => ({}), false),
return checkBoxCell(isPublicDashboard(entity));
}, () => ({}), false),
); );
} }
return columns; return columns;
@ -269,7 +267,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
{ {
name: this.translate.instant('edge.unassign-from-edge'), name: this.translate.instant('edge.unassign-from-edge'),
icon: 'assignment_return', icon: 'assignment_return',
isEnabled: (entity) => true, isEnabled: () => true,
onAction: ($event, entity) => this.unassignFromEdge($event, entity) onAction: ($event, entity) => this.unassignFromEdge($event, entity)
} }
); );
@ -383,7 +381,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
} }
} }
importDashboard($event: Event) { importDashboard(_$event: Event) {
this.importExport.importDashboard().subscribe( this.importExport.importDashboard().subscribe(
(dashboard) => { (dashboard) => {
if (dashboard) { if (dashboard) {

View File

@ -5761,7 +5761,8 @@
"add-value": "Add value", "add-value": "Add value",
"remove-value": "Remove value", "remove-value": "Remove value",
"no-values": "No values configured", "no-values": "No values configured",
"aggregation": "Aggregation" "aggregation": "Aggregation",
"aggregated-value-card-style": "Aggregated value card style"
}, },
"table": { "table": {
"common-table-settings": "Common Table Settings", "common-table-settings": "Common Table Settings",