gentics-ui-core
Version:
This is the common core framework for the Gentics CMS and Mesh UI, and other Angular applications.
378 lines • 53.2 kB
JavaScript
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, Output, Renderer2, ViewChild } from '@angular/core';
import { coerceToBoolean } from '../../common/coerce-to-boolean';
import * as i0 from "@angular/core";
import * as i1 from "@angular/common";
export const CURSOR_STYLE_CLASS = 'gtx-split-view-container-resizing';
/**
* A container that provides a ["master-detail" interface](https://en.wikipedia.org/wiki/Master%E2%80%93detail_interface)
* with two resizable panels denoted by the `left` and `right` attributes on its children.
*
* There should only be a single instance of SplitViewContainer used at a time, and it is intended to be the
* main structural container of the "master/detail" part of the app - i.e. the content listing and editing view.
*
* ```html
* <gtx-split-view-container
* [rightPanelVisible]="hasContent"
* [(focusedPanel)]="splitFocus">
*
* <div class="list-pane" left>
* <!-- Left panel contents -->
* </div>
*
* <div class="content-pane" right>
* <!-- Right panel contents -->
* </div>
*
* </gtx-split-view-container>
* ```
*
* ## Setting Scroll Positions
*
* The SplitViewContainer instance exposes two public methods which can be used to manually set the `scrollTop`
* property of either the right or left panels: `.scrollLeftPanelTo()` and `.scrollRightPanelTo()`.
*
* This can be used, for example, to scroll the contents pane (right panel) back to the top when the route changes.
* An example usage follows:
*
* ```typescript
* export class App {
* @ViewChild(SplitViewContainer)
* private splitViewContainer: SplitViewContainer;
*
* constructor(private router: Router) {
* this.subscription = router.subscribe(() => {
* // scroll the right panel to the top whenever
* // the route changes.
* this.splitViewContainer.scrollRightPanelTo(0);
* });
* }
* }
* ```
*/
export class SplitViewContainer {
constructor(ownElement, changeDetector, renderer) {
this.ownElement = ownElement;
this.changeDetector = changeDetector;
this.renderer = renderer;
/**
* Tells the SplitViewContainer which side should be focused.
* Can be used to double-bind the property:
* `<split-view-container [(focusedPanel)]="property">`
*/
this.focusedPanel = 'left';
/** Initial width of the left panel in "large" layout in percent of the container width. */
this.initialSplit = 50;
/**
* Emits when the split between the panels is changed. Allows double-binding:
* `<split-view-container [(split)]="leftWidth">...</split-view-container>`
*/
this.splitChange = new EventEmitter();
/**
* The smallest panel size in percent the left
* and right panels will shrink to when resizing.
*/
this.minPanelSizePercent = 5;
/**
* The smallest panel size in pixels the left
* and right panels will shrink to when resizing.
*/
this.minPanelSizePixels = 20;
/**
* Triggers when the right panel is closed.
*/
this.rightPanelClosed = new EventEmitter(true);
/**
* Triggers when the right panel is opened.
*/
this.rightPanelOpened = new EventEmitter(true);
/**
* Triggers when the left panel is focused.
*/
this.leftPanelFocused = new EventEmitter();
/**
* Triggers when the right panel is focused.
*/
this.rightPanelFocused = new EventEmitter();
/**
* Triggers when the focused panel changes.
* Can be used to double-bind the property:
* `<split-view-container [(focusedPanel)]="property">`
*/
this.focusedPanelChange = new EventEmitter();
/**
* Triggers when the user starts resizing the split amount between the panels.
* Receives the size of the left panel in % of the container width as argument.
*/
this.splitDragStart = new EventEmitter(true);
/**
* Triggers when the user resizes the split amount between the panels.
* Receives the size of the left panel in % of the container width as argument.
*/
this.splitDragEnd = new EventEmitter(true);
/** @internal EventTarget for tracking when the mouse leaves the page. */
this.globalEventTarget = window.document;
/** @internal The Element to which cursor styles are applied. */
this.globalCursorStyleTarget = window.document && window.document.body;
this.resizing = false;
// Actual value used in the template. If the focus is set to the right panel,
// but the right panel has no content, the left panel is focused instead.
this.rightPanelActuallyFocused = false;
/**
* When split is passed by the parent component, only emit events on resize.
* Otherwise, the width is managed by the SplitViewContainer.
*/
this.widthHandledExternally = false;
/**
* To prevent race conditions by click events, do not emit a focusedPanelChange
* when the last change detection changed the focus. Example:
* 1. element in left panel is clicked, sets focus to "right"
* 2. click event bubbles to the SplitViewContainer, leftPanelClicked
* 3. focus would be set to the left panel again, but should be ignored
*/
this.focusJustChanged = false;
this.isSwipeable = true;
this.cleanups = [];
this.moveResizer = (event) => {
let resizerXPosition = this.getAdjustedPosition(event.clientX);
this.visibleResizer.nativeElement.style.left = resizerXPosition + '%';
};
this.endResizing = (event) => {
const adjustedWith = this.getAdjustedPosition(event.clientX);
this.splitChange.emit(adjustedWith);
if (!this.widthHandledExternally) {
this.split = adjustedWith;
}
this.resizing = false;
this.changeDetector.markForCheck();
this.cleanups.forEach(cleanup => cleanup());
this.cleanups = [];
this.splitDragEnd.emit(this.split);
};
}
/**
* Disable touch swipe gestures when used.
*/
set noswipe(val) {
this.isSwipeable = !coerceToBoolean(val);
// Toggle Hammer gesture recognizers
if (this.hammerManager) {
this.hammerManager.set({ enable: this.isSwipeable });
}
}
ngOnInit() {
if (!this.split) {
this.split = this.initialSplit;
}
}
ngAfterViewInit() {
if (!this.ownElement || !this.ownElement.nativeElement) {
return;
}
// Allow any layout changes to stabilize (e.g. divs with ngIf showing/hiding)
// before we calculate the final position of the SplitViewContainer
const timeout = setTimeout(() => this.fitContainerToViewport());
this.cleanups.push(() => clearTimeout(timeout));
this.initSwipeHandler();
}
ngOnChanges(changes) {
if (changes['focusedPanel'] || changes['rightPanelVisible']) {
// Prevent an invalid state where the SplitViewContainer
// has no right panel, but the right panel should be focused.
const shouldFocusRightPanel = this.focusedPanel === 'right' && this.rightPanelVisible;
if (shouldFocusRightPanel !== this.rightPanelActuallyFocused) {
this.rightPanelActuallyFocused = shouldFocusRightPanel;
if (shouldFocusRightPanel) {
this.rightPanelFocused.emit();
}
else {
this.leftPanelFocused.emit();
}
clearTimeout(this.focusJustChangedTimeout);
this.focusJustChanged = true;
this.focusJustChangedTimeout = setTimeout(() => {
this.focusJustChanged = false;
}, 50);
}
}
const rightPanelVisibleChange = changes['rightPanelVisible'];
if (rightPanelVisibleChange && !rightPanelVisibleChange.isFirstChange()) {
if (rightPanelVisibleChange.currentValue) {
this.rightPanelOpened.emit();
}
else {
this.rightPanelClosed.emit();
}
}
if (changes['split']) {
this.widthHandledExternally = true;
}
}
ngOnDestroy() {
this.cleanups.forEach(cleanup => cleanup());
this.cleanups = [];
clearTimeout(this.focusJustChangedTimeout);
this.destroySwipeHandler();
}
/**
* Set the scrollTop of the left panel
*/
scrollLeftPanelTo(scrollTop) {
this.leftPanel.nativeElement.scrollTop = scrollTop;
}
/**
* Set the scrollTop of the right panel
*/
scrollRightPanelTo(scrollTop) {
this.rightPanel.nativeElement.scrollTop = scrollTop;
}
leftPanelClicked() {
if (this.rightPanelActuallyFocused && !this.focusJustChanged) {
this.focusedPanelChange.emit('left');
}
}
rightPanelClicked() {
if (!this.rightPanelActuallyFocused && !this.focusJustChanged && this.rightPanelVisible) {
this.focusedPanelChange.emit('right');
}
}
startResizer(event) {
if (event.which != 1 || !this.leftPanel.nativeElement) {
return;
}
event.preventDefault();
const resizeHandle = event.currentTarget;
this.resizeMouseOffset = event.clientX - resizeHandle.getBoundingClientRect().left;
let mouseMoveTarget = this.globalEventTarget;
// Use setCapture on older browsers, document:mousemove on newer browsers
if (resizeHandle.setCapture) {
mouseMoveTarget = resizeHandle;
resizeHandle.setCapture();
this.cleanups.push(() => resizeHandle.releaseCapture());
this.cleanups.push(this.renderer.listen(resizeHandle, 'losecapture', this.endResizing));
}
// Set cursor styles & bind events on document (the Angular way)
this.renderer.addClass(this.globalCursorStyleTarget, CURSOR_STYLE_CLASS);
this.cleanups.push(() => this.renderer.removeClass(this.globalCursorStyleTarget, CURSOR_STYLE_CLASS), this.renderer.listen(mouseMoveTarget, 'mousemove', this.moveResizer), this.renderer.listen(this.globalEventTarget, 'mouseup', this.endResizing));
// Start resizing
let resizerXPosition = this.getAdjustedPosition(event.clientX);
this.resizing = true;
this.visibleResizer.nativeElement.style.left = resizerXPosition + '%';
this.changeDetector.markForCheck();
this.splitDragStart.emit(resizerXPosition);
}
/**
* (hacky) After initializing the view, make this component fill the height of the viewport.
* Only applied if the split-view-container element is not styled in the consuming application.
*/
fitContainerToViewport() {
const element = this.ownElement.nativeElement;
if (element.firstElementChild.offsetParent !== element) {
const css = element.style;
css.top = element.offsetTop + 'px';
css.bottom = css.left = css.right = '0';
css.position = 'absolute';
}
}
/**
* Set up a Hammerjs-based swipe gesture handler to allow swiping between two panes.
*/
initSwipeHandler() {
// set up swipe gesture handler
this.hammerManager = new Hammer(this.ownElement.nativeElement, { enable: this.isSwipeable });
this.hammerManager.on('swipe', (e) => {
if (e.pointerType === 'touch') {
// Hammerjs represents directions with an enum,
// 2 = left, 4 = right.
if (e.direction === 4) {
this.leftPanelClicked();
}
if (e.direction === 2) {
this.rightPanelClicked();
}
}
});
}
destroySwipeHandler() {
this.hammerManager.destroy();
}
/**
* Helper function to keep the resize functionality
* within its limits (minPanelSizePixels & minPanelSizePercent).
* @return Returns the adjusted X position in % of the container width.
*/
getAdjustedPosition(mouseClientX) {
const container = this.resizeContainer.nativeElement;
const containerOffset = container.getBoundingClientRect().left;
const containerWidth = container.clientWidth;
const resizerWidth = this.resizer.nativeElement.offsetWidth;
const maxXPixels = containerWidth - resizerWidth - this.minPanelSizePixels;
const maxXPercent = 100 * (1 - resizerWidth / containerWidth) - this.minPanelSizePercent;
let relativeX = mouseClientX - this.resizeMouseOffset - containerOffset;
if (relativeX < this.minPanelSizePixels) {
relativeX = this.minPanelSizePixels;
}
else if (relativeX > maxXPixels) {
relativeX = maxXPixels;
}
let percentX = 100 * (relativeX / containerWidth);
if (percentX < this.minPanelSizePercent) {
percentX = this.minPanelSizePercent;
}
else if (percentX > maxXPercent) {
percentX = maxXPercent;
}
return percentX;
}
}
/** @nocollapse */ SplitViewContainer.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.8", ngImport: i0, type: SplitViewContainer, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component });
/** @nocollapse */ SplitViewContainer.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.8", type: SplitViewContainer, selector: "gtx-split-view-container", inputs: { rightPanelVisible: "rightPanelVisible", focusedPanel: "focusedPanel", split: "split", initialSplit: "initialSplit", minPanelSizePercent: "minPanelSizePercent", minPanelSizePixels: "minPanelSizePixels", noswipe: "noswipe" }, outputs: { splitChange: "splitChange", rightPanelClosed: "rightPanelClosed", rightPanelOpened: "rightPanelOpened", leftPanelFocused: "leftPanelFocused", rightPanelFocused: "rightPanelFocused", focusedPanelChange: "focusedPanelChange", splitDragStart: "splitDragStart", splitDragEnd: "splitDragEnd" }, viewQueries: [{ propertyName: "resizeContainer", first: true, predicate: ["resizeContainer"], descendants: true, static: true }, { propertyName: "leftPanel", first: true, predicate: ["leftPanel"], descendants: true, static: true }, { propertyName: "rightPanel", first: true, predicate: ["rightPanel"], descendants: true, static: true }, { propertyName: "resizer", first: true, predicate: ["resizer"], descendants: true, static: true }, { propertyName: "visibleResizer", first: true, predicate: ["visibleResizer"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"slideable\"\n [class.hasRightPanel]=\"rightPanelVisible\"\n [class.hasNoRightPanel]=\"!rightPanelVisible\"\n [class.focusedRight]=\"rightPanelActuallyFocused\"\n [class.focusedLeft]=\"!rightPanelActuallyFocused\"\n [class.resizing]=\"resizing\"\n #resizeContainer>\n\n <div class=\"left-panel\" #leftPanel\n (click)=\"leftPanelClicked()\"\n [style.minWidth.%]=\"split\"\n [style.maxWidth.%]=\"split\">\n <ng-content select=\"[left]\"></ng-content>\n </div>\n\n <div #resizer class=\"resizer\" (mousedown)=\"startResizer($event)\">\n <i class=\"material-icons\">more_vert</i>\n </div>\n\n <div class=\"right-panel\" #rightPanel (click)=\"rightPanelClicked()\">\n <ng-content select=\"[right]\"></ng-content>\n </div>\n\n <div class=\"focus-switcher-right\" (click)=\"rightPanelClicked()\"></div>\n <div class=\"focus-switcher-left\" (click)=\"leftPanelClicked()\"></div>\n\n <div class=\"visible-resizer\" [hidden]=\"!resizing\" #visibleResizer></div>\n <div class=\"resizing-overlay\" *ngIf=\"resizing\"></div>\n\n</div>\n", directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.8", ngImport: i0, type: SplitViewContainer, decorators: [{
type: Component,
args: [{ selector: 'gtx-split-view-container', template: "<div class=\"slideable\"\n [class.hasRightPanel]=\"rightPanelVisible\"\n [class.hasNoRightPanel]=\"!rightPanelVisible\"\n [class.focusedRight]=\"rightPanelActuallyFocused\"\n [class.focusedLeft]=\"!rightPanelActuallyFocused\"\n [class.resizing]=\"resizing\"\n #resizeContainer>\n\n <div class=\"left-panel\" #leftPanel\n (click)=\"leftPanelClicked()\"\n [style.minWidth.%]=\"split\"\n [style.maxWidth.%]=\"split\">\n <ng-content select=\"[left]\"></ng-content>\n </div>\n\n <div #resizer class=\"resizer\" (mousedown)=\"startResizer($event)\">\n <i class=\"material-icons\">more_vert</i>\n </div>\n\n <div class=\"right-panel\" #rightPanel (click)=\"rightPanelClicked()\">\n <ng-content select=\"[right]\"></ng-content>\n </div>\n\n <div class=\"focus-switcher-right\" (click)=\"rightPanelClicked()\"></div>\n <div class=\"focus-switcher-left\" (click)=\"leftPanelClicked()\"></div>\n\n <div class=\"visible-resizer\" [hidden]=\"!resizing\" #visibleResizer></div>\n <div class=\"resizing-overlay\" *ngIf=\"resizing\"></div>\n\n</div>\n" }]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: i0.Renderer2 }]; }, propDecorators: { rightPanelVisible: [{
type: Input
}], focusedPanel: [{
type: Input
}], split: [{
type: Input
}], initialSplit: [{
type: Input
}], splitChange: [{
type: Output
}], minPanelSizePercent: [{
type: Input
}], minPanelSizePixels: [{
type: Input
}], noswipe: [{
type: Input
}], rightPanelClosed: [{
type: Output
}], rightPanelOpened: [{
type: Output
}], leftPanelFocused: [{
type: Output
}], rightPanelFocused: [{
type: Output
}], focusedPanelChange: [{
type: Output
}], splitDragStart: [{
type: Output
}], splitDragEnd: [{
type: Output
}], resizeContainer: [{
type: ViewChild,
args: ['resizeContainer', { static: true }]
}], leftPanel: [{
type: ViewChild,
args: ['leftPanel', { static: true }]
}], rightPanel: [{
type: ViewChild,
args: ['rightPanel', { static: true }]
}], resizer: [{
type: ViewChild,
args: ['resizer', { static: true }]
}], visibleResizer: [{
type: ViewChild,
args: ['visibleResizer', { static: true }]
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3BsaXQtdmlldy1jb250YWluZXIuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvc3BsaXQtdmlldy1jb250YWluZXIvc3BsaXQtdmlldy1jb250YWluZXIuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvc3BsaXQtdmlldy1jb250YWluZXIvc3BsaXQtdmlldy1jb250YWluZXIudHBsLmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUVILGlCQUFpQixFQUNqQixTQUFTLEVBQ1QsVUFBVSxFQUNWLFlBQVksRUFDWixLQUFLLEVBR0wsTUFBTSxFQUNOLFNBQVMsRUFFVCxTQUFTLEVBQ1osTUFBTSxlQUFlLENBQUM7QUFDdkIsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDOzs7QUFJakUsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsbUNBQW1DLENBQUM7QUFFdEU7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTZDRztBQUtILE1BQU0sT0FBTyxrQkFBa0I7SUFpSjNCLFlBQW9CLFVBQXNCLEVBQ3RCLGNBQWlDLEVBQ2pDLFFBQW1CO1FBRm5CLGVBQVUsR0FBVixVQUFVLENBQVk7UUFDdEIsbUJBQWMsR0FBZCxjQUFjLENBQW1CO1FBQ2pDLGFBQVEsR0FBUixRQUFRLENBQVc7UUE1SXZDOzs7O1dBSUc7UUFDTSxpQkFBWSxHQUFxQixNQUFNLENBQUM7UUFVakQsMkZBQTJGO1FBRTNGLGlCQUFZLEdBQVcsRUFBRSxDQUFDO1FBRTFCOzs7V0FHRztRQUVILGdCQUFXLEdBQUcsSUFBSSxZQUFZLEVBQVUsQ0FBQztRQUV6Qzs7O1dBR0c7UUFFSCx3QkFBbUIsR0FBVyxDQUFDLENBQUM7UUFFaEM7OztXQUdHO1FBRUgsdUJBQWtCLEdBQVcsRUFBRSxDQUFDO1FBY2hDOztX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TUFBTTtnQkFTUCxrQkFBa0I7c0JBRGpCLE1BQU07Z0JBUVAsY0FBYztzQkFEYixNQUFNO2dCQVFQLFlBQVk7c0JBRFgsTUFBTTtnQkFTeUMsZUFBZTtzQkFBOUQsU0FBUzt1QkFBQyxpQkFBaUIsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUU7Z0JBQ0osU0FBUztzQkFBbEQsU0FBUzt1QkFBQyxXQUFXLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFO2dCQUNHLFVBQVU7c0JBQXBELFNBQVM7dUJBQUMsWUFBWSxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRTtnQkFDRCxPQUFPO3NCQUE5QyxTQUFTO3VCQUFDLFNBQVMsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUU7Z0JBQ1MsY0FBYztzQkFBNUQsU0FBUzt1QkFBQyxnQkFBZ0IsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICAgIEFmdGVyVmlld0luaXQsXG4gICAgQ2hhbmdlRGV0ZWN0b3JSZWYsXG4gICAgQ29tcG9uZW50LFxuICAgIEVsZW1lbnRSZWYsXG4gICAgRXZlbnRFbWl0dGVyLFxuICAgIElucHV0LFxuICAgIE9uQ2hhbmdlcyxcbiAgICBPbkRlc3Ryb3ksXG4gICAgT3V0cHV0LFxuICAgIFJlbmRlcmVyMixcbiAgICBTaW1wbGVDaGFuZ2VzLFxuICAgIFZpZXdDaGlsZFxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IGNvZXJjZVRvQm9vbGVhbiB9IGZyb20gJy4uLy4uL2NvbW1vbi9jb2VyY2UtdG8tYm9vbGVhbic7XG5cbmV4cG9ydCB0eXBlIEZvY3VzVHlwZSA9ICdsZWZ0JyB8ICdyaWdodCc7XG5cbmV4cG9ydCBjb25zdCBDVVJTT1JfU1RZTEVfQ0xBU1MgPSAnZ3R4LXNwbGl0LXZpZXctY29udGFpbmVyLXJlc2l6aW5nJztcblxuLyoqXG4gKiBBIGNvbnRhaW5lciB0aGF0IHByb3ZpZGVzIGEgW1wibWFzdGVyLWRldGFpbFwiIGludGVyZmFjZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTWFzdGVyJUUyJTgwJTkzZGV0YWlsX2ludGVyZmFjZSlcbiAqIHdpdGggdHdvIHJlc2l6YWJsZSBwYW5lbHMgZGVub3RlZCBieSB0aGUgYGxlZnRgIGFuZCBgcmlnaHRgIGF0dHJpYnV0ZXMgb24gaXRzIGNoaWxkcmVuLlxuICpcbiAqIFRoZXJlIHNob3VsZCBvbmx5IGJlIGEgc2luZ2xlIGluc3RhbmNlIG9mIFNwbGl0Vmlld0NvbnRhaW5lciB1c2VkIGF0IGEgdGltZSwgYW5kIGl0IGlzIGludGVuZGVkIHRvIGJlIHRoZVxuICogbWFpbiBzdHJ1Y3R1cmFsIGNvbnRhaW5lciBvZiB0aGUgXCJtYXN0ZXIvZGV0YWlsXCIgcGFydCBvZiB0aGUgYXBwIC0gaS5lLiB0aGUgY29udGVudCBsaXN0aW5nIGFuZCBlZGl0aW5nIHZpZXcuXG4gKlxuICogYGBgaHRtbFxuICogPGd0eC1zcGxpdC12aWV3LWNvbnRhaW5lclxuICogICAgIFtyaWdodFBhbmVsVmlzaWJsZV09XCJoYXNDb250ZW50XCJcbiAqICAgICBbKGZvY3VzZWRQYW5lbCldPVwic3BsaXRGb2N1c1wiPlxuICpcbiAqICAgICA8ZGl2IGNsYXNzPVwibGlzdC1wYW5lXCIgbGVmdD5cbiAqICAgICAgICAgPCEtLSBMZWZ0IHBhbmVsIGNvbnRlbnRzIC0tPlxuICogICAgIDwvZGl2PlxuICpcbiAqICAgICA8ZGl2IGNsYXNzPVwiY29udGVudC1wYW5lXCIgcmlnaHQ+XG4gKiAgICAgICAgIDwhLS0gUmlnaHQgcGFuZWwgY29udGVudHMgLS0+XG4gKiAgICAgPC9kaXY+XG4gKlxuICogPC9ndHgtc3BsaXQtdmlldy1jb250YWluZXI+XG4gKiBgYGBcbiAqXG4gKiAjIyBTZXR0aW5nIFNjcm9sbCBQb3NpdGlvbnNcbiAqXG4gKiBUaGUgU3BsaXRWaWV3Q29udGFpbmVyIGluc3RhbmNlIGV4cG9zZXMgdHdvIHB1YmxpYyBtZXRob2RzIHdoaWNoIGNhbiBiZSB1c2VkIHRvIG1hbnVhbGx5IHNldCB0aGUgYHNjcm9sbFRvcGBcbiAqIHByb3BlcnR5IG9mIGVpdGhlciB0aGUgcmlnaHQgb3IgbGVmdCBwYW5lbHM6IGAuc2Nyb2xsTGVmdFBhbmVsVG8oKWAgYW5kIGAuc2Nyb2xsUmlnaHRQYW5lbFRvKClgLlxuICpcbiAqIFRoaXMgY2FuIGJlIHVzZWQsIGZvciBleGFtcGxlLCB0byBzY3JvbGwgdGhlIGNvbnRlbnRzIHBhbmUgKHJpZ2h0IHBhbmVsKSBiYWNrIHRvIHRoZSB0b3Agd2hlbiB0aGUgcm91dGUgY2hhbmdlcy5cbiAqIEFuIGV4YW1wbGUgdXNhZ2UgZm9sbG93czpcbiAqXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBleHBvcnQgY2xhc3MgQXBwIHtcbiAqICAgQFZpZXdDaGlsZChTcGxpdFZpZXdDb250YWluZXIpXG4gKiAgIHByaXZhdGUgc3BsaXRWaWV3Q29udGFpbmVyOiBTcGxpdFZpZXdDb250YWluZXI7XG4gKlxuICogICBjb25zdHJ1Y3Rvcihwcml2YXRlIHJvdXRlcjogUm91dGVyKSB7XG4gKiAgICAgICB0aGlzLnN1YnNjcmlwdGlvbiA9IHJvdXRlci5zdWJzY3JpYmUoKCkgPT4ge1xuICogICAgICAgICAgIC8vIHNjcm9sbCB0aGUgcmlnaHQgcGFuZWwgdG8gdGhlIHRvcCB3aGVuZXZlclxuICogICAgICAgICAgIC8vIHRoZSByb3V0ZSBjaGFuZ2VzLlxuICogICAgICAgICAgIHRoaXMuc3BsaXRWaWV3Q29udGFpbmVyLnNjcm9sbFJpZ2h0UGFuZWxUbygwKTtcbiAqICAgICAgIH0pO1xuICogICB9XG4gKiB9XG4gKiBgYGBcbiAqL1xuQENvbXBvbmVudCh7XG4gICAgc2VsZWN0b3I6ICdndHgtc3BsaXQtdmlldy1jb250YWluZXInLFxuICAgIHRlbXBsYXRlVXJsOiAnLi9zcGxpdC12aWV3LWNvbnRhaW5lci50cGwuaHRtbCdcbn0pXG5leHBvcnQgY2xhc3MgU3BsaXRWaWV3Q29udGFpbmVyIGltcGxlbWVudHMgQWZ0ZXJWaWV3SW5pdCwgT25DaGFuZ2VzLCBPbkRlc3Ryb3kge1xuICAgIC8qKlxuICAgICAqIFRlbGxzIGlmIGEgcGFuZWwgaXMgb3BlbmVkIG9uIHRoZSByaWdodCBzaWRlIGluIHRoZSBzcGxpdCB2aWV3LlxuICAgICAqIFNldHRpbmcgdG8gZmFsc2Ugd2lsbCBhbHNvIGNoYW5nZSB7QGxpbmsgZm9jdXNlZFBhbmVsfS5cbiAgICAgKi9cbiAgICBASW5wdXQoKSByaWdodFBhbmVsVmlzaWJsZTogYm9vbGVhbjtcblxuICAgIC8qKlxuICAgICAqIFRlbGxzIHRoZSBTcGxpdFZpZXdDb250YWluZXIgd2hpY2ggc2lkZSBzaG91bGQgYmUgZm9jdXNlZC5cbiAgICAgKiBDYW4gYmUgdXNlZCB0byBkb3VibGUtYmluZCB0aGUgcHJvcGVydHk6XG4gICAgICogYDxzcGxpdC12aWV3LWNvbnRhaW5lciBbKGZvY3VzZWRQYW5lbCldPVwicHJvcGVydHlcIj5gXG4gICAgICovXG4gICAgQElucHV0KCkgZm9jdXNlZFBhbmVsOiAnbGVmdCcgfCAncmlnaHQnID0gJ2xlZnQnO1xuXG4gICAgLyoqXG4gICAgICogV2lkdGggb2YgdGhlIGxlZnQgcGFuZWwgaW4gXCJsYXJnZVwiIGxheW91dCBpbiBwZXJjZW50IG9mIHRoZSBjb250YWluZXIgd2lkdGguXG4gICAgICogVXNlIHRvIGNvbnRyb2wgc3BsaXQgcGVyY2VudGFnZSBmcm9tIHRoZSBwYXJlbnQgYnkgZG91YmxlLWJpbmRpbmcgd2l0aCB7QGxpbmsgc3BsaXRDaGFuZ2V9OlxuICAgICAqIGA8Z3R4LXNwbGl0LXZpZXctY29udGFpbmVyIFsoc3BsaXQpXT1cImxlZnRXaWR0aFBlcmNlbnRcIj4uLi48L2d0eC1zcGxpdC12aWV3LWNvbnRhaW5lcj5gXG4gICAgICovXG4gICAgQElucHV0KClcbiAgICBzcGxpdDogbnVtYmVyO1xuXG4gICAgLyoqIEluaXRpYWwgd2lkdGggb2YgdGhlIGxlZnQgcGFuZWwgaW4gXCJsYXJnZVwiIGxheW91dCBpbiBwZXJjZW50IG9mIHRoZSBjb250YWluZXIgd2lkdGguICovXG4gICAgQElucHV0KClcbiAgICBpbml0aWFsU3BsaXQ6IG51bWJlciA9IDUwO1xuXG4gICAgLyoqXG4gICAgICogRW1pdHMgd2hlbiB0aGUgc3BsaXQgYmV0d2VlbiB0aGUgcGFuZWxzIGlzIGNoYW5nZWQuIEFsbG93cyBkb3VibGUtYmluZGluZzpcbiAgICAgKiBgPHNwbGl0LXZpZXctY29udGFpbmVyIFsoc3BsaXQpXT1cImxlZnRXaWR0aFwiPi4uLjwvc3BsaXQtdmlldy1jb250YWluZXI+YFxuICAgICAqL1xuICAgIEBPdXRwdXQoKVxuICAgIHNwbGl0Q2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXI+KCk7XG5cbiAgICAvKipcbiAgICAgKiBUaGUgc21hbGxlc3QgcGFuZWwgc2l6ZSBpbiBwZXJjZW50IHRoZSBsZWZ0XG4gICAgICogYW5kIHJpZ2h0IHBhbmVscyB3aWxsIHNocmluayB0byB3aGVuIHJlc2l6aW5nLlxuICAgICAqL1xuICAgIEBJbnB1dCgpXG4gICAgbWluUGFuZWxTaXplUGVyY2VudDogbnVtYmVyID0gNTtcblxuICAgIC8qKlxuICAgICAqIFRoZSBzbWFsbGVzdCBwYW5lbCBzaXplIGluIHBpeGVscyB0aGUgbGVmdFxuICAgICAqIGFuZCByaWdodCBwYW5lbHMgd2lsbCBzaHJpbmsgdG8gd2hlbiByZXNpemluZy5cbiAgICAgKi9cbiAgICBASW5wdXQoKVxuICAgIG1pblBhbmVsU2l6ZVBpeGVsczogbnVtYmVyID0gMjA7XG5cbiAgICAvKipcbiAgICAgKiBEaXNhYmxlIHRvdWNoIHN3aXBlIGdlc3R1cmVzIHdoZW4gdXNlZC5cbiAgICAgKi9cbiAgICBASW5wdXQoKSBzZXQgbm9zd2lwZSh2YWw6IGFueSkge1xuICAgICAgICB0aGlzLmlzU3dpcGVhYmxlID0gIWNvZXJjZVRvQm9vbGVhbih2YWwpO1xuXG4gICAgICAgIC8vIFRvZ2dsZSBIYW1tZXIgZ2VzdHVyZSByZWNvZ25pemVyc1xuICAgICAgICBpZiAodGhpcy5oYW1tZXJNYW5hZ2VyKSB7XG4gICAgICAgICAgICB0aGlzLmhhbW1lck1hbmFnZXIuc2V0KHsgZW5hYmxlOiB0aGlzLmlzU3dpcGVhYmxlIH0pO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVHJpZ2dlcnMgd2hlbiB0aGUgcmlnaHQgcGFuZWwgaXMgY2xvc2VkLlxuICAgICAqL1xuICAgIEBPdXRwdXQoKVxuICAgIHJpZ2h0UGFuZWxDbG9zZWQgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KHRydWUpO1xuXG4gICAgLyoqXG4gICAgICogVHJpZ2dlcnMgd2hlbiB0aGUgcmlnaHQgcGFuZWwgaXMgb3BlbmVkLlxuICAgICAqL1xuICAgIEBPdXRwdXQoKVxuICAgIHJpZ2h0UGFuZWxPcGVuZWQgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KHRydWUpO1xuXG4gICAgLyoqXG4gICAgICogVHJpZ2dlcnMgd2hlbiB0aGUgbGVmdCBwYW5lbCBpcyBmb2N1c2VkLlxuICAgICAqL1xuICAgIEBPdXRwdXQoKVxuICAgIGxlZnRQYW5lbEZvY3VzZWQgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG5cbiAgICAvKipcbiAgICAgKiBUcmlnZ2VycyB3aGVuIHRoZSByaWdodCBwYW5lbCBpcyBmb2N1c2VkLlxuICAgICAqL1xuICAgIEBPdXRwdXQoKVxuICAgIHJpZ2h0UGFuZWxGb2N1c2VkID0gbmV3IEV2ZW50RW1pdHRlcjx2b2lkPigpO1xuXG4gICAgLyoqXG4gICAgICogVHJpZ2dlcnMgd2hlbiB0aGUgZm9jdXNlZCBwYW5lbCBjaGFuZ2VzLlxuICAgICAqIENhbiBiZSB1c2VkIHRvIGRvdWJsZS1iaW5kIHRoZSBwcm9wZXJ0eTpcbiAgICAgKiBgPHNwbGl0LXZpZXctY29udGFpbmVyIFsoZm9jdXNlZFBhbmVsKV09XCJwcm9wZXJ0eVwiPmBcbiAgICAgKi9cbiAgICBAT3V0cHV0KClcbiAgICBmb2N1c2VkUGFuZWxDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPCdsZWZ0JyB8ICdyaWdodCc+KCk7XG5cbiAgICAvKipcbiAgICAgKiBUcmlnZ2VycyB3aGVuIHRoZSB1c2VyIHN0YXJ0cyByZXNpemluZyB0aGUgc3BsaXQgYW1vdW50IGJldHdlZW4gdGhlIHBhbmVscy5cbiAgICAgKiBSZWNlaXZlcyB0aGUgc2l6ZSBvZiB0aGUgbGVmdCBwYW5lbCBpbiAlIG9mIHRoZSBjb250YWluZXIgd2lkdGggYXMgYXJndW1lbnQuXG4gICAgICovXG4gICAgQE91dHB1dCgpXG4gICAgc3BsaXREcmFnU3RhcnQgPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlcj4odHJ1ZSk7XG5cbiAgICAvKipcbiAgICAgKiBUcmlnZ2VycyB3aGVuIHRoZSB1c2VyIHJlc2l6ZXMgdGhlIHNwbGl0IGFtb3VudCBiZXR3ZWVuIHRoZSBwYW5lbHMuXG4gICAgICogUmVjZWl2ZXMgdGhlIHNpemUgb2YgdGhlIGxlZnQgcGFuZWwgaW4gJSBvZiB0aGUgY29udGFpbmVyIHdpZHRoIGFzIGFyZ3VtZW50LlxuICAgICAqL1xuICAgIEBPdXRwdXQoKVxuICAgIHNwbGl0RHJhZ0VuZCA9IG5ldyBFdmVudEVtaXR0ZXI8bnVtYmVyPih0cnVlKTtcblxuICAgIC8qKiBAaW50ZXJuYWwgRXZlbnRUYXJnZXQgZm9yIHRyYWNraW5nIHdoZW4gdGhlIG1vdXNlIGxlYXZlcyB0aGUgcGFnZS4gKi9cbiAgICBnbG9iYWxFdmVudFRhcmdldDogYW55ID0gd2luZG93LmRvY3VtZW50O1xuXG4gICAgLyoqIEBpbnRlcm5hbCBUaGUgRWxlbWVudCB0byB3aGljaCBjdXJzb3Igc3R5bGVzIGFyZSBhcHBsaWVkLiAqL1xuICAgIGdsb2JhbEN1cnNvclN0eWxlVGFyZ2V0OiBhbnkgPSB3aW5kb3cuZG9jdW1lbnQgJiYgd2luZG93LmRvY3VtZW50LmJvZHk7XG5cbiAgICBAVmlld0NoaWxkKCdyZXNpemVDb250YWluZXInLCB7IHN0YXRpYzogdHJ1ZSB9KSByZXNpemVDb250YWluZXI6IEVsZW1lbnRSZWY7XG4gICAgQFZpZXdDaGlsZCgnbGVmdFBhbmVsJywgeyBzdGF0aWM6IHRydWUgfSkgbGVmdFBhbmVsOiBFbGVtZW50UmVmO1xuICAgIEBWaWV3Q2hpbGQoJ3JpZ2h0UGFuZWwnLCB7IHN0YXRpYzogdHJ1ZSB9KSByaWdodFBhbmVsOiBFbGVtZW50UmVmO1xuICAgIEBWaWV3Q2hpbGQoJ3Jlc2l6ZXInLCB7IHN0YXRpYzogdHJ1ZSB9KSByZXNpemVyOiBFbGVtZW50UmVmO1xuICAgIEBWaWV3Q2hpbGQoJ3Zpc2libGVSZXNpemVyJywgeyBzdGF0aWM6IHRydWUgfSkgdmlzaWJsZVJlc2l6ZXI6IEVsZW1lbnRSZWY7XG5cbiAgICByZXNpemluZzogYm9vbGVhbiA9IGZhbHNlO1xuXG4gICAgLy8gQWN0dWFsIHZhbHVlIHVzZWQgaW4gdGhlIHRlbXBsYXRlLiBJZiB0aGUgZm9jdXMgaXMgc2V0IHRvIHRoZSByaWdodCBwYW5lbCxcbiAgICAvLyBidXQgdGhlIHJpZ2h0IHBhbmVsIGhhcyBubyBjb250ZW50LCB0aGUgbGVmdCBwYW5lbCBpcyBmb2N1c2VkIGluc3RlYWQuXG4gICAgcmlnaHRQYW5lbEFjdHVhbGx5Rm9jdXNlZDogYm9vbGVhbiA9IGZhbHNlO1xuXG4gICAgLyoqXG4gICAgICogV2hlbiBzcGxpdCBpcyBwYXNzZWQgYnkgdGhlIHBhcmVudCBjb21wb25lbnQsIG9ubHkgZW1pdCBldmVudHMgb24gcmVzaXplLlxuICAgICAqIE90aGVyd2lzZSwgdGhlIHdpZHRoIGlzIG1hbmFnZWQgYnkgdGhlIFNwbGl0Vmlld0NvbnRhaW5lci5cbiAgICAgKi9cbiAgICBwcml2YXRlIHdpZHRoSGFuZGxlZEV4dGVybmFsbHkgPSBmYWxzZTtcblxuICAgIC8qKlxuICAgICAqIFRvIHByZXZlbnQgcmFjZSBjb25kaXRpb25zIGJ5IGNsaWNrIGV2ZW50cywgZG8gbm90IGVtaXQgYSBmb2N1c2VkUGFuZWxDaGFuZ2VcbiAgICAgKiB3aGVuIHRoZSBsYXN0IGNoYW5nZSBkZXRlY3Rpb24gY2hhbmdlZCB0aGUgZm9jdXMuIEV4YW1wbGU6XG4gICAgICogIDEuIGVsZW1lbnQgaW4gbGVmdCBwYW5lbCBpcyBjbGlja2VkLCBzZXRzIGZvY3VzIHRvIFwicmlnaHRcIlxuICAgICAqICAyLiBjbGljayBldmVudCBidWJibGVzIHRvIHRoZSBTcGxpdFZpZXdDb250YWluZXIsIGxlZnRQYW5lbENsaWNrZWRcbiAgICAgKiAgMy4gZm9jdXMgd291bGQgYmUgc2V0IHRvIHRoZSBsZWZ0IHBhbmVsIGFnYWluLCBidXQgc2hvdWxkIGJlIGlnbm9yZWRcbiAgICAgKi9cbiAgICBwcml2YXRlIGZvY3VzSnVzdENoYW5nZWQgPSBmYWxzZTtcbiAgICBwcml2YXRlIGZvY3VzSnVzdENoYW5nZWRUaW1lb3V0OiBhbnk7XG5cbiAgICBwcml2YXRlIGlzU3dpcGVhYmxlID0gdHJ1ZTtcblxuICAgIHByaXZhdGUgcmVzaXplTW91c2VPZmZzZXQ6IG51bWJlcjtcbiAgICBwcml2YXRlIGhhbW1lck1hbmFnZXI6IEhhbW1lck1hbmFnZXI7XG4gICAgcHJpdmF0ZSBjbGVhbnVwczogRnVuY3Rpb25bXSA9IFtdO1xuXG4gICAgY29uc3RydWN0b3IocHJpdmF0ZSBvd25FbGVtZW50OiBFbGVtZW50UmVmLFxuICAgICAgICAgICAgICAgIHByaXZhdGUgY2hhbmdlRGV0ZWN0b3I6IENoYW5nZURldGVjdG9yUmVmLFxuICAgICAgICAgICAgICAgIHByaXZhdGUgcmVuZGVyZXI6IFJlbmRlcmVyMikgeyB9XG5cbiAgICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICAgICAgaWYgKCF0aGlzLnNwbGl0KSB7XG4gICAgICAgICAgICB0aGlzLnNwbGl0ID0gdGhpcy5pbml0aWFsU3BsaXQ7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBuZ0FmdGVyVmlld0luaXQoKTogdm9pZCB7XG4gICAgICAgIGlmICghdGhpcy5vd25FbGVtZW50IHx8ICF0aGlzLm93bkVsZW1lbnQubmF0aXZlRWxlbWVudCkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gQWxsb3cgYW55IGxheW91dCBjaGFuZ2VzIHRvIHN0YWJpbGl6ZSAoZS5nLiBkaXZzIHdpdGggbmdJZiBzaG93aW5nL2hpZGluZylcbiAgICAgICAgLy8gYmVmb3JlIHdlIGNhbGN1bGF0ZSB0aGUgZmluYWwgcG9zaXRpb24gb2YgdGhlIFNwbGl0Vmlld0NvbnRhaW5lclxuICAgICAgICBjb25zdCB0aW1lb3V0ID0gc2V0VGltZW91dCgoKSA9PiB0aGlzLmZpdENvbnRhaW5lclRvVmlld3BvcnQoKSk7XG4gICAgICAgIHRoaXMuY2xlYW51cHMucHVzaCgoKSA9PiBjbGVhclRpbWVvdXQodGltZW91dCkpO1xuXG4gICAgICAgIHRoaXMuaW5pdFN3aXBlSGFuZGxlcigpO1xuICAgIH1cblxuICAgIG5nT25DaGFuZ2VzKGNoYW5nZXM6IFNpbXBsZUNoYW5nZXMpOiB2b2lkIHtcbiAgICAgICAgaWYgKGNoYW5nZXNbJ2ZvY3VzZWRQYW5lbCddIHx8IGNoYW5nZXNbJ3JpZ2h0UGFuZWxWaXNpYmxlJ10pIHtcbiAgICAgICAgICAgIC8vIFByZXZlbnQgYW4gaW52YWxpZCBzdGF0ZSB3aGVyZSB0aGUgU3BsaXRWaWV3Q29udGFpbmVyXG4gICAgICAgICAgICAvLyBoYXMgbm8gcmlnaHQgcGFuZWwsIGJ1dCB0aGUgcmlnaHQgcGFuZWwgc2hvdWxkIGJlIGZvY3VzZWQuXG4gICAgICAgICAgICBjb25zdCBzaG91bGRGb2N1c1JpZ2h0UGFuZWwgPSB0aGlzLmZvY3VzZWRQYW5lbCA9PT0gJ3JpZ2h0JyAmJiB0aGlzLnJpZ2h0UGFuZWxWaXNpYmxlO1xuXG4gICAgICAgICAgICBpZiAoc2hvdWxkRm9jdXNSaWdodFBhbmVsICE9PSB0aGlzLnJpZ2h0UGFuZWxBY3R1YWxseUZvY3VzZWQpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnJpZ2h0UGFuZWxBY3R1YWxseUZvY3VzZWQgPSBzaG91bGRGb2N1c1JpZ2h0UGFuZWw7XG5cbiAgICAgICAgICAgICAgICBpZiAoc2hvdWxkRm9jdXNSaWdodFBhbmVsKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMucmlnaHRQYW5lbEZvY3VzZWQuZW1pdCgpO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMubGVmdFBhbmVsRm9jdXNlZC5lbWl0KCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuZm9jdXNKdXN0Q2hhbmdlZFRpbWVvdXQpO1xuICAgICAgICAgICAgICAgIHRoaXMuZm9jdXNKdXN0Q2hhbmdlZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgdGhpcy5mb2N1c0p1c3RDaGFuZ2VkVGltZW91dCA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmZvY3VzSnVzdENoYW5nZWQgPSBmYWxzZTtcbiAgICAgICAgICAgICAgICB9LCA1MCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCByaWdodFBhbmVsVmlzaWJsZUNoYW5nZSA9IGNoYW5nZXNbJ3JpZ2h0UGFuZWxWaXNpYmxlJ107XG4gICAgICAgIGlmIChyaWdodFBhbmVsVmlzaWJsZUNoYW5nZSAmJiAhcmlnaHRQYW5lbFZpc2libGVDaGFuZ2UuaXNGaXJzdENoYW5nZSgpKSB7XG4gICAgICAgICAgICBpZiAocmlnaHRQYW5lbFZpc2libGVDaGFuZ2UuY3VycmVudFZhbHVlKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5yaWdodFBhbmVsT3BlbmVkLmVtaXQoKTtcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgdGhpcy5yaWdodFBhbmVsQ2xvc2VkLmVtaXQoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjaGFuZ2VzWydzcGxpdCddKSB7XG4gICAgICAgICAgICB0aGlzLndpZHRoSGFuZGxlZEV4dGVybmFsbHkgPSB0cnVlO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgbmdPbkRlc3Ryb3koKTogdm9pZCB7XG4gICAgICAgIHRoaXMuY2xlYW51cHMuZm9yRWFjaChjbGVhbnVwID0+IGNsZWFudXAoKSk7XG4gICAgICAgIHRoaXMuY2xlYW51cHMgPSBbXTtcbiAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuZm9jdXNKdXN0Q2hhbmdlZFRpbWVvdXQpO1xuICAgICAgICB0aGlzLmRlc3Ryb3lTd2lwZUhhbmRsZXIoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdGhlIHNjcm9sbFRvcCBvZiB0aGUgbGVmdCBwYW5lbFxuICAgICAqL1xuICAgIHB1YmxpYyBzY3JvbGxMZWZ0UGFuZWxUbyhzY3JvbGxUb3A6IG51bWJlcik6IHZvaWQge1xuICAgICAgICB0aGlzLmxlZnRQYW5lbC5uYXRpdmVFbGVtZW50LnNjcm9sbFRvcCA9IHNjcm9sbFRvcDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdGhlIHNjcm9sbFRvcCBvZiB0aGUgcmlnaHQgcGFuZWxcbiAgICAgKi9cbiAgICBwdWJsaWMgc2Nyb2xsUmlnaHRQYW5lbFRvKHNjcm9sbFRvcDogbnVtYmVyKTogdm9pZCB7XG4gICAgICAgIHRoaXMucmlnaHRQYW5lbC5uYXRpdmVFbGVtZW50LnNjcm9sbFRvcCA9IHNjcm9sbFRvcDtcbiAgICB9XG5cbiAgICBsZWZ0UGFuZWxDbGlja2VkKCk6IHZvaWQge1xuICAgICAgICBpZiAodGhpcy5yaWdodFBhbmVsQWN0dWFsbHlGb2N1c2VkICYmICF0aGlzLmZvY3VzSnVzdENoYW5nZWQpIHtcbiAgICAgICAgICAgIHRoaXMuZm9jdXNlZFBhbmVsQ2hhbmdlLmVtaXQoJ2xlZnQnKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJpZ2h0UGFuZWxDbGlja2VkKCk6IHZvaWQge1xuICAgICAgICBpZiAoIXRoaXMucmlnaHRQYW5lbEFjdHVhbGx5Rm9jdXNlZCAmJiAhdGhpcy5mb2N1c0p1c3RDaGFuZ2VkICYmIHRoaXMucmlnaHRQYW5lbFZpc2libGUpIHtcbiAgICAgICAgICAgIHRoaXMuZm9jdXNlZFBhbmVsQ2hhbmdlLmVtaXQoJ3JpZ2h0Jyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBzdGFydFJlc2l6ZXIoZXZlbnQ6IE1vdXNlRXZlbnQpOiB2b2lkIHtcbiAgICAgICAgaWYgKGV2ZW50LndoaWNoICE9IDEgfHwgIXRoaXMubGVmdFBhbmVsLm5hdGl2ZUVsZW1lbnQpIHsgcmV0dXJuOyB9XG4gICAgICAgIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG5cbiAgICAgICAgY29uc3QgcmVzaXplSGFuZGxlID0gPEhUTUxFbGVtZW50ICYgeyBzZXRDYXB0dXJlKCk6IHZvaWQsIHJlbGVhc2VDYXB0dXJlKCk6IHZvaWQgfT4gZXZlbnQuY3VycmVudFRhcmdldDtcbiAgICAgICAgdGhpcy5yZXNpemVNb3VzZU9mZnNldCA9IGV2ZW50LmNsaWVudFggLSByZXNpemVIYW5kbGUuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkubGVmdDtcblxuICAgICAgICBsZXQgbW91c2VNb3ZlVGFyZ2V0ID0gdGhpcy5nbG9iYWxFdmVudFRhcmdldDtcblxuICAgICAgICAvLyBVc2Ugc2V0Q2FwdHVyZSBvbiBvbGRlciBicm93c2VycywgZG9jdW1lbnQ6bW91c2Vtb3ZlIG9uIG5ld2VyIGJyb3dzZXJzXG4gICAgICAgIGlmIChyZXNpemVIYW5kbGUuc2V0Q2FwdHVyZSkge1xuICAgICAgICAgICAgbW91c2VNb3ZlVGFyZ2V0ID0gcmVzaXplSGFuZGxlO1xuICAgICAgICAgICAgcmVzaXplSGFuZGxlLnNldENhcHR1cmUoKTtcbiAgICAgICAgICAgIHRoaXMuY2xlYW51cHMucHVzaCgoKSA9PiByZXNpemVIYW5kbGUucmVsZWFzZUNhcHR1cmUoKSk7XG4gICAgICAgICAgICB0aGlzLmNsZWFudXBzLnB1c2godGhpcy5yZW5kZXJlci5saXN0ZW4ocmVzaXplSGFuZGxlLCAnbG9zZWNhcHR1cmUnLCB0aGlzLmVuZFJlc2l6aW5nKSk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBTZXQgY3Vyc29yIHN0eWxlcyAmIGJpbmQgZXZlbnRzIG9uIGRvY3VtZW50ICh0aGUgQW5ndWxhciB3YXkpXG4gICAgICAgIHRoaXMucmVuZGVyZXIuYWRkQ2xhc3ModGhpcy5nbG9iYWxDdXJzb3JTdHlsZVRhcmdldCwgQ1VSU09SX1NUWUxFX0NMQVNTKTtcbiAgICAgICAgdGhpcy5jbGVhbnVwcy5wdXNoKFxuICAgICAgICAgICAgKCkgPT4gdGhpcy5yZW5kZXJlci5yZW1vdmVDbGFzcyh0aGlzLmdsb2JhbEN1cnNvclN0eWxlVGFyZ2V0LCBDVVJTT1JfU1RZTEVfQ0xBU1MpLFxuICAgICAgICAgICAgdGhpcy5yZW5kZXJlci5saXN0ZW4obW91c2VNb3ZlVGFyZ2V0LCAnbW91c2Vtb3ZlJywgdGhpcy5tb3ZlUmVzaXplciksXG4gICAgICAgICAgICB0aGlzLnJlbmRlcmVyLmxpc3Rlbih0aGlzLmdsb2JhbEV2ZW50VGFyZ2V0LCAnbW91c2V1cCcsIHRoaXMuZW5kUmVzaXppbmcpXG4gICAgICAgICk7XG5cbiAgICAgICAgLy8gU3RhcnQgcmVzaXppbmdcbiAgICAgICAgbGV0IHJlc2l6ZXJYUG9zaXRpb24gPSB0aGlzLmdldEFkanVzdGVkUG9zaXRpb24oZXZlbnQuY2xpZW50WCk7XG4gICAgICAgIHRoaXMucmVzaXppbmcgPSB0cnVlO1xuICAgICAgICB0aGlzLnZpc2libGVSZXNpemVyLm5hdGl2ZUVsZW1lbnQuc3R5bGUubGVmdCA9IHJlc2l6ZXJYUG9zaXRpb24gKyAnJSc7XG4gICAgICAgIHRoaXMuY2hhbmdlRGV0ZWN0b3IubWFya0ZvckNoZWNrKCk7XG4gICAgICAgIHRoaXMuc3BsaXREcmFnU3RhcnQuZW1pdChyZXNpemVyWFBvc2l0aW9uKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiAoaGFja3kpIEFmdGVyIGluaXRpYWxpemluZyB0aGUgdmlldywgbWFrZSB0aGlzIGNvbXBvbmVudCBmaWxsIHRoZSBoZWlnaHQgb2YgdGhlIHZpZXdwb3J0LlxuICAgICAqIE9ubHkgYXBwbGllZCBpZiB0aGUgc3BsaXQtdmlldy1jb250YWluZXIgZWxlbWVudCBpcyBub3Qgc3R5bGVkIGluIHRoZSBjb25zdW1pbmcgYXBwbGljYXRpb24uXG4gICAgICovXG4gICAgcHJpdmF0ZSBmaXRDb250YWluZXJUb1ZpZXdwb3J0KCk6IHZvaWQge1xuICAgICAgICBjb25zdCBlbGVtZW50OiBIVE1MRWxlbWVudCA9IHRoaXMub3duRWxlbWVudC5uYXRpdmVFbGVtZW50O1xuICAgICAgICBpZiAoKGVsZW1lbnQuZmlyc3RFbGVtZW50Q2hpbGQgYXMgSFRNTEVsZW1lbnQpLm9mZnNldFBhcmVudCAhPT0gZWxlbWVudCkge1xuICAgICAgICAgICAgY29uc3QgY3NzOiBDU1NTdHlsZURlY2xhcmF0aW9uID0gZWxlbWVudC5zdHlsZTtcbiAgICAgICAgICAgIGNzcy50b3AgPSBlbGVtZW50Lm9mZnNldFRvcCArICdweCc7XG4gICAgICAgICAgICBjc3MuYm90dG9tID0gY3NzLmxlZnQgPSBjc3MucmlnaHQgPSAnMCc7XG4gICAgICAgICAgICBjc3MucG9zaXRpb24gPSAnYWJzb2x1dGUnO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0IHVwIGEgSGFtbWVyanMtYmFzZWQgc3dpcGUgZ2VzdHVyZSBoYW5kbGVyIHRvIGFsbG93IHN3aXBpbmcgYmV0d2VlbiB0d28gcGFuZXMuXG4gICAgICovXG4gICAgcHJpdmF0ZSBpbml0U3dpcGVIYW5kbGVyKCk6IHZvaWQge1xuICAgICAgICAvLyBzZXQgdXAgc3dpcGUgZ2VzdHVyZSBoYW5kbGVyXG4gICAgICAgIHRoaXMuaGFtbWVyTWFuYWdlciA9IG5ldyBIYW1tZXIodGhpcy5vd25FbGVtZW50Lm5hdGl2ZUVsZW1lbnQsIHsgZW5hYmxlOiB0aGlzLmlzU3dpcGVhYmxlIH0pO1xuICAgICAgICB0aGlzLmhhbW1lck1hbmFnZXIub24oJ3N3aXBlJywgKGU6IEhhbW1lcklucHV0KSA9PiB7XG4gICAgICAgICAgICBpZiAoZS5wb2ludGVyVHlwZSA9PT0gJ3RvdWNoJykge1xuICAgICAgICAgICAgICAgIC8vIEhhbW1lcmpzIHJlcHJlc2VudHMgZGlyZWN0aW9ucyB3aXRoIGFuIGVudW0sXG4gICAgICAgICAgICAgICAgLy8gMiA9IGxlZnQsIDQgPSByaWdodC5cbiAgICAgICAgICAgICAgICBpZiAoZS5kaXJlY3Rpb24gPT09IDQpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5sZWZ0UGFuZWxDbGlja2VkKCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGlmIChlLmRpcmVjdGlvbiA9PT0gMikge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnJpZ2h0UGFuZWxDbGlja2VkKCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBwcml2YXRlIGRlc3Ryb3lTd2lwZUhhbmRsZXIoKTogdm9pZCB7XG4gICAgICAgIHRoaXMuaGFtbWVyTWFuYWdlci5kZXN0cm95KCk7XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBtb3ZlUmVzaXplciA9IChldmVudDogTW91c2VFdmVudCkgPT4ge1xuICAgICAgICBsZXQgcmVzaXplclhQb3NpdGlvbiA9IHRoaXMuZ2V0QWRqdXN0ZWRQb3NpdGlvbihldmVudC5jbGllbnRYKTtcbiAgICAgICAgdGhpcy52aXNpYmxlUmVzaXplci5uYXRpdmVFbGVtZW50LnN0eWxlLmxlZnQgPSByZXNpemVyWFBvc2l0aW9uICsgJyUnO1xuICAgIH1cblxuICAgIHByaXZhdGUgZW5kUmVzaXppbmcgPSAoZXZlbnQ6IE1vdXNlRXZlbnQpID0+IHtcbiAgICAgICAgY29uc3QgYWRqdXN0ZWRXaXRoID0gdGhpcy5nZXRBZGp1c3RlZFBvc2l0aW9uKGV2ZW50LmNsaWVudFgpO1xuICAgICAgICB0aGlzLnNwbGl0Q2hhbmdlLmVtaXQoYWRqdXN0ZWRXaXRoKTtcbiAgICAgICAgaWYgKCF0aGlzLndpZHRoSGFuZGxlZEV4dGVybmFsbHkpIHtcbiAgICAgICAgICAgIHRoaXMuc3BsaXQgPSBhZGp1c3RlZFdpdGg7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnJlc2l6aW5nID0gZmFsc2U7XG4gICAgICAgIHRoaXMuY2hhbmdlRGV0ZWN0b3IubWFya0ZvckNoZWNrKCk7XG4gICAgICAgIHRoaXMuY2xlYW51cHMuZm9yRWFjaChjbGVhbnVwID0+IGNsZWFudXAoKSk7XG4gICAgICAgIHRoaXMuY2xlYW51cHMgPSBbXTtcbiAgICAgICAgdGhpcy5zcGxpdERyYWdFbmQuZW1pdCh0aGlzLnNwbGl0KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBIZWxwZXIgZnVuY3Rpb24gdG8ga2VlcCB0aGUgcmVzaXplIGZ1bmN0aW9uYWxpdHlcbiAgICAgKiB3aXRoaW4gaXRzIGxpbWl0cyAobWluUGFuZWxTaXplUGl4ZWxzICYgbWluUGFuZWxTaXplUGVyY2VudCkuXG4gICAgICogQHJldHVyb