import { AfterViewInit, ApplicationRef, ChangeDetectorRef, Component, ComponentRef, Directive, EventEmitter, HostListener, Injector, Input, OnChanges, OnDestroy, Optional, Output, SimpleChanges, SkipSelf, TemplateRef, Type, ViewChild, ViewContainerRef } from "@angular/core"; import {Overlay, OverlayRef} from "@angular/cdk/overlay"; import {ComponentPortal} from "@angular/cdk/portal"; import {WindowRegistry} from "../window/window-state"; import {WindowComponent} from "../window/window.component"; import {RenderComponentDirective} from "../core/render-component.directive"; import {IN_DIALOG} from "../app/token"; @Component({ template: `
`, host: { '[attr.tabindex]': '1' }, styleUrls: ['./dialog-wrapper.component.scss'] }) export class DialogWrapperComponent { @Input() component?: Type; @Input() componentInputs: { [name: string]: any } = {}; actions?: TemplateRef | undefined; container?: TemplateRef | undefined; content?: TemplateRef | undefined; @ViewChild(RenderComponentDirective, {static: false}) renderComponentDirective?: RenderComponentDirective; constructor( protected cd: ChangeDetectorRef, ) { } public setActions(actions: TemplateRef | undefined) { this.actions = actions; this.cd.detectChanges(); } public setDialogContainer(container: TemplateRef | undefined) { this.container = container; this.cd.detectChanges(); } } @Component({ selector: 'dui-dialog', template: ` `, styles: [`:host { display: none; }`] }) export class DialogComponent implements AfterViewInit, OnDestroy, OnChanges { @Input() title: string = ''; @Input() visible: boolean = false; @Output() visibleChange = new EventEmitter(); @Input() minWidth?: number | string; @Input() minHeight?: number | string; @Input() width?: number | string; @Input() height?: number | string; @Input() maxWidth?: number | string; @Input() maxHeight?: number | string; @Input() center: boolean = false; @Input() backDropCloses: boolean = false; @Input() component?: Type; @Input() componentInputs: { [name: string]: any } = {}; @Output() closed = new EventEmitter(); @Output() open = new EventEmitter(); @ViewChild('template', {static: true}) template?: TemplateRef; actions?: TemplateRef | undefined; container?: TemplateRef | undefined; public overlayRef?: OverlayRef; public wrapperComponentRef?: ComponentRef; constructor( protected applicationRef: ApplicationRef, protected viewContainerRef: ViewContainerRef, protected cd: ChangeDetectorRef, protected overlay: Overlay, protected injector: Injector, protected registry: WindowRegistry, @SkipSelf() protected cdParent: ChangeDetectorRef, @Optional() protected window?: WindowComponent, ) { } public toPromise(): Promise { return new Promise((resolve) => { this.closed.subscribe((v: any) => { resolve(v); }) }) } public setDialogContainer(container: TemplateRef | undefined) { this.container = container; if (this.wrapperComponentRef) { this.wrapperComponentRef.instance.setDialogContainer(container); } } public setActions(actions: TemplateRef | undefined) { this.actions = actions; if (this.wrapperComponentRef) { this.wrapperComponentRef.instance.setActions(actions); } } ngOnChanges(changes: SimpleChanges): void { if (this.visible) { this.show(); } else { this.close(undefined); } } public show() { if (this.overlayRef) { return; } const window = this.window ? this.window.getClosestNonDialogWindow() : this.registry.getOuterActiveWindow(); const offsetTop = window && window.header ? window.header.getBottomPosition() - 1 : 0; const document = this.registry.getCurrentViewContainerRef().element.nativeElement.ownerDocument; //this is necessary for multi-window environments, but doesn't work yet. // const overlayContainer = new OverlayContainer(document); // // const overlay = new Overlay( // this.injector.get(ScrollStrategyOptions), // overlayContainer, // this.injector.get(ComponentFactoryResolver), // new OverlayPositionBuilder(this.injector.get(ViewportRuler), document, this.injector.get(Platform), overlayContainer), // this.injector.get(OverlayKeyboardDispatcher), // this.injector, // this.injector.get(NgZone), // document, // this.injector.get(Directionality), // ); const overlay = this.overlay; let positionStrategy = overlay .position() .global().centerHorizontally().top(offsetTop + 'px'); if (this.center) { positionStrategy = overlay .position() .global().centerHorizontally().centerVertically(); } this.overlayRef = overlay.create({ width: this.width || undefined, height: this.height || undefined, minWidth: this.minWidth || undefined, minHeight: this.minHeight || undefined, maxWidth: this.maxWidth || '90%', maxHeight: this.maxHeight || '90%', hasBackdrop: true, panelClass: (this.center ? 'dialog-overlay' : 'dialog-overlay-with-animation'), scrollStrategy: overlay.scrollStrategies.reposition(), positionStrategy: positionStrategy, }); if (this.backDropCloses) { this.overlayRef!.backdropClick().subscribe(() => { this.close(undefined); }); } const injector = Injector.create({ parent: this.injector, providers: [ {provide: DialogComponent, useValue: this}, {provide: WindowComponent, useValue: window}, {provide: IN_DIALOG, useValue: true}, ], }); this.open.emit(); const portal = new ComponentPortal(DialogWrapperComponent, this.viewContainerRef, injector); this.wrapperComponentRef = this.overlayRef!.attach(portal); this.wrapperComponentRef.instance.component = this.component!; this.wrapperComponentRef.instance.componentInputs = this.componentInputs; this.wrapperComponentRef.instance.content = this.template!; if (this.actions) { this.wrapperComponentRef!.instance.setActions(this.actions); } if (this.container) { this.wrapperComponentRef!.instance.setDialogContainer(this.container); } this.overlayRef!.updatePosition(); this.visible = true; this.visibleChange.emit(true); this.wrapperComponentRef!.location.nativeElement.focus(); this.wrapperComponentRef!.changeDetectorRef.detectChanges(); this.cd.detectChanges(); this.cdParent.detectChanges(); } protected beforeUnload() { if (this.overlayRef) { this.overlayRef.dispose(); delete this.overlayRef; } } ngAfterViewInit() { } public close(v?: any) { this.visible = false; this.visibleChange.emit(false); this.beforeUnload(); this.closed.emit(v); requestAnimationFrame(() => { this.applicationRef.tick(); }); } ngOnDestroy(): void { this.beforeUnload(); } } /** * This directive is necessary if you want to load and render the dialog content * only when opening the dialog. Without it, it is immediately rendered, which can cause * performance and injection issues. */ @Directive({ 'selector': '[dialogContainer]', }) export class DialogDirective { constructor(protected dialog: DialogComponent, public template: TemplateRef) { this.dialog.setDialogContainer(this.template); } } @Component({ selector: 'dui-dialog-actions', template: '' }) export class DialogActionsComponent implements AfterViewInit, OnDestroy { @ViewChild('template', {static: true}) template!: TemplateRef; constructor(protected dialog: DialogComponent) { } ngAfterViewInit(): void { this.dialog.setActions(this.template); } ngOnDestroy(): void { if (this.dialog.actions === this.template) { this.dialog.setActions(undefined); } } } @Component({ selector: 'dui-dialog-error', template: '', styleUrls: ['./dialog-error.component.scss'] }) export class DialogErrorComponent { } @Directive({ selector: '[closeDialog]' }) export class CloseDialogDirective { @Input() closeDialog: any; constructor(protected dialog: DialogComponent) { } @HostListener('click') onClick() { this.dialog.close(this.closeDialog); } }