UI: add popover component to wiget context

This commit is contained in:
Igor Kulikov 2022-10-07 19:33:18 +03:00
parent 990a50391e
commit 65438c64e7
10 changed files with 116 additions and 32 deletions

View File

@ -185,7 +185,8 @@
[isEditingWidget]="isEditingWidget"
[isMobile]="forceDashboardMobileMode"
[widgetEditMode]="widgetEditMode"
[parentDashboard]="parentDashboard">
[parentDashboard]="parentDashboard"
[popoverComponent]="popoverComponent">
</tb-dashboard-layout>
</mat-drawer>
<mat-drawer-content [fxShow]="layouts.main.show"
@ -200,7 +201,8 @@
[isEditingWidget]="isEditingWidget"
[isMobile]="forceDashboardMobileMode"
[widgetEditMode]="widgetEditMode"
[parentDashboard]="parentDashboard">
[parentDashboard]="parentDashboard"
[popoverComponent]="popoverComponent">
</tb-dashboard-layout>
</mat-drawer-content>
</mat-drawer-container>

View File

@ -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;

View File

@ -58,6 +58,7 @@
[isRemoveActionEnabled]="isEdit && !widgetEditMode"
[callbacks]="this"
[ignoreLoading]="layoutCtx.ignoreLoading"
[parentDashboard]="parentDashboard">
[parentDashboard]="parentDashboard"
[popoverComponent]="popoverComponent">
</tb-dashboard>
</div>

View File

@ -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<Subscription>();

View File

@ -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<Timewindow> = new ReplaySubject<Timewindow>();
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();
}

View File

@ -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,

View File

@ -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<Widget>;
@ -109,6 +109,8 @@ export class DashboardWidgets implements Iterable<DashboardWidget> {
parentDashboard?: IDashboardComponent;
popoverComponent?: TbPopoverComponent;
[Symbol.iterator](): Iterator<DashboardWidget> {
return this.activeDashboardWidgets[Symbol.iterator]();
}
@ -174,7 +176,7 @@ export class DashboardWidgets implements Iterable<DashboardWidget> {
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<DashboardWidget> {
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<WidgetHeaderAction>;
widgetActions: Array<WidgetAction>;
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();
}

View File

@ -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;

View File

@ -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 {
<div #popoverRoot [@popoverMotion]="tbAnimationState"
(@popoverMotion.done)="animationDone()">
<div
#popover
class="tb-popover"
[class.tb-popover-rtl]="dir === 'rtl'"
[ngClass]="classMap"
@ -340,6 +342,7 @@ export class TbPopoverComponent implements OnDestroy, OnInit {
@ViewChild('overlay', { static: false }) overlay!: CdkConnectedOverlay;
@ViewChild('popoverRoot', { static: false }) popoverRoot!: ElementRef<HTMLElement>;
@ViewChild('popover', { static: false }) popover!: ElementRef<HTMLElement>;
tbContent: string | TemplateRef<void> | null = null;
tbComponentFactory: ComponentFactory<any> | 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();

View File

@ -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<TbPopoverComponent> {
return hostView.createComponent(this.componentFactory);
}
displayPopover<T>(trigger: Element, renderer: Renderer2, hostView: ViewContainerRef,
componentType: Type<T>, 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<T>(componentRef: ComponentRef<TbPopoverComponent>, trigger: Element, renderer: Renderer2,
componentType: Type<T>, 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,