UNPKG

ngx-modalview

Version:

Simple way to implement modal in Angular (16+) projects.

222 lines 27.4 kB
import { Component, Inject, ViewContainerRef, ViewChild } from '@angular/core'; import { of } from 'rxjs'; import { DefaultNgxModalOptionConfig } from './ngx-modal-options'; import { NgxModalWrapperComponent } from './ngx-modal-wrapper.component'; import { DraggableDirective } from './ngx-modal-draggable.directive'; import * as i0 from "@angular/core"; /** * View container manager which manages a list of modals currently active * inside the viewvontainer */ class NgxModalHolderComponent { resolver; renderer; ngZone; defaultNgxModalOptions; /** * Target viewContainer to insert modals */ viewContainer; /** * modal collection, maintained by addModal and removeModal * @type {Array<NgxModalComponent> } */ modals = []; /** * if auto focus is on and no element focused, store it here to be restored back after close */ previousActiveElement = null; /** * Constructor * @param {ComponentFactoryResolver} resolver * @param renderer * @param ngZone * @param defaultNgxModalOptions */ constructor(resolver, renderer, ngZone, defaultNgxModalOptions) { this.resolver = resolver; this.renderer = renderer; this.ngZone = ngZone; this.defaultNgxModalOptions = defaultNgxModalOptions; } /** * Configures then adds modal to the modals array, and populates with data passed in * @param {Type<NgxModalComponent>} component * @param {object?} data * @param {NgxModalOptionsOverrides?} options * @return {Observable<*>} */ addModal(component, data, options) { // create component if (!this.viewContainer) { return of(null); } const factory = this.resolver.resolveComponentFactory(NgxModalWrapperComponent); const componentRef = this.viewContainer.createComponent(factory); const modalWrapper = (componentRef.instance); const { ref: _componentRef, component: _component } = modalWrapper.addComponent(component); // assign options refs _component.options = options = Object.assign({}, this.defaultNgxModalOptions, options); // set base classes for wrapper modalWrapper.modalClasses = options.wrapperDefaultClasses; // add to stack this.modals.push(_component); // wait a tick then setup the following while adding a modal this.wait().then(() => { this.toggleWrapperClass(modalWrapper.wrapper, options.wrapperClass); this.toggleBodyClass(options.bodyClass); if (options.draggable) { this.setDraggable(_componentRef, options); } this.wait(options.animationDuration).then(() => { this.autoFocusFirstElement(_component.wrapper, options.autoFocus); _component.markAsReady(); }); }); // when closing modal remove it _component.onClosing(modal => this.removeModal(modal)); // if clicking on background closes modal this.configureCloseOnClickOutside(modalWrapper); // map and return observable _component.mapDataObject(data); return _component.setupObserver(); } /** * triggers components close function * to take effect * @returns {Promise<void>} * @param closingModal */ async removeModal(closingModal) { const options = closingModal.options; this.toggleWrapperClass(closingModal.wrapper, options.wrapperClass); return this.wait(options.animationDuration).then(() => { this.removeModalFromArray(closingModal); this.toggleBodyClass(options.bodyClass); this.restorePreviousFocus(); }); } /** * Instructs all open modals to */ removeAllModals() { return Promise.all(this.modals.map(modal => this.removeModal(modal))); } /** * Bind a body class 'modal-open' to a condition of modals in pool > 0 * @param bodyClass - string to add and remove from body in document */ toggleBodyClass(bodyClass) { if (!bodyClass) { return; } const body = document.getElementsByTagName('body')[0]; const bodyClassItems = bodyClass.split(' '); if (!this.modals.length) { body.classList.remove(...bodyClassItems); } else { body.classList.add(...bodyClassItems); } } /** * if the option to close on background click is set, then hook up a callback * @param modalWrapper */ configureCloseOnClickOutside(modalWrapper) { modalWrapper.onClickOutsideModalContent(() => { if (modalWrapper.content.options.closeOnClickOutside) { modalWrapper.content.close(); } }); } /** * Auto focus o the first element if autofocus is on * @param componentWrapper * @param autoFocus */ autoFocusFirstElement(componentWrapper, autoFocus) { if (autoFocus) { const focusable = componentWrapper.nativeElement.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); if (focusable && focusable.length) { this.previousActiveElement = document.activeElement; focusable[0].focus(); } } } /** * Restores the last focus is there was one */ restorePreviousFocus() { if (this.previousActiveElement) { this.previousActiveElement.focus(); this.previousActiveElement = null; } } /** * Configure the adding and removal of a wrapper class - predominantly animation focused * @param modalWrapperEl * @param wrapperClass */ toggleWrapperClass(modalWrapperEl, wrapperClass) { const wrapperClassList = modalWrapperEl.nativeElement.classList; const wrapperClassItems = wrapperClass.split(' '); if (wrapperClassList.toString().indexOf(wrapperClass) !== -1) { wrapperClassList.remove(...wrapperClassItems); } else { wrapperClassList.add(...wrapperClassItems); } } /** * Enables the drag option on the modal if the options have it enabled * @param component * @param options * @private */ setDraggable(component, options) { const draggableDirective = new DraggableDirective(component.location, this.ngZone, this.renderer); draggableDirective.dragTarget = component.location.nativeElement; draggableDirective.dragHandle = component.instance.handle ? component.instance.handle.nativeElement : undefined; draggableDirective.ngAfterViewInit(); component.location.nativeElement.classList.add(options.draggableClass); } /** * Helper function for a more readable timeout * @param ms */ wait(ms = 0) { return new Promise((resolve, reject) => { setTimeout(() => resolve(), ms); }); } /** * Instructs the holder to remove the modal and * removes this component from the collection * @param {NgxModalComponent} component */ removeModalFromArray(component) { const index = this.modals.indexOf(component); if (index > -1) { this.viewContainer.remove(index); this.modals.splice(index, 1); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgxModalHolderComponent, deps: [{ token: i0.ComponentFactoryResolver }, { token: i0.Renderer2 }, { token: i0.NgZone }, { token: DefaultNgxModalOptionConfig }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.0.3", type: NgxModalHolderComponent, selector: "ngx-modal-holder", viewQueries: [{ propertyName: "viewContainer", first: true, predicate: ["viewContainer"], descendants: true, read: ViewContainerRef, static: true }], ngImport: i0, template: '<ng-template #viewContainer></ng-template>', isInline: true }); } export { NgxModalHolderComponent }; i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.3", ngImport: i0, type: NgxModalHolderComponent, decorators: [{ type: Component, args: [{ selector: 'ngx-modal-holder', template: '<ng-template #viewContainer></ng-template>', }] }], ctorParameters: function () { return [{ type: i0.ComponentFactoryResolver }, { type: i0.Renderer2 }, { type: i0.NgZone }, { type: undefined, decorators: [{ type: Inject, args: [DefaultNgxModalOptionConfig] }] }]; }, propDecorators: { viewContainer: [{ type: ViewChild, args: ['viewContainer', { read: ViewContainerRef, static: true }] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LW1vZGFsLWhvbGRlci5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbmd4LW1vZGFsL25neC1tb2RhbC1ob2xkZXIuY29tcG9uZW50LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQXdDLE1BQU0sRUFBUSxnQkFBZ0IsRUFBRSxTQUFTLEVBQW1DLE1BQU0sZUFBZSxDQUFDO0FBQzVKLE9BQU8sRUFBYyxFQUFFLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDdEMsT0FBTyxFQUFFLDJCQUEyQixFQUE2QyxNQUFNLHFCQUFxQixDQUFDO0FBQzdHLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBRXpFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDOztBQUVyRTs7O0dBR0c7QUFDSCxNQUlhLHVCQUF1QjtJQXlCeEI7SUFDQTtJQUNBO0lBQ3FDO0lBM0IvQzs7T0FFRztJQUNtRSxhQUFhLENBQUM7SUFFcEY7OztPQUdHO0lBQ0gsTUFBTSxHQUF1QyxFQUFFLENBQUM7SUFFaEQ7O09BRUc7SUFDSCxxQkFBcUIsR0FBRyxJQUFJLENBQUM7SUFFN0I7Ozs7OztPQU1HO0lBQ0gsWUFDVSxRQUFrQyxFQUNsQyxRQUFtQixFQUNuQixNQUFjLEVBQ3VCLHNCQUF1QztRQUg1RSxhQUFRLEdBQVIsUUFBUSxDQUEwQjtRQUNsQyxhQUFRLEdBQVIsUUFBUSxDQUFXO1FBQ25CLFdBQU0sR0FBTixNQUFNLENBQVE7UUFDdUIsMkJBQXNCLEdBQXRCLHNCQUFzQixDQUFpQjtJQUV0RixDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0ksUUFBUSxDQUFRLFNBQXlDLEVBQUUsSUFBUSxFQUFFLE9BQWtDO1FBQzVHLG1CQUFtQjtRQUNuQixJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsRUFBRTtZQUN2QixPQUFPLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FB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IEVsZW1lbnRSZWYsIEluamVjdCwgVHlwZSwgVmlld0NvbnRhaW5lclJlZiwgVmlld0NoaWxkLCBSZW5kZXJlcjIsIENvbXBvbmVudFJlZiwgTmdab25lIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IE9ic2VydmFibGUsIG9mIH0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7IERlZmF1bHROZ3hNb2RhbE9wdGlvbkNvbmZpZywgTmd4TW9kYWxPcHRpb25zLCBOZ3hNb2RhbE9wdGlvbnNPdmVycmlkZXMgfSBmcm9tICcuL25neC1tb2RhbC1vcHRpb25zJztcclxuaW1wb3J0IHsgTmd4TW9kYWxXcmFwcGVyQ29tcG9uZW50IH0gZnJvbSAnLi9uZ3gtbW9kYWwtd3JhcHBlci5jb21wb25lbnQnO1xyXG5pbXBvcnQgeyBOZ3hNb2RhbENvbXBvbmVudCB9IGZyb20gJy4vbmd4LW1vZGFsLmNvbXBvbmVudCc7XHJcbmltcG9ydCB7IERyYWdnYWJsZURpcmVjdGl2ZSB9IGZyb20gJy4vbmd4LW1vZGFsLWRyYWdnYWJsZS5kaXJlY3RpdmUnO1xyXG5cclxuLyoqXHJcbiAqIFZpZXcgY29udGFpbmVyIG1hbmFnZXIgd2hpY2ggbWFuYWdlcyBhIGxpc3Qgb2YgbW9kYWxzIGN1cnJlbnRseSBhY3RpdmVcclxuICogaW5zaWRlIHRoZSB2aWV3dm9udGFpbmVyXHJcbiAqL1xyXG5AQ29tcG9uZW50KHtcclxuICBzZWxlY3RvcjogJ25neC1tb2RhbC1ob2xkZXInLFxyXG4gIHRlbXBsYXRlOiAnPG5nLXRlbXBsYXRlICN2aWV3Q29udGFpbmVyPjwvbmctdGVtcGxhdGU+JyxcclxufSlcclxuZXhwb3J0IGNsYXNzIE5neE1vZGFsSG9sZGVyQ29tcG9uZW50IHtcclxuICAvKipcclxuICAgKiBUYXJnZXQgdmlld0NvbnRhaW5lciB0byBpbnNlcnQgbW9kYWxzXHJcbiAgICovXHJcbiAgQFZpZXdDaGlsZCgndmlld0NvbnRhaW5lcicsIHsgcmVhZDogVmlld0NvbnRhaW5lclJlZiwgc3RhdGljOiB0cnVlIH0pIHZpZXdDb250YWluZXI7XHJcblxyXG4gIC8qKlxyXG4gICAqIG1vZGFsIGNvbGxlY3Rpb24sIG1haW50YWluZWQgYnkgYWRkTW9kYWwgYW5kIHJlbW92ZU1vZGFsXHJcbiAgICogQHR5cGUge0FycmF5PE5neE1vZGFsQ29tcG9uZW50PiB9XHJcbiAgICovXHJcbiAgbW9kYWxzOiBBcnJheTxOZ3hNb2RhbENvbXBvbmVudDxhbnksIGFueT4+ID0gW107XHJcblxyXG4gIC8qKlxyXG4gICAqIGlmIGF1dG8gZm9jdXMgaXMgb24gYW5kIG5vIGVsZW1lbnQgZm9jdXNlZCwgc3RvcmUgaXQgaGVyZSB0byBiZSByZXN0b3JlZCBiYWNrIGFmdGVyIGNsb3NlXHJcbiAgICovXHJcbiAgcHJldmlvdXNBY3RpdmVFbGVtZW50ID0gbnVsbDtcclxuXHJcbiAgLyoqXHJcbiAgICogQ29uc3RydWN0b3JcclxuICAgKiBAcGFyYW0ge0NvbXBvbmVudEZhY3RvcnlSZXNvbHZlcn0gcmVzb2x2ZXJcclxuICAgKiBAcGFyYW0gcmVuZGVyZXJcclxuICAgKiBAcGFyYW0gbmdab25lXHJcbiAgICogQHBhcmFtIGRlZmF1bHROZ3hNb2RhbE9wdGlvbnNcclxuICAgKi9cclxuICBjb25zdHJ1Y3RvcihcclxuICAgIHByaXZhdGUgcmVzb2x2ZXI6IENvbXBvbmVudEZhY3RvcnlSZXNvbHZlcixcclxuICAgIHByaXZhdGUgcmVuZGVyZXI6IFJlbmRlcmVyMixcclxuICAgIHByaXZhdGUgbmdab25lOiBOZ1pvbmUsXHJcbiAgICBASW5qZWN0KERlZmF1bHROZ3hNb2RhbE9wdGlvbkNvbmZpZykgcHJpdmF0ZSBkZWZhdWx0Tmd4TW9kYWxPcHRpb25zOiBOZ3hNb2RhbE9wdGlvbnMsXHJcbiAgKSB7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDb25maWd1cmVzIHRoZW4gYWRkcyBtb2RhbCB0byB0aGUgbW9kYWxzIGFycmF5LCBhbmQgcG9wdWxhdGVzIHdpdGggZGF0YSBwYXNzZWQgaW5cclxuICAgKiBAcGFyYW0ge1R5cGU8Tmd4TW9kYWxDb21wb25lbnQ+fSBjb21wb25lbnRcclxuICAgKiBAcGFyYW0ge29iamVjdD99IGRhdGFcclxuICAgKiBAcGFyYW0ge05neE1vZGFsT3B0aW9uc092ZXJyaWRlcz99IG9wdGlvbnNcclxuICAgKiBAcmV0dXJuIHtPYnNlcnZhYmxlPCo+fVxyXG4gICAqL1xyXG4gIHB1YmxpYyBhZGRNb2RhbDxULCBUMT4oY29tcG9uZW50OiBUeXBlPE5neE1vZGFsQ29tcG9uZW50PFQsIFQxPj4sIGRhdGE/OiBULCBvcHRpb25zPzogTmd4TW9kYWxPcHRpb25zT3ZlcnJpZGVzKTogT2JzZXJ2YWJsZTxUMT4ge1xyXG4gICAgLy8gY3JlYXRlIGNvbXBvbmVudFxyXG4gICAgaWYgKCF0aGlzLnZpZXdDb250YWluZXIpIHtcclxuICAgICAgcmV0dXJuIG9mKG51bGwpO1xyXG4gICAgfVxyXG4gICAgY29uc3QgZmFjdG9yeSA9IHRoaXMucmVzb2x2ZXIucmVzb2x2ZUNvbXBvbmVudEZhY3RvcnkoTmd4TW9kYWxXcmFwcGVyQ29tcG9uZW50KTtcclxuICAgIGNvbnN0IGNvbXBvbmVudFJlZiA9IHRoaXMudmlld0NvbnRhaW5lci5jcmVhdGVDb21wb25lbnQoZmFjdG9yeSk7XHJcbiAgICBjb25zdCBtb2RhbFdyYXBwZXI6IE5neE1vZGFsV3JhcHBlckNvbXBvbmVudCA9IDxOZ3hNb2RhbFdyYXBwZXJDb21wb25lbnQ+KFxyXG4gICAgICBjb21wb25lbnRSZWYuaW5zdGFuY2VcclxuICAgICk7XHJcbiAgICBjb25zdCB7IHJlZjogX2NvbXBvbmVudFJlZiwgY29tcG9uZW50OiBfY29tcG9uZW50IH0gPSBtb2RhbFdyYXBwZXIuYWRkQ29tcG9uZW50KGNvbXBvbmVudCk7XHJcblxyXG4gICAgLy8gYXNzaWduIG9wdGlvbnMgcmVmc1xyXG4gICAgX2NvbXBvbmVudC5vcHRpb25zID0gb3B0aW9ucyA9IE9iamVjdC5hc3NpZ24oe30sIHRoaXMuZGVmYXVsdE5neE1vZGFsT3B0aW9ucywgb3B0aW9ucykgYXMgTmd4TW9kYWxPcHRpb25zO1xyXG5cclxuICAgIC8vIHNldCBiYXNlIGNsYXNzZXMgZm9yIHdyYXBwZXJcclxuICAgIG1vZGFsV3JhcHBlci5tb2RhbENsYXNzZXMgPSBvcHRpb25zLndyYXBwZXJEZWZhdWx0Q2xhc3NlcztcclxuXHJcbiAgICAvLyBhZGQgdG8gc3RhY2tcclxuICAgIHRoaXMubW9kYWxzLnB1c2goX2NvbXBvbmVudCk7XHJcblxyXG4gICAgLy8gd2FpdCBhIHRpY2sgdGhlbiBzZXR1cCB0aGUgZm9sbG93aW5nIHdoaWxlIGFkZGluZyBhIG1vZGFsXHJcbiAgICB0aGlzLndhaXQoKS50aGVuKCgpID0+IHtcclxuICAgICAgdGhpcy50b2dnbGVXcmFwcGVyQ2xhc3MobW9kYWxXcmFwcGVyLndyYXBwZXIsIG9wdGlvbnMud3JhcHBlckNsYXNzKTtcclxuICAgICAgdGhpcy50b2dnbGVCb2R5Q2xhc3Mob3B0aW9ucy5ib2R5Q2xhc3MpO1xyXG4gICAgICBpZiAob3B0aW9ucy5kcmFnZ2FibGUpIHtcclxuICAgICAgICB0aGlzLnNldERyYWdnYWJsZShfY29tcG9uZW50UmVmLCBvcHRpb25zKTtcclxuICAgICAgfVxyXG4gICAgICB0aGlzLndhaXQob3B0aW9ucy5hbmltYXRpb25EdXJhdGlvbikudGhlbigoKSA9PiB7XHJcbiAgICAgICAgdGhpcy5hdXRvRm9jdXNGaXJzdEVsZW1lbnQoX2NvbXBvbmVudC53cmFwcGVyLCBvcHRpb25zLmF1dG9Gb2N1cyk7XHJcbiAgICAgICAgX2NvbXBvbmVudC5tYXJrQXNSZWFkeSgpO1xyXG4gICAgICB9KTtcclxuICAgIH0pO1xyXG5cclxuICAgIC8vIHdoZW4gY2xvc2luZyBtb2RhbCByZW1vdmUgaXRcclxuICAgIF9jb21wb25lbnQub25DbG9zaW5nKG1vZGFsID0+IHRoaXMucmVtb3ZlTW9kYWwobW9kYWwpKTtcclxuXHJcbiAgICAvLyBpZiBjbGlja2luZyBvbiBiYWNrZ3JvdW5kIGNsb3NlcyBtb2RhbFxyXG4gICAgdGhpcy5jb25maWd1cmVDbG9zZU9uQ2xpY2tPdXRzaWRlKG1vZGFsV3JhcHBlcik7XHJcblxyXG4gICAgLy8gbWFwIGFuZCByZXR1cm4gb2JzZXJ2YWJsZVxyXG4gICAgX2NvbXBvbmVudC5tYXBEYXRhT2JqZWN0KGRhdGEpO1xyXG5cclxuICAgIHJldHVybiBfY29tcG9uZW50LnNldHVwT2JzZXJ2ZXIoKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIHRyaWdnZXJzIGNvbXBvbmVudHMgY2xvc2UgZnVuY3Rpb25cclxuICAgKiB0byB0YWtlIGVmZmVjdFxyXG4gICAqIEByZXR1cm5zIHtQcm9taXNlPHZvaWQ+fVxyXG4gICAqIEBwYXJhbSBjbG9zaW5nTW9kYWxcclxuICAgKi9cclxuICBwdWJsaWMgYXN5bmMgcmVtb3ZlTW9kYWwoY2xvc2luZ01vZGFsOiBOZ3hNb2RhbENvbXBvbmVudDxhbnksIGFueT4pOiBQcm9taXNlPGFueT4ge1xyXG4gICAgY29uc3Qgb3B0aW9ucyA9IGNsb3NpbmdNb2RhbC5vcHRpb25zO1xyXG4gICAgdGhpcy50b2dnbGVXcmFwcGVyQ2xhc3MoY2xvc2luZ01vZGFsLndyYXBwZXIsIG9wdGlvbnMud3JhcHBlckNsYXNzKTtcclxuICAgIHJldHVybiB0aGlzLndhaXQob3B0aW9ucy5hbmltYXRpb25EdXJhdGlvbikudGhlbigoKSA9PiB7XHJcbiAgICAgIHRoaXMucmVtb3ZlTW9kYWxGcm9tQXJyYXkoY2xvc2luZ01vZGFsKTtcclxuICAgICAgdGhpcy50b2dnbGVCb2R5Q2xhc3Mob3B0aW9ucy5ib2R5Q2xhc3MpO1xyXG4gICAgICB0aGlzLnJlc3RvcmVQcmV2aW91c0ZvY3VzKCk7XHJcbiAgICB9KTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEluc3RydWN0cyBhbGwgb3BlbiBtb2RhbHMgdG9cclxuICAgKi9cclxuICBwdWJsaWMgcmVtb3ZlQWxsTW9kYWxzKCk6IFByb21pc2U8YW55PiB7XHJcbiAgICByZXR1cm4gUHJvbWlzZS5hbGwodGhpcy5tb2RhbHMubWFwKG1vZGFsID0+IHRoaXMucmVtb3ZlTW9kYWwobW9kYWwpKSk7XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBCaW5kIGEgYm9keSBjbGFzcyAnbW9kYWwtb3BlbicgdG8gYSBjb25kaXRpb24gb2YgbW9kYWxzIGluIHBvb2wgPiAwXHJcbiAgICogQHBhcmFtIGJvZHlDbGFzcyAtIHN0cmluZyB0byBhZGQgYW5kIHJlbW92ZSBmcm9tIGJvZHkgaW4gZG9jdW1lbnRcclxuICAgKi9cclxuICBwcml2YXRlIHRvZ2dsZUJvZHlDbGFzcyhib2R5Q2xhc3M6IHN0cmluZyk6IHZvaWQge1xyXG4gICAgaWYgKCFib2R5Q2xhc3MpIHtcclxuICAgICAgcmV0dXJuO1xyXG4gICAgfVxyXG4gICAgY29uc3QgYm9keSA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdib2R5JylbMF07XHJcbiAgICBjb25zdCBib2R5Q2xhc3NJdGVtcyA9IGJvZHlDbGFzcy5zcGxpdCgnICcpO1xyXG4gICAgaWYgKCF0aGlzLm1vZGFscy5sZW5ndGgpIHtcclxuICAgICAgYm9keS5jbGFzc0xpc3QucmVtb3ZlKC4uLmJvZHlDbGFzc0l0ZW1zKTtcclxuICAgIH0gZWxzZSB7XHJcbiAgICAgIGJvZHkuY2xhc3NMaXN0LmFkZCguLi5ib2R5Q2xhc3NJdGVtcyk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBpZiB0aGUgb3B0aW9uIHRvIGNsb3NlIG9uIGJhY2tncm91bmQgY2xpY2sgaXMgc2V0LCB0aGVuIGhvb2sgdXAgYSBjYWxsYmFja1xyXG4gICAqIEBwYXJhbSBtb2RhbFdyYXBwZXJcclxuICAgKi9cclxuICBwcml2YXRlIGNvbmZpZ3VyZUNsb3NlT25DbGlja091dHNpZGUobW9kYWxXcmFwcGVyOiBOZ3hNb2RhbFdyYXBwZXJDb21wb25lbnQpIHtcclxuICAgIG1vZGFsV3JhcHBlci5vbkNsaWNrT3V0c2lkZU1vZGFsQ29udGVudCgoKSA9PiB7XHJcbiAgICAgIGlmIChtb2RhbFdyYXBwZXIuY29udGVudC5vcHRpb25zLmNsb3NlT25DbGlja091dHNpZGUpIHtcclxuICAgICAgICBtb2RhbFdyYXBwZXIuY29udGVudC5jbG9zZSgpO1xyXG4gICAgICB9XHJcbiAgICB9KTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEF1dG8gZm9jdXMgbyB0aGUgZmlyc3QgZWxlbWVudCBpZiBhdXRvZm9jdXMgaXMgb25cclxuICAgKiBAcGFyYW0gY29tcG9uZW50V3JhcHBlclxyXG4gICAqIEBwYXJhbSBhdXRvRm9jdXNcclxuICAgKi9cclxuICBwcml2YXRlIGF1dG9Gb2N1c0ZpcnN0RWxlbWVudChjb21wb25lbnRXcmFwcGVyOiBFbGVtZW50UmVmLCBhdXRvRm9jdXM6IGJvb2xlYW4pIHtcclxuICAgIGlmIChhdXRvRm9jdXMpIHtcclxuICAgICAgY29uc3QgZm9jdXNhYmxlID0gY29tcG9uZW50V3JhcHBlci5uYXRpdmVFbGVtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoXHJcbiAgICAgICAgJ2J1dHRvbiwgW2hyZWZdLCBpbnB1dCwgc2VsZWN0LCB0ZXh0YXJlYSwgW3RhYmluZGV4XTpub3QoW3RhYmluZGV4PVwiLTFcIl0pJyxcclxuICAgICAgKTtcclxuICAgICAgaWYgKGZvY3VzYWJsZSAmJiBmb2N1c2FibGUubGVuZ3RoKSB7XHJcbiAgICAgICAgdGhpcy5wcmV2aW91c0FjdGl2ZUVsZW1lbnQgPSBkb2N1bWVudC5hY3RpdmVFbGVtZW50O1xyXG4gICAgICAgIGZvY3VzYWJsZVswXS5mb2N1cygpO1xyXG4gICAgICB9XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBSZXN0b3JlcyB0aGUgbGFzdCBmb2N1cyBpcyB0aGVyZSB3YXMgb25lXHJcbiAgICovXHJcbiAgcHJpdmF0ZSByZXN0b3JlUHJldmlvdXNGb2N1cygpIHtcclxuICAgIGlmICh0aGlzLnByZXZpb3VzQWN0aXZlRWxlbWVudCkge1xyXG4gICAgICB0aGlzLnByZXZpb3VzQWN0aXZlRWxlbWVudC5mb2N1cygpO1xyXG4gICAgICB0aGlzLnByZXZpb3VzQWN0aXZlRWxlbWVudCA9IG51bGw7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBDb25maWd1cmUgdGhlIGFkZGluZyBhbmQgcmVtb3ZhbCBvZiBhIHdyYXBwZXIgY2xhc3MgLSBwcmVkb21pbmFudGx5IGFuaW1hdGlvbiBmb2N1c2VkXHJcbiAgICogQHBhcmFtIG1vZGFsV3JhcHBlckVsXHJcbiAgICogQHBhcmFtIHdyYXBwZXJDbGFzc1xyXG4gICAqL1xyXG4gIHByaXZhdGUgdG9nZ2xlV3JhcHBlckNsYXNzKG1vZGFsV3JhcHBlckVsOiBFbGVtZW50UmVmLCB3cmFwcGVyQ2xhc3M6IHN0cmluZyk6IHZvaWQge1xyXG4gICAgY29uc3Qgd3JhcHBlckNsYXNzTGlzdCA9IG1vZGFsV3JhcHBlckVsLm5hdGl2ZUVsZW1lbnQuY2xhc3NMaXN0O1xyXG4gICAgY29uc3Qgd3JhcHBlckNsYXNzSXRlbXMgPSB3cmFwcGVyQ2xhc3Muc3BsaXQoJyAnKTtcclxuICAgIGlmICh3cmFwcGVyQ2xhc3NMaXN0LnRvU3RyaW5nKCkuaW5kZXhPZih3cmFwcGVyQ2xhc3MpICE9PSAtMSkge1xyXG4gICAgICB3cmFwcGVyQ2xhc3NMaXN0LnJlbW92ZSguLi53cmFwcGVyQ2xhc3NJdGVtcyk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICB3cmFwcGVyQ2xhc3NMaXN0LmFkZCguLi53cmFwcGVyQ2xhc3NJdGVtcyk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBFbmFibGVzIHRoZSBkcmFnIG9wdGlvbiBvbiB0aGUgbW9kYWwgaWYgdGhlIG9wdGlvbnMgaGF2ZSBpdCBlbmFibGVkXHJcbiAgICogQHBhcmFtIGNvbXBvbmVudFxyXG4gICAqIEBwYXJhbSBvcHRpb25zXHJcbiAgICogQHByaXZhdGVcclxuICAgKi9cclxuICBwcml2YXRlIHNldERyYWdnYWJsZShjb21wb25lbnQ6IENvbXBvbmVudFJlZjxOZ3hNb2RhbENvbXBvbmVudDxhbnksIGFueT4+LCBvcHRpb25zOiBOZ3hNb2RhbE9wdGlvbnNPdmVycmlkZXMpOiB2b2lkIHtcclxuICAgIGNvbnN0IGRyYWdnYWJsZURpcmVjdGl2ZSA9IG5ldyBEcmFnZ2FibGVEaXJlY3RpdmUoY29tcG9uZW50LmxvY2F0aW9uLCB0aGlzLm5nWm9uZSwgdGhpcy5yZW5kZXJlcik7XHJcbiAgICBkcmFnZ2FibGVEaXJlY3RpdmUuZHJhZ1RhcmdldCA9IGNvbXBvbmVudC5sb2NhdGlvbi5uYXRpdmVFbGVtZW50O1xyXG4gICAgZHJhZ2dhYmxlRGlyZWN0aXZlLmRyYWdIYW5kbGUgPSBjb21wb25lbnQuaW5zdGFuY2UuaGFuZGxlID8gY29tcG9uZW50Lmluc3RhbmNlLmhhbmRsZS5uYXRpdmVFbGVtZW50IDogdW5kZWZpbmVkO1xyXG4gICAgZHJhZ2dhYmxlRGlyZWN0aXZlLm5nQWZ0ZXJWaWV3SW5pdCgpO1xyXG4gICAgY29tcG9uZW50LmxvY2F0aW9uLm5hdGl2ZUVsZW1lbnQuY2xhc3NMaXN0LmFkZChvcHRpb25zLmRyYWdnYWJsZUNsYXNzKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEhlbHBlciBmdW5jdGlvbiBmb3IgYSBtb3JlIHJlYWRhYmxlIHRpbWVvdXRcclxuICAgKiBAcGFyYW0gbXNcclxuICAgKi9cclxuICBwcml2YXRlIHdhaXQobXM6IG51bWJlciA9IDApOiBQcm9taXNlPHZvaWQ+IHtcclxuICAgIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XHJcbiAgICAgIHNldFRpbWVvdXQoKCkgPT4gcmVzb2x2ZSgpLCBtcyk7XHJcbiAgICB9KTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEluc3RydWN0cyB0aGUgaG9sZGVyIHRvIHJlbW92ZSB0aGUgbW9kYWwgYW5kXHJcbiAgICogcmVtb3ZlcyB0aGlzIGNvbXBvbmVudCBmcm9tIHRoZSBjb2xsZWN0aW9uXHJcbiAgICogQHBhcmFtIHtOZ3hNb2RhbENvbXBvbmVudH0gY29tcG9uZW50XHJcbiAgICovXHJcbiAgcHJpdmF0ZSByZW1vdmVNb2RhbEZyb21BcnJheShjb21wb25lbnQpOiB2b2lkIHtcclxuICAgIGNvbnN0IGluZGV4ID0gdGhpcy5tb2RhbHMuaW5kZXhPZihjb21wb25lbnQpO1xyXG4gICAgaWYgKGluZGV4ID4gLTEpIHtcclxuICAgICAgdGhpcy52aWV3Q29udGFpbmVyLnJlbW92ZShpbmRleCk7XHJcbiAgICAgIHRoaXMubW9kYWxzLnNwbGljZShpbmRleCwgMSk7XHJcbiAgICB9XHJcbiAgfVxyXG59XHJcbiJdfQ==