1153 lines
40 KiB
TypeScript
Raw Normal View History

///
2020-02-20 10:26:43 +02:00
/// Copyright © 2016-2020 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,
Component,
ComponentFactoryResolver,
ComponentRef,
ElementRef,
Injector,
Input,
2019-09-25 19:37:29 +03:00
NgZone,
OnChanges,
OnDestroy,
2020-02-03 17:29:01 +02:00
OnInit,
SimpleChanges,
ViewChild,
ViewContainerRef,
2019-09-25 19:37:29 +03:00
ViewEncapsulation
} from '@angular/core';
2020-02-03 17:29:01 +02:00
import { DashboardWidget } from '@home/models/dashboard-component.models';
import {
2019-09-10 15:12:10 +03:00
Datasource,
2020-02-03 17:29:01 +02:00
defaultLegendConfig,
LegendConfig,
LegendData,
LegendPosition,
Widget,
WidgetActionDescriptor,
2019-09-06 20:17:45 +03:00
widgetActionSources,
2020-04-08 19:31:08 +03:00
WidgetActionType,
WidgetComparisonSettings,
2019-09-06 20:17:45 +03:00
WidgetResource,
2019-09-10 15:12:10 +03:00
widgetType,
2020-02-03 17:29:01 +02:00
WidgetTypeParameters
} 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';
import {
IWidgetSubscription,
StateObject,
2019-09-25 19:37:29 +03:00
StateParams,
SubscriptionEntityInfo,
2019-09-06 20:17:45 +03:00
SubscriptionInfo,
WidgetSubscriptionContext,
WidgetSubscriptionOptions
} 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';
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';
2020-04-08 19:31:08 +03:00
import { ServicesMap } from '@home/models/services.map';
import { ResizeObserver } from '@juggle/resize-observer';
@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
})
export class WidgetComponent extends PageComponent implements OnInit, AfterViewInit, OnChanges, OnDestroy {
@Input()
isEdit: boolean;
@Input()
isMobile: boolean;
@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[];
widgetContext: WidgetContext;
widgetType: any;
2019-09-10 15:12:10 +03:00
typeParameters: WidgetTypeParameters;
widgetTypeInstance: WidgetTypeInstance;
widgetErrorData: ExceptionData;
2019-09-06 20:17:45 +03:00
loadingData: boolean;
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;
subscriptionContext: WidgetSubscriptionContext;
subscriptionInited = false;
2019-11-04 15:47:36 +02:00
destroyed = false;
widgetSizeDetected = false;
2019-09-10 15:12:10 +03:00
cafs: {[cafId: string]: CancelAnimationFrame} = {};
private widgetResize$: ResizeObserver;
private cssParser = new cssjs();
2019-09-10 15:12:10 +03:00
private rxSubscriptions = new Array<Subscription>();
constructor(protected store: Store<AppState>,
private route: ActivatedRoute,
private router: Router,
2019-09-06 20:17:45 +03:00
private widgetComponentService: WidgetComponentService,
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,
private alarmService: AlarmService,
2019-09-10 15:12:10 +03:00
private dashboardService: DashboardService,
private datasourceService: DatasourceService,
private utils: UtilsService,
private raf: RafService,
2019-09-13 13:10:24 +03:00
private ngZone: NgZone,
private cd: ChangeDetectorRef) {
super(store);
2019-10-24 19:52:19 +03:00
this.cssParser.testMode = false;
}
ngOnInit(): void {
2019-09-06 20:17:45 +03:00
this.loadingData = true;
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;
}
}
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);
actionDescriptor.displayName = this.utils.customTranslation(descriptor.name, descriptor.name);
actionDescriptors.push(actionDescriptor);
});
actionDescriptorsBySourceId[actionSourceId] = actionDescriptors;
}
}
this.widgetContext = this.dashboardWidget.widgetContext;
this.widgetContext.changeDetector = this.cd;
2020-02-03 17:29:01 +02:00
this.widgetContext.ngZone = this.ngZone;
2020-02-28 19:49:14 +02:00
this.widgetContext.store = this.store;
2019-10-25 17:50:12 +03:00
this.widgetContext.servicesMap = ServicesMap;
this.widgetContext.isEdit = this.isEdit;
this.widgetContext.isMobile = this.isMobile;
2019-11-04 15:47:36 +02:00
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];
}
}
};
2019-11-04 15:47:36 +02:00
this.widgetContext.actionsApi = {
actionDescriptorsBySourceId,
getActionDescriptors: this.getActionDescriptors.bind(this),
handleWidgetAction: this.handleWidgetAction.bind(this),
2020-02-25 11:43:35 +02:00
elementClick: this.elementClick.bind(this),
getActiveEntityInfo: this.getActiveEntityInfo.bind(this)
};
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;
2020-02-25 11:43:35 +02:00
const entityLabel = entityInfo ? entityInfo.entityLabel : null;
this.handleWidgetAction($event, descriptor, entityId, entityName, null, entityLabel);
}
};
this.widgetContext.customHeaderActions.push(headerAction);
});
2019-11-04 15:47:36 +02:00
this.subscriptionContext = new WidgetSubscriptionContext(this.widgetContext.dashboard);
this.subscriptionContext.timeService = this.timeService;
this.subscriptionContext.deviceService = this.deviceService;
this.subscriptionContext.alarmService = this.alarmService;
this.subscriptionContext.datasourceService = this.datasourceService;
this.subscriptionContext.utils = this.utils;
this.subscriptionContext.raf = this.raf;
this.subscriptionContext.widgetUtils = this.widgetContext.utils;
this.subscriptionContext.getServerTimeDiff = this.dashboardService.getServerTimeDiff.bind(this.dashboardService);
2019-09-06 20:17:45 +03:00
this.widgetComponentService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe(
(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-10-24 19:52:19 +03:00
setTimeout(() => {
this.dashboardWidget.updateWidgetParams();
}, 0);
}
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 {
2019-11-04 15:47:36 +02:00
this.destroyed = true;
2019-09-10 15:12:10 +03:00
this.rxSubscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
this.rxSubscriptions.length = 0;
this.onDestroy();
}
2020-02-21 19:41:02 +02:00
private displayWidgetInstance(): boolean {
if (this.widget.type !== widgetType.static) {
for (const id of Object.keys(this.widgetContext.subscriptions)) {
const subscription = this.widgetContext.subscriptions[id];
if (subscription.isDataResolved()) {
return true;
}
}
return false;
} else {
return true;
}
}
2019-09-10 15:12:10 +03:00
private onDestroy() {
2020-02-14 16:46:02 +02:00
if (this.widgetContext) {
2020-02-21 19:41:02 +02:00
const shouldDestroyWidgetInstance = this.displayWidgetInstance();
2020-02-14 16:46:02 +02:00
for (const id of Object.keys(this.widgetContext.subscriptions)) {
const subscription = this.widgetContext.subscriptions[id];
subscription.destroy();
2019-09-10 15:12:10 +03:00
}
2020-02-14 16:46:02 +02:00
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 {
2020-02-21 19:41:02 +02:00
if (shouldDestroyWidgetInstance) {
this.widgetTypeInstance.onDestroy();
}
2020-02-14 16:46:02 +02:00
} catch (e) {
this.handleWidgetException(e);
}
}
2020-02-14 16:46:02 +02:00
this.widgetContext.destroyed = true;
this.destroyDynamicWidgetComponent();
}
}
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);
}
}
}
public onLegendKeyHiddenChange(index: number) {
for (const id of Object.keys(this.widgetContext.subscriptions)) {
const subscription = this.widgetContext.subscriptions[id];
subscription.updateDataVisibility(index);
}
}
private loadFromWidgetInfo() {
2020-02-10 13:04:56 +02:00
this.widgetContext.widgetNamespace = `widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`;
const elem = this.elementRef.nativeElement;
elem.classList.add('tb-widget');
2020-02-10 13:04:56 +02:00
elem.classList.add(this.widgetContext.widgetNamespace);
this.widgetType = this.widgetInfo.widgetTypeFunction;
2019-09-10 15:12:10 +03:00
this.typeParameters = this.widgetInfo.typeParameters;
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();
},
(err) => {
// console.log(err);
2019-09-10 15:12:10 +03:00
}
);
}
2020-02-05 17:52:18 +02:00
private detectChanges() {
if (!this.destroyed) {
2020-02-14 16:46:02 +02:00
try {
this.cd.detectChanges();
} catch (e) {
// console.log(e);
}
2020-02-05 17:52:18 +02:00
}
}
2019-09-10 15:12:10 +03:00
private isReady(): boolean {
return this.subscriptionInited && this.widgetSizeDetected;
}
private onInit(skipSizeCheck?: boolean) {
2019-11-04 15:47:36 +02:00
if (!this.widgetContext.$containerParent || this.destroyed) {
2019-09-10 15:12:10 +03:00
return;
}
if (!skipSizeCheck) {
this.checkSize();
}
if (!this.widgetContext.inited && this.isReady()) {
this.widgetContext.inited = true;
if (this.cafs.init) {
this.cafs.init();
this.cafs.init = null;
2019-09-10 15:12:10 +03:00
}
this.cafs.init = this.raf.raf(() => {
try {
2020-02-21 19:41:02 +02:00
if (this.displayWidgetInstance()) {
this.widgetTypeInstance.onInit();
} else {
this.loadingData = false;
}
2020-02-14 16:46:02 +02:00
this.detectChanges();
} 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 {
2020-02-21 19:41:02 +02:00
if (this.displayWidgetInstance()) {
this.widgetTypeInstance.onResize();
}
2019-09-10 15:12:10 +03:00
} 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 {
2020-02-21 19:41:02 +02:00
if (this.displayWidgetInstance()) {
this.widgetTypeInstance.onEditModeChanged();
}
2019-09-10 15:12:10 +03:00
} 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 {
2020-02-21 19:41:02 +02:00
if (this.displayWidgetInstance()) {
this.widgetTypeInstance.onMobileModeChanged();
}
2019-09-10 15:12:10 +03:00
} catch (e) {
this.handleWidgetException(e);
}
});
}
}
}
private reInit() {
2019-11-04 15:47:36 +02:00
if (this.cafs.reinit) {
this.cafs.reinit();
this.cafs.reinit = null;
}
this.cafs.reinit = this.raf.raf(() => {
2020-02-03 19:38:28 +02:00
this.ngZone.run(() => {
this.reInitImpl();
});
2019-11-04 15:47:36 +02:00
});
}
private reInitImpl() {
2019-09-10 15:12:10 +03:00
this.onDestroy();
if (!this.typeParameters.useCustomDatasources) {
this.createDefaultSubscription().subscribe(
() => {
2019-11-04 15:47:36 +02:00
if (this.destroyed) {
this.onDestroy();
} else {
2020-02-03 19:38:28 +02:00
this.widgetContext.reset();
2019-11-04 15:47:36 +02:00
this.subscriptionInited = true;
2020-02-03 17:29:01 +02:00
this.configureDynamicWidgetComponent();
2019-11-04 15:47:36 +02:00
this.onInit();
}
2019-09-10 15:12:10 +03:00
},
() => {
2019-11-04 15:47:36 +02:00
if (this.destroyed) {
this.onDestroy();
} else {
2020-02-03 19:38:28 +02:00
this.widgetContext.reset();
2019-11-04 15:47:36 +02:00
this.subscriptionInited = true;
this.onInit();
}
2019-09-10 15:12:10 +03:00
}
);
} else {
2020-02-03 19:38:28 +02:00
this.widgetContext.reset();
2019-09-10 15:12:10 +03:00
this.subscriptionInited = true;
2020-02-03 17:29:01 +02:00
this.configureDynamicWidgetComponent();
2019-09-10 15:12:10 +03:00
this.onInit();
}
}
2019-09-10 15:12:10 +03:00
private initialize(): Observable<any> {
const initSubject = new ReplaySubject();
2019-11-04 15:47:36 +02:00
this.rxSubscriptions.push(this.widgetContext.aliasController.entityAliasesChanged.subscribe(
2019-09-10 15:12:10 +03:00
(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-11-04 15:47:36 +02:00
this.rxSubscriptions.push(this.widgetContext.dashboard.dashboardTimewindowChanged.subscribe(
(dashboardTimewindow) => {
for (const id of Object.keys(this.widgetContext.subscriptions)) {
const subscription = this.widgetContext.subscriptions[id];
subscription.onDashboardTimewindowChanged(dashboardTimewindow);
}
}
));
2019-09-10 15:12:10 +03:00
if (!this.typeParameters.useCustomDatasources) {
this.createDefaultSubscription().subscribe(
() => {
this.subscriptionInited = true;
this.configureDynamicWidgetComponent();
2019-09-10 15:12:10 +03:00
initSubject.next();
initSubject.complete();
},
(err) => {
2019-09-10 15:12:10 +03:00
this.subscriptionInited = true;
initSubject.error(err);
2019-09-10 15:12:10 +03:00
}
);
} else {
this.loadingData = false;
this.subscriptionInited = true;
this.configureDynamicWidgetComponent();
2019-09-10 15:12:10 +03:00
initSubject.next();
initSubject.complete();
}
return initSubject.asObservable();
}
private destroyDynamicWidgetComponent() {
if (this.widgetContext.$containerParent && this.widgetResize$) {
this.widgetResize$.disconnect()
}
if (this.dynamicWidgetComponentRef) {
this.dynamicWidgetComponentRef.destroy();
2020-02-14 16:46:02 +02:00
this.dynamicWidgetComponentRef = null;
}
}
private handleWidgetException(e) {
console.error(e);
this.widgetErrorData = this.utils.processWidgetException(e);
2020-02-14 16:46:02 +02:00
this.detectChanges();
}
2019-09-06 20:17:45 +03:00
private configureDynamicWidgetComponent() {
this.widgetContentContainer.clear();
const injector: Injector = Injector.create(
{
providers: [
{
provide: 'widgetContext',
useValue: this.widgetContext
},
{
provide: 'errorMessages',
useValue: this.errorMessages
}
],
parent: this.injector
}
);
2019-09-06 20:17:45 +03:00
const containerElement = $(this.elementRef.nativeElement.querySelector('#widget-container'));
this.widgetContext.$containerParent = $(containerElement);
2020-02-14 16:46:02 +02:00
try {
this.dynamicWidgetComponentRef = this.widgetContentContainer.createComponent(this.widgetInfo.componentFactory, 0, injector);
this.cd.detectChanges();
} catch (e) {
console.error(e);
2020-02-14 16:46:02 +02:00
if (this.dynamicWidgetComponentRef) {
this.dynamicWidgetComponentRef.destroy();
this.dynamicWidgetComponentRef = null;
}
this.widgetContentContainer.clear();
}
if (this.dynamicWidgetComponentRef) {
this.dynamicWidgetComponent = this.dynamicWidgetComponentRef.instance;
this.widgetContext.$container = $(this.dynamicWidgetComponentRef.location.nativeElement);
this.widgetContext.$container.css('display', 'block');
this.widgetContext.$container.css('user-select', 'none');
this.widgetContext.$container.attr('id', 'container');
if (this.widgetSizeDetected) {
this.widgetContext.$container.css('height', this.widgetContext.height + 'px');
this.widgetContext.$container.css('width', this.widgetContext.width + 'px');
}
}
this.widgetResize$ = new ResizeObserver(() => {
this.onResize();
});
this.widgetResize$.observe(this.widgetContext.$containerParent[0]);
}
2019-09-10 15:12:10 +03:00
private createSubscription(options: WidgetSubscriptionOptions, subscribe?: boolean): Observable<IWidgetSubscription> {
const createSubscriptionSubject = new ReplaySubject<IWidgetSubscription>();
2019-11-04 15:47:36 +02:00
options.dashboardTimewindow = this.widgetContext.dashboardTimewindow;
2019-09-10 15:12:10 +03:00
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();
},
(err) => {
createSubscriptionSubject.error(err);
2019-09-10 15:12:10 +03:00
}
);
return createSubscriptionSubject.asObservable();
}
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();
},
(err) => {
createSubscriptionSubject.error(err);
2019-09-10 15:12:10 +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;
2019-11-04 15:47:36 +02:00
options.timeWindowConfig = options.useDashboardTimewindow ? this.widgetContext.dashboardTimewindow : this.widget.config.timewindow;
2019-09-10 15:12:10 +03:00
options.legendConfig = null;
if (this.displayLegend) {
options.legendConfig = this.legendConfig;
}
options.decimals = this.widgetContext.decimals;
options.units = this.widgetContext.units;
options.callbacks = {
onDataUpdated: () => {
2020-02-14 16:46:02 +02:00
try {
2020-02-21 19:41:02 +02:00
if (this.displayWidgetInstance()) {
this.widgetTypeInstance.onDataUpdated();
}
2020-02-14 16:46:02 +02:00
} catch (e){}
2019-09-10 15:12:10 +03:00
},
onDataUpdateError: (subscription, e) => {
this.handleWidgetException(e);
},
dataLoading: (subscription) => {
if (this.loadingData !== subscription.loadingData) {
this.loadingData = subscription.loadingData;
2020-02-05 17:52:18 +02:00
this.detectChanges();
2019-09-10 15:12:10 +03:00
}
},
legendDataUpdated: (subscription, detectChanges) => {
2020-02-05 17:52:18 +02:00
if (detectChanges) {
this.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;
2020-02-05 17:52:18 +02:00
this.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) {
2020-02-24 17:16:02 +02:00
const comparisonSettings: WidgetComparisonSettings = this.widgetContext.settings;
2019-09-10 15:12:10 +03:00
options = {
type: this.widget.type,
2020-02-24 17:16:02 +02:00
stateData: this.typeParameters.stateData,
comparisonEnabled: comparisonSettings.comparisonEnabled,
timeForComparison: comparisonSettings.timeForComparison
2019-09-10 15:12:10 +03:00
};
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;
2020-02-21 16:31:03 +02:00
options.alarmsMaxCountLoad = isDefined(this.widget.config.alarmsMaxCountLoad) ?
this.widget.config.alarmsMaxCountLoad : 0;
options.alarmsFetchSize = isDefined(this.widget.config.alarmsFetchSize) ?
this.widget.config.alarmsFetchSize : 100;
2019-09-10 15:12:10 +03:00
} 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();
},
(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) => {
if (this.dynamicWidgetComponent) {
this.dynamicWidgetComponent.rpcEnabled = subscription.rpcEnabled;
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
2020-02-05 17:52:18 +02:00
this.detectChanges();
}
2019-09-10 15:12:10 +03:00
},
onRpcSuccess: (subscription) => {
if (this.dynamicWidgetComponent) {
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText;
this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection;
2020-02-05 17:52:18 +02:00
this.detectChanges();
}
2019-09-10 15:12:10 +03:00
},
onRpcFailed: (subscription) => {
if (this.dynamicWidgetComponent) {
this.dynamicWidgetComponent.executingRpcRequest = subscription.executingRpcRequest;
this.dynamicWidgetComponent.rpcErrorText = subscription.rpcErrorText;
this.dynamicWidgetComponent.rpcRejection = subscription.rpcRejection;
2020-02-05 17:52:18 +02:00
this.detectChanges();
}
2019-09-10 15:12:10 +03:00
},
onRpcErrorCleared: (subscription) => {
if (this.dynamicWidgetComponent) {
this.dynamicWidgetComponent.rpcErrorText = null;
this.dynamicWidgetComponent.rpcRejection = null;
2020-02-05 17:52:18 +02:00
this.detectChanges();
}
2019-09-10 15:12:10 +03:00
}
};
this.createSubscription(options).subscribe(
(subscription) => {
this.widgetContext.defaultSubscription = subscription;
createSubscriptionSubject.next();
createSubscriptionSubject.complete();
},
(err) => {
createSubscriptionSubject.error(err);
2019-09-10 15:12:10 +03:00
}
);
2020-02-05 17:52:18 +02:00
this.detectChanges();
2019-09-10 15:12:10 +03:00
} else if (this.widget.type === widgetType.static) {
this.loadingData = false;
createSubscriptionSubject.next();
createSubscriptionSubject.complete();
2020-02-05 17:52:18 +02:00
this.detectChanges();
2019-09-10 15:12:10 +03:00
} else {
createSubscriptionSubject.next();
createSubscriptionSubject.complete();
2020-02-05 17:52:18 +02:00
this.detectChanges();
2019-09-10 15:12:10 +03:00
}
return createSubscriptionSubject.asObservable();
}
private getActionDescriptors(actionSourceId: string): Array<WidgetActionDescriptor> {
let result = this.widgetContext.actionsApi.actionDescriptorsBySourceId[actionSourceId];
if (!result) {
result = [];
}
return result;
}
private handleWidgetAction($event: Event, descriptor: WidgetActionDescriptor,
2020-02-25 11:43:35 +02:00
entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string): 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());
2020-02-25 11:43:35 +02:00
this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel);
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 = {};
2020-02-25 11:43:35 +02:00
this.updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel);
if (targetDashboardStateId) {
stateObject.id = targetDashboardStateId;
}
const state = objToBase64([ stateObject ]);
2019-10-24 19:52:19 +03:00
const isSinglePage = this.route.snapshot.data.singlePageMode;
let url;
2019-10-24 19:52:19 +03:00
if (isSinglePage) {
url = `/dashboard/${targetDashboardId}?state=${state}`;
} else {
url = `/dashboards/${targetDashboardId}?state=${state}`;
}
2019-10-24 19:52:19 +03:00
this.router.navigateByUrl(url);
break;
case WidgetActionType.custom:
const customFunction = descriptor.customFunction;
if (customFunction && customFunction.length > 0) {
try {
if (!additionalParams) {
additionalParams = {};
}
const customActionFunction = new Function('$event', 'widgetContext', 'entityId',
2020-02-26 18:15:04 +02:00
'entityName', 'additionalParams', 'entityLabel', customFunction);
customActionFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel);
} catch (e) {
console.error(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',
2020-02-26 18:15:04 +02:00
'entityName', 'htmlTemplate', 'additionalParams', 'entityLabel', customPrettyFunction);
customActionPrettyFunction($event, this.widgetContext, entityId, entityName, htmlTemplate, additionalParams, entityLabel);
} catch (e) {
console.error(e);
}
}
},
(errorMessages: string[]) => {
this.processResourcesLoadErrors(errorMessages);
}
);
break;
}
}
private elementClick($event: Event) {
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) {
2020-02-25 19:11:25 +02:00
$event.stopPropagation();
const entityInfo = this.getActiveEntityInfo();
const entityId = entityInfo ? entityInfo.entityId : null;
const entityName = entityInfo ? entityInfo.entityName : null;
2020-02-25 11:43:35 +02:00
const entityLabel = entityInfo && entityInfo.entityLabel ? entityInfo.entityLabel : null;
2020-06-08 10:05:21 +03:00
this.handleWidgetAction($event, descriptor, entityId, entityName, null, entityLabel);
}
});
}
}
}
2020-02-25 11:43:35 +02:00
private updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
entityName?: string, entityLabel?: 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;
}
2020-02-25 11:43:35 +02:00
if (entityLabel) {
targetEntityParams.entityLabel = entityLabel;
}
}
}
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 (isDefined(customResources) && 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'}));
}
private getActiveEntityInfo(): SubscriptionEntityInfo {
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) {
2020-02-14 16:46:02 +02:00
if (this.widgetContext.$container) {
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;
}
}