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:
parent
965b526b7c
commit
b7b34da831
File diff suppressed because one or more lines are too long
@ -31,7 +31,6 @@ import {
|
||||
} from '@shared/models/dashboard.models';
|
||||
import { deepClone, isDefined, isDefinedAndNotNull, isString, isUndefined } from '@core/utils';
|
||||
import {
|
||||
DataKey,
|
||||
Datasource,
|
||||
datasourcesHasOnlyComparisonAggregation,
|
||||
DatasourceType,
|
||||
@ -484,6 +483,14 @@ export class DashboardUtilsService {
|
||||
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,
|
||||
targetState: string,
|
||||
targetLayout: DashboardLayoutId,
|
||||
|
||||
@ -71,7 +71,6 @@ import { MediaBreakpoints } from '@shared/models/constants';
|
||||
import { AuthUser } from '@shared/models/user.model';
|
||||
import { getCurrentAuthState } from '@core/auth/auth.selectors';
|
||||
import {
|
||||
DatasourceType,
|
||||
Widget,
|
||||
WidgetConfig,
|
||||
WidgetInfo,
|
||||
@ -415,6 +414,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
|
||||
this.updateLayoutSizes();
|
||||
});
|
||||
this.dashboardResize$.observe(this.dashboardContainer.nativeElement);
|
||||
if (!this.widgetEditMode && !this.readonly && this.dashboardUtils.isEmptyDashboard(this.dashboard)) {
|
||||
this.setEditMode(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
private init(data: DashboardPageInitData) {
|
||||
|
||||
@ -90,7 +90,7 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
|
||||
|
||||
protected setupDefaults(configData: WidgetConfigComponentData) {
|
||||
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³')
|
||||
);
|
||||
@ -143,7 +143,7 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
|
||||
showChart: [settings.showChart, []],
|
||||
chartUnits: [dataKey?.units, []],
|
||||
chartDecimals: [dataKey?.decimals, []],
|
||||
chartColor: [settings.chartColor, []],
|
||||
chartColor: [dataKey?.color, []],
|
||||
|
||||
values: [this.getValues(configData.config.datasources, keyName), []],
|
||||
|
||||
@ -188,10 +188,9 @@ export class AggregatedValueCardBasicConfigComponent extends BasicWidgetConfigCo
|
||||
if (dataKey) {
|
||||
dataKey.units = config.chartUnits;
|
||||
dataKey.decimals = config.chartDecimals;
|
||||
dataKey.color = config.chartColor;
|
||||
}
|
||||
|
||||
this.widgetConfig.config.settings.chartColor = config.chartColor;
|
||||
|
||||
this.setValues(config.values, this.widgetConfig.config.datasources);
|
||||
|
||||
this.widgetConfig.config.settings.background = config.background;
|
||||
|
||||
@ -79,7 +79,6 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
|
||||
values: {[key: string]: AggregatedValueCardValue} = {};
|
||||
|
||||
showChart = true;
|
||||
chartColor: string;
|
||||
|
||||
showDate = true;
|
||||
dateFormat: DateFormatProcessor;
|
||||
@ -123,7 +122,6 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
|
||||
}
|
||||
|
||||
this.showChart = this.settings.showChart;
|
||||
this.chartColor = this.settings.chartColor;
|
||||
if (this.showChart) {
|
||||
if (this.ctx.defaultSubscription.firstDatasource?.dataKeys?.length) {
|
||||
this.flotDataKey = this.ctx.defaultSubscription.firstDatasource?.dataKeys[0];
|
||||
@ -132,7 +130,6 @@ export class AggregatedValueCardWidgetComponent implements OnInit, AfterViewInit
|
||||
showLines: true,
|
||||
lineWidth: 2
|
||||
} as TbFlotKeySettings;
|
||||
this.flotDataKey.color = this.chartColor;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -41,7 +41,6 @@ export interface AggregatedValueCardWidgetSettings {
|
||||
dateFont: Font;
|
||||
dateColor: string;
|
||||
showChart: boolean;
|
||||
chartColor: string;
|
||||
background: BackgroundSettings;
|
||||
}
|
||||
|
||||
@ -136,7 +135,6 @@ export const aggregatedValueCardDefaultSettings: AggregatedValueCardWidgetSettin
|
||||
},
|
||||
dateColor: 'rgba(0, 0, 0, 0.38)',
|
||||
showChart: true,
|
||||
chartColor: 'rgba(0, 0, 0, 0.87)',
|
||||
background: {
|
||||
type: BackgroundType.color,
|
||||
color: '#fff',
|
||||
@ -164,7 +162,7 @@ export const aggregatedValueCardDefaultKeySettings: AggregatedValueCardKeySettin
|
||||
|
||||
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,
|
||||
settings: {
|
||||
position: AggregatedValueCardKeyPosition.center,
|
||||
@ -181,7 +179,7 @@ export const createDefaultAggregatedValueLatestDataKeys = (keyName: string, unit
|
||||
} 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,
|
||||
comparisonEnabled: true,
|
||||
timeForComparison: 'previousInterval',
|
||||
@ -210,7 +208,7 @@ export const createDefaultAggregatedValueLatestDataKeys = (keyName: string, unit
|
||||
} 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,
|
||||
comparisonEnabled: true,
|
||||
timeForComparison: 'previousInterval',
|
||||
|
||||
@ -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>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -270,6 +270,9 @@ import { WidgetSettingsCommonModule } from '@home/components/widget/lib/settings
|
||||
import {
|
||||
AggregatedValueCardKeySettingsComponent
|
||||
} 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({
|
||||
declarations: [
|
||||
@ -370,7 +373,8 @@ import {
|
||||
DocLinksWidgetSettingsComponent,
|
||||
QuickLinksWidgetSettingsComponent,
|
||||
ValueCardWidgetSettingsComponent,
|
||||
AggregatedValueCardKeySettingsComponent
|
||||
AggregatedValueCardKeySettingsComponent,
|
||||
AggregatedValueCardWidgetSettingsComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -476,7 +480,8 @@ import {
|
||||
DocLinksWidgetSettingsComponent,
|
||||
QuickLinksWidgetSettingsComponent,
|
||||
ValueCardWidgetSettingsComponent,
|
||||
AggregatedValueCardKeySettingsComponent
|
||||
AggregatedValueCardKeySettingsComponent,
|
||||
AggregatedValueCardWidgetSettingsComponent
|
||||
]
|
||||
})
|
||||
export class WidgetSettingsModule {
|
||||
@ -548,4 +553,5 @@ export const widgetSettingsComponentsMap: {[key: string]: Type<IWidgetSettingsCo
|
||||
'tb-quick-links-widget-settings': QuickLinksWidgetSettingsComponent,
|
||||
'tb-value-card-widget-settings': ValueCardWidgetSettingsComponent,
|
||||
'tb-aggregated-value-card-key-settings': AggregatedValueCardKeySettingsComponent,
|
||||
'tb-aggregated-value-card-widget-settings': AggregatedValueCardWidgetSettingsComponent
|
||||
};
|
||||
|
||||
@ -103,9 +103,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
|
||||
this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text');
|
||||
|
||||
this.config.loadEntity = id => this.dashboardService.getDashboard(id.id);
|
||||
this.config.saveEntity = dashboard => {
|
||||
return this.dashboardService.saveDashboard(dashboard as Dashboard);
|
||||
};
|
||||
this.config.saveEntity = dashboard => this.dashboardService.saveDashboard(dashboard as Dashboard);
|
||||
this.config.onEntityAction = action => this.onDashboardAction(action);
|
||||
this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' ||
|
||||
this.config.componentsData.dashboardScope === 'edge_customer_user');
|
||||
@ -118,6 +116,10 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
this.config.entityAdded = dashboard => {
|
||||
this.openDashboard(null, dashboard);
|
||||
};
|
||||
}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> {
|
||||
@ -178,13 +180,9 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
|
||||
if (dashboardScope === 'tenant') {
|
||||
columns.push(
|
||||
new EntityTableColumn<DashboardInfo>('customersTitle', 'dashboard.assignedToCustomers',
|
||||
'50%', entity => {
|
||||
return getDashboardAssignedCustomersText(entity);
|
||||
}, () => ({}), false),
|
||||
'50%', entity => getDashboardAssignedCustomersText(entity), () => ({}), false),
|
||||
new EntityTableColumn<DashboardInfo>('dashboardIsPublic', 'dashboard.public', '60px',
|
||||
entity => {
|
||||
return checkBoxCell(isPublicDashboard(entity));
|
||||
}, () => ({}), false),
|
||||
entity => checkBoxCell(isPublicDashboard(entity)), () => ({}), false),
|
||||
);
|
||||
}
|
||||
return columns;
|
||||
@ -269,7 +267,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
|
||||
{
|
||||
name: this.translate.instant('edge.unassign-from-edge'),
|
||||
icon: 'assignment_return',
|
||||
isEnabled: (entity) => true,
|
||||
isEnabled: () => true,
|
||||
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(
|
||||
(dashboard) => {
|
||||
if (dashboard) {
|
||||
|
||||
@ -5761,7 +5761,8 @@
|
||||
"add-value": "Add value",
|
||||
"remove-value": "Remove value",
|
||||
"no-values": "No values configured",
|
||||
"aggregation": "Aggregation"
|
||||
"aggregation": "Aggregation",
|
||||
"aggregated-value-card-style": "Aggregated value card style"
|
||||
},
|
||||
"table": {
|
||||
"common-table-settings": "Common Table Settings",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user