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

613 lines (604 loc) 67.3 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@angular/core'), require('@angular/material/icon'), require('@angular/platform-browser'), require('d3-decompose'), require('d3-polygon'), require('@angular/common'), require('@angular/platform-browser/animations'), require('@angular/material')) : typeof define === 'function' && define.amd ? define('ng-plan', ['exports', '@angular/core', '@angular/material/icon', '@angular/platform-browser', 'd3-decompose', 'd3-polygon', '@angular/common', '@angular/platform-browser/animations', '@angular/material'], factory) : (factory((global['ng-plan'] = {}),global.ng.core,global.ng.material.icon,global.ng.platformBrowser,null,null,global.ng.common,global.ng.platformBrowser.animations,global.ng.material)); }(this, (function (exports,core,icon,platformBrowser,d3,d3p,common,animations,material) { 'use strict'; /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ var PlanComponent = (function () { function PlanComponent(matIconRegistry, domSanitizer) { this.matIconRegistry = matIconRegistry; this.domSanitizer = domSanitizer; this.clickedRoom = new core.EventEmitter(); this.clickedCanvas = new core.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 = d3p.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 = d3.decompose(this.transform).scale; var /** @type {?} */ oldTrns = d3.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: core.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: icon.MatIconRegistry, }, { type: platformBrowser.DomSanitizer, }, ]; }; PlanComponent.propDecorators = { "clickedRoom": [{ type: core.Output },], "clickedCanvas": [{ type: core.Output },], "data": [{ type: core.Input },], "colors": [{ type: core.Input },], "centroids": [{ type: core.Input },], "toolbar": [{ type: core.Input },], "selected": [{ type: core.Input },], "selectedColor": [{ type: core.Input },], "defaultColor": [{ type: core.Input },], "planContainer": [{ type: core.ViewChild, args: ['canvas',] },], }; return PlanComponent; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ var CanvasEventsDirective = (function () { function CanvasEventsDirective() { this.zoomOut = new core.EventEmitter(); this.zoomIn = new core.EventEmitter(); this.moveInit = new core.EventEmitter(); this.move = new core.EventEmitter(); this.moveEnd = new core.EventEmitter(); this.panMode = new core.EventEmitter(); this.addNodeMode = new core.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: core.Directive, args: [{ selector: '[canvasEvents]' },] }, ]; /** @nocollapse */ CanvasEventsDirective.propDecorators = { "zoomOut": [{ type: core.Output },], "zoomIn": [{ type: core.Output },], "moveInit": [{ type: core.Output },], "move": [{ type: core.Output },], "moveEnd": [{ type: core.Output },], "panMode": [{ type: core.Output },], "addNodeMode": [{ type: core.Output },], "onMouseWheelChrome": [{ type: core.HostListener, args: ['mousewheel', ['$event'],] },], "onMouseWheelFirefox": [{ type: core.HostListener, args: ['DOMMouseScroll', ['$event'],] },], "onMouseWheelIE": [{ type: core.HostListener, args: ['onmousewheel', ['$event'],] },], "onKeyDown": [{ type: core.HostListener, args: ['document:keydown', ['$event'],] },], "onKeyUp": [{ type: core.HostListener, args: ['document:keyup', ['$event'],] },], "onMouseClick": [{ type: core.HostListener, args: ['mousedown', ['$event'],] },], "onMouseMove": [{ type: core.HostListener, args: ['mousemove', ['$event'],] },], "onMouseUp": [{ type: core.HostListener, args: ['mouseup', ['$event'],] },], }; return CanvasEventsDirective; }()); /** * @fileoverview added by tsickle * @suppress {checkTypes} checked by tsc */ var PlanModule = (function () { function PlanModule() { } PlanModule.decorators = [ { type: core.NgModule, args: [{ imports: [ common.CommonModule, animations.BrowserAnimationsModule, material.MatIconModule, material.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 */ exports.PlanModule = PlanModule; exports.ɵb = CanvasEventsDirective; exports.ɵa = PlanComponent; Object.defineProperty(exports, '__esModule', { value: true }); }))); //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmctcGxhbi51bWQuanMubWFwIiwic291cmNlcyI6WyJuZzovL25nLXBsYW4vbGliL3BsYW4uY29tcG9uZW50LnRzIiwibmc6Ly9uZy1wbGFuL2xpYi9jYW52YXMtZXZlbnRzLmRpcmVjdGl2ZS50cyIsIm5nOi8vbmctcGxhbi9saWIvcGxhbi5tb2R1bGUudHMiXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQ29tcG9uZW50LCBJbnB1dCwgT3V0cHV0LCBFdmVudEVtaXR0ZXIsIEFmdGVyVmlld0luaXQsIE9uQ2hhbmdlcywgU2ltcGxlQ2hhbmdlcywgVmlld0NoaWxkLCBFbGVtZW50UmVmLCBIb3N0TGlzdGVuZXIgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IE1hdEljb25SZWdpc3RyeSB9IGZyb20gXCJAYW5ndWxhci9tYXRlcmlhbC9pY29uXCI7XG5pbXBvcnQgeyBEb21TYW5pdGl6ZXIgfSBmcm9tIFwiQGFuZ3VsYXIvcGxhdGZvcm0tYnJvd3NlclwiO1xuXG5pbXBvcnQgKiBhcyBkMyBmcm9tICdkMy1kZWNvbXBvc2UnOyAgLy8gdGFrZXMgU1ZHIG9yIENTUzMgdHJhbnNmb3JtIHN0cmluZ3MgYW5kIGNvbnZlcnRzIHRoZW0gaW50byB1c2FibGUgdmFsdWVzXG5pbXBvcnQgKiBhcyBkM3AgZnJvbSAnZDMtcG9seWdvbic7ICAgLy8gT3BlcmF0aW9ucyBvbiBwb2x5Z29uc1xuXG5leHBvcnQgaW50ZXJmYWNlIFJvb20ge1xuICAgIG5hbWU6IHN0cmluZztcbiAgICBwb2x5Z29uczogc3RyaW5nW107XG59XG5cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAnbmctcGxhbicsXG4gICAgdGVtcGxhdGU6IGA8IS0tIFRvb2xiYXIgLS0+XHJcbjxkaXYgY2xhc3M9XCJ0b29sYmFyXCIgKm5nSWY9XCJ0b29sYmFyXCI+XHJcbiAgICA8ZGl2IGNsYXNzPVwiaWNvbi1kaXZcIiAoY2xpY2spPVwiem9vbUluKG51bGwpXCIgbWF0VG9vbHRpcD1cIlpvb20gaW5cIj5cclxuICAgICAgICA8bWF0LWljb24+em9vbV9pbjwvbWF0LWljb24+XHJcbiAgICA8L2Rpdj5cclxuICAgIDxkaXYgY2xhc3M9XCJpY29uLWRpdlwiIChjbGljayk9XCJ6b29tT3V0KG51bGwpXCIgbWF0VG9vbHRpcD1cIlpvb20gb3V0XCI+XHJcbiAgICAgICAgPG1hdC1pY29uPnpvb21fb3V0PC9tYXQtaWNvbj5cclxuICAgIDwvZGl2PlxyXG4gICAgPGRpdiBjbGFzcz1cImljb24tZGl2XCIgKGNsaWNrKT1cInpvb21FeHRlbnRzKClcIiBtYXRUb29sdGlwPVwiWm9vbSBleHRlbnRzXCI+XHJcbiAgICAgICAgPG1hdC1pY29uIHN2Z0ljb249XCJ6b29tX2V4dGVudHNcIj48L21hdC1pY29uPlxyXG4gICAgPC9kaXY+XHJcbjwvZGl2PlxyXG5cclxuPGRpdiBjbGFzcz1cImZvb3RlclwiPlxyXG4gICAgPHA+UHJlc3MgYW5kIGhvbGQgXCJBbHRcIiB0byBwYW48L3A+XHJcbjwvZGl2PlxyXG5cclxuPHN2ZyBjYW52YXNFdmVudHNcclxuICAgIFtzdHlsZS5jdXJzb3JdPVwicGFuTW9kZSA/ICdtb3ZlJyA6ICdkZWZhdWx0J1wiXHJcbiAgICAjY2FudmFzXHJcbiAgICBjbGFzcz1jYW52YXNcclxuICAgIChjbGljayk9XCJvbkNhbnZhc0NsaWNrKCRldmVudClcIlxyXG4gICAgKHpvb21PdXQpPVwiem9vbU91dCgkZXZlbnQpXCJcclxuICAgICh6b29tSW4pPVwiem9vbUluKCRldmVudClcIlxyXG4gICAgKHBhbk1vZGUpPVwicGFuTW9kZSA9ICRldmVudFwiXHJcbiAgICAobW92ZSk9XCJtb3ZlKCRldmVudClcIlxyXG4gICAgKG1vdmVFbmQpPVwibW92ZUVuZCgkZXZlbnQpXCJcclxuICAgICh3aW5kb3c6cmVzaXplKT1cIm9uV2luZG93UmVzaXplKClcIlxyXG4gICAgKGFkZE5vZGVNb2RlKT1cImFkZE5vZGVNb2RlID0gJGV2ZW50XCJcclxuICAgIGlkPVwiY2FudmFzXCI+XHJcblxyXG4gICAgPCEtLURyYXcgcm9vbXMtLT5cclxuICAgIDxnICpuZ0Zvcj1cImxldCByb29tIG9mIHJvb21zXCJcclxuICAgICAgICBbYXR0ci50cmFuc2Zvcm1dPVwidHJhbnNmb3JtXCJcclxuICAgICAgICBbaWRdPVwicm9vbS51cmlcIlxyXG4gICAgICAgIChjbGljayk9XCJzZWxlY3RSb29tKCRldmVudCwgcm9vbSlcIlxyXG4gICAgICAgIFthdHRyLmZpbGxdPVwicm9vbS5zZWxlY3RlZCA/IHNlbGVjdGVkQ29sb3IgOiByb29tLmNvbG9yXCJcclxuICAgICAgICBjbGFzcz1cInJvb21cIj5cclxuXHJcbiAgICAgICAgPCEtLSBEcmF3IHBvbHlnb25zIC0tPlxyXG4gICAgICAgIDwhLS0gPHBvbHlnb24gKm5nRm9yPVwibGV0IHBvbHlnb24gb2Ygcm9vbS5wb2x5Z29uc1wiIFxyXG4gICAgICAgICAgICBbYXR0ci5wb2ludHNdPVwicG9seWdvblwiIFxyXG4gICAgICAgICAgICBjbGFzcz1cInBvbHlnb25cIj5cclxuICAgICAgICA8L3BvbHlnb24+IC0tPlxyXG5cclxuICAgICAgICA8IS0tIERyYXcgcGF0aHMgLS0+XHJcbiAgICAgICAgPHBhdGhcclxuICAgICAgICAgICAgW2NsYXNzLnNlbGVjdGVkLXNwYWNlXT1cInJvb20uc2VsZWN0ZWRcIlxyXG4gICAgICAgICAgICBbYXR0ci5kXT1cInJvb20ucGF0aFwiXHJcbiAgICAgICAgICAgIGNsYXNzPVwicG9seWdvblwiPlxyXG4gICAgICAgIDwvcGF0aD4gXHJcblxyXG4gICAgICAgIDwhLS0gRHJhdyBjZW50cm9pZHMgLS0+XHJcbiAgICAgICAgPGNpcmNsZSBcclxuICAgICAgICAgICAgKm5nSWY9XCJjZW50cm9pZHNcIiBcclxuICAgICAgICAgICAgW2F0dHIuY3hdPVwicm9vbS5jZW50cm9pZFswXVwiIFxyXG4gICAgICAgICAgICBmaWxsPVwiYmxhY2tcIiBcclxuICAgICAgICAgICAgW2F0dHIuY3ldPVwicm9vbS5jZW50cm9pZFsxXVwiIFxyXG4gICAgICAgICAgICBbYXR0ci5yXT1cInNjYWxlZCo1MDBcIj5cclxuICAgICAgICA8L2NpcmNsZT5cclxuXHJcbiAgICAgICAgPCEtLSBBZGQgbGFiZWxzIC0tPlxyXG4gICAgICAgIDx0ZXh0IFxyXG4gICAgICAgICAgICBbc3R5bGUuZm9udC1zaXplLmVtXT1cIicxMidcIlxyXG4gICAgICAgICAgICBzdHlsZSA9IFwiZm9udC1mYW1pbHk6cm9ib3RvOyBmaWxsOiBibGFjaztcIlxyXG4gICAgICAgICAgICB0ZXh0LWFuY2hvcj1cIm1pZGRsZVwiIFxyXG4gICAgICAgICAgICBhbGlnbm1lbnQtYmFzZWxpbmU9XCJjZW50cmFsXCJcclxuICAgICAgICAgICAgW2F0dHIueF09XCJyb29tLmNlbnRyb2lkWzBdXCJcclxuICAgICAgICAgICAgW2F0dHIuZHldPVwicm9vbS5kZXNjcmlwdGlvbiA/IC0xNTAgOiAwXCJcclxuICAgICAgICAgICAgW2F0dHIueV09XCJyb29tLmNlbnRyb2lkWzFdXCI+XHJcbiAgICAgICAgICAgIHt7cm9vbS5uYW1lfX1cclxuICAgICAgICA8L3RleHQ+XHJcblxyXG4gICAgICAgIDwhLS0gQWRkIGRlc2NyaXB0aW9uIC0tPlxyXG4gICAgICAgIDx0ZXh0IFxyXG4gICAgICAgICAgICAqbmdJZiA9IFwicm9vbS5kZXNjcmlwdGlvblwiXHJcbiAgICAgICAgICAgIFtzdHlsZS5mb250LXNpemUuZW1dPVwiJzEwJ1wiXHJcbiAgICAgICAgICAgIHN0eWxlID0gXCJmb250LWZhbWlseTpyb2JvdG87IGZpbGw6IGJsYWNrO1wiXHJcbiAgICAgICAgICAgIHRleHQtYW5jaG9yPVwibWlkZGxlXCIgXHJcbiAgICAgICAgICAgIGFsaWdubWVudC1iYXNlbGluZT1cImNlbnRyYWxcIlxyXG4gICAgICAgICAgICBbYXR0ci54XT1cInJvb20uY2VudHJvaWRbMF1cIlxyXG4gICAgICAgICAgICBbYXR0ci55XT1cInJvb20uY2VudHJvaWRbMV1cIlxyXG4gICAgICAgICAgICBkeT1cIjE1MFwiPlxyXG4gICAgICAgICAgICB7e3Jvb20uZGVzY3JpcHRpb259fVxyXG4gICAgICAgIDwvdGV4dD5cclxuICAgIDwvZz5cclxuXHJcbjwvc3ZnPmAsXG4gICAgc3R5bGVzOiBbYC5jYW52YXN7LXdlYmtpdC11c2VyLXNlbGVjdDpub25lOy1tb3otdXNlci1zZWxlY3Q6bm9uZTstbXMtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO3dpZHRoOjEwMCU7aGVpZ2h0OjEwMCU7cG9zaXRpb246Zml4ZWQ7dG9wOjA7bGVmdDowO2JvdHRvbTowO3JpZ2h0OjA7ei1pbmRleDotMX0ucG9seWdvbntzdHJva2U6IzAwMDtzdHJva2Utd2lkdGg6MTt2ZWN0b3ItZWZmZWN0Om5vbi1zY2FsaW5nLXN0cm9rZTtmaWxsLXJ1bGU6ZXZlbm9kZH0ucm9vbTpob3ZlcntvcGFjaXR5Oi41O2N1cnNvcjpwb2ludGVyfS5zZWxlY3RlZC1zcGFjZXtzdHJva2Utd2lkdGg6Mn0udG9vbGJhcntwb3NpdGlvbjphYnNvbHV0ZTtjb2xvcjpyZWQ7cmlnaHQ6MjBweDt3aWR0aDoxMDAlfS5pY29uLWRpdj4ubWF0LWljb257ZmxvYXQ6cmlnaHQ7LXdlYmtpdC11c2VyLXNlbGVjdDpub25lOy1tb3otdXNlci1zZWxlY3Q6bm9uZTstbXMtdXNlci1zZWxlY3Q6bm9uZTt1c2VyLXNlbGVjdDpub25lO2JhY2tncm91bmQtY29sb3I6cmdiYSgyMjAsMjIwLDIyMCwuMyk7Ym9yZGVyLXdpZHRoOjFweDtib3JkZXItcmFkaXVzOjVweDttYXJnaW4tbGVmdDo1cHg7cGFkZGluZzo1cHg7ei1pbmRleDoxfS5tYXQtaWNvbntjb2xvcjpyZ2JhKDAsMCwwLC42KTtmb250LXNpemU6MzBweDtoZWlnaHQ6MzBweDt3aWR0aDozMHB4O3otaW5kZXg6MTB9Lm1hdC1pY29uOmhvdmVye2N1cnNvcjpwb2ludGVyO2NvbG9yOnJnYmEoMCwwLDAsMSl9LmZvb3RlcntiYWNrZ3JvdW5kLWNvbG9yOnJnYmEoMjIwLDIyMCwyMjAsLjMpO3Bvc2l0aW9uOmFic29sdXRlO2ZvbnQtZmFtaWx5OmNvbnNvbGFzO2ZvbnQtc2l6ZToxMnB4O3BhZGRpbmc6MCAxMHB4O2JvcmRlci1yYWRpdXM6NXB4O2JvdHRvbToxMHB4fWBdXG59KVxuZXhwb3J0IGNsYXNzIFBsYW5Db21wb25lbnQgaW1wbGVtZW50cyBBZnRlclZpZXdJbml0IHtcblxuICAgIEBPdXRwdXQoKSBjbGlja2VkUm9vbSA9IG5ldyBFdmVudEVtaXR0ZXIoKTsgICAgICAgICAgICAgLy8gQ2xpY2tlZCByb29tXG4gICAgQE91dHB1dCgpIGNsaWNrZWRDYW52YXMgPSBuZXcgRXZlbnRFbWl0dGVyKCk7XG5cbiAgICBASW5wdXQoKSBwcml2YXRlIGRhdGE7ICAgICAgICAgICAgICAgICAgICAgICAvL2dlb0pTT05cbiAgICBASW5wdXQoKSBwcml2YXRlIGNvbG9yczsgICAgICAgICAgICAgICAgICAgICAvL2NvbG9yIHNjaGVtYVxuICAgIEBJbnB1dCgpIHB1YmxpYyBjZW50cm9pZHM6IGJvb2xlYW47ICAgICAgICAgIC8vY29sb3Igc2NoZW1hXG4gICAgQElucHV0KCkgcHVibGljIHRvb2xiYXI6IGJvb2xlYW47ICAgICAgICAgICAgLy90b29sYmFyIHZpc2libGU/XG4gICAgQElucHV0KCkgcHJpdmF0ZSBzZWxlY3RlZDogc3RyaW5nW107ICAgICAgICAgLy9TcGFjZXMgdG8gYmUgaGlnaGxpZ2h0ZWRcblxuICAgIC8vIFN0eWxlc1xuICAgIEBJbnB1dCgpIHB1YmxpYyBzZWxlY3RlZENvbG9yOiBzdHJpbmcgPSAnI2ViZWZlNCc7XG4gICAgQElucHV0KCkgcHVibGljIGRlZmF1bHRDb2xvcjogc3RyaW5nID0gJyNmMmZsZWMnO1xuXG4gICAgcHVibGljIHJvb21zO1xuXG4gICAgLy8gQ2FudmFzXG4gICAgcHJpdmF0ZSBjYW52YXNXaWR0aDtcbiAgICBwcml2YXRlIGNhbnZhc0hlaWdodDtcbiAgICBwcml2YXRlIGNhbnZhc0NlbnRyb2lkO1xuXG4gICAgLy8gU2NhbGUgLyBvZmZzZXRcbiAgICAvLyBUaGVzZSBmYWN0b3JzIGFyZSBjYWxjdWxhdGVkIGZyb20gZ2VvbWV0cnkgZXh0ZW5kc1xuICAgIHByaXZhdGUgYmFzZVNjYWxlID0gMC45O1xuICAgIHByaXZhdGUgYmFzZU9mZnNldFggPSAwO1xuICAgIHByaXZhdGUgYmFzZU9mZnNldFkgPSAwO1xuXG4gICAgLy8gZ2VvbWV0cnlcbiAgICBwdWJsaWMgdHJhbnNmb3JtID0gYHRyYW5zbGF0ZSgke3RoaXMuYmFzZU9mZnNldFh9LCR7dGhpcy5iYXNlT2Zmc2V0WX0pIHNjYWxlKCR7dGhpcy5iYXNlU2NhbGV9LCR7dGhpcy5iYXNlU2NhbGV9KWA7XG4gICAgcHJpdmF0ZSBtb3ZlZFg6IG51bWJlciA9IDA7IC8vIHN0b3JlIG1vdmUgc3RhdGVcbiAgICBwcml2YXRlIG1vdmVkWTogbnVtYmVyID0gMDsgLy8gc3RvcmUgbW92ZSBzdGF0ZVxuICAgIHB1YmxpYyBzY2FsZWQ6IG51bWJlciA9IDEgLy8gc3RvcmUgc2NhbGUgc3RhdGVcbiAgICBwdWJsaWMgYm91bmRpbmdCb3g7ICAgICAgICAgLy8gU3RvcmVzIGJvdW5kaW5nQm94IGFzIFt4TWluLCB5TWluLCB4TWF4LCB5TWF4XVxuXG4gICAgLy8gbW9kZXNcbiAgICBwdWJsaWMgcGFuTW9kZTogYm9vbGVhbiA9IGZhbHNlO1xuICAgIHB1YmxpYyBhZGROb2RlTW9kZTogYm9vbGVhbiA9IGZhbHNlO1xuXG4gICAgQFZpZXdDaGlsZCgnY2FudmFzJykgcHJpdmF0ZSBwbGFuQ29udGFpbmVyOiBFbGVtZW50UmVmO1xuXG4gICAgY29uc3RydWN0b3IoXG4gICAgICBwcml2YXRlIG1hdEljb25SZWdpc3RyeTogTWF0SWNvblJlZ2lzdHJ5LFxuICAgICAgcHJpdmF0ZSBkb21TYW5pdGl6ZXI6IERvbVNhbml0aXplclxuICAgICkge31cblxuICAgIG5nQWZ0ZXJWaWV3SW5pdCgpe1xuICAgICAgICB0aGlzLmdldENhbnZhc1NpemUoKTtcbiAgICAgICAgLy8gdGhpcy5hZGRTVkdpY29ucygpO1xuICAgIH1cblxuICAgIG5nT25DaGFuZ2VzKGNoYW5nZXM6IFNpbXBsZUNoYW5nZXMpIHtcbiAgICAgICAgaWYgKGNoYW5nZXMuZGF0YSAmJiBjaGFuZ2VzLmRhdGEuY3VycmVudFZhbHVlKSB7XG4gICAgICAgICAgICB0aGlzLmRhdGEgPSBjaGFuZ2VzLmRhdGEuY3VycmVudFZhbHVlO1xuICAgICAgICAgICAgdGhpcy5leHRyYWN0Um9vbXMoKTtcbiAgICAgICAgICAgIHRoaXMuZ2V0U2NhbGVPZmZzZXQoKTtcbiAgICAgICAgICAgIHRoaXMuem9vbUV4dGVudHMoKTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoY2hhbmdlcy5jb2xvcnMgJiYgY2hhbmdlcy5jb2xvcnMuY3VycmVudFZhbHVlKXtcbiAgICAgICAgICAgIHRoaXMuZGVmaW5lQ29sb3JzKCk7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNoYW5nZXMuc2VsZWN0ZWQpe1xuICAgICAgICAgICAgdGhpcy51cGRhdGVTZWxlY3Rpb24oKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIGFkZFNWR2ljb25zKCl7XG4gICAgLy8gICB0aGlzLm1hdEljb25SZWdpc3RyeS5hZGRTdmdJY29uKFxuICAgIC8vICAgICBcInpvb21fZXh0ZW50c1wiLFxuICAgIC8vICAgICB0aGlzLmRvbVNhbml0aXplci5ieXBhc3NTZWN1cml0eVRydXN0UmVzb3VyY2VVcmwoXCIuLi9hc3NldHMvem9vbV9leHRlbnRzLnN2Z1wiKVxuICAgIC8vICAgKTtcbiAgICAvLyB9XG5cbiAgICB1cGRhdGVTZWxlY3Rpb24oKXtcbiAgICAgICAgaWYoIXRoaXMucm9vbXMpIHJldHVybjtcbiAgICAgICAgdGhpcy5yb29tcyA9IHRoaXMucm9vbXMubWFwKHggPT4ge1xuICAgICAgICAgICAgaWYodGhpcy5zZWxlY3RlZC5pbmRleE9mKHgudXJpKSAhPSAtMSl7XG4gICAgICAgICAgICAgICAgeC5zZWxlY3RlZCA9IHRydWU7XG4gICAgICAgICAgICB9ZWxzZXtcbiAgICAgICAgICAgICAgICB4LnNlbGVjdGVkID0gZmFsc2U7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4geDtcbiAgICAgICAgfSlcbiAgICB9XG5cbiAgICBkZWZpbmVDb2xvcnMoKXtcbiAgICAgICAgdGhpcy5yb29tcy5tYXAoeCA9PiB7XG4gICAgICAgICAgICB2YXIgbWF0Y2ggPSB0aGlzLmNvbG9ycy5maWx0ZXIoeSA9PiB5LnVyaSA9PSB4LnVyaSk7XG4gICAgICAgICAgICBpZihtYXRjaC5sZW5ndGggPiAwKXtcbiAgICAgICAgICAgICAgICB4LmNvbG9yID0gbWF0Y2hbMF0uY29sb3I7XG4gICAgICAgICAgICAgICAgeC5kZXNjcmlwdGlvbiA9IG1hdGNoWzBdLnZhbHVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIHg7XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIGdldENhbnZhc1NpemUoKXtcbiAgICAgICAgY29uc3QgZWxlbWVudCA9IHRoaXMucGxhbkNvbnRhaW5lci5uYXRpdmVFbGVtZW50O1xuICAgICAgICBjb25zdCBzaXplID0gZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcbiAgICAgICAgdGhpcy5jYW52YXNXaWR0aCA9IHNpemUud2lkdGg7XG4gICAgICAgIHRoaXMuY2FudmFzSGVpZ2h0ID0gc2l6ZS5oZWlnaHQ7XG4gICAgICAgIHRoaXMuY2FudmFzQ2VudHJvaWQgPSBbdGhpcy5jYW52YXNXaWR0aC8yLCB0aGlzLmNhbnZhc0hlaWdodC8yXTsgICAgICAgIFxuICAgIH1cblxuICAgIGV4dHJhY3RSb29tcygpe1xuICAgICAgICBpZighdGhpcy5kYXRhLmZlYXR1cmVzKSByZXR1cm4gdGhpcy5yb29tcyA9IG51bGw7XG5cbiAgICAgICAgLy8gRm9yIGJvdW5kaW5nIGJveCBbeE1pbiwgeU1pbiwgeE1heCwgeU1heF1cbiAgICAgICAgdmFyIHhNaW47XG4gICAgICAgIHZhciB5TWluO1xuICAgICAgICB2YXIgeE1heDtcbiAgICAgICAgdmFyIHlNYXg7XG4gICAgICAgIFxuICAgICAgICB0aGlzLnJvb21zID0gdGhpcy5kYXRhLmZlYXR1cmVzLm1hcChyb29tID0+IHtcbiAgICAgICAgICAgIHZhciBwb2x5Z29ucyA9IFtdO1xuICAgICAgICAgICAgdmFyIHJvb21QYXRoID0gJyc7XG4gICAgICAgICAgICB2YXIgY2VudHJvaWQ7XG4gICAgICAgICAgICBpZihyb29tLmdlb21ldHJ5ICYmIHJvb20uZ2VvbWV0cnkudHlwZSA9PSBcIlBvbHlnb25cIil7XG4gICAgICAgICAgICAgICAgXG4gICAgICAgICAgICAgICAgdmFyIHJvb21Qb2x5Z29ucyA9IFtdOyAgLy8gQWxsb3cgbXVsdGkgcG9seWdvbnMgaWYgZG91Z2hudXQgcm9vbVxuICAgICAgICAgICAgICAgIHJvb20uZ2VvbWV0cnkuY29vcmRpbmF0ZXMuZm9yRWFjaChwb2x5Z29uID0+IHtcblxuICAgICAgICAgICAgICAgICAgICB2YXIgcG9pbnRzID0gJyc7XG4gICAgICAgICAgICAgICAgICAgIHZhciBwYXRoID0gJyc7XG4gICAgICAgICAgICAgICAgICAgIC8vIFJlZmxlY3QgeSBjb29yZGluYXRlcyB0byBmaXQgYnJvd3NlciBjb29yZGluYXRlIHN5c3RlbSBhbmQgZXh0cmFjdCB0byBwb2x5Z29uXG4gICAgICAgICAgICAgICAgICAgIHBvbHlnb24ubWFwKChjb29yZGluYXRlLGluZGV4KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgeCA9IGNvb3JkaW5hdGVbMF07XG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgeSA9IC1jb29yZGluYXRlWzFdOyAvLyByZWZsZWN0IHNpbmNlIFNWRyB1c2VzIHJlZmxlY3RlZCBjb29yZGluYXRlIHN5c3RlbVxuXG4gICAgICAgICAgICAgICAgICAgICAgICBwb2ludHMrPSBgJHt4fSwke3l9IGA7XG4gICAgICAgICAgICAgICAgICAgICAgICBwYXRoKz0gKGluZGV4ID09IDApID8gYE0ke3h9LCR7eX0gYCA6IGBMJHt4fSwke3l9IGA7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGNvb3JkaW5hdGVbMV0gPSB5OyAvLyBVcGRhdGUgcG9seWdvbiB3aXRoIG5ldyB5XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIEdldCBib3VuZGluZyBib3hcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmKCF4TWluIHx8IHggPCB4TWluKSB4TWluID0geDtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmKCF4TWF4IHx8IHggPiB4TWF4KSB4TWF4ID0geDtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmKCF5TWluIHx8IHkgPCB5TWluKSB5TWluID0geTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmKCF5TWF4IHx8IHkgPiB5TWF4KSB5TWF4ID0geTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGNvb3JkaW5hdGU7XG4gICAgICAgICAgICAgICAgICAgIH0pXG4gICAgICAgICAgICAgICAgICAgIHBvaW50cyA9IHBvaW50cy50cmltKCk7ICAgIC8vIHJlbW92ZSBsYXN0IHNwYWNlXG4gICAgICAgICAgICAgICAgICAgIHBhdGggPSBwYXRoLnRyaW0oKTsgICAgICAgICAvLyByZW1vdmUgbGFzdCBzcGFjZVxuXG4gICAgICAgICAgICAgICAgICAgIC8vIEdldCBwb2x5Z29uIGNlbnRyb2lkIGZyb20gZmlyc3QgcG9seWdvblxuICAgICAgICAgICAgICAgICAgICBpZihyb29tUG9seWdvbnMubGVuZ3RoIDwgMSl7XG4gICAgICAgICAgICAgICAgICAgICAgICBjZW50cm9pZCA9IGQzcC5wb2x5Z29uQ2VudHJvaWQocG9seWdvbik7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICByb29tUG9seWdvbnMucHVzaChwb2ludHMpO1xuICAgICAgICAgICAgICAgICAgICByb29tUGF0aCs9cGF0aDtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICBwb2x5Z29ucy5wdXNoKHJvb21Qb2x5Z29ucyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICB2YXIgbmFtZSA9IHJvb20ucHJvcGVydGllcy5uYW1lO1xuICAgICAgICAgICAgdmFyIHVyaSA9IHJvb20ucHJvcGVydGllcy51cmk7XG4gICAgICAgICAgICB2YXIgY29sb3IgPSByb29tLnByb3BlcnRpZXMuY29sb3IgPyByb29tLnByb3BlcnRpZXMuY29sb3IgOiB0aGlzLmRlZmF1bHRDb2xvcjtcbiAgICAgICAgICAgIHZhciBkZXNjcmlwdGlvbiA9IHJvb20ucHJvcGVydGllcy5kZXNjcmlwdGlvbiA/IHJvb20ucHJvcGVydGllcy5kZXNjcmlwdGlvbiA6ICcnO1xuICAgICAgICAgICAgXG4gICAgICAgICAgICAvLyBTdG9yZSBib3VuZGluZyBib3hcbiAgICAgICAgICAgIHRoaXMuYm91bmRpbmdCb3ggPSBbeE1pbiwgeU1pbiwgeE1heCwgeU1heF07XG5cbiAgICAgICAgICAgIHJldHVybiB7bmFtZTogbmFtZSwgdXJpOiB1cmksIGRlc2NyaXB0aW9uOiBkZXNjcmlwdGlvbiwgY29sb3I6IGNvbG9yLCBwb2x5Z29uczogcG9seWdvbnMsIHBhdGg6cm9vbVBhdGgsIGNlbnRyb2lkOiBjZW50cm9pZH1cbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgZ2V0U2NhbGVPZmZzZXQoKSB7ICAgXG4gICAgICAgIC8vIENhbGN1bGF0ZSBkYXRhIHNpemVcbiAgICAgICAgdmFyIGJiID0gdGhpcy5ib3VuZGluZ0JveDtcbiAgICAgICAgdmFyIGRhdGFXaWR0aCA9IGJiWzJdLWJiWzBdO1xuICAgICAgICB2YXIgZGF0YUhlaWdodCA9IGJiWzNdLWJiWzFdO1xuXG4gICAgICAgIC8vIENhbGN1bGF0ZSBzY2FsZSBmYWN0b3JzXG4gICAgICAgIHZhciBzY2FsZVdpZHRoID0gdGhpcy5jYW52YXNXaWR0aC9kYXRhV2lkdGg7XG4gICAgICAgIHZhciBzY2FsZUhlaWdodCA9IHRoaXMuY2FudmFzSGVpZ2h0L2RhdGFIZWlnaHQ7XG4gICAgICAgIHZhciBzY2FsZSA9IE1hdGgubWluKHNjYWxlSGVpZ2h0LHNjYWxlV2lkdGgpO1xuXG4gICAgICAgIHZhciBzY2FsZWREYXRhQ2VudHJvaWQgPSBbc2NhbGUqKGJiWzBdK2RhdGFXaWR0aC8yKSwgc2NhbGUqKGJiWzFdK2RhdGFIZWlnaHQvMildO1xuXG4gICAgICAgIC8vIENhbGN1bGF0ZSBvZmZzZXQgZmFjdG9yc1xuICAgICAgICB2YXIgb2Zmc2V0WCA9IHRoaXMuY2FudmFzQ2VudHJvaWRbMF0tc2NhbGVkRGF0YUNlbnRyb2lkWzBdO1xuICAgICAgICB2YXIgb2Zmc2V0WSA9IHRoaXMuY2FudmFzQ2VudHJvaWRbMV0tc2NhbGVkRGF0YUNlbnRyb2lkWzFdO1xuXG4gICAgICAgIC8vIFNldCBnbG9iYWwgdmFyaWFibGVzXG4gICAgICAgIHRoaXMuYmFzZU9mZnNldFggPSBvZmZzZXRYO1xuICAgICAgICB0aGlzLmJhc2VPZmZzZXRZID0gb2Zmc2V0WTtcbiAgICAgICAgdGhpcy5iYXNlU2NhbGUgPSBzY2FsZTtcbiAgICBcbiAgICAgICAgcmV0dXJuIFtzY2FsZSxvZmZzZXRYLG9mZnNldFldXG4gICAgfVxuXG4gICAgbW92ZShkaXNwbGFjZW1lbnQpe1xuICAgICAgICBpZihkaXNwbGFjZW1lbnQpe1xuICAgICAgICAgICAgdGhpcy56b29tSGFuZGxlcih1bmRlZmluZWQsZGlzcGxhY2VtZW50KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIG1vdmVFbmQoZGlzcGxhY2VtZW50KXtcbiAgICAgICAgLy8gVXBkYXRlIG1vdmVkIGNvb3JkaW5hdGVzXG4gICAgICAgIHRoaXMubW92ZWRYID0gdGhpcy5tb3ZlZFgrZGlzcGxhY2VtZW50WzBdO1xuICAgICAgICB0aGlzLm1vdmVkWSA9IHRoaXMubW92ZWRZK2Rpc3BsYWNlbWVudFsxXTtcbiAgICB9XG5cbiAgICBvbldpbmRvd1Jlc2l6ZSgpe1xuICAgICAgICB0aGlzLmdldENhbnZhc1NpemUoKTtcbiAgICAgICAgdGhpcy5nZXRTY2FsZU9mZnNldCgpO1xuICAgICAgICB0aGlzLnpvb21FeHRlbnRzKCk7XG4gICAgfVxuXG4gICAgem9vbUV4dGVudHMoKXtcbiAgICAgICAgdGhpcy5zY2FsZWQgPSB0aGlzLmJhc2VTY2FsZSowLjk1O1xuICAgICAgICB0aGlzLm1vdmVkWCA9IDA7XG4gICAgICAgIHRoaXMubW92ZWRZID0gMDtcbiAgICAgICAgdGhpcy56b29tSGFuZGxlcigpO1xuICAgIH1cblxuICAgIHpvb21PdXQoZXYpe1xuICAgICAgICAvLyBDYWxjdWxhdGUgY3VycmVudCBzY2FsZVxuICAgICAgICB2YXIgc2NhbGVGYWN0b3IgPSB0aGlzLmJhc2VTY2FsZS81O1xuICAgICAgICB0aGlzLnNjYWxlZCA9IHRoaXMuc2NhbGVkLXNjYWxlRmFjdG9yO1xuICAgICAgICB0aGlzLnpvb21IYW5kbGVyKGV2KTtcbiAgICB9XG5cbiAgICB6b29tSW4oZXYpe1xuICAgICAgICB2YXIgc2NhbGVGYWN0b3IgPSB0aGlzLmJhc2VTY2FsZS81O1xuICAgICAgICB0aGlzLnNjYWxlZCA9IHRoaXMuc2NhbGVkK3NjYWxlRmFjdG9yO1xuICAgICAgICB0aGlzLnpvb21IYW5kbGVyKGV2KTtcbiAgICB9XG5cbiAgICB6b29tSGFuZGxlcihldj8sZGlzcGxhY2VtZW50Pyl7XG5cbiAgICAgICAgdmFyIHRybnMxID0gJyc7XG5cbiAgICAgICAgaWYoZGlzcGxhY2VtZW50KXtcbiAgICAgICAgICAgIHZhciBkeCA9IHRoaXMuYmFzZU9mZnNldFggKyB0aGlzLm1vdmVkWCArIGRpc3BsYWNlbWVudFswXTtcbiAgICAgICAgICAgIHZhciBkeSA9IHRoaXMuYmFzZU9mZnNldFkgKyB0aGlzLm1vdmVkWSArIGRpc3BsYWNlbWVudFsxXTtcbiAgICAgICAgfWVsc2V7XG4gICAgICAgICAgICBkeCA9IHRoaXMuYmFzZU9mZnNldFggKyB0aGlzLm1vdmVkWDtcbiAgICAgICAgICAgIGR5ID0gdGhpcy5iYXNlT2Zmc2V0WSArIHRoaXMubW92ZWRZO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gVGhpcyBzaG91bGQgYmUgYWRqdXN0ZWQgdG8gY2VudGVyIGF0IG1vdXNlIHBvc2l0aW9uIHdoaWxlIHpvb21pbmdcblxuICAgICAgICAvLyBpZihldiAmJiBldi5vZmZzZXRYKXtcbiAgICAgICAgLy8gICAgIC8vIEdldCBtb3VzZSBwb3NpdGlvbiByZWxhdGl2ZSB0byBjYW52YXMgY2VudGVyIHBvaW50IGFuZCBtb3ZlXG4gICAgICAgIC8vICAgICB2YXIgbXggPSBldi5vZmZzZXRYLXRoaXMuY2FudmFzQ2VudHJvaWRbMF07XG4gICAgICAgIC8vICAgICB2YXIgbXkgPSBldi5vZmZzZXRZLXRoaXMuY2FudmFzQ2VudHJvaWRbMV07XG4gICAgICAgIC8vICAgICB2YXIgZHgyID0gbXg7XG4gICAgICAgIC8vICAgICB2YXIgZHkyID0gbXk7XG4gICAgICAgIC8vICAgICB0cm5zMSA9IGB0cmFuc2xhdGUoJHtkeDJ9LCR7ZHkyfSlgO1xuICAgICAgICAvLyAgICAgY29uc29sZS5sb2codHJuczEpXG4gICAgICAgIC8vIH1cblxuICAgICAgICB2YXIgb2xkU2NhbGUgPSBkMy5kZWNvbXBvc2UodGhpcy50cmFuc2Zvcm0pLnNjYWxlO1xuICAgICAgICB2YXIgb2xkVHJucyA9IGQzLmRlY29tcG9zZSh0aGlzLnRyYW5zZm9ybSkudHJhbnNsYXRlO1xuICAgICAgICB2YXIgc2NhbGUgPSBgc2NhbGUoJHt0aGlzLnNjYWxlZH0sJHt0aGlzLnNjYWxlZH0pYDtcblxuICAgICAgICB2YXIgdHJucyA9IGB0cmFuc2xhdGUoJHtkeH0sJHtkeX0pYDtcblxuICAgICAgICAvLyBVcGRhdGUgdHJhbnNmb3JtIGF0dHJpYnV0ZVxuICAgICAgICB0aGlzLnRyYW5zZm9ybSA9IHRybnMrdHJuczErc2NhbGU7XG4gICAgICAgIC8vIHRoaXMudHJhbnNmb3JtID0gdGhpcy50cmFuc2Zvcm0ucmVwbGFjZShvbGRTY2FsZSxzY2FsZSk7XG4gICAgfVxuXG4gICAgc2VsZWN0Um9vbShldiwgcm9vbSl7XG4gICAgICAgIC8vIEdldCBjb29yZGluYXRlc1xuICAgICAgICB2YXIgc2NhbGUgPSB0aGlzLnNjYWxlZDtcbiAgICAgICAgdmFyIG9mZnNldFggPSB0aGlzLmJhc2VPZmZzZXRYK3RoaXMubW92ZWRYO1xuICAgICAgICB2YXIgb2Zmc2V0WSA9IHRoaXMuYmFzZU9mZnNldFkrdGhpcy5tb3ZlZFk7XG4gICAgICAgIHZhciBzY3JlZW5YID0gZXYub2Zmc2V0WDtcbiAgICAgICAgdmFyIHNjcmVlblkgPSBldi5vZmZzZXRYO1xuXG4gICAgICAgIHZhciB4ID0gKHNjcmVlblgtb2Zmc2V0WCkvc2NhbGU7XG4gICAgICAgIHZhciB5ID0gKHNjcmVlblktb2Zmc2V0WSkvc2NhbGU7XG4gICAgICAgIHZhciBjb29yZGluYXRlcyA9IFt4LC15XTtcblxuICAgICAgICAvLyBFbWl0IGV2ZW50XG4gICAgICAgIHRoaXMuY2xpY2tlZFJvb20uZW1pdCh7dXJpOiByb29tLnVyaSwgY29vcmRpbmF0ZXM6IGNvb3JkaW5hdGVzfSk7XG4gICAgfVxuXG4gICAgb25DYW52YXNDbGljayhldil7XG4gICAgICAgIGlmKGV2LnRhcmdldC5pZCAhPSAnY2FudmFzJykgcmV0dXJuO1xuICAgICAgICB0aGlzLmNsaWNrZWRDYW52YXMuZW1pdCgpO1xuICAgIH1cblxufSIsImltcG9ydCB7IERpcmVjdGl2ZSwgT3V0cHV0LCBIb3N0TGlzdGVuZXIsIEV2ZW50RW1pdHRlciB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5cclxuQERpcmVjdGl2ZSh7IHNlbGVjdG9yOiAnW2NhbnZhc0V2ZW50c10nIH0pXHJcbmV4cG9ydCBjbGFzcyBDYW52YXNFdmVudHNEaXJlY3RpdmUge1xyXG5cclxuICBAT3V0cHV0KCkgem9vbU91dCA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcclxuICBAT3V0cHV0KCkgem9vbUluID0gbmV3IEV2ZW50RW1pdHRlcigpO1xyXG4gIEBPdXRwdXQoKSBtb3ZlSW5pdCA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcclxuICBAT3V0cHV0KCkgbW92ZSA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcclxuICBAT3V0cHV0KCkgbW92ZUVuZCA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcclxuICBAT3V0cHV0KCkgcGFuTW9kZSA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcclxuICBAT3V0cHV0KCkgYWRkTm9kZU1vZGUgPSBuZXcgRXZlbnRFbWl0dGVyKCk7XHJcblxyXG4gIHByaXZhdGUgbW91c2VEb3duOiBib29sZWFuID0gZmFsc2U7XHJcbiAgcHJpdmF0ZSBkcmFnTW9kZTogYm9vbGVhbiA9IGZhbHNlO1xyXG5cclxuICBwcml2YXRlIGNsaWNrWDtcclxuICBwcml2YXRlIGNsaWNrWTtcclxuXHJcbiAgQEhvc3RMaXN0ZW5lcignbW91c2V3aGVlbCcsIFsnJGV2ZW50J10pIG9uTW91c2VXaGVlbENocm9tZShldmVudDogYW55KSB7XHJcbiAgICB0aGlzLm1vdXNlV2hlZWxGdW5jKGV2ZW50KTtcclxuICB9XHJcblxyXG4gIEBIb3N0TGlzdGVuZXIoJ0RPTU1vdXNlU2Nyb2xsJywgWyckZXZlbnQnXSkgb25Nb3VzZVdoZWVsRmlyZWZveChldmVudDogYW55KSB7XHJcbiAgICB0aGlzLm1vdXNlV2hlZWxGdW5jKGV2ZW50KTtcclxuICB9XHJcblxyXG4gIEBIb3N0TGlzdGVuZXIoJ29ubW91c2V3aGVlbCcsIFsnJGV2ZW50J10pIG9uTW91c2VXaGVlbElFKGV2ZW50OiBhbnkpIHtcclxuICAgIHRoaXMubW91c2VXaGVlbEZ1bmMoZXZlbnQpO1xyXG4gIH1cclxuXHJcbiAgQEhvc3RMaXN0ZW5lcignZG9jdW1lbnQ6a2V5ZG93bicsIFsnJGV2ZW50J10pXHJcbiAgb25LZXlEb3duKGV2OktleWJvYXJkRXZlbnQpe1xyXG4gICAgICBpZihldi5rZXkgPT0gXCJBcnJvd0Rvd25cIil7XHJcbiAgICAgICAgdGhpcy56b29tSW4uZW1pdChldik7XHJcbiAgICAgIH1cclxuICAgICAgaWYoZXYua2V5ID09IFwiQXJyb3dVcFwiKXtcclxuICAgICAgICB0aGlzLnpvb21PdXQuZW1pdChldik7XHJcbiAgICAgIH1cclxuICAgICAgaWYoZXYua2V5ID09IFwiQWx0XCIpe1xyXG4gICAgICAgIC8vIGVuYWJsZSBkcmFnIG1vZGUgaWYgaG9sZGluZyBBbHRcclxuICAgICAgICB0aGlzLmRyYWdNb2RlID0gdHJ1ZTtcclxuICAgICAgICB0aGlzLnBhbk1vZGUuZW1pdCh0cnVlKTtcclxuICAgICAgfVxyXG4gICAgICBpZihldi5rZXkgPT0gXCJDb250cm9sXCIpe1xyXG4gICAgICAgIHRoaXMuYWRkTm9kZU1vZGUuZW1pdCh0cnVlKTtcclxuICAgICAgfVxyXG4gIH1cclxuXHJcbiAgQEhvc3RMaXN0ZW5lcignZG9jdW1lbnQ6a2V5dXAnLCBbJyRldmVudCddKVxyXG4gIG9uS2V5VXAoZXY6S2V5Ym9hcmRFdmVudCl7XHJcbiAgICAgIGlmKGV2LmtleSA9PSBcIkFsdFwiKXtcclxuICAgICAgICAvLyBkaXNhYmxlIGRyYWcgbW9kZSBpZiByZWxlYXNpbmcgQWx0XHJcbiAgICAgICAgdGhpcy5kcmFnTW9kZSA9IGZhbHNlO1xyXG4gICAgICAgIHRoaXMucGFuTW9kZS5lbWl0KGZhbHNlKTtcclxuICAgICAgfVxyXG4gICAgICBpZihldi5rZXkgPT0gXCJDb250cm9sXCIpe1xyXG4gICAgICAgIHRoaXMuYWRkTm9kZU1vZGUuZW1pdChmYWxzZSk7XHJcbiAgICAgIH1cclxuICAgICAgXHJcbiAgfVxyXG5cclxuICBASG9zdExpc3RlbmVyKCdtb3VzZWRvd24nLCBbJyRldmVudCddKVxyXG4gIG9uTW91c2VDbGljayhldjogTW91c2VFdmVudCkge1xyXG5cclxuICAgICAgdGhpcy5tb3VzZURvd24gPSB0cnVlO1xyXG5cclxuICAgICAgLy8gZW5hYmxlIGRyYWcgbW9kZSBpZiBjbGlja2luZyBtaWRkbGUgYnV0dG9uXHJcbiAgICAgIGlmKGV2LmJ1dHRvbiA9PSAxKSB0aGlzLmRyYWdNb2RlID0gdHJ1ZTtcclxuXHJcbiAgICAgIGlmKHRoaXMuZHJhZ01vZGUpe1xyXG4gICAgICAgIHRoaXMuY2xpY2tYID0gZXYub2Zmc2V0WDtcclxuICAgICAgICB0aGlzLmNsaWNrWSA9IGV2Lm9mZnNldFk7XHJcbiAgICAgICAgdGhpcy5wYW5Nb2RlLmVtaXQodHJ1ZSk7XHJcbiAgICAgICAgdGhpcy5tb3ZlSW5pdC5lbWl0