diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 4caa27b0a2..db40fdc5d8 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -185,7 +185,8 @@ [isEditingWidget]="isEditingWidget" [isMobile]="forceDashboardMobileMode" [widgetEditMode]="widgetEditMode" - [parentDashboard]="parentDashboard"> + [parentDashboard]="parentDashboard" + [popoverComponent]="popoverComponent"> + [parentDashboard]="parentDashboard" + [popoverComponent]="popoverComponent"> diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index c145794163..14e566411f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -17,13 +17,18 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, - Component, ElementRef, EventEmitter, HostBinding, + Component, + ElementRef, + EventEmitter, + HostBinding, Inject, Injector, Input, NgZone, OnDestroy, - OnInit, Optional, Renderer2, + OnInit, + Optional, + Renderer2, StaticProvider, ViewChild, ViewContainerRef, @@ -48,7 +53,7 @@ import { } from '@app/shared/models/dashboard.models'; import { WINDOW } from '@core/services/window.service'; import { WindowMessage } from '@shared/models/window-message.model'; -import { deepClone, guid, hashCode, isDefined, isDefinedAndNotNull, isNotEmptyStr } from '@app/core/utils'; +import { deepClone, guid, isDefined, isDefinedAndNotNull, isNotEmptyStr } from '@app/core/utils'; import { DashboardContext, DashboardPageLayout, @@ -129,7 +134,8 @@ import { MobileService } from '@core/services/mobile.service'; import { DashboardImageDialogComponent, - DashboardImageDialogData, DashboardImageDialogResult + DashboardImageDialogData, + DashboardImageDialogResult } from '@home/components/dashboard-page/dashboard-image-dialog.component'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import cssjs from '@core/css/css'; @@ -139,6 +145,7 @@ import { MatButton } from '@angular/material/button'; import { VersionControlComponent } from '@home/components/vc/version-control.component'; import { TbPopoverService } from '@shared/components/popover.service'; import { tap } from 'rxjs/operators'; +import { TbPopoverComponent } from '@shared/components/popover.component'; // @dynamic @Component({ @@ -184,6 +191,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC @Input() parentDashboard?: IDashboardComponent = null; + @Input() + popoverComponent?: TbPopoverComponent = null; + @Input() parentAliasController?: IAliasController = null; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html index 4e7d11255b..0255ec8ade 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html @@ -58,6 +58,7 @@ [isRemoveActionEnabled]="isEdit && !widgetEditMode" [callbacks]="this" [ignoreLoading]="layoutCtx.ignoreLoading" - [parentDashboard]="parentDashboard"> + [parentDashboard]="parentDashboard" + [popoverComponent]="popoverComponent"> diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts index fcee0b2f0a..de3533d14e 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts @@ -33,6 +33,7 @@ import { TranslateService } from '@ngx-translate/core'; import { ItemBufferService } from '@app/core/services/item-buffer.service'; import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; import { TbCheatSheetComponent } from '@shared/components/cheatsheet.component'; +import { TbPopoverComponent } from '@shared/components/popover.component'; @Component({ selector: 'tb-dashboard-layout', @@ -81,6 +82,9 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo @Input() parentDashboard?: IDashboardComponent = null; + @Input() + popoverComponent?: TbPopoverComponent = null; + @ViewChild('dashboard', {static: true}) dashboard: IDashboardComponent; private rxSubscriptions = new Array(); diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index 62598ac1dd..627294b40f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -15,7 +15,9 @@ /// import { - AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, DoCheck, Input, @@ -56,6 +58,7 @@ import { distinct } from 'rxjs/operators'; import { ResizeObserver } from '@juggle/resize-observer'; import { UtilsService } from '@core/services/utils.service'; import { WidgetComponentAction, WidgetComponentActionType } from '@home/components/widget/widget-container.component'; +import { TbPopoverComponent } from '@shared/components/popover.component'; @Component({ selector: 'tb-dashboard', @@ -136,6 +139,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo @Input() parentDashboard?: IDashboardComponent = null; + @Input() + popoverComponent?: TbPopoverComponent = null; + dashboardTimewindowChangedSubject: Subject = new ReplaySubject(); dashboardTimewindowChanged = this.dashboardTimewindowChangedSubject.asObservable().pipe( @@ -191,6 +197,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo ngOnInit(): void { this.dashboardWidgets.parentDashboard = this.parentDashboard; + this.dashboardWidgets.popoverComponent = this.popoverComponent; if (!this.dashboardTimewindow) { this.dashboardTimewindow = this.timeService.defaultTimewindow(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 8826ff860f..790daf1a42 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -28,7 +28,8 @@ import { NgZone, OnChanges, OnDestroy, - OnInit, Renderer2, + OnInit, + Renderer2, SimpleChanges, ViewChild, ViewContainerRef, @@ -39,12 +40,14 @@ import { defaultLegendConfig, LegendConfig, LegendData, - LegendPosition, MobileActionResult, + LegendPosition, Widget, WidgetActionDescriptor, widgetActionSources, WidgetActionType, - WidgetComparisonSettings, WidgetMobileActionDescriptor, WidgetMobileActionType, + WidgetComparisonSettings, + WidgetMobileActionDescriptor, + WidgetMobileActionType, WidgetResource, widgetType, WidgetTypeParameters @@ -65,7 +68,9 @@ import { validateEntityId } from '@core/utils'; import { - IDynamicWidgetComponent, ShowWidgetHeaderActionFunction, updateEntityParams, + IDynamicWidgetComponent, + ShowWidgetHeaderActionFunction, + updateEntityParams, WidgetContext, WidgetHeaderAction, WidgetInfo, @@ -109,9 +114,7 @@ import { MobileService } from '@core/services/mobile.service'; import { DialogService } from '@core/services/dialog.service'; import { PopoverPlacement } from '@shared/components/popover.models'; import { TbPopoverService } from '@shared/components/popover.service'; -import { - DASHBOARD_PAGE_COMPONENT_TOKEN -} from '@home/components/tokens'; +import { DASHBOARD_PAGE_COMPONENT_TOKEN } from '@home/components/tokens'; @Component({ selector: 'tb-widget', @@ -1378,8 +1381,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } ] }); - const component = this.popoverService.displayPopover(trigger, this.renderer, - this.widgetContentContainer, this.dashboardPageComponent, preferredPlacement, hideOnClickOutside, + const componentRef = this.popoverService.createPopoverRef(this.widgetContentContainer); + const component = this.popoverService.displayPopoverWithComponentRef(componentRef, trigger, this.renderer, + this.dashboardPageComponent, preferredPlacement, hideOnClickOutside, injector, { embedded: true, @@ -1388,7 +1392,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI currentState: objToBase64([stateObject]), dashboard, parentDashboard: this.widgetContext.parentDashboard ? - this.widgetContext.parentDashboard : this.widgetContext.dashboard + this.widgetContext.parentDashboard : this.widgetContext.dashboard, + popoverComponent: componentRef.instance }, {width: popoverWidth, height: popoverHeight}, popoverStyle, diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 6721f7bc25..33fa5111c5 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -16,8 +16,8 @@ import { GridsterComponent, GridsterConfig, GridsterItem, GridsterItemComponentInterface } from 'angular-gridster2'; import { - Datasource, - datasourcesHasAggregation, datasourcesHasOnlyComparisonAggregation, + datasourcesHasAggregation, + datasourcesHasOnlyComparisonAggregation, FormattedData, Widget, WidgetPosition, @@ -25,14 +25,14 @@ import { } from '@app/shared/models/widget.models'; import { WidgetLayout, WidgetLayouts } from '@app/shared/models/dashboard.models'; import { IDashboardWidget, WidgetAction, WidgetContext, WidgetHeaderAction } from './widget-component.models'; -import { AggregationType, Timewindow } from '@shared/models/time/time.models'; +import { Timewindow } from '@shared/models/time/time.models'; import { Observable, of, Subject } from 'rxjs'; import { formattedDataFormDatasourceData, guid, isDefined, isEqual, isUndefined } from '@app/core/utils'; import { IterableDiffer, KeyValueDiffer } from '@angular/core'; import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; import { enumerable } from '@shared/decorators/enumerable'; import { UtilsService } from '@core/services/utils.service'; -import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; +import { TbPopoverComponent } from '@shared/components/popover.component'; export interface WidgetsData { widgets: Array; @@ -109,6 +109,8 @@ export class DashboardWidgets implements Iterable { parentDashboard?: IDashboardComponent; + popoverComponent?: TbPopoverComponent; + [Symbol.iterator](): Iterator { return this.activeDashboardWidgets[Symbol.iterator](); } @@ -174,7 +176,7 @@ export class DashboardWidgets implements Iterable { switch (record.operation) { case 'add': this.dashboardWidgets.push( - new DashboardWidget(this.dashboard, record.widget, record.widgetLayout, this.parentDashboard) + new DashboardWidget(this.dashboard, record.widget, record.widgetLayout, this.parentDashboard, this.popoverComponent) ); break; case 'remove': @@ -190,7 +192,7 @@ export class DashboardWidgets implements Iterable { if (!isEqual(prevDashboardWidget.widget, record.widget) || !isEqual(prevDashboardWidget.widgetLayout, record.widgetLayout)) { this.dashboardWidgets[index] = new DashboardWidget(this.dashboard, record.widget, record.widgetLayout, - this.parentDashboard); + this.parentDashboard, this.popoverComponent); this.dashboardWidgets[index].highlighted = prevDashboardWidget.highlighted; this.dashboardWidgets[index].selected = prevDashboardWidget.selected; } else { @@ -345,7 +347,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { customHeaderActions: Array; widgetActions: Array; - widgetContext = new WidgetContext(this.dashboard, this, this.widget, this.parentDashboard); + widgetContext = new WidgetContext(this.dashboard, this, this.widget, this.parentDashboard, this.popoverComponent); widgetId: string; @@ -388,7 +390,8 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { private dashboard: IDashboardComponent, public widget: Widget, public widgetLayout?: WidgetLayout, - private parentDashboard?: IDashboardComponent) { + private parentDashboard?: IDashboardComponent, + private popoverComponent?: TbPopoverComponent) { if (!widget.id) { widget.id = guid(); } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 4686fe88e6..0bcfa8fb01 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -18,7 +18,8 @@ import { IDashboardComponent } from '@home/models/dashboard-component.models'; import { DataSet, Datasource, - DatasourceData, FormattedData, + DatasourceData, + FormattedData, JsonSettingsSchema, Widget, WidgetActionDescriptor, @@ -37,7 +38,8 @@ import { IStateController, IWidgetSubscription, IWidgetUtils, - RpcApi, StateParams, + RpcApi, + StateParams, SubscriptionEntityInfo, TimewindowFunctions, WidgetActionsApi, @@ -113,7 +115,8 @@ export class WidgetContext { constructor(public dashboard: IDashboardComponent, private dashboardWidget: IDashboardWidget, private widget: Widget, - public parentDashboard?: IDashboardComponent) {} + public parentDashboard?: IDashboardComponent, + public popoverComponent?: TbPopoverComponent) {} get stateController(): IStateController { return this.parentDashboard ? this.parentDashboard.stateController : this.dashboard.stateController; diff --git a/ui-ngx/src/app/shared/components/popover.component.ts b/ui-ngx/src/app/shared/components/popover.component.ts index 99321974af..20815fd5fb 100644 --- a/ui-ngx/src/app/shared/components/popover.component.ts +++ b/ui-ngx/src/app/shared/components/popover.component.ts @@ -57,6 +57,7 @@ import { } from '@shared/components/popover.models'; import { distinctUntilChanged, take, takeUntil } from 'rxjs/operators'; import { isNotEmptyStr, onParentScrollOrWindowResize } from '@core/utils'; +import { animate, AnimationBuilder, AnimationMetadata, style } from '@angular/animations'; export type TbPopoverTrigger = 'click' | 'focus' | 'hover' | null; @@ -304,6 +305,7 @@ export class TbPopoverDirective implements OnChanges, OnDestroy, AfterViewInit {
; + @ViewChild('popover', { static: false }) popover!: ElementRef; tbContent: string | TemplateRef | null = null; tbComponentFactory: ComponentFactory | null = null; @@ -442,6 +445,7 @@ export class TbPopoverComponent implements OnDestroy, OnInit { constructor( public cdr: ChangeDetectorRef, private renderer: Renderer2, + private animationBuilder: AnimationBuilder, @Optional() private directionality: Directionality ) {} @@ -532,6 +536,35 @@ export class TbPopoverComponent implements OnDestroy, OnInit { }); } + resize(width: string, height: string, animationDurationMs?: number) { + if (animationDurationMs && animationDurationMs > 0) { + const prevWidth = this.popover.nativeElement.offsetWidth; + const prevHeight = this.popover.nativeElement.offsetHeight; + const animationMetadata: AnimationMetadata[] = [style({width: prevWidth + 'px', height: prevHeight + 'px'}), + animate(animationDurationMs + 'ms', style({width, height}))]; + const factory = this.animationBuilder.build(animationMetadata); + const player = factory.create(this.popover.nativeElement); + player.play(); + const resize$ = new ResizeObserver(() => { + this.updatePosition(); + }); + resize$.observe(this.popover.nativeElement); + player.onDone(() => { + player.destroy(); + resize$.disconnect(); + this.setSize(width, height); + }); + } else { + this.setSize(width, height); + } + } + + private setSize(width: string, height: string) { + this.renderer.setStyle(this.popover.nativeElement, 'width', width); + this.renderer.setStyle(this.popover.nativeElement, 'height', height); + this.updatePosition(); + } + updatePosition(): void { if (this.origin && this.overlay && this.overlay.overlayRef) { this.overlay.overlayRef.updatePosition(); diff --git a/ui-ngx/src/app/shared/components/popover.service.ts b/ui-ngx/src/app/shared/components/popover.service.ts index 713e8d337b..3f05ed02a6 100644 --- a/ui-ngx/src/app/shared/components/popover.service.ts +++ b/ui-ngx/src/app/shared/components/popover.service.ts @@ -16,8 +16,12 @@ import { ComponentFactory, - ComponentFactoryResolver, ElementRef, Inject, - Injectable, Injector, + ComponentFactoryResolver, + ComponentRef, + ElementRef, + Inject, + Injectable, + Injector, Renderer2, Type, ViewContainerRef @@ -53,11 +57,23 @@ export class TbPopoverService { } } + createPopoverRef(hostView: ViewContainerRef): ComponentRef { + return hostView.createComponent(this.componentFactory); + } + displayPopover(trigger: Element, renderer: Renderer2, hostView: ViewContainerRef, componentType: Type, preferredPlacement: PopoverPlacement = 'top', hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {}, popoverStyle: any = {}, style?: any, showCloseButton = true): TbPopoverComponent { - const componentRef = hostView.createComponent(this.componentFactory); + const componentRef = this.createPopoverRef(hostView); + return this.displayPopoverWithComponentRef(componentRef, trigger, renderer, componentType, preferredPlacement, hideOnClickOutside, + injector, context, overlayStyle, popoverStyle, style, showCloseButton); + } + + displayPopoverWithComponentRef(componentRef: ComponentRef, trigger: Element, renderer: Renderer2, + componentType: Type, preferredPlacement: PopoverPlacement = 'top', + hideOnClickOutside = true, injector?: Injector, context?: any, overlayStyle: any = {}, + popoverStyle: any = {}, style?: any, showCloseButton = true): TbPopoverComponent { const component = componentRef.instance; this.popoverWithTriggers.push({ trigger,