2019-09-05 21:15:40 +03:00
|
|
|
///
|
|
|
|
|
/// Copyright © 2016-2019 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 {
|
|
|
|
|
AfterViewInit,
|
2019-09-25 19:37:29 +03:00
|
|
|
ChangeDetectionStrategy,
|
|
|
|
|
ChangeDetectorRef,
|
2019-09-05 21:15:40 +03:00
|
|
|
Component,
|
|
|
|
|
ComponentFactoryResolver,
|
|
|
|
|
ComponentRef,
|
|
|
|
|
ElementRef,
|
|
|
|
|
Injector,
|
|
|
|
|
Input,
|
2019-09-25 19:37:29 +03:00
|
|
|
NgZone,
|
2019-09-05 21:15:40 +03:00
|
|
|
OnChanges,
|
|
|
|
|
OnDestroy,
|
|
|
|
|
OnInit,
|
|
|
|
|
SimpleChanges,
|
|
|
|
|
ViewChild,
|
|
|
|
|
ViewContainerRef,
|
2019-09-25 19:37:29 +03:00
|
|
|
ViewEncapsulation
|
2019-09-05 21:15:40 +03:00
|
|
|
} from '@angular/core';
|
|
|
|
|
import { DashboardWidget, IDashboardComponent } from '@home/models/dashboard-component.models';
|
|
|
|
|
import {
|
2019-09-10 15:12:10 +03:00
|
|
|
Datasource,
|
2019-09-05 21:15:40 +03:00
|
|
|
LegendConfig,
|
|
|
|
|
LegendData,
|
2019-10-24 19:52:19 +03:00
|
|
|
LegendDirection,
|
2019-09-05 21:15:40 +03:00
|
|
|
LegendPosition,
|
|
|
|
|
Widget,
|
|
|
|
|
WidgetActionDescriptor,
|
2019-09-06 20:17:45 +03:00
|
|
|
widgetActionSources,
|
2019-09-05 21:15:40 +03:00
|
|
|
WidgetActionType,
|
2019-09-06 20:17:45 +03:00
|
|
|
WidgetResource,
|
2019-09-10 15:12:10 +03:00
|
|
|
widgetType,
|
2019-10-24 19:52:19 +03:00
|
|
|
WidgetTypeParameters,
|
|
|
|
|
defaultLegendConfig
|
2019-09-05 21:15:40 +03:00
|
|
|
} from '@shared/models/widget.models';
|
|
|
|
|
import { PageComponent } from '@shared/components/page.component';
|
|
|
|
|
import { Store } from '@ngrx/store';
|
|
|
|
|
import { AppState } from '@core/core.state';
|
|
|
|
|
import { WidgetService } from '@core/http/widget.service';
|
|
|
|
|
import { UtilsService } from '@core/services/utils.service';
|
2019-09-10 15:12:10 +03:00
|
|
|
import { forkJoin, Observable, of, ReplaySubject, Subscription, throwError } from 'rxjs';
|
2019-09-25 19:37:29 +03:00
|
|
|
import { deepClone, isDefined, objToBase64 } from '@core/utils';
|
2019-09-06 20:17:45 +03:00
|
|
|
import {
|
|
|
|
|
IDynamicWidgetComponent,
|
|
|
|
|
WidgetContext,
|
|
|
|
|
WidgetHeaderAction,
|
|
|
|
|
WidgetInfo,
|
|
|
|
|
WidgetTypeInstance
|
|
|
|
|
} from '@home/models/widget-component.models';
|
2019-09-05 21:15:40 +03:00
|
|
|
import {
|
|
|
|
|
IWidgetSubscription,
|
|
|
|
|
StateObject,
|
2019-09-25 19:37:29 +03:00
|
|
|
StateParams,
|
|
|
|
|
SubscriptionEntityInfo,
|
2019-09-06 20:17:45 +03:00
|
|
|
SubscriptionInfo,
|
|
|
|
|
WidgetSubscriptionContext,
|
|
|
|
|
WidgetSubscriptionOptions
|
2019-09-05 21:15:40 +03:00
|
|
|
} from '@core/api/widget-api.models';
|
|
|
|
|
import { EntityId } from '@shared/models/id/entity-id';
|
2019-09-06 20:17:45 +03:00
|
|
|
import { ActivatedRoute, Router } from '@angular/router';
|
2019-09-05 21:15:40 +03:00
|
|
|
import cssjs from '@core/css/css';
|
|
|
|
|
import { ResourcesService } from '@core/services/resources.service';
|
|
|
|
|
import { catchError, switchMap } from 'rxjs/operators';
|
|
|
|
|
import { ActionNotificationShow } from '@core/notification/notification.actions';
|
|
|
|
|
import { TimeService } from '@core/services/time.service';
|
|
|
|
|
import { DeviceService } from '@app/core/http/device.service';
|
|
|
|
|
import { AlarmService } from '@app/core/http/alarm.service';
|
|
|
|
|
import { ExceptionData } from '@shared/models/error.models';
|
2019-09-06 20:17:45 +03:00
|
|
|
import { WidgetComponentService } from './widget-component.service';
|
2019-09-10 15:12:10 +03:00
|
|
|
import { Timewindow } from '@shared/models/time/time.models';
|
|
|
|
|
import { AlarmSearchStatus } from '@shared/models/alarm.models';
|
|
|
|
|
import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
|
|
|
|
|
import { DashboardService } from '@core/http/dashboard.service';
|
|
|
|
|
import { DatasourceService } from '@core/api/datasource.service';
|
|
|
|
|
import { WidgetSubscription } from '@core/api/widget-subscription';
|
|
|
|
|
import { EntityService } from '@core/http/entity.service';
|
2019-09-05 21:15:40 +03:00
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
|
selector: 'tb-widget',
|
|
|
|
|
templateUrl: './widget.component.html',
|
|
|
|
|
styleUrls: ['./widget.component.scss'],
|
2019-09-13 13:10:24 +03:00
|
|
|
encapsulation: ViewEncapsulation.None,
|
|
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush
|
2019-09-05 21:15:40 +03:00
|
|
|
})
|
|
|
|
|
export class WidgetComponent extends PageComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
isEdit: boolean;
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
isMobile: boolean;
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
dashboard: IDashboardComponent;
|
|
|
|
|
|
|
|
|
|
@Input()
|
|
|
|
|
dashboardWidget: DashboardWidget;
|
|
|
|
|
|
|
|
|
|
@ViewChild('widgetContent', {read: ViewContainerRef, static: true}) widgetContentContainer: ViewContainerRef;
|
|
|
|
|
|
|
|
|
|
widget: Widget;
|
|
|
|
|
widgetInfo: WidgetInfo;
|
2019-09-06 20:17:45 +03:00
|
|
|
errorMessages: string[];
|
2019-09-05 21:15:40 +03:00
|
|
|
widgetContext: WidgetContext;
|
|
|
|
|
widgetType: any;
|
2019-09-10 15:12:10 +03:00
|
|
|
typeParameters: WidgetTypeParameters;
|
2019-09-05 21:15:40 +03:00
|
|
|
widgetTypeInstance: WidgetTypeInstance;
|
|
|
|
|
widgetErrorData: ExceptionData;
|
2019-09-06 20:17:45 +03:00
|
|
|
loadingData: boolean;
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
displayLegend: boolean;
|
|
|
|
|
legendConfig: LegendConfig;
|
|
|
|
|
legendData: LegendData;
|
|
|
|
|
isLegendFirst: boolean;
|
|
|
|
|
legendContainerLayoutType: string;
|
|
|
|
|
legendStyle: {[klass: string]: any};
|
|
|
|
|
|
|
|
|
|
dynamicWidgetComponentRef: ComponentRef<IDynamicWidgetComponent>;
|
|
|
|
|
dynamicWidgetComponent: IDynamicWidgetComponent;
|
2019-09-05 21:15:40 +03:00
|
|
|
|
|
|
|
|
subscriptionContext: WidgetSubscriptionContext;
|
|
|
|
|
|
|
|
|
|
subscriptionInited = false;
|
|
|
|
|
widgetSizeDetected = false;
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
cafs: {[cafId: string]: CancelAnimationFrame} = {};
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
onResizeListener = this.onResize.bind(this);
|
|
|
|
|
|
|
|
|
|
private cssParser = new cssjs();
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
private rxSubscriptions = new Array<Subscription>();
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
constructor(protected store: Store<AppState>,
|
|
|
|
|
private route: ActivatedRoute,
|
|
|
|
|
private router: Router,
|
2019-09-06 20:17:45 +03:00
|
|
|
private widgetComponentService: WidgetComponentService,
|
2019-09-05 21:15:40 +03:00
|
|
|
private componentFactoryResolver: ComponentFactoryResolver,
|
|
|
|
|
private elementRef: ElementRef,
|
|
|
|
|
private injector: Injector,
|
|
|
|
|
private widgetService: WidgetService,
|
|
|
|
|
private resources: ResourcesService,
|
|
|
|
|
private timeService: TimeService,
|
|
|
|
|
private deviceService: DeviceService,
|
2019-09-10 15:12:10 +03:00
|
|
|
private entityService: EntityService,
|
2019-09-05 21:15:40 +03:00
|
|
|
private alarmService: AlarmService,
|
2019-09-10 15:12:10 +03:00
|
|
|
private dashboardService: DashboardService,
|
|
|
|
|
private datasourceService: DatasourceService,
|
|
|
|
|
private utils: UtilsService,
|
2019-09-12 19:58:42 +03:00
|
|
|
private raf: RafService,
|
2019-09-13 13:10:24 +03:00
|
|
|
private ngZone: NgZone,
|
2019-09-12 19:58:42 +03:00
|
|
|
private cd: ChangeDetectorRef) {
|
2019-09-05 21:15:40 +03:00
|
|
|
super(store);
|
2019-10-24 19:52:19 +03:00
|
|
|
this.cssParser.testMode = false;
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnInit(): void {
|
2019-09-06 20:17:45 +03:00
|
|
|
|
|
|
|
|
this.loadingData = true;
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
this.widget = this.dashboardWidget.widget;
|
|
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
this.displayLegend = isDefined(this.widget.config.showLegend) ? this.widget.config.showLegend
|
|
|
|
|
: this.widget.type === widgetType.timeseries;
|
|
|
|
|
|
|
|
|
|
this.legendContainerLayoutType = 'column';
|
|
|
|
|
|
|
|
|
|
if (this.displayLegend) {
|
2019-10-24 19:52:19 +03:00
|
|
|
this.legendConfig = this.widget.config.legendConfig || defaultLegendConfig(this.widget.type);
|
2019-09-06 20:17:45 +03:00
|
|
|
this.legendData = {
|
|
|
|
|
keys: [],
|
|
|
|
|
data: []
|
|
|
|
|
};
|
|
|
|
|
if (this.legendConfig.position === LegendPosition.top ||
|
|
|
|
|
this.legendConfig.position === LegendPosition.bottom) {
|
|
|
|
|
this.legendContainerLayoutType = 'column';
|
2019-10-24 19:52:19 +03:00
|
|
|
this.isLegendFirst = this.legendConfig.position === LegendPosition.top;
|
2019-09-06 20:17:45 +03:00
|
|
|
} else {
|
|
|
|
|
this.legendContainerLayoutType = 'row';
|
2019-10-24 19:52:19 +03:00
|
|
|
this.isLegendFirst = this.legendConfig.position === LegendPosition.left;
|
2019-09-06 20:17:45 +03:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
const actionDescriptorsBySourceId: {[actionSourceId: string]: Array<WidgetActionDescriptor>} = {};
|
|
|
|
|
if (this.widget.config.actions) {
|
|
|
|
|
for (const actionSourceId of Object.keys(this.widget.config.actions)) {
|
|
|
|
|
const descriptors = this.widget.config.actions[actionSourceId];
|
|
|
|
|
const actionDescriptors: Array<WidgetActionDescriptor> = [];
|
|
|
|
|
descriptors.forEach((descriptor) => {
|
2019-09-10 15:12:10 +03:00
|
|
|
const actionDescriptor: WidgetActionDescriptor = deepClone(descriptor);
|
2019-09-05 21:15:40 +03:00
|
|
|
actionDescriptor.displayName = this.utils.customTranslation(descriptor.name, descriptor.name);
|
|
|
|
|
actionDescriptors.push(actionDescriptor);
|
|
|
|
|
});
|
|
|
|
|
actionDescriptorsBySourceId[actionSourceId] = actionDescriptors;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.widgetContext = this.dashboardWidget.widgetContext;
|
|
|
|
|
this.widgetContext.inited = false;
|
|
|
|
|
this.widgetContext.hideTitlePanel = false;
|
|
|
|
|
this.widgetContext.isEdit = this.isEdit;
|
|
|
|
|
this.widgetContext.isMobile = this.isMobile;
|
|
|
|
|
this.widgetContext.dashboard = this.dashboard;
|
|
|
|
|
this.widgetContext.widgetConfig = this.widget.config;
|
|
|
|
|
this.widgetContext.settings = this.widget.config.settings;
|
|
|
|
|
this.widgetContext.units = this.widget.config.units || '';
|
|
|
|
|
this.widgetContext.decimals = isDefined(this.widget.config.decimals) ? this.widget.config.decimals : 2;
|
|
|
|
|
this.widgetContext.subscriptions = {};
|
|
|
|
|
this.widgetContext.defaultSubscription = null;
|
|
|
|
|
this.widgetContext.dashboardTimewindow = this.dashboard.dashboardTimewindow;
|
|
|
|
|
this.widgetContext.timewindowFunctions = {
|
|
|
|
|
onUpdateTimewindow: (startTimeMs, endTimeMs, interval) => {
|
|
|
|
|
if (this.widgetContext.defaultSubscription) {
|
|
|
|
|
this.widgetContext.defaultSubscription.onUpdateTimewindow(startTimeMs, endTimeMs, interval);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onResetTimewindow: () => {
|
|
|
|
|
if (this.widgetContext.defaultSubscription) {
|
|
|
|
|
this.widgetContext.defaultSubscription.onResetTimewindow();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.widgetContext.subscriptionApi = {
|
|
|
|
|
createSubscription: this.createSubscription.bind(this),
|
|
|
|
|
createSubscriptionFromInfo: this.createSubscriptionFromInfo.bind(this),
|
|
|
|
|
removeSubscription: (id) => {
|
|
|
|
|
const subscription = this.widgetContext.subscriptions[id];
|
|
|
|
|
if (subscription) {
|
|
|
|
|
subscription.destroy();
|
|
|
|
|
delete this.widgetContext.subscriptions[id];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.widgetContext.controlApi = {
|
|
|
|
|
sendOneWayCommand: (method, params, timeout) => {
|
|
|
|
|
if (this.widgetContext.defaultSubscription) {
|
|
|
|
|
return this.widgetContext.defaultSubscription.sendOneWayCommand(method, params, timeout);
|
|
|
|
|
} else {
|
|
|
|
|
return of(null);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
sendTwoWayCommand: (method, params, timeout) => {
|
|
|
|
|
if (this.widgetContext.defaultSubscription) {
|
|
|
|
|
return this.widgetContext.defaultSubscription.sendTwoWayCommand(method, params, timeout);
|
|
|
|
|
} else {
|
|
|
|
|
return of(null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.widgetContext.utils = {
|
2019-09-12 19:58:42 +03:00
|
|
|
formatValue: this.formatValue.bind(this)
|
2019-09-05 21:15:40 +03:00
|
|
|
};
|
|
|
|
|
this.widgetContext.actionsApi = {
|
|
|
|
|
actionDescriptorsBySourceId,
|
|
|
|
|
getActionDescriptors: this.getActionDescriptors.bind(this),
|
|
|
|
|
handleWidgetAction: this.handleWidgetAction.bind(this),
|
|
|
|
|
elementClick: this.elementClick.bind(this)
|
|
|
|
|
};
|
|
|
|
|
this.widgetContext.stateController = this.dashboard.stateController;
|
|
|
|
|
this.widgetContext.aliasController = this.dashboard.aliasController;
|
|
|
|
|
|
|
|
|
|
this.widgetContext.customHeaderActions = [];
|
|
|
|
|
const headerActionsDescriptors = this.getActionDescriptors(widgetActionSources.headerButton.value);
|
|
|
|
|
headerActionsDescriptors.forEach((descriptor) => {
|
|
|
|
|
const headerAction: WidgetHeaderAction = {
|
|
|
|
|
name: descriptor.name,
|
|
|
|
|
displayName: descriptor.displayName,
|
|
|
|
|
icon: descriptor.icon,
|
|
|
|
|
descriptor,
|
|
|
|
|
onAction: $event => {
|
|
|
|
|
const entityInfo = this.getActiveEntityInfo();
|
|
|
|
|
const entityId = entityInfo ? entityInfo.entityId : null;
|
|
|
|
|
const entityName = entityInfo ? entityInfo.entityName : null;
|
|
|
|
|
this.handleWidgetAction($event, descriptor, entityId, entityName);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.widgetContext.customHeaderActions.push(headerAction);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.subscriptionContext = {
|
|
|
|
|
timeService: this.timeService,
|
|
|
|
|
deviceService: this.deviceService,
|
|
|
|
|
alarmService: this.alarmService,
|
2019-09-10 15:12:10 +03:00
|
|
|
datasourceService: this.datasourceService,
|
2019-09-05 21:15:40 +03:00
|
|
|
utils: this.utils,
|
2019-09-12 19:58:42 +03:00
|
|
|
raf: this.raf,
|
2019-09-05 21:15:40 +03:00
|
|
|
widgetUtils: this.widgetContext.utils,
|
2019-09-10 15:12:10 +03:00
|
|
|
dashboardTimewindowApi: {
|
|
|
|
|
onResetTimewindow: this.dashboard.onResetTimewindow.bind(this.dashboard),
|
|
|
|
|
onUpdateTimewindow: this.dashboard.onUpdateTimewindow.bind(this.dashboard)
|
|
|
|
|
},
|
|
|
|
|
getServerTimeDiff: this.dashboardService.getServerTimeDiff.bind(this.dashboardService),
|
2019-09-05 21:15:40 +03:00
|
|
|
aliasController: this.dashboard.aliasController
|
|
|
|
|
};
|
|
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
this.widgetComponentService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe(
|
2019-09-05 21:15:40 +03:00
|
|
|
(widgetInfo) => {
|
|
|
|
|
this.widgetInfo = widgetInfo;
|
|
|
|
|
this.loadFromWidgetInfo();
|
2019-09-06 20:17:45 +03:00
|
|
|
},
|
|
|
|
|
(errorData) => {
|
|
|
|
|
this.widgetInfo = errorData.widgetInfo;
|
|
|
|
|
this.errorMessages = errorData.errorMessages;
|
|
|
|
|
this.loadFromWidgetInfo();
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
);
|
2019-10-24 19:52:19 +03:00
|
|
|
setTimeout(() => {
|
|
|
|
|
this.dashboardWidget.updateWidgetParams();
|
|
|
|
|
}, 0);
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngAfterViewInit(): void {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
|
|
|
for (const propName of Object.keys(changes)) {
|
|
|
|
|
const change = changes[propName];
|
|
|
|
|
if (!change.firstChange && change.currentValue !== change.previousValue) {
|
|
|
|
|
if (propName === 'isEdit') {
|
|
|
|
|
this.onEditModeChanged();
|
|
|
|
|
} else if (propName === 'isMobile') {
|
|
|
|
|
this.onMobileModeChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
ngOnDestroy(): void {
|
|
|
|
|
this.rxSubscriptions.forEach((subscription) => {
|
|
|
|
|
subscription.unsubscribe();
|
|
|
|
|
});
|
|
|
|
|
this.rxSubscriptions.length = 0;
|
|
|
|
|
this.onDestroy();
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
private onDestroy() {
|
|
|
|
|
for (const id of Object.keys(this.widgetContext.subscriptions)) {
|
|
|
|
|
const subscription = this.widgetContext.subscriptions[id];
|
|
|
|
|
subscription.destroy();
|
|
|
|
|
}
|
|
|
|
|
this.subscriptionInited = false;
|
|
|
|
|
this.widgetContext.subscriptions = {};
|
|
|
|
|
if (this.widgetContext.inited) {
|
|
|
|
|
this.widgetContext.inited = false;
|
|
|
|
|
for (const cafId of Object.keys(this.cafs)) {
|
|
|
|
|
if (this.cafs[cafId]) {
|
|
|
|
|
this.cafs[cafId]();
|
|
|
|
|
this.cafs[cafId] = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
this.widgetTypeInstance.onDestroy();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.handleWidgetException(e);
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 15:12:10 +03:00
|
|
|
this.destroyDynamicWidgetComponent();
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
public onTimewindowChanged(timewindow: Timewindow) {
|
|
|
|
|
for (const id of Object.keys(this.widgetContext.subscriptions)) {
|
|
|
|
|
const subscription = this.widgetContext.subscriptions[id];
|
|
|
|
|
if (!subscription.useDashboardTimewindow) {
|
|
|
|
|
subscription.updateTimewindowConfig(timewindow);
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-12 19:58:42 +03:00
|
|
|
public onLegendKeyHiddenChange(index: number) {
|
|
|
|
|
for (const id of Object.keys(this.widgetContext.subscriptions)) {
|
|
|
|
|
const subscription = this.widgetContext.subscriptions[id];
|
|
|
|
|
subscription.updateDataVisibility(index);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-05 21:15:40 +03:00
|
|
|
private loadFromWidgetInfo() {
|
|
|
|
|
const widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`;
|
|
|
|
|
const elem = this.elementRef.nativeElement;
|
|
|
|
|
elem.classList.add('tb-widget');
|
|
|
|
|
elem.classList.add(widgetNamespace);
|
|
|
|
|
this.widgetType = this.widgetInfo.widgetTypeFunction;
|
2019-09-10 15:12:10 +03:00
|
|
|
this.typeParameters = this.widgetInfo.typeParameters;
|
2019-09-05 21:15:40 +03:00
|
|
|
|
|
|
|
|
if (!this.widgetType) {
|
|
|
|
|
this.widgetTypeInstance = {};
|
|
|
|
|
} else {
|
|
|
|
|
try {
|
|
|
|
|
this.widgetTypeInstance = new this.widgetType(this.widgetContext);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.handleWidgetException(e);
|
|
|
|
|
this.widgetTypeInstance = {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!this.widgetTypeInstance.onInit) {
|
|
|
|
|
this.widgetTypeInstance.onInit = () => {};
|
|
|
|
|
}
|
|
|
|
|
if (!this.widgetTypeInstance.onDataUpdated) {
|
|
|
|
|
this.widgetTypeInstance.onDataUpdated = () => {};
|
|
|
|
|
}
|
|
|
|
|
if (!this.widgetTypeInstance.onResize) {
|
|
|
|
|
this.widgetTypeInstance.onResize = () => {};
|
|
|
|
|
}
|
|
|
|
|
if (!this.widgetTypeInstance.onEditModeChanged) {
|
|
|
|
|
this.widgetTypeInstance.onEditModeChanged = () => {};
|
|
|
|
|
}
|
|
|
|
|
if (!this.widgetTypeInstance.onMobileModeChanged) {
|
|
|
|
|
this.widgetTypeInstance.onMobileModeChanged = () => {};
|
|
|
|
|
}
|
|
|
|
|
if (!this.widgetTypeInstance.onDestroy) {
|
|
|
|
|
this.widgetTypeInstance.onDestroy = () => {};
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
this.initialize().subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.onInit();
|
2019-10-11 19:22:03 +03:00
|
|
|
},
|
|
|
|
|
(err) => {
|
|
|
|
|
// console.log(err);
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private isReady(): boolean {
|
|
|
|
|
return this.subscriptionInited && this.widgetSizeDetected;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onInit(skipSizeCheck?: boolean) {
|
|
|
|
|
if (!this.widgetContext.$containerParent) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!skipSizeCheck) {
|
|
|
|
|
this.checkSize();
|
|
|
|
|
}
|
|
|
|
|
if (!this.widgetContext.inited && this.isReady()) {
|
|
|
|
|
this.widgetContext.inited = true;
|
2019-09-12 19:58:42 +03:00
|
|
|
if (this.cafs.init) {
|
|
|
|
|
this.cafs.init();
|
|
|
|
|
this.cafs.init = null;
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
2019-09-12 19:58:42 +03:00
|
|
|
this.cafs.init = this.raf.raf(() => {
|
|
|
|
|
try {
|
|
|
|
|
this.widgetTypeInstance.onInit();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.handleWidgetException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
2019-09-10 15:12:10 +03:00
|
|
|
if (!this.typeParameters.useCustomDatasources && this.widgetContext.defaultSubscription) {
|
|
|
|
|
this.widgetContext.defaultSubscription.subscribe();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onResize() {
|
|
|
|
|
if (this.checkSize()) {
|
|
|
|
|
if (this.widgetContext.inited) {
|
|
|
|
|
if (this.cafs.resize) {
|
|
|
|
|
this.cafs.resize();
|
|
|
|
|
this.cafs.resize = null;
|
|
|
|
|
}
|
|
|
|
|
this.cafs.resize = this.raf.raf(() => {
|
|
|
|
|
try {
|
|
|
|
|
this.widgetTypeInstance.onResize();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.handleWidgetException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
this.onInit(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onEditModeChanged() {
|
|
|
|
|
if (this.widgetContext.isEdit !== this.isEdit) {
|
|
|
|
|
this.widgetContext.isEdit = this.isEdit;
|
|
|
|
|
if (this.widgetContext.inited) {
|
|
|
|
|
if (this.cafs.editMode) {
|
|
|
|
|
this.cafs.editMode();
|
|
|
|
|
this.cafs.editMode = null;
|
|
|
|
|
}
|
|
|
|
|
this.cafs.editMode = this.raf.raf(() => {
|
|
|
|
|
try {
|
|
|
|
|
this.widgetTypeInstance.onEditModeChanged();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.handleWidgetException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onMobileModeChanged() {
|
|
|
|
|
if (this.widgetContext.isMobile !== this.isMobile) {
|
|
|
|
|
this.widgetContext.isMobile = this.isMobile;
|
|
|
|
|
if (this.widgetContext.inited) {
|
|
|
|
|
if (this.cafs.mobileMode) {
|
|
|
|
|
this.cafs.mobileMode();
|
|
|
|
|
this.cafs.mobileMode = null;
|
|
|
|
|
}
|
|
|
|
|
this.cafs.mobileMode = this.raf.raf(() => {
|
|
|
|
|
try {
|
|
|
|
|
this.widgetTypeInstance.onMobileModeChanged();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
this.handleWidgetException(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private reInit() {
|
2019-09-10 15:12:10 +03:00
|
|
|
this.onDestroy();
|
|
|
|
|
this.configureDynamicWidgetComponent();
|
|
|
|
|
if (!this.typeParameters.useCustomDatasources) {
|
|
|
|
|
this.createDefaultSubscription().subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.subscriptionInited = true;
|
|
|
|
|
this.onInit();
|
|
|
|
|
},
|
|
|
|
|
() => {
|
|
|
|
|
this.subscriptionInited = true;
|
|
|
|
|
this.onInit();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
this.subscriptionInited = true;
|
|
|
|
|
this.onInit();
|
|
|
|
|
}
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
private initialize(): Observable<any> {
|
|
|
|
|
|
|
|
|
|
const initSubject = new ReplaySubject();
|
|
|
|
|
|
|
|
|
|
this.rxSubscriptions.push(this.dashboard.aliasController.entityAliasesChanged.subscribe(
|
|
|
|
|
(aliasIds) => {
|
|
|
|
|
let subscriptionChanged = false;
|
|
|
|
|
for (const id of Object.keys(this.widgetContext.subscriptions)) {
|
|
|
|
|
const subscription = this.widgetContext.subscriptions[id];
|
|
|
|
|
subscriptionChanged = subscriptionChanged || subscription.onAliasesChanged(aliasIds);
|
|
|
|
|
}
|
|
|
|
|
if (subscriptionChanged && !this.typeParameters.useCustomDatasources) {
|
|
|
|
|
this.reInit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
2019-09-12 19:58:42 +03:00
|
|
|
this.rxSubscriptions.push(this.dashboard.dashboardTimewindowChanged.subscribe(
|
|
|
|
|
(dashboardTimewindow) => {
|
|
|
|
|
for (const id of Object.keys(this.widgetContext.subscriptions)) {
|
|
|
|
|
const subscription = this.widgetContext.subscriptions[id];
|
|
|
|
|
subscription.onDashboardTimewindowChanged(dashboardTimewindow);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
this.configureDynamicWidgetComponent();
|
2019-09-10 15:12:10 +03:00
|
|
|
if (!this.typeParameters.useCustomDatasources) {
|
|
|
|
|
// this.cre
|
|
|
|
|
this.createDefaultSubscription().subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.subscriptionInited = true;
|
|
|
|
|
initSubject.next();
|
|
|
|
|
initSubject.complete();
|
|
|
|
|
},
|
2019-10-11 19:22:03 +03:00
|
|
|
(err) => {
|
2019-09-10 15:12:10 +03:00
|
|
|
this.subscriptionInited = true;
|
2019-10-11 19:22:03 +03:00
|
|
|
initSubject.error(err);
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
this.loadingData = false;
|
|
|
|
|
this.subscriptionInited = true;
|
|
|
|
|
initSubject.next();
|
|
|
|
|
initSubject.complete();
|
|
|
|
|
}
|
|
|
|
|
return initSubject.asObservable();
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private destroyDynamicWidgetComponent() {
|
|
|
|
|
if (this.widgetContext.$containerParent) {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
removeResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener);
|
|
|
|
|
}
|
|
|
|
|
if (this.dynamicWidgetComponentRef) {
|
|
|
|
|
this.dynamicWidgetComponentRef.destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private handleWidgetException(e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
this.widgetErrorData = this.utils.processWidgetException(e);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
private configureDynamicWidgetComponent() {
|
|
|
|
|
this.widgetContentContainer.clear();
|
|
|
|
|
this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentFactory);
|
|
|
|
|
this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance;
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
this.dynamicWidgetComponent.widgetContext = this.widgetContext;
|
|
|
|
|
this.dynamicWidgetComponent.errorMessages = this.errorMessages;
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
this.widgetContext.$scope = this.dynamicWidgetComponent;
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container'));
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
this.widgetContext.$container = $('> ng-component', containerElement);
|
|
|
|
|
this.widgetContext.$container.css('display', 'block');
|
2019-09-13 13:10:24 +03:00
|
|
|
this.widgetContext.$container.css('user-select', 'none');
|
2019-09-06 20:17:45 +03:00
|
|
|
this.widgetContext.$container.attr('id', 'container');
|
|
|
|
|
this.widgetContext.$containerParent = $(containerElement);
|
2019-09-05 21:15:40 +03:00
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
if (this.widgetSizeDetected) {
|
|
|
|
|
this.widgetContext.$container.css('height', this.widgetContext.height + 'px');
|
|
|
|
|
this.widgetContext.$container.css('width', this.widgetContext.width + 'px');
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2019-09-06 20:17:45 +03:00
|
|
|
// @ts-ignore
|
|
|
|
|
addResizeListener(this.widgetContext.$containerParent[0], this.onResizeListener);
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
2019-09-10 15:12:10 +03:00
|
|
|
private createSubscription(options: WidgetSubscriptionOptions, subscribe?: boolean): Observable<IWidgetSubscription> {
|
|
|
|
|
const createSubscriptionSubject = new ReplaySubject<IWidgetSubscription>();
|
|
|
|
|
options.dashboardTimewindow = this.dashboard.dashboardTimewindow;
|
|
|
|
|
const subscription: IWidgetSubscription = new WidgetSubscription(this.subscriptionContext, options);
|
|
|
|
|
subscription.init$.subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
this.widgetContext.subscriptions[subscription.id] = subscription;
|
|
|
|
|
if (subscribe) {
|
|
|
|
|
subscription.subscribe();
|
|
|
|
|
}
|
|
|
|
|
createSubscriptionSubject.next(subscription);
|
|
|
|
|
createSubscriptionSubject.complete();
|
|
|
|
|
},
|
2019-10-11 19:22:03 +03:00
|
|
|
(err) => {
|
|
|
|
|
createSubscriptionSubject.error(err);
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return createSubscriptionSubject.asObservable();
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private createSubscriptionFromInfo(type: widgetType, subscriptionsInfo: Array<SubscriptionInfo>,
|
|
|
|
|
options: WidgetSubscriptionOptions, useDefaultComponents: boolean,
|
|
|
|
|
subscribe: boolean): Observable<IWidgetSubscription> {
|
2019-09-10 15:12:10 +03:00
|
|
|
const createSubscriptionSubject = new ReplaySubject<IWidgetSubscription>();
|
|
|
|
|
options.type = type;
|
|
|
|
|
|
|
|
|
|
if (useDefaultComponents) {
|
|
|
|
|
this.defaultComponentsOptions(options);
|
|
|
|
|
} else {
|
|
|
|
|
if (!options.timeWindowConfig) {
|
|
|
|
|
options.useDashboardTimewindow = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
let createDatasourcesObservable: Observable<Array<Datasource> | Datasource>;
|
|
|
|
|
if (options.type === widgetType.alarm) {
|
|
|
|
|
createDatasourcesObservable = this.entityService.createAlarmSourceFromSubscriptionInfo(subscriptionsInfo[0]);
|
|
|
|
|
} else {
|
|
|
|
|
createDatasourcesObservable = this.entityService.createDatasourcesFromSubscriptionsInfo(subscriptionsInfo);
|
|
|
|
|
}
|
|
|
|
|
createDatasourcesObservable.subscribe(
|
|
|
|
|
(result) => {
|
|
|
|
|
if (options.type === widgetType.alarm) {
|
|
|
|
|
options.alarmSource = result as Datasource;
|
|
|
|
|
} else {
|
|
|
|
|
options.datasources = result as Array<Datasource>;
|
|
|
|
|
}
|
|
|
|
|
this.createSubscription(options, subscribe).subscribe(
|
|
|
|
|
(subscription) => {
|
|
|
|
|
if (useDefaultComponents) {
|
|
|
|
|
this.defaultSubscriptionOptions(subscription, options);
|
|
|
|
|
}
|
|
|
|
|
createSubscriptionSubject.next(subscription);
|
|
|
|
|
createSubscriptionSubject.complete();
|
|
|
|
|
},
|
2019-10-11 19:22:03 +03:00
|
|
|
(err) => {
|
|
|
|
|
createSubscriptionSubject.error(err);
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
},
|
2019-10-11 19:22:03 +03:00
|
|
|
(err) => {
|
|
|
|
|
createSubscriptionSubject.error(err);
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
return createSubscriptionSubject.asObservable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private defaultComponentsOptions(options: WidgetSubscriptionOptions) {
|
|
|
|
|
options.useDashboardTimewindow = isDefined(this.widget.config.useDashboardTimewindow)
|
|
|
|
|
? this.widget.config.useDashboardTimewindow : true;
|
|
|
|
|
options.displayTimewindow = isDefined(this.widget.config.displayTimewindow)
|
|
|
|
|
? this.widget.config.displayTimewindow : !options.useDashboardTimewindow;
|
|
|
|
|
options.timeWindowConfig = options.useDashboardTimewindow ? this.dashboard.dashboardTimewindow : this.widget.config.timewindow;
|
|
|
|
|
options.legendConfig = null;
|
|
|
|
|
if (this.displayLegend) {
|
|
|
|
|
options.legendConfig = this.legendConfig;
|
|
|
|
|
}
|
|
|
|
|
options.decimals = this.widgetContext.decimals;
|
|
|
|
|
options.units = this.widgetContext.units;
|
|
|
|
|
options.callbacks = {
|
|
|
|
|
onDataUpdated: () => {
|
|
|
|
|
this.widgetTypeInstance.onDataUpdated();
|
|
|
|
|
},
|
|
|
|
|
onDataUpdateError: (subscription, e) => {
|
|
|
|
|
this.handleWidgetException(e);
|
|
|
|
|
},
|
|
|
|
|
dataLoading: (subscription) => {
|
|
|
|
|
if (this.loadingData !== subscription.loadingData) {
|
|
|
|
|
this.loadingData = subscription.loadingData;
|
2019-09-12 19:58:42 +03:00
|
|
|
this.cd.detectChanges();
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
},
|
2019-09-12 19:58:42 +03:00
|
|
|
legendDataUpdated: (subscription, detectChanges) => {
|
|
|
|
|
if (detectChanges) {
|
|
|
|
|
this.cd.detectChanges();
|
|
|
|
|
}
|
2019-09-10 15:12:10 +03:00
|
|
|
},
|
|
|
|
|
timeWindowUpdated: (subscription, timeWindowConfig) => {
|
2019-09-13 13:10:24 +03:00
|
|
|
this.ngZone.run(() => {
|
|
|
|
|
this.widget.config.timewindow = timeWindowConfig;
|
2019-10-24 19:52:19 +03:00
|
|
|
this.cd.detectChanges();
|
2019-09-13 13:10:24 +03:00
|
|
|
});
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private defaultSubscriptionOptions(subscription: IWidgetSubscription, options: WidgetSubscriptionOptions) {
|
|
|
|
|
if (this.displayLegend) {
|
|
|
|
|
this.legendData = subscription.legendData;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private createDefaultSubscription(): Observable<any> {
|
|
|
|
|
const createSubscriptionSubject = new ReplaySubject();
|
|
|
|
|
let options: WidgetSubscriptionOptions;
|
|
|
|
|
if (this.widget.type !== widgetType.rpc && this.widget.type !== widgetType.static) {
|
|
|
|
|
options = {
|
|
|
|
|
type: this.widget.type,
|
|
|
|
|
stateData: this.typeParameters.stateData
|
|
|
|
|
};
|
|
|
|
|
if (this.widget.type === widgetType.alarm) {
|
|
|
|
|
options.alarmSource = deepClone(this.widget.config.alarmSource);
|
|
|
|
|
options.alarmSearchStatus = isDefined(this.widget.config.alarmSearchStatus) ?
|
|
|
|
|
this.widget.config.alarmSearchStatus : AlarmSearchStatus.ANY;
|
|
|
|
|
options.alarmsPollingInterval = isDefined(this.widget.config.alarmsPollingInterval) ?
|
|
|
|
|
this.widget.config.alarmsPollingInterval * 1000 : 5000;
|
|
|
|
|
} else {
|
|
|
|
|
options.datasources = deepClone(this.widget.config.datasources);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.defaultComponentsOptions(options);
|
|
|
|
|
|
|
|
|
|
this.createSubscription(options).subscribe(
|
|
|
|
|
(subscription) => {
|
|
|
|
|
this.defaultSubscriptionOptions(subscription, options);
|
|
|
|
|
|
|
|
|
|
// backward compatibility
|
|
|
|
|
this.widgetContext.datasources = subscription.datasources;
|
|
|
|
|
this.widgetContext.data = subscription.data;
|
|
|
|
|
this.widgetContext.hiddenData = subscription.hiddenData;
|
|
|
|
|
this.widgetContext.timeWindow = subscription.timeWindow;
|
|
|
|
|
this.widgetContext.defaultSubscription = subscription;
|
|
|
|
|
createSubscriptionSubject.next();
|
|
|
|
|
createSubscriptionSubject.complete();
|
|
|
|
|
},
|
2019-10-11 19:22:03 +03:00
|
|
|
(err) => {
|
|
|
|
|
createSubscriptionSubject.error(err);
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
} else if (this.widget.type === widgetType.rpc) {
|
|
|
|
|
this.loadingData = false;
|
|
|
|
|
options = {
|
|
|
|
|
type: this.widget.type,
|
|
|
|
|
targetDeviceAliasIds: this.widget.config.targetDeviceAliasIds
|
|
|
|
|
};
|
|
|
|
|
options.callbacks = {
|
|
|
|
|
rpcStateChanged: (subscription) => {
|
|
|
|
|
this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled;
|
|
|
|
|
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
|
|
|
|
|
},
|
|
|
|
|
onRpcSuccess: (subscription) => {
|
|
|
|
|
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
|
|
|
|
|
this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText;
|
|
|
|
|
this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection;
|
|
|
|
|
},
|
|
|
|
|
onRpcFailed: (subscription) => {
|
|
|
|
|
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
|
|
|
|
|
this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText;
|
|
|
|
|
this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection;
|
|
|
|
|
},
|
|
|
|
|
onRpcErrorCleared: (subscription) => {
|
|
|
|
|
this.dynamicWidgetComponent.rpcErrorText = null;
|
|
|
|
|
this.dynamicWidgetComponent.rpcRejection = null;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
this.createSubscription(options).subscribe(
|
|
|
|
|
(subscription) => {
|
|
|
|
|
this.widgetContext.defaultSubscription = subscription;
|
|
|
|
|
createSubscriptionSubject.next();
|
|
|
|
|
createSubscriptionSubject.complete();
|
|
|
|
|
},
|
2019-10-11 19:22:03 +03:00
|
|
|
(err) => {
|
|
|
|
|
createSubscriptionSubject.error(err);
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
);
|
2019-09-23 20:35:31 +03:00
|
|
|
this.cd.detectChanges();
|
2019-09-10 15:12:10 +03:00
|
|
|
} else if (this.widget.type === widgetType.static) {
|
|
|
|
|
this.loadingData = false;
|
|
|
|
|
createSubscriptionSubject.next();
|
|
|
|
|
createSubscriptionSubject.complete();
|
2019-09-23 20:35:31 +03:00
|
|
|
this.cd.detectChanges();
|
2019-09-10 15:12:10 +03:00
|
|
|
} else {
|
|
|
|
|
createSubscriptionSubject.next();
|
|
|
|
|
createSubscriptionSubject.complete();
|
2019-09-23 20:35:31 +03:00
|
|
|
this.cd.detectChanges();
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|
|
|
|
|
return createSubscriptionSubject.asObservable();
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private isNumeric(value: any): boolean {
|
|
|
|
|
return (value - parseFloat( value ) + 1) >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined {
|
|
|
|
|
if (isDefined(value) &&
|
|
|
|
|
value != null && this.isNumeric(value)) {
|
|
|
|
|
let formatted: string | number = Number(value);
|
|
|
|
|
if (isDefined(dec)) {
|
|
|
|
|
formatted = formatted.toFixed(dec);
|
|
|
|
|
}
|
|
|
|
|
if (!showZeroDecimals) {
|
|
|
|
|
formatted = (Number(formatted) * 1);
|
|
|
|
|
}
|
|
|
|
|
formatted = formatted.toString();
|
|
|
|
|
if (isDefined(units) && units.length > 0) {
|
|
|
|
|
formatted += ' ' + units;
|
|
|
|
|
}
|
|
|
|
|
return formatted;
|
|
|
|
|
} else {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getActionDescriptors(actionSourceId: string): Array<WidgetActionDescriptor> {
|
|
|
|
|
let result = this.widgetContext.actionsApi.actionDescriptorsBySourceId[actionSourceId];
|
|
|
|
|
if (!result) {
|
|
|
|
|
result = [];
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private handleWidgetAction($event: Event, descriptor: WidgetActionDescriptor,
|
|
|
|
|
entityId?: EntityId, entityName?: string, additionalParams?: any): void {
|
|
|
|
|
const type = descriptor.type;
|
|
|
|
|
const targetEntityParamName = descriptor.stateEntityParamName;
|
|
|
|
|
let targetEntityId: EntityId;
|
|
|
|
|
if (descriptor.setEntityId) {
|
|
|
|
|
targetEntityId = entityId;
|
|
|
|
|
}
|
|
|
|
|
switch (type) {
|
|
|
|
|
case WidgetActionType.openDashboardState:
|
|
|
|
|
case WidgetActionType.updateDashboardState:
|
|
|
|
|
let targetDashboardStateId = descriptor.targetDashboardStateId;
|
2019-09-10 15:12:10 +03:00
|
|
|
const params = deepClone(this.widgetContext.stateController.getStateParams());
|
2019-09-05 21:15:40 +03:00
|
|
|
this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName);
|
|
|
|
|
if (type === WidgetActionType.openDashboardState) {
|
|
|
|
|
this.widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout);
|
|
|
|
|
} else {
|
|
|
|
|
this.widgetContext.stateController.updateState(targetDashboardStateId, params, descriptor.openRightLayout);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case WidgetActionType.openDashboard:
|
|
|
|
|
const targetDashboardId = descriptor.targetDashboardId;
|
|
|
|
|
targetDashboardStateId = descriptor.targetDashboardStateId;
|
|
|
|
|
const stateObject: StateObject = {};
|
|
|
|
|
stateObject.params = {};
|
|
|
|
|
this.updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName);
|
|
|
|
|
if (targetDashboardStateId) {
|
|
|
|
|
stateObject.id = targetDashboardStateId;
|
|
|
|
|
}
|
|
|
|
|
const state = objToBase64([ stateObject ]);
|
2019-10-24 19:52:19 +03:00
|
|
|
const isSinglePage = this.route.snapshot.data.singlePageMode;
|
2019-09-05 21:15:40 +03:00
|
|
|
let url;
|
2019-10-24 19:52:19 +03:00
|
|
|
if (isSinglePage) {
|
|
|
|
|
url = `/dashboard/${targetDashboardId}?state=${state}`;
|
|
|
|
|
} else {
|
|
|
|
|
url = `/dashboards/${targetDashboardId}?state=${state}`;
|
2019-09-05 21:15:40 +03:00
|
|
|
}
|
2019-10-24 19:52:19 +03:00
|
|
|
const urlTree = this.router.parseUrl(url);
|
|
|
|
|
this.router.navigateByUrl(url);
|
2019-09-05 21:15:40 +03:00
|
|
|
break;
|
|
|
|
|
case WidgetActionType.custom:
|
|
|
|
|
const customFunction = descriptor.customFunction;
|
|
|
|
|
if (isDefined(customFunction) && customFunction.length > 0) {
|
|
|
|
|
try {
|
|
|
|
|
if (!additionalParams) {
|
|
|
|
|
additionalParams = {};
|
|
|
|
|
}
|
|
|
|
|
const customActionFunction = new Function('$event', 'widgetContext', 'entityId',
|
|
|
|
|
'entityName', 'additionalParams', customFunction);
|
|
|
|
|
customActionFunction($event, this.widgetContext, entityId, entityName, additionalParams);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
//
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case WidgetActionType.customPretty:
|
|
|
|
|
const customPrettyFunction = descriptor.customFunction;
|
|
|
|
|
const customHtml = descriptor.customHtml;
|
|
|
|
|
const customCss = descriptor.customCss;
|
|
|
|
|
const customResources = descriptor.customResources;
|
|
|
|
|
const actionNamespace = `custom-action-pretty-${descriptor.name.toLowerCase()}`;
|
|
|
|
|
let htmlTemplate = '';
|
|
|
|
|
if (isDefined(customHtml) && customHtml.length > 0) {
|
|
|
|
|
htmlTemplate = customHtml;
|
|
|
|
|
}
|
|
|
|
|
this.loadCustomActionResources(actionNamespace, customCss, customResources).subscribe(
|
|
|
|
|
() => {
|
|
|
|
|
if (isDefined(customPrettyFunction) && customPrettyFunction.length > 0) {
|
|
|
|
|
try {
|
|
|
|
|
if (!additionalParams) {
|
|
|
|
|
additionalParams = {};
|
|
|
|
|
}
|
|
|
|
|
const customActionPrettyFunction = new Function('$event', 'widgetContext', 'entityId',
|
|
|
|
|
'entityName', 'htmlTemplate', 'additionalParams', customPrettyFunction);
|
|
|
|
|
customActionPrettyFunction($event, this.widgetContext, entityId, entityName, htmlTemplate, additionalParams);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
//
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
(errorMessages: string[]) => {
|
|
|
|
|
this.processResourcesLoadErrors(errorMessages);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private elementClick($event: Event) {
|
|
|
|
|
$event.stopPropagation();
|
|
|
|
|
const e = ($event.target || $event.srcElement) as Element;
|
|
|
|
|
if (e.id) {
|
|
|
|
|
const descriptors = this.getActionDescriptors('elementClick');
|
|
|
|
|
if (descriptors.length) {
|
|
|
|
|
descriptors.forEach((descriptor) => {
|
|
|
|
|
if (descriptor.name === e.id) {
|
|
|
|
|
const entityInfo = this.getActiveEntityInfo();
|
|
|
|
|
const entityId = entityInfo ? entityInfo.entityId : null;
|
|
|
|
|
const entityName = entityInfo ? entityInfo.entityName : null;
|
|
|
|
|
this.handleWidgetAction(event, descriptor, entityId, entityName);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId, entityName?: string) {
|
|
|
|
|
if (targetEntityId) {
|
|
|
|
|
let targetEntityParams: StateParams;
|
|
|
|
|
if (targetEntityParamName && targetEntityParamName.length) {
|
|
|
|
|
targetEntityParams = params[targetEntityParamName];
|
|
|
|
|
if (!targetEntityParams) {
|
|
|
|
|
targetEntityParams = {};
|
|
|
|
|
params[targetEntityParamName] = targetEntityParams;
|
|
|
|
|
params.targetEntityParamName = targetEntityParamName;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
targetEntityParams = params;
|
|
|
|
|
}
|
|
|
|
|
targetEntityParams.entityId = targetEntityId;
|
|
|
|
|
if (entityName) {
|
|
|
|
|
targetEntityParams.entityName = entityName;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private loadCustomActionResources(actionNamespace: string, customCss: string, customResources: Array<WidgetResource>): Observable<any> {
|
|
|
|
|
if (isDefined(customCss) && customCss.length > 0) {
|
|
|
|
|
this.cssParser.cssPreviewNamespace = actionNamespace;
|
|
|
|
|
this.cssParser.createStyleElement(actionNamespace, customCss, 'nonamespace');
|
|
|
|
|
}
|
|
|
|
|
const resourceTasks: Observable<string>[] = [];
|
|
|
|
|
if (customResources.length > 0) {
|
|
|
|
|
customResources.forEach((resource) => {
|
|
|
|
|
resourceTasks.push(
|
|
|
|
|
this.resources.loadResource(resource.url).pipe(
|
|
|
|
|
catchError(e => of(`Failed to load custom action resource: '${resource.url}'`))
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
return forkJoin(resourceTasks).pipe(
|
|
|
|
|
switchMap(msgs => {
|
|
|
|
|
let errors: string[];
|
|
|
|
|
if (msgs && msgs.length) {
|
|
|
|
|
errors = msgs.filter(msg => msg && msg.length > 0);
|
|
|
|
|
}
|
|
|
|
|
if (errors && errors.length) {
|
|
|
|
|
return throwError(errors);
|
|
|
|
|
} else {
|
|
|
|
|
return of(null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
} else {
|
|
|
|
|
return of(null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private processResourcesLoadErrors(errorMessages: string[]) {
|
|
|
|
|
let messageToShow = '';
|
|
|
|
|
errorMessages.forEach(error => {
|
|
|
|
|
messageToShow += `<div>${error}</div>`;
|
|
|
|
|
});
|
|
|
|
|
this.store.dispatch(new ActionNotificationShow({message: messageToShow, type: 'error'}));
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 20:10:52 +03:00
|
|
|
private getActiveEntityInfo(): SubscriptionEntityInfo {
|
2019-09-05 21:15:40 +03:00
|
|
|
let entityInfo = this.widgetContext.activeEntityInfo;
|
|
|
|
|
if (!entityInfo) {
|
|
|
|
|
for (const id of Object.keys(this.widgetContext.subscriptions)) {
|
|
|
|
|
const subscription = this.widgetContext.subscriptions[id];
|
|
|
|
|
entityInfo = subscription.getFirstEntityInfo();
|
|
|
|
|
if (entityInfo) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return entityInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private checkSize(): boolean {
|
|
|
|
|
const width = this.widgetContext.$containerParent.width();
|
|
|
|
|
const height = this.widgetContext.$containerParent.height();
|
|
|
|
|
let sizeChanged = false;
|
|
|
|
|
|
|
|
|
|
if (!this.widgetContext.width || this.widgetContext.width !== width ||
|
|
|
|
|
!this.widgetContext.height || this.widgetContext.height !== height) {
|
|
|
|
|
if (width > 0 && height > 0) {
|
|
|
|
|
this.widgetContext.$container.css('height', height + 'px');
|
|
|
|
|
this.widgetContext.$container.css('width', width + 'px');
|
|
|
|
|
this.widgetContext.width = width;
|
|
|
|
|
this.widgetContext.height = height;
|
|
|
|
|
sizeChanged = true;
|
|
|
|
|
this.widgetSizeDetected = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return sizeChanged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|