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