UNPKG

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