thingsboard/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts

529 lines
17 KiB
TypeScript
Raw Normal View History

2019-09-03 19:31:16 +03:00
///
2021-01-11 13:42:16 +02:00
/// Copyright © 2016-2021 The Thingsboard Authors
2019-09-03 19:31:16 +03:00
///
/// 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.
///
2019-09-25 19:37:29 +03:00
import { GridsterComponent, GridsterConfig, GridsterItem, GridsterItemComponentInterface } from 'angular-gridster2';
import { Widget, WidgetPosition, widgetType } from '@app/shared/models/widget.models';
2019-09-03 19:31:16 +03:00
import { WidgetLayout, WidgetLayouts } from '@app/shared/models/dashboard.models';
import { IDashboardWidget, WidgetAction, WidgetContext, WidgetHeaderAction } from './widget-component.models';
2019-09-03 19:31:16 +03:00
import { Timewindow } from '@shared/models/time/time.models';
2019-09-25 19:37:29 +03:00
import { Observable, of, Subject } from 'rxjs';
2020-02-26 18:15:04 +02:00
import { guid, isDefined, isEqual, isUndefined } from '@app/core/utils';
import { IterableDiffer, KeyValueDiffer } from '@angular/core';
2019-09-25 19:37:29 +03:00
import { IAliasController, IStateController } from '@app/core/api/widget-api.models';
import { enumerable } from '@shared/decorators/enumerable';
import { UtilsService } from '@core/services/utils.service';
2019-09-03 19:31:16 +03:00
export interface WidgetsData {
widgets: Array<Widget>;
widgetLayouts?: WidgetLayouts;
}
2019-09-25 19:37:29 +03:00
export interface ContextMenuItem {
enabled: boolean;
shortcut?: string;
icon: string;
value: string;
}
export interface DashboardContextMenuItem extends ContextMenuItem {
action: (contextMenuEvent: MouseEvent) => void;
}
export interface WidgetContextMenuItem extends ContextMenuItem {
action: (contextMenuEvent: MouseEvent, widget: Widget) => void;
}
export interface DashboardCallbacks {
onEditWidget?: ($event: Event, widget: Widget) => void;
onExportWidget?: ($event: Event, widget: Widget) => void;
onRemoveWidget?: ($event: Event, widget: Widget) => void;
onWidgetMouseDown?: ($event: Event, widget: Widget) => void;
onWidgetClicked?: ($event: Event, widget: Widget) => void;
2019-09-25 19:37:29 +03:00
prepareDashboardContextMenu?: ($event: Event) => Array<DashboardContextMenuItem>;
prepareWidgetContextMenu?: ($event: Event, widget: Widget) => Array<WidgetContextMenuItem>;
2019-09-03 19:31:16 +03:00
}
export interface IDashboardComponent {
utils: UtilsService;
2019-09-03 19:31:16 +03:00
gridsterOpts: GridsterConfig;
gridster: GridsterComponent;
dashboardWidgets: DashboardWidgets;
mobileAutofillHeight: boolean;
2019-09-03 19:31:16 +03:00
isMobileSize: boolean;
autofillHeight: boolean;
dashboardTimewindow: Timewindow;
dashboardTimewindowChanged: Observable<Timewindow>;
aliasController: IAliasController;
stateController: IStateController;
onUpdateTimewindow(startTimeMs: number, endTimeMs: number, interval?: number, persist?: boolean): void;
2019-09-10 15:12:10 +03:00
onResetTimewindow(): void;
2019-09-25 19:37:29 +03:00
resetHighlight(): void;
highlightWidget(widgetId: string, delay?: number);
selectWidget(widgetId: string, delay?: number);
2019-09-25 19:37:29 +03:00
getSelectedWidget(): Widget;
getEventGridPosition(event: Event): WidgetPosition;
2019-10-24 19:52:19 +03:00
notifyGridsterOptionsChanged();
pauseChangeNotifications();
resumeChangeNotifications();
notifyLayoutUpdated();
detectChanges();
2019-09-25 19:37:29 +03:00
}
declare type DashboardWidgetUpdateOperation = 'add' | 'remove' | 'update';
interface DashboardWidgetUpdateRecord {
widget?: Widget;
widgetLayout?: WidgetLayout;
widgetId: string;
2019-09-25 19:37:29 +03:00
operation: DashboardWidgetUpdateOperation;
2019-09-03 19:31:16 +03:00
}
export class DashboardWidgets implements Iterable<DashboardWidget> {
2019-09-25 19:37:29 +03:00
highlightedMode = false;
dashboardWidgets: Array<DashboardWidget> = [];
widgets: Iterable<Widget>;
2019-09-25 19:37:29 +03:00
widgetLayouts: WidgetLayouts;
[Symbol.iterator](): Iterator<DashboardWidget> {
return this.dashboardWidgets[Symbol.iterator]();
}
2019-09-25 19:37:29 +03:00
constructor(private dashboard: IDashboardComponent,
2020-02-03 19:38:28 +02:00
private widgetsDiffer: IterableDiffer<Widget>,
private widgetLayoutsDiffer: KeyValueDiffer<string, WidgetLayout>) {
}
2019-09-25 19:37:29 +03:00
doCheck() {
const widgetChange = this.widgetsDiffer.diff(this.widgets);
2020-02-03 19:38:28 +02:00
const widgetLayoutChange = this.widgetLayoutsDiffer.diff(this.widgetLayouts);
const updateRecords: Array<DashboardWidgetUpdateRecord> = [];
2019-09-25 19:37:29 +03:00
2020-02-03 19:38:28 +02:00
if (widgetChange !== null) {
2019-09-25 19:37:29 +03:00
widgetChange.forEachAddedItem((added) => {
updateRecords.push({
widget: added.item,
widgetId: added.item.id,
2019-11-08 17:42:31 +02:00
widgetLayout: this.widgetLayouts ? this.widgetLayouts[added.item.id] : null,
2019-09-25 19:37:29 +03:00
operation: 'add'
});
});
widgetChange.forEachRemovedItem((removed) => {
let operation = updateRecords.find((record) => record.widgetId === removed.item.id);
2019-09-25 19:37:29 +03:00
if (operation) {
operation.operation = 'update';
} else {
operation = {
widgetId: removed.item.id,
2019-09-25 19:37:29 +03:00
operation: 'remove'
};
updateRecords.push(operation);
}
});
2020-02-03 19:38:28 +02:00
}
if (widgetLayoutChange !== null) {
widgetLayoutChange.forEachChangedItem((changed) => {
const operation = updateRecords.find((record) => record.widgetId === changed.key);
2020-02-03 19:38:28 +02:00
if (!operation) {
const widget = this.dashboardWidgets.find((dashboardWidget) => dashboardWidget.widgetId === changed.key);
if (widget) {
2020-02-03 19:38:28 +02:00
updateRecords.push({
widget: widget.widget,
widgetId: changed.key,
widgetLayout: changed.currentValue,
operation: 'update'
});
}
}
});
}
if (updateRecords.length) {
2019-09-25 19:37:29 +03:00
updateRecords.forEach((record) => {
switch (record.operation) {
case 'add':
this.dashboardWidgets.push(
new DashboardWidget(this.dashboard, record.widget, record.widgetLayout)
2019-09-25 19:37:29 +03:00
);
break;
case 'remove':
let index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === record.widgetId);
2019-09-25 19:37:29 +03:00
if (index > -1) {
this.dashboardWidgets.splice(index, 1);
}
break;
case 'update':
index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === record.widgetId);
2019-09-25 19:37:29 +03:00
if (index > -1) {
const prevDashboardWidget = this.dashboardWidgets[index];
2020-02-26 18:15:04 +02:00
if (!isEqual(prevDashboardWidget.widget, record.widget) ||
!isEqual(prevDashboardWidget.widgetLayout, record.widgetLayout)) {
this.dashboardWidgets[index] = new DashboardWidget(this.dashboard, record.widget, record.widgetLayout);
2019-09-25 19:37:29 +03:00
this.dashboardWidgets[index].highlighted = prevDashboardWidget.highlighted;
this.dashboardWidgets[index].selected = prevDashboardWidget.selected;
} else {
this.dashboardWidgets[index].widget = record.widget;
this.dashboardWidgets[index].widgetLayout = record.widgetLayout;
}
}
break;
}
});
2020-02-03 19:38:28 +02:00
this.updateRowsAndSort();
2019-09-25 19:37:29 +03:00
}
}
widgetLayoutsUpdated() {
for (const w of Object.keys(this.widgetLayouts)) {
const widgetLayout = this.widgetLayouts[w];
const index = this.dashboardWidgets.findIndex((dashboardWidget) => dashboardWidget.widgetId === w);
if (index > -1) {
this.dashboardWidgets[index].widgetLayout = widgetLayout;
}
}
this.updateRowsAndSort();
}
setWidgets(widgets: Iterable<Widget>, widgetLayouts: WidgetLayouts) {
2019-09-25 19:37:29 +03:00
this.highlightedMode = false;
this.widgets = widgets;
this.widgetLayouts = widgetLayouts;
}
highlightWidget(widgetId: string): DashboardWidget {
const widget = this.findWidgetById(widgetId);
2019-10-24 19:52:19 +03:00
if (widget && (!this.highlightedMode || !widget.highlighted || this.highlightedMode && widget.highlighted)) {
2019-09-25 19:37:29 +03:00
this.highlightedMode = true;
widget.highlighted = true;
this.dashboardWidgets.forEach((dashboardWidget) => {
2019-09-25 19:37:29 +03:00
if (dashboardWidget !== widget) {
dashboardWidget.highlighted = false;
}
});
2019-09-25 19:37:29 +03:00
return widget;
} else {
return null;
}
2019-09-25 19:37:29 +03:00
}
selectWidget(widgetId: string): DashboardWidget {
const widget = this.findWidgetById(widgetId);
2019-09-25 19:37:29 +03:00
if (widget && (!widget.selected)) {
widget.selected = true;
this.dashboardWidgets.forEach((dashboardWidget) => {
if (dashboardWidget !== widget) {
dashboardWidget.selected = false;
}
});
return widget;
} else {
return null;
}
}
resetHighlight(): DashboardWidget {
const highlighted = this.dashboardWidgets.find((dashboardWidget) => dashboardWidget.highlighted);
this.highlightedMode = false;
this.dashboardWidgets.forEach((dashboardWidget) => {
dashboardWidget.highlighted = false;
dashboardWidget.selected = false;
});
return highlighted;
}
isHighlighted(widget: DashboardWidget): boolean {
return (this.highlightedMode && widget.highlighted) || (widget.selected);
}
isNotHighlighted(widget: DashboardWidget): boolean {
return this.highlightedMode && !widget.highlighted;
}
getSelectedWidget(): DashboardWidget {
return this.dashboardWidgets.find((dashboardWidget) => dashboardWidget.selected);
}
private findWidgetById(widgetId: string): DashboardWidget {
return this.dashboardWidgets.find((dashboardWidget) => dashboardWidget.widgetId === widgetId);
2019-09-25 19:37:29 +03:00
}
private updateRowsAndSort() {
let maxRows = this.dashboard.gridsterOpts.maxRows;
this.dashboardWidgets.forEach((dashboardWidget) => {
const bottom = dashboardWidget.y + dashboardWidget.rows;
maxRows = Math.max(maxRows, bottom);
});
this.sortWidgets();
this.dashboard.gridsterOpts.maxRows = maxRows;
2019-10-24 19:52:19 +03:00
this.dashboard.notifyGridsterOptionsChanged();
}
sortWidgets() {
this.dashboardWidgets.sort((widget1, widget2) => {
const row1 = widget1.widgetOrder;
const row2 = widget2.widgetOrder;
let res = row1 - row2;
if (res === 0) {
res = widget1.x - widget2.x;
}
return res;
});
}
}
export class DashboardWidget implements GridsterItem, IDashboardWidget {
2019-09-03 19:31:16 +03:00
2019-09-25 19:37:29 +03:00
highlighted = false;
selected = false;
2019-09-03 19:31:16 +03:00
isFullscreen = false;
color: string;
backgroundColor: string;
padding: string;
margin: string;
title: string;
customTranslatedTitle: string;
2020-02-25 19:11:25 +02:00
titleTooltip: string;
2019-09-03 19:31:16 +03:00
showTitle: boolean;
titleStyle: {[klass: string]: any};
titleIcon: string;
showTitleIcon: boolean;
titleIconStyle: {[klass: string]: any};
dropShadow: boolean;
enableFullscreen: boolean;
hasTimewindow: boolean;
hasAggregation: boolean;
style: {[klass: string]: any};
showWidgetTitlePanel: boolean;
showWidgetActions: boolean;
customHeaderActions: Array<WidgetHeaderAction>;
widgetActions: Array<WidgetAction>;
widgetContext = new WidgetContext(this.dashboard, this, this.widget);
2019-09-03 19:31:16 +03:00
widgetId: string;
2019-09-25 19:37:29 +03:00
private gridsterItemComponentSubject = new Subject<GridsterItemComponentInterface>();
private gridsterItemComponentValue: GridsterItemComponentInterface;
set gridsterItemComponent(item: GridsterItemComponentInterface) {
this.gridsterItemComponentValue = item;
this.gridsterItemComponentSubject.next(this.gridsterItemComponentValue);
this.gridsterItemComponentSubject.complete();
}
2019-09-03 19:31:16 +03:00
constructor(
private dashboard: IDashboardComponent,
public widget: Widget,
2019-09-25 19:37:29 +03:00
public widgetLayout?: WidgetLayout) {
if (!widget.id) {
widget.id = guid();
}
this.widgetId = widget.id;
this.updateWidgetParams(false);
2019-09-03 19:31:16 +03:00
}
2019-09-25 19:37:29 +03:00
gridsterItemComponent$(): Observable<GridsterItemComponentInterface> {
if (this.gridsterItemComponentValue) {
return of(this.gridsterItemComponentValue);
} else {
return this.gridsterItemComponentSubject.asObservable();
}
}
updateWidgetParams(detectChanges = true) {
2019-09-03 19:31:16 +03:00
this.color = this.widget.config.color || 'rgba(0, 0, 0, 0.87)';
this.backgroundColor = this.widget.config.backgroundColor || '#fff';
this.padding = this.widget.config.padding || '8px';
this.margin = this.widget.config.margin || '0px';
this.title = isDefined(this.widgetContext.widgetTitle)
&& this.widgetContext.widgetTitle.length ? this.widgetContext.widgetTitle : this.widget.config.title;
this.customTranslatedTitle = this.dashboard.utils.customTranslation(this.title, this.title);
2020-02-25 19:11:25 +02:00
this.titleTooltip = isDefined(this.widgetContext.widgetTitleTooltip)
&& this.widgetContext.widgetTitleTooltip.length ? this.widgetContext.widgetTitleTooltip : this.widget.config.titleTooltip;
this.titleTooltip = this.dashboard.utils.customTranslation(this.titleTooltip, this.titleTooltip);
2019-09-03 19:31:16 +03:00
this.showTitle = isDefined(this.widget.config.showTitle) ? this.widget.config.showTitle : true;
this.titleStyle = this.widget.config.titleStyle ? this.widget.config.titleStyle : {};
this.titleIcon = isDefined(this.widget.config.titleIcon) ? this.widget.config.titleIcon : '';
this.showTitleIcon = isDefined(this.widget.config.showTitleIcon) ? this.widget.config.showTitleIcon : false;
this.titleIconStyle = {};
if (this.widget.config.iconColor) {
this.titleIconStyle.color = this.widget.config.iconColor;
}
if (this.widget.config.iconSize) {
this.titleIconStyle.fontSize = this.widget.config.iconSize;
}
this.dropShadow = isDefined(this.widget.config.dropShadow) ? this.widget.config.dropShadow : true;
this.enableFullscreen = isDefined(this.widget.config.enableFullscreen) ? this.widget.config.enableFullscreen : true;
this.hasTimewindow = (this.widget.type === widgetType.timeseries || this.widget.type === widgetType.alarm) ?
(isDefined(this.widget.config.useDashboardTimewindow) ?
(!this.widget.config.useDashboardTimewindow && (isUndefined(this.widget.config.displayTimewindow)
|| this.widget.config.displayTimewindow)) : false)
: false;
this.hasAggregation = this.widget.type === widgetType.timeseries;
this.style = {cursor: 'pointer',
color: this.color,
backgroundColor: this.backgroundColor,
padding: this.padding,
margin: this.margin};
if (this.widget.config.widgetStyle) {
2020-04-23 12:01:58 +03:00
this.style = {...this.style, ...this.widget.config.widgetStyle};
2019-09-03 19:31:16 +03:00
}
this.showWidgetTitlePanel = this.widgetContext.hideTitlePanel ? false :
2020-02-26 19:43:16 +02:00
this.showTitle || this.hasTimewindow;
2019-09-03 19:31:16 +03:00
this.showWidgetActions = this.widgetContext.hideTitlePanel ? false : true;
this.customHeaderActions = this.widgetContext.customHeaderActions ? this.widgetContext.customHeaderActions : [];
this.widgetActions = this.widgetContext.widgetActions ? this.widgetContext.widgetActions : [];
if (detectChanges) {
this.dashboard.detectChanges();
}
2019-09-03 19:31:16 +03:00
}
@enumerable(true)
2019-09-03 19:31:16 +03:00
get x(): number {
2019-09-25 19:37:29 +03:00
let res;
2019-09-03 19:31:16 +03:00
if (this.widgetLayout) {
2019-09-25 19:37:29 +03:00
res = this.widgetLayout.col;
2019-09-03 19:31:16 +03:00
} else {
2019-09-25 19:37:29 +03:00
res = this.widget.col;
2019-09-03 19:31:16 +03:00
}
2019-09-25 19:37:29 +03:00
return Math.floor(res);
2019-09-03 19:31:16 +03:00
}
set x(x: number) {
if (!this.dashboard.isMobileSize) {
if (this.widgetLayout) {
this.widgetLayout.col = x;
} else {
this.widget.col = x;
}
}
}
@enumerable(true)
2019-09-03 19:31:16 +03:00
get y(): number {
2019-09-25 19:37:29 +03:00
let res;
2019-09-03 19:31:16 +03:00
if (this.widgetLayout) {
2019-09-25 19:37:29 +03:00
res = this.widgetLayout.row;
2019-09-03 19:31:16 +03:00
} else {
2019-09-25 19:37:29 +03:00
res = this.widget.row;
2019-09-03 19:31:16 +03:00
}
2019-09-25 19:37:29 +03:00
return Math.floor(res);
2019-09-03 19:31:16 +03:00
}
set y(y: number) {
if (!this.dashboard.isMobileSize) {
if (this.widgetLayout) {
this.widgetLayout.row = y;
} else {
this.widget.row = y;
}
}
}
@enumerable(true)
2019-09-03 19:31:16 +03:00
get cols(): number {
2019-09-25 19:37:29 +03:00
let res;
2019-09-03 19:31:16 +03:00
if (this.widgetLayout) {
2019-09-25 19:37:29 +03:00
res = this.widgetLayout.sizeX;
2019-09-03 19:31:16 +03:00
} else {
2019-09-25 19:37:29 +03:00
res = this.widget.sizeX;
2019-09-03 19:31:16 +03:00
}
2019-09-25 19:37:29 +03:00
return Math.floor(res);
2019-09-03 19:31:16 +03:00
}
set cols(cols: number) {
if (!this.dashboard.isMobileSize) {
if (this.widgetLayout) {
this.widgetLayout.sizeX = cols;
} else {
this.widget.sizeX = cols;
}
}
}
@enumerable(true)
2019-09-03 19:31:16 +03:00
get rows(): number {
2019-09-25 19:37:29 +03:00
let res;
if (this.dashboard.isMobileSize && !this.dashboard.mobileAutofillHeight) {
2019-09-03 19:31:16 +03:00
let mobileHeight;
if (this.widgetLayout) {
mobileHeight = this.widgetLayout.mobileHeight;
}
if (!mobileHeight && this.widget.config.mobileHeight) {
mobileHeight = this.widget.config.mobileHeight;
}
if (mobileHeight) {
2019-09-25 19:37:29 +03:00
res = mobileHeight;
2019-09-03 19:31:16 +03:00
} else {
2020-04-23 13:27:21 +03:00
const sizeY = this.widgetLayout ? this.widgetLayout.sizeY : this.widget.sizeY;
res = sizeY * 24 / this.dashboard.gridsterOpts.minCols;
2019-09-03 19:31:16 +03:00
}
} else {
if (this.widgetLayout) {
2019-09-25 19:37:29 +03:00
res = this.widgetLayout.sizeY;
2019-09-03 19:31:16 +03:00
} else {
2019-09-25 19:37:29 +03:00
res = this.widget.sizeY;
2019-09-03 19:31:16 +03:00
}
}
2019-09-25 19:37:29 +03:00
return Math.floor(res);
2019-09-03 19:31:16 +03:00
}
set rows(rows: number) {
if (!this.dashboard.isMobileSize && !this.dashboard.autofillHeight) {
2019-09-03 19:31:16 +03:00
if (this.widgetLayout) {
this.widgetLayout.sizeY = rows;
} else {
this.widget.sizeY = rows;
}
}
}
@enumerable(true)
2019-09-03 19:31:16 +03:00
get widgetOrder(): number {
let order;
if (this.widgetLayout && isDefined(this.widgetLayout.mobileOrder) && this.widgetLayout.mobileOrder >= 0) {
order = this.widgetLayout.mobileOrder;
} else if (isDefined(this.widget.config.mobileOrder) && this.widget.config.mobileOrder >= 0) {
order = this.widget.config.mobileOrder;
} else if (this.widgetLayout) {
order = this.widgetLayout.row;
} else {
order = this.widget.row;
}
return order;
}
}