ng-hub-ui-portal
Version:
A flexible Angular portal library for dynamic content rendering with advanced positioning and interaction control
371 lines • 59.9 kB
JavaScript
import { DOCUMENT } from '@angular/common';
import { ApplicationRef, createComponent, EnvironmentInjector, EventEmitter, inject, Injectable, Injector, NgZone, TemplateRef } from '@angular/core';
import { ContentRef, hubFocusTrap, isDefined, isString, ScrollBar } from 'ng-hub-ui-utils';
import { Subject } from 'rxjs';
import { take } from 'rxjs/operators';
import { HubActivePortal, HubPortalRef } from './portal-ref';
import { HubPortalWindow } from './portal-window';
import * as i0 from "@angular/core";
export class HubPortalStack {
constructor() {
this._applicationRef = inject(ApplicationRef);
this._injector = inject(Injector);
this._environmentInjector = inject(EnvironmentInjector);
this._document = inject(DOCUMENT);
this._scrollBar = inject(ScrollBar);
this._activeWindowCmptHasChanged = new Subject();
this._ariaHiddenValues = new Map();
this._scrollBarRestoreFn = null;
this._portalRefs = [];
this._windowCmpts = [];
this._activeInstances = new EventEmitter();
const ngZone = inject(NgZone);
// Trap focus on active WindowCmpt
this._activeWindowCmptHasChanged.subscribe(() => {
if (this._windowCmpts.length) {
const activeWindowCmpt = this._windowCmpts[this._windowCmpts.length - 1];
hubFocusTrap(ngZone, activeWindowCmpt.location.nativeElement, this._activeWindowCmptHasChanged);
this._revertAriaHidden();
this._setAriaHidden(activeWindowCmpt.location.nativeElement);
}
});
}
_restoreScrollBar() {
const scrollBarRestoreFn = this._scrollBarRestoreFn;
if (scrollBarRestoreFn) {
this._scrollBarRestoreFn = null;
scrollBarRestoreFn();
}
}
_hideScrollBar() {
if (!this._scrollBarRestoreFn) {
this._scrollBarRestoreFn = this._scrollBar.hide();
}
}
open(contentInjector, content, options) {
const containerEl = options.container instanceof HTMLElement
? options.container
: isDefined(options.container)
? this._document.querySelector(options.container)
: this._document.body;
if (!containerEl) {
throw new Error(`The specified portal container "${options.container || 'body'}" was not found in the DOM.`);
}
this._hideScrollBar();
const activePortal = new HubActivePortal();
contentInjector = options.injector || contentInjector;
const environmentInjector = contentInjector.get(EnvironmentInjector, null) ||
this._environmentInjector;
const contentRef = this._getContentRef(contentInjector, environmentInjector, content, activePortal, options);
const windowCmptRef = this._createWindowComponent(contentRef.nodes, options);
this._attachWindowComponent(containerEl, windowCmptRef);
const hubPortalRef = new HubPortalRef(windowCmptRef, contentRef, options.beforeDismiss);
this._registerPortalRef(hubPortalRef);
this._registerWindowCmpt(windowCmptRef);
// We have to cleanup DOM after the last portal when BOTH 'hidden' was emitted and 'result' promise was resolved:
// - with animations OFF, 'hidden' emits synchronously, then 'result' is resolved asynchronously
// - with animations ON, 'result' is resolved asynchronously, then 'hidden' emits asynchronously
hubPortalRef.hidden.pipe(take(1)).subscribe(() => Promise.resolve(true).then(() => {
if (!this._portalRefs.length) {
this._document.body.classList.remove('portal-open');
this._restoreScrollBar();
this._revertAriaHidden();
}
}));
activePortal.close = (result) => {
hubPortalRef.close(result);
};
activePortal.dismiss = (reason) => {
hubPortalRef.dismiss(reason);
};
activePortal.update = (options) => {
hubPortalRef.update(options);
};
hubPortalRef.update(options);
if (this._portalRefs.length === 1) {
this._document.body.classList.add('portal-open');
}
windowCmptRef.changeDetectorRef.detectChanges();
return hubPortalRef;
}
/**
* Toggles a portal by dismissing all existing portals and waiting for them to be hidden
* before showing the new one.
*
* @param contentInjector - The injector to use for dependency injection
* @param content - The content to display (component, template, or string)
* @param options - Portal configuration options
* @returns A reference to the newly created portal
*/
toggle(contentInjector, content, options) {
// Get current portals before creating the new one
const existingPortals = [...this._portalRefs];
// Create the new portal but suppress normal registration
const containerEl = options.container instanceof HTMLElement
? options.container
: isDefined(options.container)
? this._document.querySelector(options.container)
: this._document.body;
if (!containerEl) {
throw new Error(`The specified portal container "${options.container || 'body'}" was not found in the DOM.`);
}
this._hideScrollBar();
const activePortal = new HubActivePortal();
contentInjector = options.injector || contentInjector;
const environmentInjector = contentInjector.get(EnvironmentInjector, null) ||
this._environmentInjector;
const contentRef = this._getContentRef(contentInjector, environmentInjector, content, activePortal, options);
const windowCmptRef = this._createWindowComponent(contentRef.nodes, options);
const newPortalRef = new HubPortalRef(windowCmptRef, contentRef, options.beforeDismiss);
// Setup active portal methods
activePortal.close = (result) => {
newPortalRef.close(result);
};
activePortal.dismiss = (reason) => {
newPortalRef.dismiss(reason);
};
activePortal.update = (options) => {
newPortalRef.update(options);
};
// Dismiss all existing portals and wait for them to be hidden
const hidePromises = existingPortals.map((portal) => new Promise((resolve) => {
portal.hidden.pipe(take(1)).subscribe(() => {
resolve();
});
portal.dismiss('toggle');
}));
// After all portals are hidden, register and show the new one
Promise.all(hidePromises).then(() => {
this._attachWindowComponent(containerEl, windowCmptRef);
this._registerPortalRef(newPortalRef);
this._registerWindowCmpt(windowCmptRef);
if (this._portalRefs.length === 1) {
this._document.body.classList.add('portal-open');
}
newPortalRef.update(options);
windowCmptRef.changeDetectorRef.detectChanges();
});
// Setup hidden cleanup like in open()
newPortalRef.hidden.pipe(take(1)).subscribe(() => Promise.resolve(true).then(() => {
if (!this._portalRefs.length) {
this._document.body.classList.remove('portal-open');
this._restoreScrollBar();
this._revertAriaHidden();
}
}));
return newPortalRef;
}
get activeInstances() {
return this._activeInstances;
}
dismissAll(reason) {
this._portalRefs.forEach((hubPortalRef) => hubPortalRef.dismiss(reason));
}
hasOpenPortals() {
return this._portalRefs.length > 0;
}
_createWindowComponent([headerNodes, bodyNodes, footerNodes], options) {
const singleContent = !options.headerSelector && !options.footerSelector;
let windowCmptRef = createComponent(HubPortalWindow, {
environmentInjector: this._applicationRef.injector,
elementInjector: this._injector,
projectableNodes: singleContent
? [bodyNodes]
: [[], headerNodes, bodyNodes, footerNodes]
});
Object.assign(windowCmptRef.instance, { singleContent });
return windowCmptRef;
}
_attachWindowComponent(containerEl, windowCmptRef) {
this._applicationRef.attachView(windowCmptRef.hostView);
containerEl.appendChild(windowCmptRef.location.nativeElement);
return windowCmptRef;
}
_getContentRef(contentInjector, environmentInjector, content, activePortal, options) {
if (!content) {
return new ContentRef([]);
}
else if (content instanceof TemplateRef) {
return this._createFromTemplateRef(content, activePortal, options);
}
else if (isString(content)) {
return this._createFromString(content);
}
else {
return this._createFromComponent(contentInjector, environmentInjector, content, activePortal, options);
}
}
_createFromTemplateRef(templateRef, activePortal, options) {
const context = {
$implicit: activePortal,
close(result) {
activePortal.close(result);
},
dismiss(reason) {
activePortal.dismiss(reason);
}
};
const viewRef = templateRef.createEmbeddedView(context);
this._applicationRef.attachView(viewRef);
const containerNode = document.createElement('ng-container');
containerNode.append(...viewRef.rootNodes);
this._addDismissEventListener(containerNode, context, options);
this._addCloseEventListener(containerNode, context, options);
return new ContentRef([
options.headerSelector
? extractAndRemoveNodesBySelector(containerNode, options.headerSelector)
: [],
containerNode.childNodes,
options.footerSelector
? extractAndRemoveNodesBySelector(containerNode, options.footerSelector)
: []
], viewRef);
}
_createFromString(content) {
const component = this._document.createTextNode(`${content}`);
return new ContentRef([[component]]);
}
_createFromComponent(contentInjector, environmentInjector, componentType, context, options) {
const elementInjector = Injector.create({
providers: [{ provide: HubActivePortal, useValue: context }],
parent: contentInjector
});
const componentRef = createComponent(componentType, {
environmentInjector,
elementInjector
});
const componentNativeEl = componentRef.location.nativeElement;
if (options.scrollable) {
componentNativeEl.classList.add('component-host-scrollable');
}
this._applicationRef.attachView(componentRef.hostView);
this._addDismissEventListener(componentNativeEl, context, options);
this._addCloseEventListener(componentNativeEl, context, options);
// FIXME: we should here get rid of the component nativeElement
// and use `[Array.from(componentNativeEl.childNodes)]` instead and remove the above CSS class.
return new ContentRef([
options.headerSelector
? extractAndRemoveNodesBySelector(componentNativeEl, options.headerSelector)
: [],
componentNativeEl.childNodes,
options.footerSelector
? extractAndRemoveNodesBySelector(componentNativeEl, options.footerSelector)
: []
], componentRef.hostView, componentRef);
}
_setAriaHidden(element) {
const parent = element.parentElement;
if (parent && element !== this._document.body) {
Array.from(parent.children).forEach((sibling) => {
if (sibling !== element && sibling.nodeName !== 'SCRIPT') {
this._ariaHiddenValues.set(sibling, sibling.getAttribute('aria-hidden'));
sibling.setAttribute('aria-hidden', 'true');
}
});
this._setAriaHidden(parent);
}
}
_revertAriaHidden() {
this._ariaHiddenValues.forEach((value, element) => {
if (value) {
element.setAttribute('aria-hidden', value);
}
else {
element.removeAttribute('aria-hidden');
}
});
this._ariaHiddenValues.clear();
}
_registerPortalRef(hubPortalRef) {
const unregisterPortalRef = () => {
const index = this._portalRefs.indexOf(hubPortalRef);
if (index > -1) {
this._portalRefs.splice(index, 1);
this._activeInstances.emit(this._portalRefs);
}
};
this._portalRefs.push(hubPortalRef);
this._activeInstances.emit(this._portalRefs);
hubPortalRef.result.then(unregisterPortalRef, unregisterPortalRef);
}
_registerWindowCmpt(hubWindowCmpt) {
this._windowCmpts.push(hubWindowCmpt);
this._activeWindowCmptHasChanged.next();
hubWindowCmpt.onDestroy(() => {
const index = this._windowCmpts.indexOf(hubWindowCmpt);
if (index > -1) {
this._windowCmpts.splice(index, 1);
this._activeWindowCmptHasChanged.next();
}
});
}
/**
* Attaches click event listeners to elements within a container based on a specified dismiss selector to dismiss a portal.
*
* @param {HTMLElement} container - The `container` parameter is an HTMLElement that represents the DOM element which contains the
* portal content.
* @param {HubActivePortal} context - The `context` parameter in the `_addDismissEventListener` function refers to the active portal
* instance that is being displayed. It is used to call the `dismiss` method on the portal instance when a dismissible element is
* clicked.
* @param {HubPortalOptions} options - The `options` parameter is an object that contains configuration options for the portal. It
* may include properties such as `dismissSelector`, which is used to specify a CSS selector for elements that, when clicked, will
* dismiss the portal by calling the `dismiss` method on the `context` object.
*/
_addDismissEventListener(container, context, options) {
if (options.dismissSelector) {
const dismissaable = container.querySelectorAll(options.dismissSelector);
for (const item of Array.from(dismissaable)) {
item.addEventListener('click', () => context.dismiss());
}
}
}
/**
* Attaches click event listeners to elements matching a specified selector to close a portal window.
*
* @param {HTMLElement} container - The `container` parameter is an HTMLElement that represents the DOM element which contains the
* portal content.
* @param {HubActivePortal} context - The `context` parameter in the `_addCloseEventListener` function is of type `HubActivePortal`.
* It is used to reference the active portal instance within the function and call the `close()` method on it when a close event is
* triggered.
* @param {HubPortalOptions} options - The `options` parameter is an object that contains configuration options for the portal. It
* may include properties such as `closeSelector`, which is used to specify the selector for elements that can trigger the portal
* to close when clicked.
*/
_addCloseEventListener(container, context, options) {
if (options.closeSelector) {
const dismissaable = container.querySelectorAll(options.closeSelector);
for (const item of Array.from(dismissaable)) {
item.addEventListener('click', () => context.close());
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.5", ngImport: i0, type: HubPortalStack, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.5", ngImport: i0, type: HubPortalStack, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.5", ngImport: i0, type: HubPortalStack, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* Extracts child nodes matching a selector from a container element, removes those nodes from the DOM, and returns them as an array.
*
* @param {HTMLElement} container - The `container` parameter in the `extractAndRemoveNodesBySelector` function is an HTMLElement
* that represents the parent element within which we want to search for nodes matching a specific selector and remove them.
* @param {string} selector - The `selector` parameter in the `extractAndRemoveNodesBySelector` function is a string that
* represents a CSS selector. This selector is used to query and select specific elements within the `container` HTMLElement.
*
* @returns An array of nodes that were extracted from the container element based on the provided selector, and then removes those
* nodes from the DOM.
*/
function extractAndRemoveNodesBySelector(container, selector) {
let containerNodes = container.querySelectorAll(selector);
const nodes = Array.from(containerNodes).reduce((acc, c) => {
return [...acc, ...Array.from(c.childNodes)];
}, []);
// Selecciona los nodos dentro del contenedor que coincidan con el selector
const nodesToRemove = container.querySelectorAll(selector);
// Convertir NodeList a array y eliminar cada nodo del DOM
Array.from(nodesToRemove).forEach((node) => node.remove());
return nodes;
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9ydGFsLXN0YWNrLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvcG9ydGFsL3NyYy9saWIvcG9ydGFsLXN0YWNrLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMzQyxPQUFPLEVBQ04sY0FBYyxFQUVkLGVBQWUsRUFDZixtQkFBbUIsRUFDbkIsWUFBWSxFQUNaLE1BQU0sRUFDTixVQUFVLEVBQ1YsUUFBUSxFQUNSLE1BQU0sRUFDTixXQUFXLEVBRVgsTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUNOLFVBQVUsRUFDVixZQUFZLEVBQ1osU0FBUyxFQUNULFFBQVEsRUFDUixTQUFTLEVBQ1QsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QixPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQy9CLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUV0QyxPQUFPLEVBQUUsZUFBZSxFQUFFLFlBQVksRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUM3RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0saUJBQWlCLENBQUM7O0FBR2xELE1BQU0sT0FBTyxjQUFjO0lBYzFCO1FBYlEsb0JBQWUsR0FBRyxNQUFNLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDekMsY0FBUyxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUM3Qix5QkFBb0IsR0FBRyxNQUFNLENBQUMsbUJBQW1CLENBQUMsQ0FBQztRQUNuRCxjQUFTLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQzdCLGVBQVUsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7UUFFL0IsZ0NBQTJCLEdBQUcsSUFBSSxPQUFPLEVBQVEsQ0FBQztRQUNsRCxzQkFBaUIsR0FBZ0MsSUFBSSxHQUFHLEVBQUUsQ0FBQztRQUMzRCx3QkFBbUIsR0FB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cnQge1xuXHRBcHBsaWNhdGlvblJlZixcblx0Q29tcG9uZW50UmVmLFxuXHRjcmVhdGVDb21wb25lbnQsXG5cdEVudmlyb25tZW50SW5qZWN0b3IsXG5cdEV2ZW50RW1pdHRlcixcblx0aW5qZWN0LFxuXHRJbmplY3RhYmxlLFxuXHRJbmplY3Rvcixcblx0Tmdab25lLFxuXHRUZW1wbGF0ZVJlZixcblx0VHlwZVxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7XG5cdENvbnRlbnRSZWYsXG5cdGh1YkZvY3VzVHJhcCxcblx0aXNEZWZpbmVkLFxuXHRpc1N0cmluZyxcblx0U2Nyb2xsQmFyXG59IGZyb20gJ25nLWh1Yi11aS11dGlscyc7XG5pbXBvcnQgeyBTdWJqZWN0IH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyB0YWtlIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuaW1wb3J0IHsgSHViUG9ydGFsT3B0aW9ucywgSHViUG9ydGFsVXBkYXRhYmxlT3B0aW9ucyB9IGZyb20gJy4vcG9ydGFsLWNvbmZpZyc7XG5pbXBvcnQgeyBIdWJBY3RpdmVQb3J0YWwsIEh1YlBvcnRhbFJlZiB9IGZyb20gJy4vcG9ydGFsLXJlZic7XG5pbXBvcnQgeyBIdWJQb3J0YWxXaW5kb3cgfSBmcm9tICcuL3BvcnRhbC13aW5kb3cnO1xuXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIEh1YlBvcnRhbFN0YWNrIHtcblx0cHJpdmF0ZSBfYXBwbGljYXRpb25SZWYgPSBpbmplY3QoQXBwbGljYXRpb25SZWYpO1xuXHRwcml2YXRlIF9pbmplY3RvciA9IGluamVjdChJbmplY3Rvcik7XG5cdHByaXZhdGUgX2Vudmlyb25tZW50SW5qZWN0b3IgPSBpbmplY3QoRW52aXJvbm1lbnRJbmplY3Rvcik7XG5cdHByaXZhdGUgX2RvY3VtZW50ID0gaW5qZWN0KERPQ1VNRU5UKTtcblx0cHJpdmF0ZSBfc2Nyb2xsQmFyID0gaW5qZWN0KFNjcm9sbEJhcik7XG5cblx0cHJpdmF0ZSBfYWN0aXZlV2luZG93Q21wdEhhc0NoYW5nZWQgPSBuZXcgU3ViamVjdDx2b2lkPigpO1xuXHRwcml2YXRlIF9hcmlhSGlkZGVuVmFsdWVzOiBNYXA8RWxlbWVudCwgc3RyaW5nIHwgbnVsbD4gPSBuZXcgTWFwKCk7XG5cdHByaXZhdGUgX3Njcm9sbEJhclJlc3RvcmVGbjogbnVsbCB8ICgoKSA9PiB2b2lkKSA9IG51bGw7XG5cdHByaXZhdGUgX3BvcnRhbFJlZnM6IEh1YlBvcnRhbFJlZltdID0gW107XG5cdHByaXZhdGUgX3dpbmRvd0NtcHRzOiBDb21wb25lbnRSZWY8SHViUG9ydGFsV2luZG93PltdID0gW107XG5cdHByaXZhdGUgX2FjdGl2ZUluc3RhbmNlczogRXZlbnRFbWl0dGVyPEh1YlBvcnRhbFJlZltdPiA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcblxuXHRjb25zdHJ1Y3RvcigpIHtcblx0XHRjb25zdCBuZ1pvbmUgPSBpbmplY3QoTmdab25lKTtcblxuXHRcdC8vIFRyYXAgZm9jdXMgb24gYWN0aXZlIFdpbmRvd0NtcHRcblx0XHR0aGlzLl9hY3RpdmVXaW5kb3dDbXB0SGFzQ2hhbmdlZC5zdWJzY3JpYmUoKCkgPT4ge1xuXHRcdFx0aWYgKHRoaXMuX3dpbmRvd0NtcHRzLmxlbmd0aCkge1xuXHRcdFx0XHRjb25zdCBhY3RpdmVXaW5kb3dDbXB0ID1cblx0XHRcdFx0XHR0aGlzLl93aW5kb3dDbXB0c1t0aGlzLl93aW5kb3dDbXB0cy5sZW5ndGggLSAxXTtcblx0XHRcdFx0aHViRm9jdXNUcmFwKFxuXHRcdFx0XHRcdG5nWm9uZSxcblx0XHRcdFx0XHRhY3RpdmVXaW5kb3dDbXB0LmxvY2F0aW9uLm5hdGl2ZUVsZW1lbnQsXG5cdFx0XHRcdFx0dGhpcy5fYWN0aXZlV2luZG93Q21wdEhhc0NoYW5nZWRcblx0XHRcdFx0KTtcblx0XHRcdFx0dGhpcy5fcmV2ZXJ0QXJpYUhpZGRlbigpO1xuXHRcdFx0XHR0aGlzLl9zZXRBcmlhSGlkZGVuKGFjdGl2ZVdpbmRvd0NtcHQubG9jYXRpb24ubmF0aXZlRWxlbWVudCk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXHRwcml2YXRlIF9yZXN0b3JlU2Nyb2xsQmFyKCkge1xuXHRcdGNvbnN0IHNjcm9sbEJhclJlc3RvcmVGbiA9IHRoaXMuX3Njcm9sbEJhclJlc3RvcmVGbjtcblx0XHRpZiAoc2Nyb2xsQmFyUmVzdG9yZUZuKSB7XG5cdFx0XHR0aGlzLl9zY3JvbGxCYXJSZXN0b3JlRm4gPSBudWxsO1xuXHRcdFx0c2Nyb2xsQmFyUmVzdG9yZUZuKCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBfaGlkZVNjcm9sbEJhcigpIHtcblx0XHRpZiAoIXRoaXMuX3Njcm9sbEJhclJlc3RvcmVGbikge1xuXHRcdFx0dGhpcy5fc2Nyb2xsQmFyUmVzdG9yZUZuID0gdGhpcy5fc2Nyb2xsQmFyLmhpZGUoKTtcblx0XHR9XG5cdH1cblxuXHRvcGVuKFxuXHRcdGNvbnRlbnRJbmplY3RvcjogSW5qZWN0b3IsXG5cdFx0Y29udGVudDogYW55LFxuXHRcdG9wdGlvbnM6IEh1YlBvcnRhbE9wdGlvbnNcblx0KTogSHViUG9ydGFsUmVmIHtcblx0XHRjb25zdCBjb250YWluZXJFbCA9XG5cdFx0XHRvcHRpb25zLmNvbnRhaW5lciBpbnN0YW5jZW9mIEhUTUxFbGVtZW50XG5cdFx0XHRcdD8gb3B0aW9ucy5jb250YWluZXJcblx0XHRcdFx0OiBpc0RlZmluZWQob3B0aW9ucy5jb250YWluZXIpXG5cdFx0XHRcdD8gdGhpcy5fZG9jdW1lbnQucXVlcnlTZWxlY3RvcihvcHRpb25zLmNvbnRhaW5lciEpXG5cdFx0XHRcdDogdGhpcy5fZG9jdW1lbnQuYm9keTtcblxuXHRcdGlmICghY29udGFpbmVyRWwpIHtcblx0XHRcdHRocm93IG5ldyBFcnJvcihcblx0XHRcdFx0YFRoZSBzcGVjaWZpZWQgcG9ydGFsIGNvbnRhaW5lciBcIiR7XG5cdFx0XHRcdFx0b3B0aW9ucy5jb250YWluZXIgfHwgJ2JvZHknXG5cdFx0XHRcdH1cIiB3YXMgbm90IGZvdW5kIGluIHRoZSBET00uYFxuXHRcdFx0KTtcblx0XHR9XG5cblx0XHR0aGlzLl9oaWRlU2Nyb2xsQmFyKCk7XG5cblx0XHRjb25zdCBhY3RpdmVQb3J0YWwgPSBuZXcgSHViQWN0aXZlUG9ydGFsKCk7XG5cblx0XHRjb250ZW50SW5qZWN0b3IgPSBvcHRpb25zLmluamVjdG9yIHx8IGNvbnRlbnRJbmplY3Rvcjtcblx0XHRjb25zdCBlbnZpcm9ubWVudEluamVjdG9yID1cblx0XHRcdGNvbnRlbnRJbmplY3Rvci5nZXQoRW52aXJvbm1lbnRJbmplY3RvciwgbnVsbCkgfHxcblx0XHRcdHRoaXMuX2Vudmlyb25tZW50SW5qZWN0b3I7XG5cdFx0Y29uc3QgY29udGVudFJlZiA9IHRoaXMuX2dldENvbnRlbnRSZWYoXG5cdFx0XHRjb250ZW50SW5qZWN0b3IsXG5cdFx0XHRlbnZpcm9ubWVudEluamVjdG9yLFxuXHRcdFx0Y29udGVudCxcblx0XHRcdGFjdGl2ZVBvcnRhbCxcblx0XHRcdG9wdGlvbnNcblx0XHQpO1xuXG5cdFx0Y29uc3Qgd2luZG93Q21wdFJlZjogQ29tcG9uZW50UmVmPEh1YlBvcnRhbFdpbmRvdz4gPVxuXHRcdFx0dGhpcy5fY3JlYXRlV2luZG93Q29tcG9uZW50KGNvbnRlbnRSZWYubm9kZXMsIG9wdGlvbnMpO1xuXHRcdHRoaXMuX2F0dGFjaFdpbmRvd0NvbXBvbmVudChjb250YWluZXJFbCwgd2luZG93Q21wdFJlZik7XG5cdFx0Y29uc3QgaHViUG9ydGFsUmVmOiBIdWJQb3J0YWxSZWYgPSBuZXcgSHViUG9ydGFsUmVmKFxuXHRcdFx0d2luZG93Q21wdFJlZixcblx0XHRcdGNvbnRlbnRSZWYsXG5cdFx0XHRvcHRpb25zLmJlZm9yZURpc21pc3Ncblx0XHQpO1xuXG5cdFx0dGhpcy5fcmVnaXN0ZXJQb3J0YWxSZWYoaHViUG9ydGFsUmVmKTtcblx0XHR0aGlzLl9yZWdpc3RlcldpbmRvd0NtcHQod2luZG93Q21wdFJlZik7XG5cblx0XHQvLyBXZSBoYXZlIHRvIGNsZWFudXAgRE9NIGFmdGVyIHRoZSBsYXN0IHBvcnRhbCB3aGVuIEJPVEggJ2hpZGRlbicgd2FzIGVtaXR0ZWQgYW5kICdyZXN1bHQnIHByb21pc2Ugd2FzIHJlc29sdmVkOlxuXHRcdC8vIC0gd2l0aCBhbmltYXRpb25zIE9GRiwgJ2hpZGRlbicgZW1pdHMgc3luY2hyb25vdXNseSwgdGhlbiAncmVzdWx0JyBpcyByZXNvbHZlZCBhc3luY2hyb25vdXNseVxuXHRcdC8vIC0gd2l0aCBhbmltYXRpb25zIE9OLCAncmVzdWx0JyBpcyByZXNvbHZlZCBhc3luY2hyb25vdXNseSwgdGhlbiAnaGlkZGVuJyBlbWl0cyBhc3luY2hyb25vdXNseVxuXHRcdGh1YlBvcnRhbFJlZi5oaWRkZW4ucGlwZSh0YWtlKDEpKS5zdWJzY3JpYmUoKCkgPT5cblx0XHRcdFByb21pc2UucmVzb2x2ZSh0cnVlKS50aGVuKCgpID0+IHtcblx0XHRcdFx0aWYgKCF0aGlzLl9wb3J0YWxSZWZzLmxlbmd0aCkge1xuXHRcdFx0XHRcdHRoaXMuX2RvY3VtZW50LmJvZHkuY2xhc3NMaXN0LnJlbW92ZSgncG9ydGFsLW9wZW4nKTtcblx0XHRcdFx0XHR0aGlzLl9yZXN0b3JlU2Nyb2xsQmFyKCk7XG5cdFx0XHRcdFx0dGhpcy5fcmV2ZXJ0QXJpYUhpZGRlbigpO1xuXHRcdFx0XHR9XG5cdFx0XHR9KVxuXHRcdCk7XG5cblx0XHRhY3RpdmVQb3J0YWwuY2xvc2UgPSAocmVzdWx0OiBhbnkpID0+IHtcblx0XHRcdGh1YlBvcnRhbFJlZi5jbG9zZShyZXN1bHQpO1xuXHRcdH07XG5cdFx0YWN0aXZlUG9ydGFsLmRpc21pc3MgPSAocmVhc29uOiBhbnkpID0+IHtcblx0XHRcdGh1YlBvcnRhbFJlZi5kaXNtaXNzKHJlYXNvbik7XG5cdFx0fTtcblxuXHRcdGFjdGl2ZVBvcnRhbC51cGRhdGUgPSAob3B0aW9uczogSHViUG9ydGFsVXBkYXRhYmxlT3B0aW9ucykgPT4ge1xuXHRcdFx0aHViUG9ydGFsUmVmLnVwZGF0ZShvcHRpb25zKTtcblx0XHR9O1xuXG5cdFx0aHViUG9ydGFsUmVmLnVwZGF0ZShvcHRpb25zKTtcblx0XHRpZiAodGhpcy5fcG9ydGFsUmVmcy5sZW5ndGggPT09IDEpIHtcblx0XHRcdHRoaXMuX2RvY3VtZW50LmJvZHkuY2xhc3NMaXN0LmFkZCgncG9ydGFsLW9wZW4nKTtcblx0XHR9XG5cblx0XHR3aW5kb3dDbXB0UmVmLmNoYW5nZURldGVjdG9yUmVmLmRldGVjdENoYW5nZXMoKTtcblx0XHRyZXR1cm4gaHViUG9ydGFsUmVmO1xuXHR9XG5cblx0LyoqXG5cdCAqIFRvZ2dsZXMgYSBwb3J0YWwgYnkgZGlzbWlzc2luZyBhbGwgZXhpc3RpbmcgcG9ydGFscyBhbmQgd2FpdGluZyBmb3IgdGhlbSB0byBiZSBoaWRkZW5cblx0ICogYmVmb3JlIHNob3dpbmcgdGhlIG5ldyBvbmUuXG5cdCAqXG5cdCAqIEBwYXJhbSBjb250ZW50SW5qZWN0b3IgLSBUaGUgaW5qZWN0b3IgdG8gdXNlIGZvciBkZXBlbmRlbmN5IGluamVjdGlvblxuXHQgKiBAcGFyYW0gY29udGVudCAtIFRoZSBjb250ZW50IHRvIGRpc3BsYXkgKGNvbXBvbmVudCwgdGVtcGxhdGUsIG9yIHN0cmluZylcblx0ICogQHBhcmFtIG9wdGlvbnMgLSBQb3J0YWwgY29uZmlndXJhdGlvbiBvcHRpb25zXG5cdCAqIEByZXR1cm5zIEEgcmVmZXJlbmNlIHRvIHRoZSBuZXdseSBjcmVhdGVkIHBvcnRhbFxuXHQgKi9cblx0dG9nZ2xlKFxuXHRcdGNvbnRlbnRJbmplY3RvcjogSW5qZWN0b3IsXG5cdFx0Y29udGVudDogYW55LFxuXHRcdG9wdGlvbnM6IEh1YlBvcnRhbE9wdGlvbnNcblx0KTogSHViUG9ydGFsUmVmIHtcblx0XHQvLyBHZXQgY3VycmVudCBwb3J0YWxzIGJlZm9yZSBjcmVhdGluZyB0aGUgbmV3IG9uZVxuXHRcdGNvbnN0IGV4aXN0aW5nUG9ydGFscyA9IFsuLi50aGlzLl9wb3J0YWxSZWZzXTtcblxuXHRcdC8vIENyZWF0ZSB0aGUgbmV3IHBvcnRhbCBidXQgc3VwcHJlc3Mgbm9ybWFsIHJlZ2lzdHJhdGlvblxuXHRcdGNvbnN0IGNvbnRhaW5lckVsID1cblx0XHRcdG9wdGlvbnMuY29udGFpbmVyIGluc3RhbmNlb2YgSFRNTEVsZW1lbnRcblx0XHRcdFx0PyBvcHRpb25zLmNvbnRhaW5lclxuXHRcdFx0XHQ6IGlzRGVmaW5lZChvcHRpb25zLmNvbnRhaW5lcilcblx0XHRcdFx0PyB0aGlzLl9kb2N1bWVudC5xdWVyeVNlbGVjdG9yKG9wdGlvbnMuY29udGFpbmVyISlcblx0XHRcdFx0OiB0aGlzLl9kb2N1bWVudC5ib2R5O1xuXG5cdFx0aWYgKCFjb250YWluZXJFbCkge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgVGhlIHNwZWNpZmllZCBwb3J0YWwgY29udGFpbmVyIFwiJHtcblx0XHRcdFx0XHRvcHRpb25zLmNvbnRhaW5lciB8fCAnYm9keSdcblx0XHRcdFx0fVwiIHdhcyBub3QgZm91bmQgaW4gdGhlIERPTS5gXG5cdFx0XHQpO1xuXHRcdH1cblxuXHRcdHRoaXMuX2hpZGVTY3JvbGxCYXIoKTtcblxuXHRcdGNvbnN0IGFjdGl2ZVBvcnRhbCA9IG5ldyBIdWJBY3RpdmVQb3J0YWwoKTtcblx0XHRjb250ZW50SW5qZWN0b3IgPSBvcHRpb25zLmluamVjdG9yIHx8IGNvbnRlbnRJbmplY3Rvcjtcblx0XHRjb25zdCBlbnZpcm9ubWVudEluamVjdG9yID1cblx0XHRcdGNvbnRlbnRJbmplY3Rvci5nZXQoRW52aXJvbm1lbnRJbmplY3RvciwgbnVsbCkgfHxcblx0XHRcdHRoaXMuX2Vudmlyb25tZW50SW5qZWN0b3I7XG5cblx0XHRjb25zdCBjb250ZW50UmVmID0gdGhpcy5fZ2V0Q29udGVudFJlZihcblx0XHRcdGNvbnRlbnRJbmplY3Rvcixcblx0XHRcdGVudmlyb25tZW50SW5qZWN0b3IsXG5cdFx0XHRjb250ZW50LFxuXHRcdFx0YWN0aXZlUG9ydGFsLFxuXHRcdFx0b3B0aW9uc1xuXHRcdCk7XG5cblx0XHRjb25zdCB3aW5kb3dDbXB0UmVmID0gdGhpcy5fY3JlYXRlV2luZG93Q29tcG9uZW50KFxuXHRcdFx0Y29udGVudFJlZi5ub2Rlcyxcblx0XHRcdG9wdGlvbnNcblx0XHQpO1xuXG5cdFx0Y29uc3QgbmV3UG9ydGFsUmVmID0gbmV3IEh1YlBvcnRhbFJlZihcblx0XHRcdHdpbmRvd0NtcHRSZWYsXG5cdFx0XHRjb250ZW50UmVmLFxuXHRcdFx0b3B0aW9ucy5iZWZvcmVEaXNtaXNzXG5cdFx0KTtcblxuXHRcdC8vIFNldHVwIGFjdGl2ZSBwb3J0YWwgbWV0aG9kc1xuXHRcdGFjdGl2ZVBvcnRhbC5jbG9zZSA9IChyZXN1bHQ6IGFueSkgPT4ge1xuXHRcdFx0bmV3UG9ydGFsUmVmLmNsb3NlKHJlc3VsdCk7XG5cdFx0fTtcblx0XHRhY3RpdmVQb3J0YWwuZGlzbWlzcyA9IChyZWFzb246IGFueSkgPT4ge1xuXHRcdFx0bmV3UG9ydGFsUmVmLmRpc21pc3MocmVhc29uKTtcblx0XHR9O1xuXHRcdGFjdGl2ZVBvcnRhbC51cGRhdGUgPSAob3B0aW9uczogSHViUG9ydGFsVXBkYXRhYmxlT3B0aW9ucykgPT4ge1xuXHRcdFx0bmV3UG9ydGFsUmVmLnVwZGF0ZShvcHRpb25zKTtcblx0XHR9O1xuXG5cdFx0Ly8gRGlzbWlzcyBhbGwgZXhpc3RpbmcgcG9ydGFscyBhbmQgd2FpdCBmb3IgdGhlbSB0byBiZSBoaWRkZW5cblx0XHRjb25zdCBoaWRlUHJvbWlzZXMgPSBleGlzdGluZ1BvcnRhbHMubWFwKFxuXHRcdFx0KHBvcnRhbCkgPT5cblx0XHRcdFx0bmV3IFByb21pc2U8dm9pZD4oKHJlc29sdmUpID0+IHtcblx0XHRcdFx0XHRwb3J0YWwuaGlkZGVuLnBpcGUodGFrZSgxKSkuc3Vic2NyaWJlKCgpID0+IHtcblx0XHRcdFx0XHRcdHJlc29sdmUoKTtcblx0XHRcdFx0XHR9KTtcblx0XHRcdFx0XHRwb3J0YWwuZGlzbWlzcygndG9nZ2xlJyk7XG5cdFx0XHRcdH0pXG5cdFx0KTtcblxuXHRcdC8vIEFmdGVyIGFsbCBwb3J0YWxzIGFyZSBoaWRkZW4sIHJlZ2lzdGVyIGFuZCBzaG93IHRoZSBuZXcgb25lXG5cdFx0UHJvbWlzZS5hbGwoaGlkZVByb21pc2VzKS50aGVuKCgpID0+IHtcblx0XHRcdHRoaXMuX2F0dGFjaFdpbmRvd0NvbXBvbmVudChjb250YWluZXJFbCwgd2luZG93Q21wdFJlZik7XG5cblx0XHRcdHRoaXMuX3JlZ2lzdGVyUG9ydGFsUmVmKG5ld1BvcnRhbFJlZik7XG5cdFx0XHR0aGlzLl9yZWdpc3RlcldpbmRvd0NtcHQod2luZG93Q21wdFJlZik7XG5cblx0XHRcdGlmICh0aGlzLl9wb3J0YWxSZWZzLmxlbmd0aCA9PT0gMSkge1xuXHRcdFx0XHR0aGlzLl9kb2N1bWVudC5ib2R5LmNsYXNzTGlzdC5hZGQoJ3BvcnRhbC1vcGVuJyk7XG5cdFx0XHR9XG5cblx0XHRcdG5ld1BvcnRhbFJlZi51cGRhdGUob3B0aW9ucyk7XG5cdFx0XHR3aW5kb3dDbXB0UmVmLmNoYW5nZURldGVjdG9yUmVmLmRldGVjdENoYW5nZXMoKTtcblx0XHR9KTtcblxuXHRcdC8vIFNldHVwIGhpZGRlbiBjbGVhbnVwIGxpa2UgaW4gb3BlbigpXG5cdFx0bmV3UG9ydGFsUmVmLmhpZGRlbi5waXBlKHRha2UoMSkpLnN1YnNjcmliZSgoKSA9PlxuXHRcdFx0UHJvbWlzZS5yZXNvbHZlKHRydWUpLnRoZW4oKCkgPT4ge1xuXHRcdFx0XHRpZiAoIXRoaXMuX3BvcnRhbFJlZnMubGVuZ3RoKSB7XG5cdFx0XHRcdFx0dGhpcy5fZG9jdW1lbnQuYm9keS5jbGFzc0xpc3QucmVtb3ZlKCdwb3J0YWwtb3BlbicpO1xuXHRcdFx0XHRcdHRoaXMuX3Jlc3RvcmVTY3JvbGxCYXIoKTtcblx0XHRcdFx0XHR0aGlzLl9yZXZlcnRBcmlhSGlkZGVuKCk7XG5cdFx0XHRcdH1cblx0XHRcdH0pXG5cdFx0KTtcblxuXHRcdHJldHVybiBuZXdQb3J0YWxSZWY7XG5cdH1cblxuXHRnZXQgYWN0aXZlSW5zdGFuY2VzKCkge1xuXHRcdHJldHVybiB0aGlzLl9hY3RpdmVJbnN0YW5jZXM7XG5cdH1cblxuXHRkaXNtaXNzQWxsKHJlYXNvbj86IGFueSkge1xuXHRcdHRoaXMuX3BvcnRhbFJlZnMuZm9yRWFjaCgoaHViUG9ydGFsUmVmKSA9PlxuXHRcdFx0aHViUG9ydGFsUmVmLmRpc21pc3MocmVhc29uKVxuXHRcdCk7XG5cdH1cblxuXHRoYXNPcGVuUG9ydGFscygpOiBib29sZWFuIHtcblx0XHRyZXR1cm4gdGhpcy5fcG9ydGFsUmVmcy5sZW5ndGggPiAwO1xuXHR9XG5cblx0cHJpdmF0ZSBfY3JlYXRlV2luZG93Q29tcG9uZW50KFxuXHRcdFtoZWFkZXJOb2RlcywgYm9keU5vZGVzLCBmb290ZXJOb2Rlc106IE5vZGVbXVtdLFxuXHRcdG9wdGlvbnM6IEh1YlBvcnRhbE9wdGlvbnNcblx0KTogQ29tcG9uZW50UmVmPEh1YlBvcnRhbFdpbmRvdz4ge1xuXHRcdGNvbnN0IHNpbmdsZUNvbnRlbnQgPVxuXHRcdFx0IW9wdGlvbnMuaGVhZGVyU2VsZWN0b3IgJiYgIW9wdGlvbnMuZm9vdGVyU2VsZWN0b3I7XG5cdFx0bGV0IHdpbmRvd0NtcHRSZWYgPSBjcmVhdGVDb21wb25lbnQoSHViUG9ydGFsV2luZG93LCB7XG5cdFx0XHRlbnZpcm9ubWVudEluamVjdG9yOiB0aGlzLl9hcHBsaWNhdGlvblJlZi5pbmplY3Rvcixcblx0XHRcdGVsZW1lbnRJbmplY3RvcjogdGhpcy5faW5qZWN0b3IsXG5cdFx0XHRwcm9qZWN0YWJsZU5vZGVzOiBzaW5nbGVDb250ZW50XG5cdFx0XHRcdD8gW2JvZHlOb2Rlc11cblx0XHRcdFx0OiBbW10sIGhlYWRlck5vZGVzLCBib2R5Tm9kZXMsIGZvb3Rlck5vZGVzXVxuXHRcdH0pO1xuXG5cdFx0T2JqZWN0LmFzc2lnbih3aW5kb3dDbXB0UmVmLmluc3RhbmNlLCB7IHNpbmdsZUNvbnRlbnQgfSk7XG5cblx0XHRyZXR1cm4gd2luZG93Q21wdFJlZjtcblx0fVxuXG5cdHByaXZhdGUgX2F0dGFjaFdpbmRvd0NvbXBvbmVudChcblx0XHRjb250YWluZXJFbDogRWxlbWVudCxcblx0XHR3aW5kb3dDbXB0UmVmOiBDb21wb25lbnRSZWY8SHViUG9ydGFsV2luZG93PlxuXHQpOiBDb21wb25lbnRSZWY8SHViUG9ydGFsV2luZG93PiB7XG5cdFx0dGhpcy5fYXBwbGljYXRpb25SZWYuYXR0YWNoVmlldyh3aW5kb3dDbXB0UmVmLmhvc3RWaWV3KTtcblx0XHRjb250YWluZXJFbC5hcHBlbmRDaGlsZCh3aW5kb3dDbXB0UmVmLmxvY2F0aW9uLm5hdGl2ZUVsZW1lbnQpO1xuXHRcdHJldHVybiB3aW5kb3dDbXB0UmVmO1xuXHR9XG5cblx0cHJpdmF0ZSBfZ2V0Q29udGVudFJlZihcblx0XHRjb250ZW50SW5qZWN0b3I6IEluamVjdG9yLFxuXHRcdGVudmlyb25tZW50SW5qZWN0b3I6IEVudmlyb25tZW50SW5qZWN0b3IsXG5cdFx0Y29udGVudDogVHlwZTxhbnk+IHwgVGVtcGxhdGVSZWY8YW55PiB8IHN0cmluZyxcblx0XHRhY3RpdmVQb3J0YWw6IEh1YkFjdGl2ZVBvcnRhbCxcblx0XHRvcHRpb25zOiBIdWJQb3J0YWxPcHRpb25zXG5cdCk6IENvbnRlbnRSZWYge1xuXHRcdGlmICghY29udGVudCkge1xuXHRcdFx0cmV0dXJuIG5ldyBDb250ZW50UmVmKFtdKTtcblx0XHR9IGVsc2UgaWYgKGNvbnRlbnQgaW5zdGFuY2VvZiBUZW1wbGF0ZVJlZikge1xuXHRcdFx0cmV0dXJuIHRoaXMuX2NyZWF0ZUZyb21UZW1wbGF0ZVJlZihjb250ZW50LCBhY3RpdmVQb3J0YWwsIG9wdGlvbnMpO1xuXHRcdH0gZWxzZSBpZiAoaXNTdHJpbmcoY29udGVudCkpIHtcblx0XHRcdHJldHVybiB0aGlzLl9jcmVhdGVGcm9tU3RyaW5nKGNvbnRlbnQpO1xuXHRcdH0gZWxzZSB7XG5cdFx0XHRyZXR1cm4gdGhpcy5fY3JlYXRlRnJvbUNvbXBvbmVudChcblx0XHRcdFx0Y29udGVudEluamVjdG9yLFxuXHRcdFx0XHRlbnZpcm9ubWVudEluamVjdG9yLFxuXHRcdFx0XHRjb250ZW50LFxuXHRcdFx0XHRhY3RpdmVQb3J0YWwsXG5cdFx0XHRcdG9wdGlvbnNcblx0XHRcdCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBfY3JlYXRlRnJvbVRlbXBsYXRlUmVmKFxuXHRcdHRlbXBsYXRlUmVmOiBUZW1wbGF0ZVJlZjxhbnk+LFxuXHRcdGFjdGl2ZVBvcnRhbDogSHViQWN0aXZlUG9ydGFsLFxuXHRcdG9wdGlvbnM6IEh1YlBvcnRhbE9wdGlvbnNcblx0KTogQ29udGVudFJlZiB7XG5cdFx0Y29uc3QgY29udGV4dCA9IHtcblx0XHRcdCRpbXBsaWNpdDogYWN0aXZlUG9ydGFsLFxuXHRcdFx0Y2xvc2UocmVzdWx0KSB7XG5cdFx0XHRcdGFjdGl2ZVBvcnRhbC5jbG9zZShyZXN1bHQpO1xuXHRcdFx0fSxcblx0XHRcdGRpc21pc3MocmVhc29uKSB7XG5cdFx0XHRcdGFjdGl2ZVBvcnRhbC5kaXNtaXNzKHJlYXNvbik7XG5cdFx0XHR9XG5cdFx0fTtcblx0XHRjb25zdCB2aWV3UmVmID0gdGVtcGxhdGVSZWYuY3JlYXRlRW1iZWRkZWRWaWV3KGNvbnRleHQpO1xuXHRcdHRoaXMuX2FwcGxpY2F0aW9uUmVmLmF0dGFjaFZpZXcodmlld1JlZik7XG5cblx0XHRjb25zdCBjb250YWluZXJOb2RlID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnbmctY29udGFpbmVyJyk7XG5cdFx0Y29udGFpbmVyTm9kZS5hcHBlbmQoLi4udmlld1JlZi5yb290Tm9kZXMpO1xuXG5cdFx0dGhpcy5fYWRkRGlzbWlzc0V2ZW50TGlzdGVuZXIoY29udGFpbmVyTm9kZSwgY29udGV4dCBhcyBhbnksIG9wdGlvbnMpO1xuXHRcdHRoaXMuX2FkZENsb3NlRXZlbnRMaXN0ZW5lcihjb250YWluZXJOb2RlLCBjb250ZXh0IGFzIGFueSwgb3B0aW9ucyk7XG5cblx0XHRyZXR1cm4gbmV3IENvbnRlbnRSZWYoXG5cdFx0XHRbXG5cdFx0XHRcdG9wdGlvbnMuaGVhZGVyU2VsZWN0b3Jcblx0XHRcdFx0XHQ/IGV4dHJhY3RBbmRSZW1vdmVOb2Rlc0J5U2VsZWN0b3IoXG5cdFx0XHRcdFx0XHRcdGNvbnRhaW5lck5vZGUsXG5cdFx0XHRcdFx0XHRcdG9wdGlvbnMuaGVhZGVyU2VsZWN0b3Jcblx0XHRcdFx0XHQgIClcblx0XHRcdFx0XHQ6IFtdLFxuXHRcdFx0XHRjb250YWluZXJOb2RlLmNoaWxkTm9kZXMgYXMgYW55LFxuXHRcdFx0XHRvcHRpb25zLmZvb3RlclNlbGVjdG9yXG5cdFx0XHRcdFx0PyBleHRyYWN0QW5kUmVtb3ZlTm9kZXNCeVNlbGVjdG9yKFxuXHRcdFx0XHRcdFx0XHRjb250YWluZXJOb2RlLFxuXHRcdFx0XHRcdFx0XHRvcHRpb25zLmZvb3RlclNlbGVjdG9yXG5cdFx0XHRcdFx0ICApXG5cdFx0XHRcdFx0OiBbXVxuXHRcdFx0XSxcblx0XHRcdHZpZXdSZWZcblx0XHQpO1xuXHR9XG5cblx0cHJpdmF0ZSBfY3JlYXRlRnJvbVN0cmluZyhjb250ZW50OiBzdHJpbmcpOiBDb250ZW50UmVmIHtcblx0XHRjb25zdCBjb21wb25lbnQgPSB0aGlzLl9kb2N1bWVudC5jcmVhdGVUZXh0Tm9kZShgJHtjb250ZW50fWApO1xuXHRcdHJldHVybiBuZXcgQ29udGVudFJlZihbW2NvbXBvbmVudF1dKTt