UNPKG

ng-plan

Version:

This module will draw a 2D plan drawing from a geoJSON object. It was built to visualize 2D space boundaries from the Building Information Modeling (BIM) authring tool, Revit. It is quite generic, but does have some features that are mainly related to the

608 lines (601 loc) 62.4 kB
import { Component, Input, Output, EventEmitter, ViewChild, Directive, HostListener, NgModule } from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; import { decompose } from 'd3-decompose'; import { polygonCentroid } from 'd3-polygon'; import { CommonModule } from '@angular/common'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { MatIconModule, MatTooltipModule } from '@angular/material'; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ var PlanComponent = /** @class */ (function () { function PlanComponent(matIconRegistry, domSanitizer) { this.matIconRegistry = matIconRegistry; this.domSanitizer = domSanitizer; this.clickedRoom = new EventEmitter(); this.clickedCanvas = new EventEmitter(); // Styles this.selectedColor = '#ebefe4'; this.defaultColor = '#f2flec'; this.baseScale = 0.9; this.baseOffsetX = 0; this.baseOffsetY = 0; this.transform = "translate(" + this.baseOffsetX + "," + this.baseOffsetY + ") scale(" + this.baseScale + "," + this.baseScale + ")"; this.movedX = 0; this.movedY = 0; this.scaled = 1; this.panMode = false; this.addNodeMode = false; } /** * @return {?} */ PlanComponent.prototype.ngAfterViewInit = /** * @return {?} */ function () { this.getCanvasSize(); // this.addSVGicons(); }; /** * @param {?} changes * @return {?} */ PlanComponent.prototype.ngOnChanges = /** * @param {?} changes * @return {?} */ function (changes) { if (changes["data"] && changes["data"].currentValue) { this.data = changes["data"].currentValue; this.extractRooms(); this.getScaleOffset(); this.zoomExtents(); } if (changes["colors"] && changes["colors"].currentValue) { this.defineColors(); } if (changes["selected"]) { this.updateSelection(); } }; // addSVGicons(){ // this.matIconRegistry.addSvgIcon( // "zoom_extents", // this.domSanitizer.bypassSecurityTrustResourceUrl("../assets/zoom_extents.svg") // ); // } /** * @return {?} */ PlanComponent.prototype.updateSelection = /** * @return {?} */ function () { var _this = this; if (!this.rooms) return; this.rooms = this.rooms.map(function (x) { if (_this.selected.indexOf(x.uri) != -1) { x.selected = true; } else { x.selected = false; } return x; }); }; /** * @return {?} */ PlanComponent.prototype.defineColors = /** * @return {?} */ function () { var _this = this; this.rooms.map(function (x) { var /** @type {?} */ match = _this.colors.filter(function (y) { return y.uri == x.uri; }); if (match.length > 0) { x.color = match[0].color; x.description = match[0].value; } return x; }); }; /** * @return {?} */ PlanComponent.prototype.getCanvasSize = /** * @return {?} */ function () { var /** @type {?} */ element = this.planContainer.nativeElement; var /** @type {?} */ size = element.getBoundingClientRect(); this.canvasWidth = size.width; this.canvasHeight = size.height; this.canvasCentroid = [this.canvasWidth / 2, this.canvasHeight / 2]; }; /** * @return {?} */ PlanComponent.prototype.extractRooms = /** * @return {?} */ function () { var _this = this; if (!this.data.features) return this.rooms = null; // For bounding box [xMin, yMin, xMax, yMax] var /** @type {?} */ xMin; var /** @type {?} */ yMin; var /** @type {?} */ xMax; var /** @type {?} */ yMax; this.rooms = this.data.features.map(function (room) { var /** @type {?} */ polygons = []; var /** @type {?} */ roomPath = ''; var /** @type {?} */ centroid; if (room.geometry && room.geometry.type == "Polygon") { var /** @type {?} */ roomPolygons = []; // Allow multi polygons if doughnut room room.geometry.coordinates.forEach(function (polygon) { var /** @type {?} */ points = ''; var /** @type {?} */ path = ''; // Reflect y coordinates to fit browser coordinate system and extract to polygon polygon.map(function (coordinate, index) { var /** @type {?} */ x = coordinate[0]; var /** @type {?} */ y = -coordinate[1]; // reflect since SVG uses reflected coordinate system points += x + "," + y + " "; path += (index == 0) ? "M" + x + "," + y + " " : "L" + x + "," + y + " "; coordinate[1] = y; // Update polygon with new y // Get bounding box if (!xMin || x < xMin) xMin = x; if (!xMax || x > xMax) xMax = x; if (!yMin || y < yMin) yMin = y; if (!yMax || y > yMax) yMax = y; return coordinate; }); points = points.trim(); // remove last space path = path.trim(); // remove last space // Get polygon centroid from first polygon if (roomPolygons.length < 1) { centroid = polygonCentroid(polygon); } roomPolygons.push(points); roomPath += path; }); polygons.push(roomPolygons); } var /** @type {?} */ name = room.properties.name; var /** @type {?} */ uri = room.properties.uri; var /** @type {?} */ color = room.properties.color ? room.properties.color : _this.defaultColor; var /** @type {?} */ description = room.properties.description ? room.properties.description : ''; // Store bounding box // Store bounding box _this.boundingBox = [xMin, yMin, xMax, yMax]; return { name: name, uri: uri, description: description, color: color, polygons: polygons, path: roomPath, centroid: centroid }; }); }; /** * @return {?} */ PlanComponent.prototype.getScaleOffset = /** * @return {?} */ function () { // Calculate data size var /** @type {?} */ bb = this.boundingBox; var /** @type {?} */ dataWidth = bb[2] - bb[0]; var /** @type {?} */ dataHeight = bb[3] - bb[1]; // Calculate scale factors var /** @type {?} */ scaleWidth = this.canvasWidth / dataWidth; var /** @type {?} */ scaleHeight = this.canvasHeight / dataHeight; var /** @type {?} */ scale = Math.min(scaleHeight, scaleWidth); var /** @type {?} */ scaledDataCentroid = [scale * (bb[0] + dataWidth / 2), scale * (bb[1] + dataHeight / 2)]; // Calculate offset factors var /** @type {?} */ offsetX = this.canvasCentroid[0] - scaledDataCentroid[0]; var /** @type {?} */ offsetY = this.canvasCentroid[1] - scaledDataCentroid[1]; // Set global variables this.baseOffsetX = offsetX; this.baseOffsetY = offsetY; this.baseScale = scale; return [scale, offsetX, offsetY]; }; /** * @param {?} displacement * @return {?} */ PlanComponent.prototype.move = /** * @param {?} displacement * @return {?} */ function (displacement) { if (displacement) { this.zoomHandler(undefined, displacement); } }; /** * @param {?} displacement * @return {?} */ PlanComponent.prototype.moveEnd = /** * @param {?} displacement * @return {?} */ function (displacement) { // Update moved coordinates this.movedX = this.movedX + displacement[0]; this.movedY = this.movedY + displacement[1]; }; /** * @return {?} */ PlanComponent.prototype.onWindowResize = /** * @return {?} */ function () { this.getCanvasSize(); this.getScaleOffset(); this.zoomExtents(); }; /** * @return {?} */ PlanComponent.prototype.zoomExtents = /** * @return {?} */ function () { this.scaled = this.baseScale * 0.95; this.movedX = 0; this.movedY = 0; this.zoomHandler(); }; /** * @param {?} ev * @return {?} */ PlanComponent.prototype.zoomOut = /** * @param {?} ev * @return {?} */ function (ev) { // Calculate current scale var /** @type {?} */ scaleFactor = this.baseScale / 5; this.scaled = this.scaled - scaleFactor; this.zoomHandler(ev); }; /** * @param {?} ev * @return {?} */ PlanComponent.prototype.zoomIn = /** * @param {?} ev * @return {?} */ function (ev) { var /** @type {?} */ scaleFactor = this.baseScale / 5; this.scaled = this.scaled + scaleFactor; this.zoomHandler(ev); }; /** * @param {?=} ev * @param {?=} displacement * @return {?} */ PlanComponent.prototype.zoomHandler = /** * @param {?=} ev * @param {?=} displacement * @return {?} */ function (ev, displacement) { var /** @type {?} */ trns1 = ''; if (displacement) { var /** @type {?} */ dx = this.baseOffsetX + this.movedX + displacement[0]; var /** @type {?} */ dy = this.baseOffsetY + this.movedY + displacement[1]; } else { dx = this.baseOffsetX + this.movedX; dy = this.baseOffsetY + this.movedY; } // This should be adjusted to center at mouse position while zooming // if(ev && ev.offsetX){ // // Get mouse position relative to canvas center point and move // var mx = ev.offsetX-this.canvasCentroid[0]; // var my = ev.offsetY-this.canvasCentroid[1]; // var dx2 = mx; // var dy2 = my; // trns1 = `translate(${dx2},${dy2})`; // console.log(trns1) // } var /** @type {?} */ oldScale = decompose(this.transform).scale; var /** @type {?} */ oldTrns = decompose(this.transform).translate; var /** @type {?} */ scale = "scale(" + this.scaled + "," + this.scaled + ")"; var /** @type {?} */ trns = "translate(" + dx + "," + dy + ")"; // Update transform attribute this.transform = trns + trns1 + scale; // this.transform = this.transform.replace(oldScale,scale); }; /** * @param {?} ev * @param {?} room * @return {?} */ PlanComponent.prototype.selectRoom = /** * @param {?} ev * @param {?} room * @return {?} */ function (ev, room) { // Get coordinates var /** @type {?} */ scale = this.scaled; var /** @type {?} */ offsetX = this.baseOffsetX + this.movedX; var /** @type {?} */ offsetY = this.baseOffsetY + this.movedY; var /** @type {?} */ screenX = ev.offsetX; var /** @type {?} */ screenY = ev.offsetX; var /** @type {?} */ x = (screenX - offsetX) / scale; var /** @type {?} */ y = (screenY - offsetY) / scale; var /** @type {?} */ coordinates = [x, -y]; // Emit event this.clickedRoom.emit({ uri: room.uri, coordinates: coordinates }); }; /** * @param {?} ev * @return {?} */ PlanComponent.prototype.onCanvasClick = /** * @param {?} ev * @return {?} */ function (ev) { if (ev.target.id != 'canvas') return; this.clickedCanvas.emit(); }; PlanComponent.decorators = [ { type: Component, args: [{ selector: 'ng-plan', template: "<!-- Toolbar -->\n<div class=\"toolbar\" *ngIf=\"toolbar\">\n <div class=\"icon-div\" (click)=\"zoomIn(null)\" matTooltip=\"Zoom in\">\n <mat-icon>zoom_in</mat-icon>\n </div>\n <div class=\"icon-div\" (click)=\"zoomOut(null)\" matTooltip=\"Zoom out\">\n <mat-icon>zoom_out</mat-icon>\n </div>\n <div class=\"icon-div\" (click)=\"zoomExtents()\" matTooltip=\"Zoom extents\">\n <mat-icon svgIcon=\"zoom_extents\"></mat-icon>\n </div>\n</div>\n\n<div class=\"footer\">\n <p>Press and hold \"Alt\" to pan</p>\n</div>\n\n<svg canvasEvents\n [style.cursor]=\"panMode ? 'move' : 'default'\"\n #canvas\n class=canvas\n (click)=\"onCanvasClick($event)\"\n (zoomOut)=\"zoomOut($event)\"\n (zoomIn)=\"zoomIn($event)\"\n (panMode)=\"panMode = $event\"\n (move)=\"move($event)\"\n (moveEnd)=\"moveEnd($event)\"\n (window:resize)=\"onWindowResize()\"\n (addNodeMode)=\"addNodeMode = $event\"\n id=\"canvas\">\n\n <!--Draw rooms-->\n <g *ngFor=\"let room of rooms\"\n [attr.transform]=\"transform\"\n [id]=\"room.uri\"\n (click)=\"selectRoom($event, room)\"\n [attr.fill]=\"room.selected ? selectedColor : room.color\"\n class=\"room\">\n\n <!-- Draw polygons -->\n <!-- <polygon *ngFor=\"let polygon of room.polygons\" \n [attr.points]=\"polygon\" \n class=\"polygon\">\n </polygon> -->\n\n <!-- Draw paths -->\n <path\n [class.selected-space]=\"room.selected\"\n [attr.d]=\"room.path\"\n class=\"polygon\">\n </path> \n\n <!-- Draw centroids -->\n <circle \n *ngIf=\"centroids\" \n [attr.cx]=\"room.centroid[0]\" \n fill=\"black\" \n [attr.cy]=\"room.centroid[1]\" \n [attr.r]=\"scaled*500\">\n </circle>\n\n <!-- Add labels -->\n <text \n [style.font-size.em]=\"'12'\"\n style = \"font-family:roboto; fill: black;\"\n text-anchor=\"middle\" \n alignment-baseline=\"central\"\n [attr.x]=\"room.centroid[0]\"\n [attr.dy]=\"room.description ? -150 : 0\"\n [attr.y]=\"room.centroid[1]\">\n {{room.name}}\n </text>\n\n <!-- Add description -->\n <text \n *ngIf = \"room.description\"\n [style.font-size.em]=\"'10'\"\n style = \"font-family:roboto; fill: black;\"\n text-anchor=\"middle\" \n alignment-baseline=\"central\"\n [attr.x]=\"room.centroid[0]\"\n [attr.y]=\"room.centroid[1]\"\n dy=\"150\">\n {{room.description}}\n </text>\n </g>\n\n</svg>", styles: [".canvas{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%;height:100%;position:fixed;top:0;left:0;bottom:0;right:0;z-index:-1}.polygon{stroke:#000;stroke-width:1;vector-effect:non-scaling-stroke;fill-rule:evenodd}.room:hover{opacity:.5;cursor:pointer}.selected-space{stroke-width:2}.toolbar{position:absolute;color:red;right:20px;width:100%}.icon-div>.mat-icon{float:right;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:rgba(220,220,220,.3);border-width:1px;border-radius:5px;margin-left:5px;padding:5px;z-index:1}.mat-icon{color:rgba(0,0,0,.6);font-size:30px;height:30px;width:30px;z-index:10}.mat-icon:hover{cursor:pointer;color:rgba(0,0,0,1)}.footer{background-color:rgba(220,220,220,.3);position:absolute;font-family:consolas;font-size:12px;padding:0 10px;border-radius:5px;bottom:10px}"] },] }, ]; /** @nocollapse */ PlanComponent.ctorParameters = function () { return [ { type: MatIconRegistry, }, { type: DomSanitizer, }, ]; }; PlanComponent.propDecorators = { "clickedRoom": [{ type: Output },], "clickedCanvas": [{ type: Output },], "data": [{ type: Input },], "colors": [{ type: Input },], "centroids": [{ type: Input },], "toolbar": [{ type: Input },], "selected": [{ type: Input },], "selectedColor": [{ type: Input },], "defaultColor": [{ type: Input },], "planContainer": [{ type: ViewChild, args: ['canvas',] },], }; return PlanComponent; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ var CanvasEventsDirective = /** @class */ (function () { function CanvasEventsDirective() { this.zoomOut = new EventEmitter(); this.zoomIn = new EventEmitter(); this.moveInit = new EventEmitter(); this.move = new EventEmitter(); this.moveEnd = new EventEmitter(); this.panMode = new EventEmitter(); this.addNodeMode = new EventEmitter(); this.mouseDown = false; this.dragMode = false; } /** * @param {?} event * @return {?} */ CanvasEventsDirective.prototype.onMouseWheelChrome = /** * @param {?} event * @return {?} */ function (event) { this.mouseWheelFunc(event); }; /** * @param {?} event * @return {?} */ CanvasEventsDirective.prototype.onMouseWheelFirefox = /** * @param {?} event * @return {?} */ function (event) { this.mouseWheelFunc(event); }; /** * @param {?} event * @return {?} */ CanvasEventsDirective.prototype.onMouseWheelIE = /** * @param {?} event * @return {?} */ function (event) { this.mouseWheelFunc(event); }; /** * @param {?} ev * @return {?} */ CanvasEventsDirective.prototype.onKeyDown = /** * @param {?} ev * @return {?} */ function (ev) { if (ev.key == "ArrowDown") { this.zoomIn.emit(ev); } if (ev.key == "ArrowUp") { this.zoomOut.emit(ev); } if (ev.key == "Alt") { // enable drag mode if holding Alt this.dragMode = true; this.panMode.emit(true); } if (ev.key == "Control") { this.addNodeMode.emit(true); } }; /** * @param {?} ev * @return {?} */ CanvasEventsDirective.prototype.onKeyUp = /** * @param {?} ev * @return {?} */ function (ev) { if (ev.key == "Alt") { // disable drag mode if releasing Alt this.dragMode = false; this.panMode.emit(false); } if (ev.key == "Control") { this.addNodeMode.emit(false); } }; /** * @param {?} ev * @return {?} */ CanvasEventsDirective.prototype.onMouseClick = /** * @param {?} ev * @return {?} */ function (ev) { this.mouseDown = true; // enable drag mode if clicking middle button if (ev.button == 1) this.dragMode = true; if (this.dragMode) { this.clickX = ev.offsetX; this.clickY = ev.offsetY; this.panMode.emit(true); this.moveInit.emit([ev.offsetX, ev.offsetY]); } }; /** * @param {?} ev * @return {?} */ CanvasEventsDirective.prototype.onMouseMove = /** * @param {?} ev * @return {?} */ function (ev) { if (this.dragMode && this.mouseDown) { this.move.emit([ev.offsetX - this.clickX, ev.offsetY - this.clickY]); } }; /** * @param {?} ev * @return {?} */ CanvasEventsDirective.prototype.onMouseUp = /** * @param {?} ev * @return {?} */ function (ev) { this.mouseDown = false; if (this.dragMode) { this.dragMode = false; this.panMode.emit(false); this.moveEnd.emit([ev.offsetX - this.clickX, ev.offsetY - this.clickY]); } }; /** * @param {?} event * @return {?} */ CanvasEventsDirective.prototype.mouseWheelFunc = /** * @param {?} event * @return {?} */ function (event) { var /** @type {?} */ event = window.event || event; // old IE support var /** @type {?} */ delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail))); if (delta > 0) { this.zoomIn.emit(event); } else if (delta < 0) { this.zoomOut.emit(event); } // for IE event.returnValue = false; // for Chrome and Firefox if (event.preventDefault) { event.preventDefault(); } }; CanvasEventsDirective.decorators = [ { type: Directive, args: [{ selector: '[canvasEvents]' },] }, ]; /** @nocollapse */ CanvasEventsDirective.propDecorators = { "zoomOut": [{ type: Output },], "zoomIn": [{ type: Output },], "moveInit": [{ type: Output },], "move": [{ type: Output },], "moveEnd": [{ type: Output },], "panMode": [{ type: Output },], "addNodeMode": [{ type: Output },], "onMouseWheelChrome": [{ type: HostListener, args: ['mousewheel', ['$event'],] },], "onMouseWheelFirefox": [{ type: HostListener, args: ['DOMMouseScroll', ['$event'],] },], "onMouseWheelIE": [{ type: HostListener, args: ['onmousewheel', ['$event'],] },], "onKeyDown": [{ type: HostListener, args: ['document:keydown', ['$event'],] },], "onKeyUp": [{ type: HostListener, args: ['document:keyup', ['$event'],] },], "onMouseClick": [{ type: HostListener, args: ['mousedown', ['$event'],] },], "onMouseMove": [{ type: HostListener, args: ['mousemove', ['$event'],] },], "onMouseUp": [{ type: HostListener, args: ['mouseup', ['$event'],] },], }; return CanvasEventsDirective; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ var PlanModule = /** @class */ (function () { function PlanModule() { } PlanModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule, BrowserAnimationsModule, MatIconModule, MatTooltipModule ], declarations: [PlanComponent, CanvasEventsDirective], exports: [PlanComponent] },] }, ]; return PlanModule; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ export { PlanModule, CanvasEventsDirective as ɵb, PlanComponent as ɵa }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmctcGxhbi5qcy5tYXAiLCJzb3VyY2VzIjpbIm5nOi8vbmctcGxhbi9saWIvcGxhbi5jb21wb25lbnQudHMiLCJuZzovL25nLXBsYW4vbGliL2NhbnZhcy1ldmVudHMuZGlyZWN0aXZlLnRzIiwibmc6Ly9uZy1wbGFuL2xpYi9wbGFuLm1vZHVsZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0LCBPdXRwdXQsIEV2ZW50RW1pdHRlciwgQWZ0ZXJWaWV3SW5pdCwgT25DaGFuZ2VzLCBTaW1wbGVDaGFuZ2VzLCBWaWV3Q2hpbGQsIEVsZW1lbnRSZWYsIEhvc3RMaXN0ZW5lciB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgTWF0SWNvblJlZ2lzdHJ5IH0gZnJvbSBcIkBhbmd1bGFyL21hdGVyaWFsL2ljb25cIjtcbmltcG9ydCB7IERvbVNhbml0aXplciB9IGZyb20gXCJAYW5ndWxhci9wbGF0Zm9ybS1icm93c2VyXCI7XG5cbmltcG9ydCAqIGFzIGQzIGZyb20gJ2QzLWRlY29tcG9zZSc7ICAvLyB0YWtlcyBTVkcgb3IgQ1NTMyB0cmFuc2Zvcm0gc3RyaW5ncyBhbmQgY29udmVydHMgdGhlbSBpbnRvIHVzYWJsZSB2YWx1ZXNcbmltcG9ydCAqIGFzIGQzcCBmcm9tICdkMy1wb2x5Z29uJzsgICAvLyBPcGVyYXRpb25zIG9uIHBvbHlnb25zXG5cbmV4cG9ydCBpbnRlcmZhY2UgUm9vbSB7XG4gICAgbmFtZTogc3RyaW5nO1xuICAgIHBvbHlnb25zOiBzdHJpbmdbXTtcbn1cblxuQENvbXBvbmVudCh7XG4gICAgc2VsZWN0b3I6ICduZy1wbGFuJyxcbiAgICB0ZW1wbGF0ZTogYDwhLS0gVG9vbGJhciAtLT5cclxuPGRpdiBjbGFzcz1cInRvb2xiYXJcIiAqbmdJZj1cInRvb2xiYXJcIj5cclxuICAgIDxkaXYgY2xhc3M9XCJpY29uLWRpdlwiIChjbGljayk9XCJ6b29tSW4obnVsbClcIiBtYXRUb29sdGlwPVwiWm9vbSBpblwiPlxyXG4gICAgICAgIDxtYXQtaWNvbj56b29tX2luPC9tYXQtaWNvbj5cclxuICAgIDwvZGl2PlxyXG4gICAgPGRpdiBjbGFzcz1cImljb24tZGl2XCIgKGNsaWNrKT1cInpvb21PdXQobnVsbClcIiBtYXRUb29sdGlwPVwiWm9vbSBvdXRcIj5cclxuICAgICAgICA8bWF0LWljb24+em9vbV9vdXQ8L21hdC1pY29uPlxyXG4gICAgPC9kaXY+XHJcbiAgICA8ZGl2IGNsYXNzPVwiaWNvbi1kaXZcIiAoY2xpY2spPVwiem9vbUV4dGVudHMoKVwiIG1hdFRvb2x0aXA9XCJab29tIGV4dGVudHNcIj5cclxuICAgICAgICA8bWF0LWljb24gc3ZnSWNvbj1cInpvb21fZXh0ZW50c1wiPjwvbWF0LWljb24+XHJcbiAgICA8L2Rpdj5cclxuPC9kaXY+XHJcblxyXG48ZGl2IGNsYXNzPVwiZm9vdGVyXCI+XHJcbiAgICA8cD5QcmVzcyBhbmQgaG9sZCBcIkFsdFwiIHRvIHBhbjwvcD5cclxuPC9kaXY+XHJcblxyXG48c3ZnIGNhbnZhc0V2ZW50c1xyXG4gICAgW3N0eWxlLmN1cnNvcl09XCJwYW5Nb2RlID8gJ21vdmUnIDogJ2RlZmF1bHQnXCJcclxuICAgICNjYW52YXNcclxuICAgIGNsYXNzPWNhbnZhc1xyXG4gICAgKGNsaWNrKT1cIm9uQ2FudmFzQ2xpY2soJGV2ZW50KVwiXHJcbiAgICAoem9vbU91dCk9XCJ6b29tT3V0KCRldmVudClcIlxyXG4gICAgKHpvb21Jbik9XCJ6b29tSW4oJGV2ZW50KVwiXHJcbiAgICAocGFuTW9kZSk9XCJwYW5Nb2RlID0gJGV2ZW50XCJcclxuICAgIChtb3ZlKT1cIm1vdmUoJGV2ZW50KVwiXHJcbiAgICAobW92ZUVuZCk9XCJtb3ZlRW5kKCRldmVudClcIlxyXG4gICAgKHdpbmRvdzpyZXNpemUpPVwib25XaW5kb3dSZXNpemUoKVwiXHJcbiAgICAoYWRkTm9kZU1vZGUpPVwiYWRkTm9kZU1vZGUgPSAkZXZlbnRcIlxyXG4gICAgaWQ9XCJjYW52YXNcIj5cclxuXHJcbiAgICA8IS0tRHJhdyByb29tcy0tPlxyXG4gICAgPGcgKm5nRm9yPVwibGV0IHJvb20gb2Ygcm9vbXNcIlxyXG4gICAgICAgIFthdHRyLnRyYW5zZm9ybV09XCJ0cmFuc2Zvcm1cIlxyXG4gICAgICAgIFtpZF09XCJyb29tLnVyaVwiXHJcbiAgICAgICAgKGNsaWNrKT1cInNlbGVjdFJvb20oJGV2ZW50LCByb29tKVwiXHJcbiAgICAgICAgW2F0dHIuZmlsbF09XCJyb29tLnNlbGVjdGVkID8gc2VsZWN0ZWRDb2xvciA6IHJvb20uY29sb3JcIlxyXG4gICAgICAgIGNsYXNzPVwicm9vbVwiPlxyXG5cclxuICAgICAgICA8IS0tIERyYXcgcG9seWdvbnMgLS0+XHJcbiAgICAgICAgPCEtLSA8cG9seWdvbiAqbmdGb3I9XCJsZXQgcG9seWdvbiBvZiByb29tLnBvbHlnb25zXCIgXHJcbiAgICAgICAgICAgIFthdHRyLnBvaW50c109XCJwb2x5Z29uXCIgXHJcbiAgICAgICAgICAgIGNsYXNzPVwicG9seWdvblwiPlxyXG4gICAgICAgIDwvcG9seWdvbj4gLS0+XHJcblxyXG4gICAgICAgIDwhLS0gRHJhdyBwYXRocyAtLT5cclxuICAgICAgICA8cGF0aFxyXG4gICAgICAgICAgICBbY2xhc3Muc2VsZWN0ZWQtc3BhY2VdPVwicm9vbS5zZWxlY3RlZFwiXHJcbiAgICAgICAgICAgIFthdHRyLmRdPVwicm9vbS5wYXRoXCJcclxuICAgICAgICAgICAgY2xhc3M9XCJwb2x5Z29uXCI+XHJcbiAgICAgICAgPC9wYXRoPiBcclxuXHJcbiAgICAgICAgPCEtLSBEcmF3IGNlbnRyb2lkcyAtLT5cclxuICAgICAgICA8Y2lyY2xlIFxyXG4gICAgICAgICAgICAqbmdJZj1cImNlbnRyb2lkc1wiIFxyXG4gICAgICAgICAgICBbYXR0ci5jeF09XCJyb29tLmNlbnRyb2lkWzBdXCIgXHJcbiAgICAgICAgICAgIGZpbGw9XCJibGFja1wiIFxyXG4gICAgICAgICAgICBbYXR0ci5jeV09XCJyb29tLmNlbnRyb2lkWzFdXCIgXHJcbiAgICAgICAgICAgIFthdHRyLnJdPVwic2NhbGVkKjUwMFwiPlxyXG4gICAgICAgIDwvY2lyY2xlPlxyXG5cclxuICAgICAgICA8IS0tIEFkZCBsYWJlbHMgLS0+XHJcbiAgICAgICAgPHRleHQgXHJcbiAgICAgICAgICAgIFtzdHlsZS5mb250LXNpemUuZW1dPVwiJzEyJ1wiXHJcbiAgICAgICAgICAgIHN0eWxlID0gXCJmb250LWZhbWlseTpyb2JvdG87IGZpbGw6IGJsYWNrO1wiXHJcbiAgICAgICAgICAgIHRleHQtYW5jaG9yPVwibWlkZGxlXCIgXHJcbiAgICAgICAgICAgIGFsaWdubWVudC1iYXNlbGluZT1cImNlbnRyYWxcIlxyXG4gICAgICAgICAgICBbYXR0ci54XT1cInJvb20uY2VudHJvaWRbMF1cIlxyXG4gICAgICAgICAgICBbYXR0ci5keV09XCJyb29tLmRlc2NyaXB0aW9uID8gLTE1MCA6IDBcIlxyXG4gICAgICAgICAgICBbYXR0ci55XT1cInJvb20uY2VudHJvaWRbMV1cIj5cclxuICAgICAgICAgICAge3tyb29tLm5hbWV9fVxyXG4gICAgICAgIDwvdGV4dD5cclxuXHJcbiAgICAgICAgPCEtLSBBZGQgZGVzY3JpcHRpb24gLS0+XHJcbiAgICAgICAgPHRleHQgXHJcbiAgICAgICAgICAgICpuZ0lmID0gXCJyb29tLmRlc2NyaXB0aW9uXCJcclxuICAgICAgICAgICAgW3N0eWxlLmZvbnQtc2l6ZS5lbV09XCInMTAnXCJcclxuICAgICAgICAgICAgc3R5bGUgPSBcImZvbnQtZmFtaWx5OnJvYm90bzsgZmlsbDogYmxhY2s7XCJcclxuICAgICAgICAgICAgdGV4dC1hbmNob3I9XCJtaWRkbGVcIiBcclxuICAgICAgICAgICAgYWxpZ25tZW50LWJhc2VsaW5lPVwiY2VudHJhbFwiXHJcbiAgICAgICAgICAgIFthdHRyLnhdPVwicm9vbS5jZW50cm9pZFswXVwiXHJcbiAgICAgICAgICAgIFthdHRyLnldPVwicm9vbS5jZW50cm9pZFsxXVwiXHJcbiAgICAgICAgICAgIGR5PVwiMTUwXCI+XHJcbiAgICAgICAgICAgIHt7cm9vbS5kZXNjcmlwdGlvbn19XHJcbiAgICAgICAgPC90ZXh0PlxyXG4gICAgPC9nPlxyXG5cclxuPC9zdmc+YCxcbiAgICBzdHlsZXM6IFtgLmNhbnZhc3std2Via2l0LXVzZXItc2VsZWN0Om5vbmU7LW1vei11c2VyLXNlbGVjdDpub25lOy1tcy11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7d2lkdGg6MTAwJTtoZWlnaHQ6MTAwJTtwb3NpdGlvbjpmaXhlZDt0b3A6MDtsZWZ0OjA7Ym90dG9tOjA7cmlnaHQ6MDt6LWluZGV4Oi0xfS5wb2x5Z29ue3N0cm9rZTojMDAwO3N0cm9rZS13aWR0aDoxO3ZlY3Rvci1lZmZlY3Q6bm9uLXNjYWxpbmctc3Ryb2tlO2ZpbGwtcnVsZTpldmVub2RkfS5yb29tOmhvdmVye29wYWNpdHk6LjU7Y3Vyc29yOnBvaW50ZXJ9LnNlbGVjdGVkLXNwYWNle3N0cm9rZS13aWR0aDoyfS50b29sYmFye3Bvc2l0aW9uOmFic29sdXRlO2NvbG9yOnJlZDtyaWdodDoyMHB4O3dpZHRoOjEwMCV9Lmljb24tZGl2Pi5tYXQtaWNvbntmbG9hdDpyaWdodDstd2Via2l0LXVzZXItc2VsZWN0Om5vbmU7LW1vei11c2VyLXNlbGVjdDpub25lOy1tcy11c2VyLXNlbGVjdDpub25lO3VzZXItc2VsZWN0Om5vbmU7YmFja2dyb3VuZC1jb2xvcjpyZ2JhKDIyMCwyMjAsMjIwLC4zKTtib3JkZXItd2lkdGg6MXB4O2JvcmRlci1yYWRpdXM6NXB4O21hcmdpbi1sZWZ0OjVweDtwYWRkaW5nOjVweDt6LWluZGV4OjF9Lm1hdC1pY29ue2NvbG9yOnJnYmEoMCwwLDAsLjYpO2ZvbnQtc2l6ZTozMHB4O2hlaWdodDozMHB4O3dpZHRoOjMwcHg7ei1pbmRleDoxMH0ubWF0LWljb246aG92ZXJ7Y3Vyc29yOnBvaW50ZXI7Y29sb3I6cmdiYSgwLDAsMCwxKX0uZm9vdGVye2JhY2tncm91bmQtY29sb3I6cmdiYSgyMjAsMjIwLDIyMCwuMyk7cG9zaXRpb246YWJzb2x1dGU7Zm9udC1mYW1pbHk6Y29uc29sYXM7Zm9udC1zaXplOjEycHg7cGFkZGluZzowIDEwcHg7Ym9yZGVyLXJhZGl1czo1cHg7Ym90dG9tOjEwcHh9YF1cbn0pXG5leHBvcnQgY2xhc3MgUGxhbkNvbXBvbmVudCBpbXBsZW1lbnRzIEFmdGVyVmlld0luaXQge1xuXG4gICAgQE91dHB1dCgpIGNsaWNrZWRSb29tID0gbmV3IEV2ZW50RW1pdHRlcigpOyAgICAgICAgICAgICAvLyBDbGlja2VkIHJvb21cbiAgICBAT3V0cHV0KCkgY2xpY2tlZENhbnZhcyA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcblxuICAgIEBJbnB1dCgpIHByaXZhdGUgZGF0YTsgICAgICAgICAgICAgICAgICAgICAgIC8vZ2VvSlNPTlxuICAgIEBJbnB1dCgpIHByaXZhdGUgY29sb3JzOyAgICAgICAgICAgICAgICAgICAgIC8vY29sb3Igc2NoZW1hXG4gICAgQElucHV0KCkgcHVibGljIGNlbnRyb2lkczogYm9vbGVhbjsgICAgICAgICAgLy9jb2xvciBzY2hlbWFcbiAgICBASW5wdXQoKSBwdWJsaWMgdG9vbGJhcjogYm9vbGVhbjsgICAgICAgICAgICAvL3Rvb2xiYXIgdmlzaWJsZT9cbiAgICBASW5wdXQoKSBwcml2YXRlIHNlbGVjdGVkOiBzdHJpbmdbXTsgICAgICAgICAvL1NwYWNlcyB0byBiZSBoaWdobGlnaHRlZFxuXG4gICAgLy8gU3R5bGVzXG4gICAgQElucHV0KCkgcHVibGljIHNlbGVjdGVkQ29sb3I6IHN0cmluZyA9ICcjZWJlZmU0JztcbiAgICBASW5wdXQoKSBwdWJsaWMgZGVmYXVsdENvbG9yOiBzdHJpbmcgPSAnI2YyZmxlYyc7XG5cbiAgICBwdWJsaWMgcm9vbXM7XG5cbiAgICAvLyBDYW52YXNcbiAgICBwcml2YXRlIGNhbnZhc1dpZHRoO1xuICAgIHByaXZhdGUgY2FudmFzSGVpZ2h0O1xuICAgIHByaXZhdGUgY2FudmFzQ2VudHJvaWQ7XG5cbiAgICAvLyBTY2FsZSAvIG9mZnNldFxuICAgIC8vIFRoZXNlIGZhY3RvcnMgYXJlIGNhbGN1bGF0ZWQgZnJvbSBnZW9tZXRyeSBleHRlbmRzXG4gICAgcHJpdmF0ZSBiYXNlU2NhbGUgPSAwLjk7XG4gICAgcHJpdmF0ZSBiYXNlT2Zmc2V0WCA9IDA7XG4gICAgcHJpdmF0ZSBiYXNlT2Zmc2V0WSA9IDA7XG5cbiAgICAvLyBnZW9tZXRyeVxuICAgIHB1YmxpYyB0cmFuc2Zvcm0gPSBgdHJhbnNsYXRlKCR7dGhpcy5iYXNlT2Zmc2V0WH0sJHt0aGlzLmJhc2VPZmZzZXRZfSkgc2NhbGUoJHt0aGlzLmJhc2VTY2FsZX0sJHt0aGlzLmJhc2VTY2FsZX0pYDtcbiAgICBwcml2YXRlIG1vdmVkWDogbnVtYmVyID0gMDsgLy8gc3RvcmUgbW92ZSBzdGF0ZVxuICAgIHByaXZhdGUgbW92ZWRZOiBudW1iZXIgPSAwOyAvLyBzdG9yZSBtb3ZlIHN0YXRlXG4gICAgcHVibGljIHNjYWxlZDogbnVtYmVyID0gMSAvLyBzdG9yZSBzY2FsZSBzdGF0ZVxuICAgIHB1YmxpYyBib3VuZGluZ0JveDsgICAgICAgICAvLyBTdG9yZXMgYm91bmRpbmdCb3ggYXMgW3hNaW4sIHlNaW4sIHhNYXgsIHlNYXhdXG5cbiAgICAvLyBtb2Rlc1xuICAgIHB1YmxpYyBwYW5Nb2RlOiBib29sZWFuID0gZmFsc2U7XG4gICAgcHVibGljIGFkZE5vZGVNb2RlOiBib29sZWFuID0gZmFsc2U7XG5cbiAgICBAVmlld0NoaWxkKCdjYW52YXMnKSBwcml2YXRlIHBsYW5Db250YWluZXI6IEVsZW1lbnRSZWY7XG5cbiAgICBjb25zdHJ1Y3RvcihcbiAgICAgIHByaXZhdGUgbWF0SWNvblJlZ2lzdHJ5OiBNYXRJY29uUmVnaXN0cnksXG4gICAgICBwcml2YXRlIGRvbVNhbml0aXplcjogRG9tU2FuaXRpemVyXG4gICAgKSB7fVxuXG4gICAgbmdBZnRlclZpZXdJbml0KCl7XG4gICAgICAgIHRoaXMuZ2V0Q2FudmFzU2l6ZSgpO1xuICAgICAgICAvLyB0aGlzLmFkZFNWR2ljb25zKCk7XG4gICAgfVxuXG4gICAgbmdPbkNoYW5nZXMoY2hhbmdlczogU2ltcGxlQ2hhbmdlcykge1xuICAgICAgICBpZiAoY2hhbmdlcy5kYXRhICYmIGNoYW5nZXMuZGF0YS5jdXJyZW50VmFsdWUpIHtcbiAgICAgICAgICAgIHRoaXMuZGF0YSA9IGNoYW5nZXMuZGF0YS5jdXJyZW50VmFsdWU7XG4gICAgICAgICAgICB0aGlzLmV4dHJhY3RSb29tcygpO1xuICAgICAgICAgICAgdGhpcy5nZXRTY2FsZU9mZnNldCgpO1xuICAgICAgICAgICAgdGhpcy56b29tRXh0ZW50cygpO1xuICAgICAgICB9XG4gICAgICAgIGlmIChjaGFuZ2VzLmNvbG9ycyAmJiBjaGFuZ2VzLmNvbG9ycy5jdXJyZW50VmFsdWUpe1xuICAgICAgICAgICAgdGhpcy5kZWZpbmVDb2xvcnMoKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoY2hhbmdlcy5zZWxlY3RlZCl7XG4gICAgICAgICAgICB0aGlzLnVwZGF0ZVNlbGVjdGlvbigpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgLy8gYWRkU1ZHaWNvbnMoKXtcbiAgICAvLyAgIHRoaXMubWF0SWNvblJlZ2lzdHJ5LmFkZFN2Z0ljb24oXG4gICAgLy8gICAgIFwiem9vbV9leHRlbnRzXCIsXG4gICAgLy8gICAgIHRoaXMuZG9tU2FuaXRpemVyLmJ5cGFzc1NlY3VyaXR5VHJ1c3RSZXNvdXJjZVVybChcIi4uL2Fzc2V0cy96b29tX2V4dGVudHMuc3ZnXCIpXG4gICAgLy8gICApO1xuICAgIC8vIH1cblxuICAgIHVwZGF0ZVNlbGVjdGlvbigpe1xuICAgICAgICBpZighdGhpcy5yb29tcykgcmV0dXJuO1xuICAgICAgICB0aGlzLnJvb21zID0gdGhpcy5yb29tcy5tYXAoeCA9PiB7XG4gICAgICAgICAgICBpZih0aGlzLnNlbGVjdGVkLmluZGV4T2YoeC51cmkpICE9IC0xKXtcbiAgICAgICAgICAgICAgICB4LnNlbGVjdGVkID0gdHJ1ZTtcbiAgICAgICAgICAgIH1lbHNle1xuICAgICAgICAgICAgICAgIHguc2VsZWN0ZWQgPSBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybiB4O1xuICAgICAgICB9KVxuICAgIH1cblxuICAgIGRlZmluZUNvbG9ycygpe1xuICAgICAgICB0aGlzLnJvb21zLm1hcCh4ID0+IHtcbiAgICAgICAgICAgIHZhciBtYXRjaCA9IHRoaXMuY29sb3JzLmZpbHRlcih5ID0+IHkudXJpID09IHgudXJpKTtcbiAgICAgICAgICAgIGlmKG1hdGNoLmxlbmd0aCA+IDApe1xuICAgICAgICAgICAgICAgIHguY29sb3IgPSBtYXRjaFswXS5jb2xvcjtcbiAgICAgICAgICAgICAgICB4LmRlc2NyaXB0aW9uID0gbWF0Y2hbMF0udmFsdWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4geDtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgZ2V0Q2FudmFzU2l6ZSgpe1xuICAgICAgICBjb25zdCBlbGVtZW50ID0gdGhpcy5wbGFuQ29udGFpbmVyLm5hdGl2ZUVsZW1lbnQ7XG4gICAgICAgIGNvbnN0IHNpemUgPSBlbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuICAgICAgICB0aGlzLmNhbnZhc1dpZHRoID0gc2l6ZS53aWR0aDtcbiAgICAgICAgdGhpcy5jYW52YXNIZWlnaHQgPSBzaXplLmhlaWdodDtcbiAgICAgICAgdGhpcy5jYW52YXNDZW50cm9pZCA9IFt0aGlzLmNhbnZhc1dpZHRoLzIsIHRoaXMuY2FudmFzSGVpZ2h0LzJdOyAgICAgICAgXG4gICAgfVxuXG4gICAgZXh0cmFjdFJvb21zKCl7XG4gICAgICAgIGlmKCF0aGlzLmRhdGEuZmVhdHVyZXMpIHJldHVybiB0aGlzLnJvb21zID0gbnVsbDtcblxuICAgICAgICAvLyBGb3IgYm91bmRpbmcgYm94IFt4TWluLCB5TWluLCB4TWF4LCB5TWF4XVxuICAgICAgICB2YXIgeE1pbjtcbiAgICAgICAgdmFyIHlNaW47XG4gICAgICAgIHZhciB4TWF4O1xuICAgICAgICB2YXIgeU1heDtcbiAgICAgICAgXG4gICAgICAgIHRoaXMucm9vbXMgPSB0aGlzLmRhdGEuZmVhdHVyZXMubWFwKHJvb20gPT4ge1xuICAgICAgICAgICAgdmFyIHBvbHlnb25zID0gW107XG4gICAgICAgICAgICB2YXIgcm9vbVBhdGggPSAnJztcbiAgICAgICAgICAgIHZhciBjZW50cm9pZDtcbiAgICAgICAgICAgIGlmKHJvb20uZ2VvbWV0cnkgJiYgcm9vbS5nZW9tZXRyeS50eXBlID09IFwiUG9seWdvblwiKXtcbiAgICAgICAgICAgICAgICBcbiAgICAgICAgICAgICAgICB2YXIgcm9vbVBvbHlnb25zID0gW107ICAvLyBBbGxvdyBtdWx0aSBwb2x5Z29ucyBpZiBkb3VnaG51dCByb29tXG4gICAgICAgICAgICAgICAgcm9vbS5nZW9tZXRyeS5jb29yZGluYXRlcy5mb3JFYWNoKHBvbHlnb24gPT4ge1xuXG4gICAgICAgICAgICAgICAgICAgIHZhciBwb2ludHMgPSAnJztcbiAgICAgICAgICAgICAgICAgICAgdmFyIHBhdGggPSAnJztcbiAgICAgICAgICAgICAgICAgICAgLy8gUmVmbGVjdCB5IGNvb3JkaW5hdGVzIHRvIGZpdCBicm93c2VyIGNvb3JkaW5hdGUgc3lzdGVtIGFuZCBleHRyYWN0IHRvIHBvbHlnb25cbiAgICAgICAgICAgICAgICAgICAgcG9seWdvbi5tYXAoKGNvb3JkaW5hdGUsaW5kZXgpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciB4ID0gY29vcmRpbmF0ZVswXTtcbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciB5ID0gLWNvb3JkaW5hdGVbMV07IC8vIHJlZmxlY3Qgc2luY2UgU1ZHIHVzZXMgcmVmbGVjdGVkIGNvb3JkaW5hdGUgc3lzdGVtXG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50cys9IGAke3h9LCR7eX0gYDtcbiAgICAgICAgICAgICAgICAgICAgICAgIHBhdGgrPSAoaW5kZXggPT0gMCkgPyBgTSR7eH0sJHt5fSBgIDogYEwke3h9LCR7eX0gYDtcblxuICAgICAgICAgICAgICAgICAgICAgICAgY29vcmRpbmF0ZVsxXSA9IHk7IC8vIFVwZGF0ZSBwb2x5Z29uIHdpdGggbmV3IHlcblxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gR2V0IGJvdW5kaW5nIGJveFxuICAgICAgICAgICAgICAgICAgICAgICAgaWYoIXhNaW4gfHwgeCA8IHhNaW4pIHhNaW4gPSB4O1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYoIXhNYXggfHwgeCA+IHhNYXgpIHhNYXggPSB4O1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYoIXlNaW4gfHwgeSA8IHlNaW4pIHlNaW4gPSB5O1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYoIXlNYXggfHwgeSA+IHlNYXgpIHlNYXggPSB5O1xuXG4gICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gY29vcmRpbmF0ZTtcbiAgICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgICAgcG9pbnRzID0gcG9pbnRzLnRyaW0oKTsgICAgLy8gcmVtb3ZlIGxhc3Qgc3BhY2VcbiAgICAgICAgICAgICAgICAgICAgcGF0aCA9IHBhdGgudHJpbSgpOyAgICAgICAgIC8vIHJlbW92ZSBsYXN0IHNwYWNlXG5cbiAgICAgICAgICAgICAgICAgICAgLy8gR2V0IHBvbHlnb24gY2VudHJvaWQgZnJvbSBmaXJzdCBwb2x5Z29uXG4gICAgICAgICAgICAgICAgICAgIGlmKHJvb21Qb2x5Z29ucy5sZW5ndGggPCAxKXtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRyb2lkID0gZDNwLnBvbHlnb25DZW50cm9pZChwb2x5Z29uKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIHJvb21Qb2x5Z29ucy5wdXNoKHBvaW50cyk7XG4gICAgICAgICAgICAgICAgICAgIHJvb21QYXRoKz1wYXRoO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgICAgIHBvbHlnb25zLnB1c2gocm9vbVBvbHlnb25zKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHZhciBuYW1lID0gcm9vbS5wcm9wZXJ0aWVzLm5hbWU7XG4gICAgICAgICAgICB2YXIgdXJpID0gcm9vbS5wcm9wZXJ0aWVzLnVyaTtcbiAgICAgICAgICAgIHZhciBjb2xvciA9IHJvb20ucHJvcGVydGllcy5jb2xvciA/IHJvb20ucHJvcGVydGllcy5jb2xvciA6IHRoaXMuZGVmYXVsdENvbG9yO1xuICAgICAgICAgICAgdmFyIGRlc2NyaXB0aW9uID0gcm9vbS5wcm9wZXJ0aWVzLmRlc2NyaXB0aW9uID8gcm9vbS5wcm9wZXJ0aWVzLmRlc2NyaXB0aW9uIDogJyc7XG4gICAgICAgICAgICBcbiAgICAgICAgICAgIC8vIFN0b3JlIGJvdW5kaW5nIGJveFxuICAgICAgICAgICAgdGhpcy5ib3VuZGluZ0JveCA9IFt4TWluLCB5TWluLCB4TWF4LCB5TWF4XTtcblxuICAgICAgICAgICAgcmV0dXJuIHtuYW1lOiBuYW1lLCB1cmk6IHVyaSwgZGVzY3JpcHRpb246IGRlc2NyaXB0aW9uLCBjb2xvcjogY29sb3IsIHBvbHlnb25zOiBwb2x5Z29ucywgcGF0aDpyb29tUGF0aCwgY2VudHJvaWQ6IGNlbnRyb2lkfVxuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBnZXRTY2FsZU9mZnNldCgpIHsgICBcbiAgICAgICAgLy8gQ2FsY3VsYXRlIGRhdGEgc2l6ZVxuICAgICAgICB2YXIgYmIgPSB0aGlzLmJvdW5kaW5nQm94O1xuICAgICAgICB2YXIgZGF0YVdpZHRoID0gYmJbMl0tYmJbMF07XG4gICAgICAgIHZhciBkYXRhSGVpZ2h0ID0gYmJbM10tYmJbMV07XG5cbiAgICAgICAgLy8gQ2FsY3VsYXRlIHNjYWxlIGZhY3RvcnNcbiAgICAgICAgdmFyIHNjYWxlV2lkdGggPSB0aGlzLmNhbnZhc1dpZHRoL2RhdGFXaWR0aDtcbiAgICAgICAgdmFyIHNjYWxlSGVpZ2h0ID0gdGhpcy5jYW52YXNIZWlnaHQvZGF0YUhlaWdodDtcbiAgICAgICAgdmFyIHNjYWxlID0gTWF0aC5taW4oc2NhbGVIZWlnaHQsc2NhbGVXaWR0aCk7XG5cbiAgICAgICAgdmFyIHNjYWxlZERhdGFDZW50cm9pZCA9IFtzY2FsZSooYmJbMF0rZGF0YVdpZHRoLzIpLCBzY2FsZSooYmJbMV0rZGF0YUhlaWdodC8yKV07XG5cbiAgICAgICAgLy8gQ2FsY3VsYXRlIG9mZnNldCBmYWN0b3JzXG4gICAgICAgIHZhciBvZmZzZXRYID0gdGhpcy5jYW52YXNDZW50cm9pZFswXS1zY2FsZWREYXRhQ2VudHJvaWRbMF07XG4gICAgICAgIHZhciBvZmZzZXRZID0gdGhpcy5jYW52YXNDZW50cm9pZFsxXS1zY2FsZWREYXRhQ2VudHJvaWRbMV07XG5cbiAgICAgICAgLy8gU2V0IGdsb2JhbCB2YXJpYWJsZXNcbiAgICAgICAgdGhpcy5iYXNlT2Zmc2V0WCA9IG9mZnNldFg7XG4gICAgICAgIHRoaXMuYmFzZU9mZnNldFkgPSBvZmZzZXRZO1xuICAgICAgICB0aGlzLmJhc2VTY2FsZSA9IHNjYWxlO1xuICAgIFxuICAgICAgICByZXR1cm4gW3NjYWxlLG9mZnNldFgsb2Zmc2V0WV1cbiAgICB9XG5cbiAgICBtb3ZlKGRpc3BsYWNlbWVudCl7XG4gICAgICAgIGlmKGRpc3BsYWNlbWVudCl7XG4gICAgICAgICAgICB0aGlzLnpvb21IYW5kbGVyKHVuZGVmaW5lZCxkaXNwbGFjZW1lbnQpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgbW92ZUVuZChkaXNwbGFjZW1lbnQpe1xuICAgICAgICAvLyBVcGRhdGUgbW92ZWQgY29vcmRpbmF0ZXNcbiAgICAgICAgdGhpcy5tb3ZlZFggPSB0aGlzLm1vdmVkWCtkaXNwbGFjZW1lbnRbMF07XG4gICAgICAgIHRoaXMubW92ZWRZID0gdGhpcy5tb3ZlZFkrZGlzcGxhY2VtZW50WzFdO1xuICAgIH1cblxuICAgIG9uV2luZG93UmVzaXplKCl7XG4gICAgICAgIHRoaXMuZ2V0Q2FudmFzU2l6ZSgpO1xuICAgICAgICB0aGlzLmdldFNjYWxlT2Zmc2V0KCk7XG4gICAgICAgIHRoaXMuem9vbUV4dGVudHMoKTtcbiAgICB9XG5cbiAgICB6b29tRXh0ZW50cygpe1xuICAgICAgICB0aGlzLnNjYWxlZCA9IHRoaXMuYmFzZVNjYWxlKjAuOTU7XG4gICAgICAgIHRoaXMubW92ZWRYID0gMDtcbiAgICAgICAgdGhpcy5tb3ZlZFkgPSAwO1xuICAgICAgICB0aGlzLnpvb21IYW5kbGVyKCk7XG4gICAgfVxuXG4gICAgem9vbU91dChldil7XG4gICAgICAgIC8vIENhbGN1bGF0ZSBjdXJyZW50IHNjYWxlXG4gICAgICAgIHZhciBzY2FsZUZhY3RvciA9IHRoaXMuYmFzZVNjYWxlLzU7XG4gICAgICAgIHRoaXMuc2NhbGVkID0gdGhpcy5zY2FsZWQtc2NhbGVGYWN0b3I7XG4gICAgICAgIHRoaXMuem9vbUhhbmRsZXIoZXYpO1xuICAgIH1cblxuICAgIHpvb21Jbihldil7XG4gICAgICAgIHZhciBzY2FsZUZhY3RvciA9IHRoaXMuYmFzZVNjYWxlLzU7XG4gICAgICAgIHRoaXMuc2NhbGVkID0gdGhpcy5zY2FsZWQrc2NhbGVGYWN0b3I7XG4gICAgICAgIHRoaXMuem9vbUhhbmRsZXIoZXYpO1xuICAgIH1cblxuICAgIHpvb21IYW5kbGVyKGV2PyxkaXNwbGFjZW1lbnQ/KXtcblxuICAgICAgICB2YXIgdHJuczEgPSAnJztcblxuICAgICAgICBpZihkaXNwbGFjZW1lbnQpe1xuICAgICAgICAgICAgdmFyIGR4ID0gdGhpcy5iYXNlT2Zmc2V0WCArIHRoaXMubW92ZWRYICsgZGlzcGxhY2VtZW50WzBdO1xuICAgICAgICAgICAgdmFyIGR5ID0gdGhpcy5iYXNlT2Zmc2V0WSArIHRoaXMubW92ZWRZICsgZGlzcGxhY2VtZW50WzFdO1xuICAgICAgICB9ZWxzZXtcbiAgICAgICAgICAgIGR4ID0gdGhpcy5iYXNlT2Zmc2V0WCArIHRoaXMubW92ZWRYO1xuICAgICAgICAgICAgZHkgPSB0aGlzLmJhc2VPZmZzZXRZICsgdGhpcy5tb3ZlZFk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUaGlzIHNob3VsZCBiZSBhZGp1c3RlZCB0byBjZW50ZXIgYXQgbW91c2UgcG9zaXRpb24gd2hpbGUgem9vbWluZ1xuXG4gICAgICAgIC8vIGlmKGV2ICYmIGV2Lm9mZnNldFgpe1xuICAgICAgICAvLyAgICAgLy8gR2V0IG1vdXNlIHBvc2l0aW9uIHJlbGF0aXZlIHRvIGNhbnZhcyBjZW50ZXIgcG9pbnQgYW5kIG1vdmVcbiAgICAgICAgLy8gICAgIHZhciBteCA9IGV2Lm9mZnNldFgtdGhpcy5jYW52YXNDZW50cm9pZFswXTtcbiAgICAgICAgLy8gICAgIHZhciBteSA9IGV2Lm9mZnNldFktdGhpcy5jYW52YXNDZW50cm9pZFsxXTtcbiAgICAgICAgLy8gICAgIHZhciBkeDIgPSBteDtcbiAgICAgICAgLy8gICAgIHZhciBkeTIgPSBteTtcbiAgICAgICAgLy8gICAgIHRybnMxID0gYHRyYW5zbGF0ZSgke2R4Mn0sJHtkeTJ9KWA7XG4gICAgICAgIC8vICAgICBjb25zb2xlLmxvZyh0cm5zMSlcbiAgICAgICAgLy8gfVxuXG4gICAgICAgIHZhciBvbGRTY2FsZSA9IGQzLmRlY29tcG9zZSh0aGlzLnRyYW5zZm9ybSkuc2NhbGU7XG4gICAgICAgIHZhciBvbGRUcm5zID0gZDMuZGVjb21wb3NlKHRoaXMudHJhbnNmb3JtKS50cmFuc2xhdGU7XG4gICAgICAgIHZhciBzY2FsZSA9IGBzY2FsZSgke3RoaXMuc2NhbGVkfSwke3RoaXMuc2NhbGVkfSlgO1xuXG4gICAgICAgIHZhciB0cm5zID0gYHRyYW5zbGF0ZSgke2R4fSwke2R5fSlgO1xuXG4gICAgICAgIC8vIFVwZGF0ZSB0cmFuc2Zvcm0gYXR0cmlidXRlXG4gICAgICAgIHRoaXMudHJhbnNmb3JtID0gdHJucyt0cm5zMStzY2FsZTtcbiAgICAgICAgLy8gdGhpcy50cmFuc2Zvcm0gPSB0aGlzLnRyYW5zZm9ybS5yZXBsYWNlKG9sZFNjYWxlLHNjYWxlKTtcbiAgICB9XG5cbiAgICBzZWxlY3RSb29tKGV2LCByb29tKXtcbiAgICAgICAgLy8gR2V0IGNvb3JkaW5hdGVzXG4gICAgICAgIHZhciBzY2FsZSA9IHRoaXMuc2NhbGVkO1xuICAgICAgICB2YXIgb2Zmc2V0WCA9IHRoaXMuYmFzZU9mZnNldFgrdGhpcy5tb3ZlZFg7XG4gICAgICAgIHZhciBvZmZzZXRZID0gdGhpcy5iYXNlT2Zmc2V0WSt0aGlzLm1vdmVkWTtcbiAgICAgICAgdmFyIHNjcmVlblggPSBldi5vZmZzZXRYO1xuICAgICAgICB2YXIgc2NyZWVuWSA9IGV2Lm9mZnNldFg7XG5cbiAgICAgICAgdmFyIHggPSAoc2NyZWVuWC1vZmZzZXRYKS9zY2FsZTtcbiAgICAgICAgdmFyIHkgPSAoc2NyZWVuWS1vZmZzZXRZKS9zY2FsZTtcbiAgICAgICAgdmFyIGNvb3JkaW5hdGVzID0gW3gsLXldO1xuXG4gICAgICAgIC8vIEVtaXQgZXZlbnRcbiAgICAgICAgdGhpcy5jbGlja2VkUm9vbS5lbWl0KHt1cmk6IHJvb20udXJpLCBjb29yZGluYXRlczogY29vcmRpbmF0ZXN9KTtcbiAgICB9XG5cbiAgICBvbkNhbnZhc0NsaWNrKGV2KXtcbiAgICAgICAgaWYoZXYudGFyZ2V0LmlkICE9ICdjYW52YXMnKSByZXR1cm47XG4gICAgICAgIHRoaXMuY2xpY2tlZENhbnZhcy5lbWl0KCk7XG4gICAgfVxuXG59IiwiaW1wb3J0IHsgRGlyZWN0aXZlLCBPdXRwdXQsIEhvc3RMaXN0ZW5lciwgRXZlbnRFbWl0dGVyIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcblxyXG5ARGlyZWN0aXZlKHsgc2VsZWN0b3I6ICdbY2FudmFzRXZlbnRzXScgfSlcclxuZXhwb3J0IGNsYXNzIENhbnZhc0V2ZW50c0RpcmVjdGl2ZSB7XHJcblxyXG4gIEBPdXRwdXQoKSB6b29tT3V0ID0gbmV3IEV2ZW50RW1pdHRlcigpO1xyXG4gIEBPdXRwdXQoKSB6b29tSW4gPSBuZXcgRXZlbnRFbWl0dGVyKCk7XHJcbiAgQE91dHB1dCgpIG1vdmVJbml0ID0gbmV3IEV2ZW50RW1pdHRlcigpO1xyXG4gIEBPdXRwdXQoKSBtb3ZlID0gbmV3IEV2ZW50RW1pdHRlcigpO1xyXG4gIEBPdXRwdXQoKSBtb3ZlRW5kID0gbmV3IEV2ZW50RW1pdHRlcigpO1xyXG4gIEBPdXRwdXQoKSBwYW5Nb2RlID0gbmV3IEV2ZW50RW1pdHRlcigpO1xyXG4gIEBPdXRwdXQoKSBhZGROb2RlTW9kZSA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcclxuXHJcbiAgcHJpdmF0ZSBtb3VzZURvd246IGJvb2xlYW4gPSBmYWxzZTtcclxuICBwcml2YXRlIGRyYWdNb2RlOiBib29sZWFuID0gZmFsc2U7XHJcblxyXG4gIHByaXZhdGUgY2xpY2tYO1xyXG4gIHByaXZhdGUgY2xpY2tZO1xyXG5cclxuICBASG9zdExpc3RlbmVyKCdtb3VzZXdoZWVsJywgWyckZXZlbnQnXSkgb25Nb3VzZVdoZWVsQ2hyb21lKGV2ZW50OiBhbnkpIHtcclxuICAgIHRoaXMubW91c2VXaGVlbEZ1bmMoZXZlbnQpO1xyXG4gIH1cclxuXHJcbiAgQEhvc3RMaXN0ZW5lcignRE9NTW91c2VTY3JvbGwnLCBbJyRldmVudCddKSBvbk1vdXNlV2hlZWxGaXJlZm94KGV2ZW50OiBhbnkpIHtcclxuICAgIHRoaXMubW91c2VXaGVlbEZ1bmMoZXZlbnQpO1xyXG4gIH1cclxuXHJcbiAgQEhvc3RMaXN0ZW5lcignb25tb3VzZXdoZWVsJywgWyckZXZlbnQnXSkgb25Nb3VzZVdoZWVsSUUoZXZlbnQ6IGFueSkge1xyXG4gICAgdGhpcy5tb3VzZVdoZWVsRnVuYyhldmVudCk7XHJcbiAgfVxyXG5cclxuICBASG9zdExpc3RlbmVyKCdkb2N1bWVudDprZXlkb3duJywgWyckZXZlbnQnXSlcclxuICBvbktleURvd24oZXY6S2V5Ym9hcmRFdmVudCl7XHJcbiAgICAgIGlmKGV2LmtleSA9PSBcIkFycm93RG93blwiKXtcclxuICAgICAgICB0aGlzLnpvb21Jbi5lbWl0KGV2KTtcclxuICAgICAgfVxyXG4gICAgICBpZihldi5rZXkgPT0gXCJBcnJvd1VwXCIpe1xyXG4gICAgICAgIHRoaXMuem9vbU91dC5lbWl0KGV2KTtcclxuICAgICAgfVxyXG4gICAgICBpZihldi5rZXkgPT0gXCJBbHRcIil7XHJcbiAgICAgICAgLy8gZW5hYmxlIGRyYWcgbW9kZSBpZiBob2xkaW5nIEFsdFxyXG4gICAgICAgIHRoaXMuZHJhZ01vZGUgPSB0cnVlO1xyXG4gICAgICAgIHRoaXMucGFuTW9kZS5lbWl0KHRydWUpO1xyXG4gICAgICB9XHJcbiAgICAgIGlmKGV2LmtleSA9PSBcIkNvbnRyb2xcIil7XHJcbiAgICAgICAgdGhpcy5hZGROb2RlTW9kZS5lbWl0KHRydWUpO1xyXG4gICAgICB9XHJcbiAgfVxyXG5cclxuICBASG9zdExpc3RlbmVyKCdkb2N1bWVudDprZXl1cCcsIFsnJGV2ZW50J10pXHJcbiAgb25LZXlVcChldjpLZXlib2FyZEV2ZW50KXtcclxuICAgICAgaWYoZXYua2V5ID09IFwiQWx0XCIpe1xyXG4gICAgICAgIC8vIGRpc2FibGUgZHJhZyBtb2RlIGlmIHJlbGVhc2luZyBBbHRcclxuICAgICAgICB0aGlzLmRyYWdNb2RlID0gZmFsc2U7XHJcbiAgICAgICAgdGhpcy5wYW5Nb2RlLmVtaXQoZmFsc2UpO1xyXG4gICAgICB9XHJcbiAgICAgIGlmKGV2LmtleSA9PSBcIkNvbnRyb2xcIil7XHJcbiAgICAgICAgdGhpcy5hZGROb2RlTW9kZS5lbWl0KGZhbHNlKTtcclxuICAgICAgfVxyXG4gICAgICBcclxuICB9XHJcblxyXG4gIEBIb3N0TGlzdGVuZXIoJ21vdXNlZG93bicsIFsnJGV2ZW50J10pXHJcbiAgb25Nb3VzZUNsaWNrKGV2OiBNb3VzZUV2ZW50KSB7XHJcblxyXG4gICAgICB0aGlzLm1vdXNlRG93biA9IHRydWU7XHJcblxyXG4gICAgICAvLyBlbmFibGUgZHJhZyBtb2RlIGlmIGNsaWNraW5nIG1pZGRsZSBidXR0b25cclxuICAgICAgaWYoZXYuYnV0dG9uID09IDEpIHRoaXMuZHJhZ01vZGUgPSB0cnVlO1xyXG5cclxuICAgICAgaWYodGhpcy5kcmFnTW9kZSl7XHJcbiAgICAgICAgdGhpcy5jbGlja1ggPSBldi5vZmZzZXRYO1xyXG4gICAgICAgIHRoaXMuY2xpY2tZID0gZXYub2Zmc2V0WTtcclxuICAgICAgICB0aGlzLnBhbk1vZGUuZW1pdCh0cnVlKTtcclxuICAgICAgICB0aGlzLm1vdmVJbml0LmVtaXQoW2V2Lm9mZnNldFgsZXYub2Zmc2V0WV0pO1xyXG4gICAgICB9XHJcbiAgfVxyXG5cclxuICBASG9zdExpc3RlbmVyKCdtb3VzZW1vdmUnLCBbJyRldmVudCddKVxyXG4gIG9uTW91c2VNb3ZlKGV2OiBNb3VzZUV2ZW50KSB7XHJcbiAgICAgIGlmKHRoaXMuZHJhZ01vZGUgJiYgdGhpcy5tb3VzZURvd24pe1xyXG4gICAgICAgICAgdGhpcy5tb3ZlLmVtaXQoW2V2Lm9mZnNldFgtdGhpcy5jbGlja1gsZXYub2Zmc2V0WS10aGlzLmNsaWNrWV0pO1xyXG4gICAgICB9XHJcbiAgfVxyXG5cclxuICBASG9zdExpc3RlbmVyKCdtb3VzZXVwJywgWyckZXZlbnQnXSlcclxuICBvbk1vdXNlVXAoZXY6IE1vdXNlRXZlbnQpIHtcclxuICAgICAgdGhpcy5tb3VzZURvd24gPSBmYWxzZTtcclxuICAgICAgaWYodGhpcy5kcmFnTW9kZSl7XHJcbiAgICAgICAgdGhpcy5kcmFnTW9kZSA9IGZhbHNlO1xyXG4gICAgICAgIHRoaXMucGFuTW9kZS5lbWl0KGZhbHNlKTtcclxuICAgICAgICB0aGlzLm1vdmVFbmQuZW1pdChbZXYub2Zmc2V0WC10aGlzLmNsaWNrWCxldi5vZmZzZXRZLXRoaXMuY2xpY2tZXSk7XHJcbiAgICAgIH1cclxuICB9XHJcblxyXG4gIG1vdXNlV2hlZWxGdW5jKGV2ZW50OiBhbnkpIHtcclxuICAgIHZhciBldmVudCA9IHdpbmRvdy5ldmVudCB8fCBldmVudDsgLy8gb2xkIElFIHN1cHBvcnRcclxuICAgIHZhciBkZWx0YSA9IE1hdGgubWF4KC0xLCBNYXRoLm1pbigxLCAoZXZlbnQud2hlZWxEZWx0YSB8fCAtZXZlbnQuZGV0YWlsKSkpO1xyXG4gICAgaWYoZGVsdGEgPiAwKSB7XHJcbiAgICAgICAgdGhpcy56b29tSW4uZW1pdChldmVudCk7XHJcbiAgICB9IGVsc2UgaWYoZGVsdGEgPCAwKSB7XHJcbiAgICAgICAgdGhpcy56b29tT3V0LmVtaXQoZXZlbnQpO1xyXG4gICAgfVxyXG4gICAgLy8gZm9yIElFXHJcbiAgICBldmVudC5yZXR1cm5WYWx1ZSA9IGZhbHNlO1xyXG4gICAgLy8gZm9yIENocm9tZSBhbmQgRmlyZWZveFxyXG4gICAgaWYoZXZlbnQucHJldmVudERlZmF1bHQpIHtcclxuICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xyXG4gICAgfVxyXG4gIH1cclxufSIsImltcG9ydCB7IENvbW1vbk1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XG5pbXBvcnQgeyBOZ01vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5pbXBvcnQgeyBQbGFuQ29tcG9uZW50IH0gZnJvbSAnLi9wbGFuLmNvbXBvbmVudCc7XG5cbi8vIEFuZ3VsYXIgbWF0ZXJpYWxcbmltcG9ydCB7IEJyb3dzZXJBbmltYXRpb25zTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvcGxhdGZvcm0tYnJvd3Nlci9hbmltYXRpb25zJztcbmltcG9ydCB7IE1hdEljb25Nb2R1bGUsIFxuICAgICAgICAgTWF0VG9vbHRpcE1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL21hdGVyaWFsJztcblxuLy8gRGlyZWN0aXZlc1xuaW1wb3J0IHsgQ2FudmFzRXZlbnRzRGlyZWN0aXZlIH0gZnJvbSAnLi9jYW52YXMtZXZlbnRzLmRpcmVjdGl2ZSc7XG5cbkBOZ01vZHVsZSh7XG4gIGltcG9ydHM6IFtcbiAgICBDb21tb25Nb2R1bGUsXG4gICAgQnJvd3NlckFuaW1hdGlvbnNNb2R1bGUsXG4gICAgTWF0SWNvbk1vZHVsZSxcbiAgICBNYXRUb29sdGlwTW9kdWxlXG4gIF0sXG4gIGRlY2xhcmF0aW9uczogW1BsYW5Db21wb25lbnQsIENhbnZhc0V2ZW50c0RpcmVjdGl2ZV0sXG4gIGV4cG9ydHM6IFtQbGFuQ29tcG9uZW50XVxufSlcbmV4cG9ydCBjbGFzcyBQbGFuTW9kdWxlIHsgfVxuIl0sIm5hbWVzIjpbImQzcC5wb2x5Z29uQ2VudHJvaWQiLCJkMy5kZWNvbXBvc2UiXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7QUFBQTtJQWlKSSx1QkFDVSxpQkFDQTtRQURBLG9CQUFlLEdBQWYsZUFBZTtRQUNmLGlCQUFZLEdBQVosWUFBWTsyQkF6Q0UsSUFBSSxZQUFZLEVBQUU7NkJBQ2hCLElBQUksWUFBWSxFQUFFOzs2QkFTSixTQUFTOzRCQUNWLFNBQVM7eUJBVzVCLEdBQUc7MkJBQ0QsQ0FBQzsyQkFDRCxDQUFDO3lCQUdKLGVBQWEsSUFBSSxDQUFDLFdBQVcsU0FBSSxJQUFJLENBQUMsV0FBVyxnQkFBVyxJQUFJLENBQUMsU0FBUyxTQUFJLElBQUksQ0FBQyxTQUFTLE1BQUc7c0JBQ3pGLENBQUM7c0JBQ0QsQ0FBQztzQkFDRixDQUFDO3VCQUlDLEtBQUs7MkJBQ0QsS0FBSztLQU8vQjs7OztJQUVKLHVDQUFlOzs7SUFBZjtRQUNJLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQzs7S0FFeEI7Ozs7O0lBRUQsbUNBQVc7Ozs7SUFBWCxVQUFZLE9BQXNCO1FBQzlCLElBQUksT0FBTyxZQUFTLE9BQU8sU0FBTSxZQUFZLEVBQUU7WUFDM0MsSUFBSSxDQUFDLElBQUksR0FBRyxPQUFPLFNBQU0sWUFBWSxDQUFDO1lBQ3RDLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztZQUNwQixJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1NBQ3RCO1FBQ0QsSUFBSSxPQUFPLGNBQVcsT0FBTyxXQUFRLFlBQVksRUFBQztZQUM5QyxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7U0FDdkI7UUFDRCxJQUFJLE9BQU8sY0FBVTtZQUNqQixJQUFJLENBQUMsZUFBZSxFQUFFLENBQUM7U0FDMUI7S0FDSjs7Ozs7Ozs7OztJQVNELHVDQUFlOzs7SUFBZjtRQUFBLGlCQVVDO1FBVEcsSUFBRyxDQUFDLElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTztRQUN2QixJQUFJLENBQUMsS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLFVBQUEsQ0FBQztZQUN6QixJQUFHLEtBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsRUFBQztnQkFDbEMsQ0FBQyxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUM7YUFDckI7aUJBQUk7Z0JBQ0QsQ0FBQyxDQUFDLFFBQVEsR0FBRyxLQUFLLENBQUM7YUFDdEI7WUFDRCxPQUFPLENBQUMsQ0FBQztTQUNaLENBQUMsQ0FBQTtLQUNMOzs7O0lBRUQsb0NBQVk7OztJQUFaO1FBQUEsaUJBU0M7UUFSRyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxVQUFBLENBQUM7WUFDWixxQkFBSSxLQUFLLEdBQUcsS0FBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsVUFBQSxDQUFDLElBQUksT0FBQSxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxHQUFHLEdBQUEsQ0FBQyxDQUFDO1lBQ3BELElBQUcsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUM7Z0JBQ2hCLENBQUMsQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FB