UNPKG

@ux-aspects/ux-aspects

Version:

Open source user interface framework for building modern, responsive, mobile big data applications

333 lines 60.3 kB
import { animate, style, transition, trigger } from '@angular/animations'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, inject, Input, ViewChild } from '@angular/core'; import { ColorService, ThemeColor } from '../../services/color/index'; import { SankeyChart } from './sankey-chart'; import { SankeyFocusManager } from './sankey-focus-manager'; import * as i0 from "@angular/core"; import * as i1 from "../../directives/accessibility/focus-indicator/focus-indicator.directive"; import * as i2 from "@angular/common"; import * as i3 from "../../directives/resize/resize.directive"; import * as i4 from "../tooltip/tooltip.component"; import * as i5 from "./sankey-chart-node.directive"; export class SankeyChartComponent { constructor() { this._focusManager = inject(SankeyFocusManager); this._changeDetector = inject(ChangeDetectorRef); this._colorService = inject(ColorService); /** Define the nodes to display */ this.nodes = []; /** Define the links to display */ this.links = []; /** Define the headers of each column */ this.columns = []; /** Define the minimum width of a node */ this.minWidth = 0; /** Define the maximum width of a node */ this.maxWidth = Infinity; /** The minimum height of a node. */ this.minHeight = 0; /** Define the function to get the contents of a link tooltip */ this.linkTooltip = this.getLinkTooltip; /** Define the function to get the contents of a falloff tooltip */ this.falloffTooltip = this.getFalloffTooltip; /** Define the nodes that should be rendered */ this._nodes = []; /** Define the columns to display */ this._columns = []; /** Determine if the tooltip should be visible or not */ this._isTooltipOpen = false; /** Define the position of the tooltip */ this._tooltipPosition = { x: 0, y: 0 }; /** Determine if the component is initialised */ this._isInitialised = false; /** Store the instance of the sankey layout */ this._sankey = new SankeyChart(); } ngAfterViewInit() { // verify we have a node template defined before proceeding if (!this.nodeTemplate) { throw new Error('Sankey Chart - Node Template has not been defined.'); } // set the initial chart size this._width = this.nodeContainer.nativeElement.offsetWidth; this._height = this.nodeContainer.nativeElement.offsetHeight; // perform the initial render this._render(); // mark the component as initialised this._isInitialised = true; } /** * Detect any changes from Inputs. We can skip * the first function call as this happens before * the initial render so it has no effect. */ ngOnChanges() { if (this._isInitialised) { this._render(); } } /** Re-render the chart */ _render() { this._nodes = this._sankey .nodes(this.nodes) .links(this.links) .spacing(14) .size(this.minWidth, this.maxWidth, this.minHeight) .width(this._width || this.nodeContainer.nativeElement.offsetWidth) .height(this._height || this.nodeContainer.nativeElement.offsetHeight) .layout(); // ensure the focus manager has the latest node data this._focusManager.setNodes(this._nodes); this._columns = this.getColumns(); this._changeDetector.detectChanges(); } /** Update the layout whenever the dimensions change changes */ _onResize(dimensions) { this._width = dimensions.width; this._height = dimensions.height; this._render(); } /** * Column count should be based on the data, not the titles * as they may not specify titles but the nodes will still be * rendered. */ _getColumnCount() { return this._nodes.reduce((column, node) => Math.max(column, node.column), 0); } /** * Get the SVG path that defines the shape of the link */ _getPath(link) { return this._sankey.link(link); } /** * Set the active state of a node and the inputs and outputs * associated with this node. */ _setNodeActive(nodeLink, active) { // set the node active state nodeLink.active = active; // set the active state of each link nodeLink.inputs.forEach(link => link.active = active); nodeLink.outputs.forEach(link => link.active = active); // set the active state of all input and output nodes nodeLink.inputs.map(link => this._sankey.getNodeLink(link.source)).forEach(_node => _node.active = active); nodeLink.inputs.map(link => this._sankey.getNodeLink(link.target)).forEach(_node => _node.active = active); nodeLink.outputs.map(link => this._sankey.getNodeLink(link.source)).forEach(_node => _node.active = active); nodeLink.outputs.map(link => this._sankey.getNodeLink(link.target)).forEach(_node => _node.active = active); // ensure we update the view to show highlights this._changeDetector.detectChanges(); } /** * Set the focused state of a node and the inputs and outputs * associated with this node. */ _setNodeFocus(nodeLink, focused, element) { // set the node focus state nodeLink.focus = focused; // set the active state of each link nodeLink.inputs.forEach(link => link.focus = focused); nodeLink.outputs.forEach(link => link.focus = focused); // set the active state of all input and output nodes nodeLink.inputs.map(link => this._sankey.getNodeLink(link.source)).forEach(_node => _node.focus = focused); nodeLink.inputs.map(link => this._sankey.getNodeLink(link.target)).forEach(_node => _node.focus = focused); nodeLink.outputs.map(link => this._sankey.getNodeLink(link.source)).forEach(_node => _node.focus = focused); nodeLink.outputs.map(link => this._sankey.getNodeLink(link.target)).forEach(_node => _node.focus = focused); // we need to add the focus indicator here programmatically. The default quantum-ux-aspects focus indicator // styling uses `!important` so our inline style needs to also be `!important` to override this, and unfortunately // there is a known issue with `NgStyle` and `[style.xyz]` bindings preventing them from adding the `!important` // modifier so we must do it manually (not using `Renderer2`). if (this.color) { element.style.setProperty('box-shadow', this._getFocusIndicator(nodeLink), 'important'); } // ensure we update the view to show highlights this._changeDetector.detectChanges(); } /** * Set the active state of a link and the source and target * nodes associated with the link */ _setLinkActive(link, active) { link.active = active; if (link.source !== undefined) { this._sankey.getNodeLink(link.source).active = active; } if (link.target !== undefined) { this._sankey.getNodeLink(link.target).active = active; } // update the tooltip visibility this._isTooltipOpen = active; // update the tooltip content this._tooltipContent = active ? this.linkTooltip(link) : ''; // ensure we update the view to show highlights this._changeDetector.detectChanges(); } /** * This is required because we want to toggle a class based on the `active` * property on a link, however toggling classes using `NgClass` or the class * binding syntax `[class.xyz]` does not work in IE when applied to an SVG * element. (https://github.com/angular/angular/issues/6327) * * The alternatice is to bind directly to the `class` attribute and return a * string that will toggle the class based on the `active` property. */ _getLinkClass(link) { return `ux-sankey-chart-link ${(link.active || link.focus) ? 'ux-sankey-chart-link-active' : ''}`; } /** * Get the SVG path that defines the shape of the falloff indicator */ _getFalloffPath(node) { return this._sankey.getFalloffPath(node); } /** * Falloff represents the amount of data that does not get passed on, * for example, if a node gets 1,000,000 items from inputs and only outputs * 500,000 then there is falloff of 500,000. However, items in the last column * never pass on any information, so tecnhically 100% of their input is falloff * so we shouldn't show it in the last column. */ _showFalloff(nodeLink) { return nodeLink.column < this._columns.length - 1; } /** Update the visibility and content of the tooltip on falloff hover */ _setFalloffTooltip(nodeLink, isVisible) { this._isTooltipOpen = isVisible; this._tooltipContent = isVisible ? this.falloffTooltip(nodeLink.falloff) : ''; this._changeDetector.detectChanges(); } /** * Update the position of the tooltip */ _setTooltipPosition(event) { const { left, top } = this.nodeContainer.nativeElement.getBoundingClientRect(); const x = (event.pageX - left) - (window.scrollX || document.documentElement.scrollLeft); const y = (event.pageY - top) - (window.scrollY || document.documentElement.scrollTop); this._tooltipPosition = { x, y }; this._changeDetector.detectChanges(); } /** * Correctly track the node changes in `*ngFor` based on * the unique node ids to prevent unnecessary re-rendering */ _trackNodeBy(_index, nodeLink) { return nodeLink.node.id; } /** * Correctly track the link changes in `*ngFor` based on * the source and target to prevent unnecessary re-rendering */ _trackLinkBy(_index, link) { return `${link.source}-${link.target}`; } /** * Get the color of node based on whether or not * the `color` input has been provided. */ _getColor(item) { // if we are not node hovering or focusing or no custom color is defined then return nothing if (!item.active && !item.focus || !this.color) { return; } // return an rgba value if it is a `ThemeColor` to support transparency return this.color instanceof ThemeColor ? this.color.toRgba() : this._colorService.resolve(this.color); } /** * We want the focus indicator color to match the active color, * which if programmatically defined need to be overriden */ _getFocusIndicator(nodeLink) { // if the node is not focused or there is no custom color // then return null in which case CSS indicator will show if (!nodeLink.focus || !this.color) { return ''; } // otherwise return the shadow based on the color provided. const color = this.color instanceof ThemeColor ? this.color : ThemeColor.parse(this._colorService.resolve(this.color)); // generate a box shadow based on the specified color return `0 0 0 1px #fff, 0 0 0 3px ${color.setAlpha(0.5).toRgba()}`; } /** * Get columns mapped with their title if they have any */ getColumns() { return this._sankey.columns().map((width, index) => ({ width, title: this.columns[index] || '', position: this.getColumnPosition(index) })); } /** * Get the start position of a column which can be determined * by finding a node that is in that column and using its * x position as all nodes start at the same position within a column. */ getColumnPosition(column) { // find a node in this column and take its x position const node = this._nodes.find(_node => _node.column === column); return node ? node.x : 0; } /** * Get the default content of a link tooltip */ getLinkTooltip(link) { return link.value.toLocaleString('en') + ' items'; } /** * Get the default content of a falloff tooltip */ getFalloffTooltip(falloff) { return falloff.toLocaleString('en') + ' items'; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: SankeyChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.12", type: SankeyChartComponent, selector: "ux-sankey-chart", inputs: { nodes: "nodes", links: "links", columns: "columns", minWidth: "minWidth", maxWidth: "maxWidth", minHeight: "minHeight", linkTooltip: "linkTooltip", falloffTooltip: "falloffTooltip", color: "color" }, queries: [{ propertyName: "nodeTemplate", first: true, predicate: ["sankeyNodeTemplate"], descendants: true }], viewQueries: [{ propertyName: "linkContainer", first: true, predicate: ["linkContainer"], descendants: true, static: true }, { propertyName: "nodeContainer", first: true, predicate: ["nodeContainer"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "@if (columns && columns.length > 0) {\n <div class=\"ux-sankey-chart-columns\">\n @for (column of _columns; track column.title) {\n <div class=\"ux-sankey-chart-column\" [style.width.px]=\"column.width\">\n <p class=\"ux-sankey-chart-column-title\" [style.left.px]=\"column.position\">\n {{ column.title }}\n </p>\n </div>\n }\n </div>\n}\n\n<svg\n #linkContainer\n [attr.width]=\"_width\"\n [attr.height]=\"_height\"\n class=\"ux-sankey-chart-links\"\n [style.top.px]=\"!columns || columns.length === 0 ? 8 : null\"\n>\n <defs>\n <linearGradient id=\"falloff-gradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop class=\"ux-sankey-chart-falloff-gradient-start\" offset=\"25%\"></stop>\n <stop class=\"ux-sankey-chart-falloff-gradient-end\" offset=\"100%\"></stop>\n </linearGradient>\n </defs>\n <g>\n @for (node of _nodes; track _trackNodeBy($index, node)) {\n <ng-container>\n @for (link of node.outputs; track _trackLinkBy($index, link)) {\n <path\n [attr.class]=\"_getLinkClass(link)\"\n [attr.d]=\"_getPath(link)\"\n [style.fill]=\"_getColor(link)\"\n (mouseenter)=\"_setLinkActive(link, true)\"\n (mouseleave)=\"_setLinkActive(link, false)\"\n (mousemove)=\"_setTooltipPosition($event)\"\n ></path>\n }\n @if (node.falloff && _showFalloff(node)) {\n <path\n class=\"ux-sankey-chart-falloff-indicator\"\n [attr.d]=\"_getFalloffPath(node)\"\n (mouseenter)=\"_setFalloffTooltip(node, true)\"\n (mouseleave)=\"_setFalloffTooltip(node, false)\"\n (mousemove)=\"_setTooltipPosition($event)\"\n ></path>\n }\n </ng-container>\n }\n </g>\n</svg>\n\n<div\n #nodeContainer\n class=\"ux-sankey-chart-nodes\"\n (uxResize)=\"_onResize($event)\"\n [style.top.px]=\"!columns || columns.length === 0 ? 8 : null\"\n>\n @for (node of _nodes; track _trackNodeBy($index, node)) {\n <div\n #nodeElement\n [uxSankeyNode]=\"node\"\n uxFocusIndicator\n class=\"ux-sankey-chart-node\"\n [class.ux-sankey-chart-node-active]=\"node.active || node.focus\"\n [style.left.px]=\"node.x\"\n [style.top.px]=\"node.y\"\n [style.width.px]=\"node.width\"\n [style.height.px]=\"node.height\"\n [style.background-color]=\"_getColor(node)\"\n (mouseenter)=\"_setNodeActive(node, true)\"\n (mouseleave)=\"_setNodeActive(node, false)\"\n (indicator)=\"_setNodeFocus(node, $event, nodeElement)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ node: node.node, active: node.active, focus: node.focus }\"\n >\n </ng-container>\n </div>\n }\n\n @if (_isTooltipOpen) {\n <ux-tooltip\n class=\"ux-sankey-tooltip\"\n placement=\"top\"\n [content]=\"_tooltipContent\"\n alignment=\"center\"\n [style.left.px]=\"_tooltipPosition.x\"\n [style.top.px]=\"_tooltipPosition.y\"\n [@tooltipAnimation]\n >\n </ux-tooltip>\n }\n</div>\n", dependencies: [{ kind: "directive", type: i1.FocusIndicatorDirective, selector: "[uxFocusIndicator]", inputs: ["checkChildren", "mouseFocusIndicator", "touchFocusIndicator", "keyboardFocusIndicator", "programmaticFocusIndicator"], outputs: ["indicator"], exportAs: ["ux-focus-indicator"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i3.ResizeDirective, selector: "[uxResize]", inputs: ["throttle"], outputs: ["uxResize"] }, { kind: "component", type: i4.TooltipComponent, selector: "ux-tooltip", inputs: ["content", "context", "placement", "alignment"] }, { kind: "directive", type: i5.SankeyNodeDirective, selector: "[uxSankeyNode]", inputs: ["uxSankeyNode"] }], viewProviders: [SankeyFocusManager], animations: [ trigger('tooltipAnimation', [ transition(':enter', [ style({ opacity: 0 }), animate(160, style({ opacity: 1 })) ]), transition(':leave', [ animate(160, style({ opacity: 0 })) ]) ]) ], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: SankeyChartComponent, decorators: [{ type: Component, args: [{ selector: 'ux-sankey-chart', changeDetection: ChangeDetectionStrategy.OnPush, viewProviders: [SankeyFocusManager], animations: [ trigger('tooltipAnimation', [ transition(':enter', [ style({ opacity: 0 }), animate(160, style({ opacity: 1 })) ]), transition(':leave', [ animate(160, style({ opacity: 0 })) ]) ]) ], template: "@if (columns && columns.length > 0) {\n <div class=\"ux-sankey-chart-columns\">\n @for (column of _columns; track column.title) {\n <div class=\"ux-sankey-chart-column\" [style.width.px]=\"column.width\">\n <p class=\"ux-sankey-chart-column-title\" [style.left.px]=\"column.position\">\n {{ column.title }}\n </p>\n </div>\n }\n </div>\n}\n\n<svg\n #linkContainer\n [attr.width]=\"_width\"\n [attr.height]=\"_height\"\n class=\"ux-sankey-chart-links\"\n [style.top.px]=\"!columns || columns.length === 0 ? 8 : null\"\n>\n <defs>\n <linearGradient id=\"falloff-gradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop class=\"ux-sankey-chart-falloff-gradient-start\" offset=\"25%\"></stop>\n <stop class=\"ux-sankey-chart-falloff-gradient-end\" offset=\"100%\"></stop>\n </linearGradient>\n </defs>\n <g>\n @for (node of _nodes; track _trackNodeBy($index, node)) {\n <ng-container>\n @for (link of node.outputs; track _trackLinkBy($index, link)) {\n <path\n [attr.class]=\"_getLinkClass(link)\"\n [attr.d]=\"_getPath(link)\"\n [style.fill]=\"_getColor(link)\"\n (mouseenter)=\"_setLinkActive(link, true)\"\n (mouseleave)=\"_setLinkActive(link, false)\"\n (mousemove)=\"_setTooltipPosition($event)\"\n ></path>\n }\n @if (node.falloff && _showFalloff(node)) {\n <path\n class=\"ux-sankey-chart-falloff-indicator\"\n [attr.d]=\"_getFalloffPath(node)\"\n (mouseenter)=\"_setFalloffTooltip(node, true)\"\n (mouseleave)=\"_setFalloffTooltip(node, false)\"\n (mousemove)=\"_setTooltipPosition($event)\"\n ></path>\n }\n </ng-container>\n }\n </g>\n</svg>\n\n<div\n #nodeContainer\n class=\"ux-sankey-chart-nodes\"\n (uxResize)=\"_onResize($event)\"\n [style.top.px]=\"!columns || columns.length === 0 ? 8 : null\"\n>\n @for (node of _nodes; track _trackNodeBy($index, node)) {\n <div\n #nodeElement\n [uxSankeyNode]=\"node\"\n uxFocusIndicator\n class=\"ux-sankey-chart-node\"\n [class.ux-sankey-chart-node-active]=\"node.active || node.focus\"\n [style.left.px]=\"node.x\"\n [style.top.px]=\"node.y\"\n [style.width.px]=\"node.width\"\n [style.height.px]=\"node.height\"\n [style.background-color]=\"_getColor(node)\"\n (mouseenter)=\"_setNodeActive(node, true)\"\n (mouseleave)=\"_setNodeActive(node, false)\"\n (indicator)=\"_setNodeFocus(node, $event, nodeElement)\"\n >\n <ng-container\n [ngTemplateOutlet]=\"nodeTemplate\"\n [ngTemplateOutletContext]=\"{ node: node.node, active: node.active, focus: node.focus }\"\n >\n </ng-container>\n </div>\n }\n\n @if (_isTooltipOpen) {\n <ux-tooltip\n class=\"ux-sankey-tooltip\"\n placement=\"top\"\n [content]=\"_tooltipContent\"\n alignment=\"center\"\n [style.left.px]=\"_tooltipPosition.x\"\n [style.top.px]=\"_tooltipPosition.y\"\n [@tooltipAnimation]\n >\n </ux-tooltip>\n }\n</div>\n" }] }], propDecorators: { nodes: [{ type: Input }], links: [{ type: Input }], columns: [{ type: Input }], minWidth: [{ type: Input }], maxWidth: [{ type: Input }], minHeight: [{ type: Input }], linkTooltip: [{ type: Input }], falloffTooltip: [{ type: Input }], color: [{ type: Input }], nodeTemplate: [{ type: ContentChild, args: ['sankeyNodeTemplate', { static: false }] }], linkContainer: [{ type: ViewChild, args: ['linkContainer', { static: true }] }], nodeContainer: [{ type: ViewChild, args: ['nodeContainer', { static: true }] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Fua2V5LWNoYXJ0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jb21wb25lbnRzL3NhbmtleS1jaGFydC9zYW5rZXktY2hhcnQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvc2Fua2V5LWNoYXJ0L3NhbmtleS1jaGFydC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDMUUsT0FBTyxFQUFpQix1QkFBdUIsRUFBRSxpQkFBaUIsRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFjLE1BQU0sRUFBRSxLQUFLLEVBQTBCLFNBQVMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUVqTCxPQUFPLEVBQUUsWUFBWSxFQUFFLFVBQVUsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBSXRFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUM3QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQzs7Ozs7OztBQW1CNUQsTUFBTSxPQUFPLG9CQUFvQjtJQWpCakM7UUFrQnFCLGtCQUFhLEdBQUcsTUFBTSxDQUF3QixrQkFBa0IsQ0FBQyxDQUFDO1FBRWxFLG9CQUFlLEdBQUcsTUFBTSxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFFNUMsa0JBQWEsR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7UUFFdEQsa0NBQWtDO1FBQ3pCLFVBQUssR0FBaUMsRUFBRSxDQUFDO1FBRWxELGtDQUFrQztRQUN6QixVQUFLLEdBQThCLEVBQUUsQ0FBQztRQUUvQyx3Q0FBd0M7UUFDL0IsWUFBTyxHQUFhLEVBQUUsQ0FBQztRQUVoQyx5Q0FBeUM7UUFDaEMsYUFBUSxHQUFXLENBQUMsQ0FBQztRQUU5Qix5Q0FBeUM7UUFDaEMsYUFBUSxHQUFXLFFBQVEsQ0FBQztRQUVyQyxvQ0FBb0M7UUFDM0IsY0FBUyxHQUFXLENBQUMsQ0FBQztRQUUvQixnRUFBZ0U7UUFDdkQsZ0JBQVcsR0FBaUMsSUFBSSxDQUFDLGNBQWMsQ0FBQztRQUV6RSxtRUFBbUU7UUFDMUQsbUJBQWMsR0FBZ0MsSUFBSSxDQUFDLGlCQUFpQixDQUFDO1FBb0I5RSwrQ0FBK0M7UUFDL0MsV0FBTSxHQUFxQyxFQUFFLENBQUM7UUFFOUMsb0NBQW9DO1FBQ3BDLGFBQVEsR0FBZ0MsRUFBRSxDQUFDO1FBRTNDLHdEQUF3RDtRQUN4RCxtQkFBYyxHQUFZLEtBQUssQ0FBQztRQUVoQyx5Q0FBeUM7UUFDekMscUJBQWdCLEdBQTBCLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUM7UUFLekQsZ0RBQWdEO1FBQ3hDLG1CQUFjLEdBQVksS0FBSyxDQUFDO1FBRXhDLDhDQUE4QztRQUM3QixZQUFPLEdBQUcsSUFBSSxXQUFXLEVBQUssQ0FBQztLQTBSbkQ7SUF4UkcsZUFBZTtRQUVYLDJEQUEyRDtRQUMzRCxJQUFJLENBQUMsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3JCLE1BQU0sSUFBSSxLQUFLLENBQUMsb0RBQW9ELENBQUMsQ0FBQztRQUMxRSxDQUFDO1FBRUQsNkJBQTZCO1FBQzdCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDO1FBQzNELElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsWUFBWSxDQUFDO1FBRTdELDZCQUE2QjtRQUM3QixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFFZixvQ0FBb0M7UUFDcEMsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUM7SUFDL0IsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxXQUFXO1FBQ1AsSUFBSSxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7WUFDdEIsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ25CLENBQUM7SUFDTCxDQUFDO0lBRUQsMEJBQTBCO0lBQzFCLE9BQU87UUFDSCxJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxPQUFPO2FBQ3JCLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDO2FBQ2pCLEtBQUssQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDO2FBQ2pCLE9BQU8sQ0FBQyxFQUFFLENBQUM7YUFDWCxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUM7YUFDbEQsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLGFBQWEsQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDO2FBQ2xFLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxhQUFhLENBQUMsYUFBYSxDQUFDLFlBQVksQ0FBQzthQUNyRSxNQUFNLEVBQUUsQ0FBQztRQUVkLG9EQUFvRDtRQUNwRCxJQUFJLENBQUMsYUFBYSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFFekMsSUFBSSxDQUFDLFFBQVEsR0FBRyxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDbEMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxhQUFhLEVBQUUsQ0FBQztJQUN6QyxDQUFDO0lBRUQsK0RBQStEO0lBQy9ELFNBQVMsQ0FBQyxVQUE0QjtRQUNsQyxJQUFJLENBQUMsTUFBTSxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUM7UUFDL0IsSUFBSSxDQUFDLE9BQU8sR0FBRyxVQUFVLENBQUMsTUFBTSxDQUFDO1FBQ2pDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUNuQixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNILGVBQWU7UUFDWCxPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2xGLENBQUM7SUFFRDs7T0FFRztJQUNILFFBQVEsQ0FBQyxJQUFnQjtRQUNyQixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFRDs7O09BR0c7SUFDSCxjQUFjLENBQUMsUUFBMkIsRUFBRSxNQUFlO1FBRXZELDRCQUE0QjtRQUM1QixRQUFRLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUV6QixvQ0FBb0M7UUFDcEMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxDQUFDO1FBQ3RELFFBQVEsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsQ0FBQztRQUV2RCxxREFBcUQ7UUFDckQsUUFBUSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxDQUFDO1FBQzNHLFFBQVEsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsQ0FBQztRQUMzRyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDLENBQUM7UUFDNUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQyxDQUFDO1FBRTVHLCtDQUErQztRQUMvQyxJQUFJLENBQUMsZUFBZSxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQ3pDLENBQUM7SUFHRDs7O09BR0c7SUFDSCxhQUFhLENBQUMsUUFBMkIsRUFBRSxPQUFnQixFQUFFLE9BQXVCO1FBRWhGLDJCQUEyQjtRQUMzQixRQUFRLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQztRQUV6QixvQ0FBb0M7UUFDcEMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQyxDQUFDO1FBQ3RELFFBQVEsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLEtBQUssR0FBRyxPQUFPLENBQUMsQ0FBQztRQUV2RCxxREFBcUQ7UUFDckQsUUFBUSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQyxDQUFDO1FBQzNHLFFBQVEsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsS0FBSyxDQUFDLEtBQUssR0FBRyxPQUFPLENBQUMsQ0FBQztRQUMzRyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQyxLQUFLLEdBQUcsT0FBTyxDQUFDLENBQUM7UUFDNUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQyxDQUFDO1FBRTVHLDJHQUEyRztRQUMzRyxrSEFBa0g7UUFDbEgsZ0hBQWdIO1FBQ2hILDhEQUE4RDtRQUM5RCxJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNiLE9BQU8sQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLFlBQVksRUFBRSxJQUFJLENBQUMsa0JBQWtCLENBQUMsUUFBUSxDQUFDLEVBQUUsV0FBVyxDQUFDLENBQUM7UUFDNUYsQ0FBQztRQUVELCtDQUErQztRQUMvQyxJQUFJLENBQUMsZUFBZSxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQ3pDLENBQUM7SUFFRDs7O09BR0c7SUFDSCxjQUFjLENBQUMsSUFBeUQsRUFBRSxNQUFlO1FBQ3JGLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBRXJCLElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTLEVBQUUsQ0FBQztZQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUMxRCxDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQzVCLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQzFELENBQUM7UUFFRCxnQ0FBZ0M7UUFDaEMsSUFBSSxDQUFDLGNBQWMsR0FBRyxNQUFNLENBQUM7UUFFN0IsNkJBQTZCO1FBQzdCLElBQUksQ0FBQyxlQUFlLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFFNUQsK0NBQStDO1FBQy9DLElBQUksQ0FBQyxlQUFlLENBQUMsYUFBYSxFQUFFLENBQUM7SUFDekMsQ0FBQztJQUVEOzs7Ozs7OztPQVFHO0lBQ0gsYUFBYSxDQUFDLElBQXlEO1FBQ25FLE9BQU8sd0JBQXdCLENBQUMsSUFBSSxDQUFDLE1BQU0sSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLDZCQUE2QixDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQztJQUN0RyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxlQUFlLENBQUMsSUFBdUI7UUFDbkMsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUM3QyxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0gsWUFBWSxDQUFDLFFBQTJCO1FBQ3BDLE9BQU8sUUFBUSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUM7SUFDdEQsQ0FBQztJQUVELHdFQUF3RTtJQUN4RSxrQkFBa0IsQ0FBQyxRQUEyQixFQUFFLFNBQWtCO1FBQzlELElBQUksQ0FBQyxjQUFjLEdBQUcsU0FBUyxDQUFDO1FBQ2hDLElBQUksQ0FBQyxlQUFlLEdBQUcsU0FBUyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1FBQzlFLElBQUksQ0FBQyxlQUFlLENBQUMsYUFBYSxFQUFFLENBQUM7SUFDekMsQ0FBQztJQUVEOztPQUVHO0lBQ0gsbUJBQW1CLENBQUMsS0FBaUI7UUFDakMsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsR0FBSSxJQUFJLENBQUMsYUFBYSxDQUFDLGFBQTZCLENBQUMscUJBQXFCLEVBQUUsQ0FBQztRQUNoRyxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxLQUFLLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxNQUFNLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDekYsTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLE9BQU8sSUFBSSxRQUFRLENBQUMsZUFBZSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBRXZGLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQztRQUNqQyxJQUFJLENBQUMsZUFBZSxDQUFDLGFBQWEsRUFBRSxDQUFDO0lBQ3pDLENBQUM7SUFFRDs7O09BR0c7SUFDSCxZQUFZLENBQUMsTUFBYyxFQUFFLFFBQTJCO1FBQ3BELE9BQU8sUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUM7SUFDNUIsQ0FBQztJQUVEOzs7T0FHRztJQUNILFlBQVksQ0FBQyxNQUFjLEVBQUUsSUFBZ0I7UUFDekMsT0FBTyxHQUFHLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQzNDLENBQUM7SUFFRDs7O09BR0c7SUFDSCxTQUFTLENBQUMsSUFBNEQ7UUFDbEUsNEZBQTRGO1FBQzVGLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUM3QyxPQUFPO1FBQ1gsQ0FBQztRQUVELHVFQUF1RTtRQUN2RSxPQUFPLElBQUksQ0FBQyxLQUFLLFlBQVksVUFBVSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDM0csQ0FBQztJQUVEOzs7T0FHRztJQUNILGtCQUFrQixDQUFDLFFBQTJCO1FBQzFDLHlEQUF5RDtRQUN6RCx5REFBeUQ7UUFDekQsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDakMsT0FBTyxFQUFFLENBQUM7UUFDZCxDQUFDO1FBRUQsMkRBQTJEO1FBQzNELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxLQUFLLFlBQVksVUFBVSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBRXZILHFEQUFxRDtRQUNyRCxPQUFPLDZCQUE2QixLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFLENBQUM7SUFDdkUsQ0FBQztJQUVEOztPQUVHO0lBQ0ssVUFBVTtRQUNkLE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ2hKLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ssaUJBQWlCLENBQUMsTUFBYztRQUNwQyxxREFBcUQ7UUFDckQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsTUFBTSxLQUFLLE1BQU0sQ0FBQyxDQUFDO1FBQ2hFLE9BQU8sSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDN0IsQ0FBQztJQUVEOztPQUVHO0lBQ0ssY0FBYyxDQUFDLElBQWdCO1FBQ25DLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLEdBQUcsUUFBUSxDQUFDO0lBQ3RELENBQUM7SUFFRDs7T0FFRztJQUNLLGlCQUFpQixDQUFDLE9BQWU7UUFDckMsT0FBTyxPQUFPLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxHQUFHLFFBQVEsQ0FBQztJQUNuRCxDQUFDOytHQTVWUSxvQkFBb0I7bUdBQXBCLG9CQUFvQiwybkJDM0JqQyx3bEdBK0ZBLGkwQkRqRm1CLENBQUMsa0JBQWtCLENBQUMsY0FDdkI7WUFDUixPQUFPLENBQUMsa0JBQWtCLEVBQUU7Z0JBQ3hCLFVBQVUsQ0FBQyxRQUFRLEVBQUU7b0JBQ2pCLEtBQUssQ0FBQyxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQztvQkFDckIsT0FBTyxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsRUFBRSxPQUFPLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztpQkFDdEMsQ0FBQztnQkFDRixVQUFVLENBQUMsUUFBUSxFQUFFO29CQUNqQixPQUFPLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDO2lCQUN0QyxDQUFDO2FBQ0wsQ0FBQztTQUNMOzs0RkFFUSxvQkFBb0I7a0JBakJoQyxTQUFTOytCQUNJLGlCQUFpQixtQkFFVix1QkFBdUIsQ0FBQyxNQUFNLGlCQUNoQyxDQUFDLGtCQUFrQixDQUFDLGNBQ3ZCO3dCQUNSLE9BQU8sQ0FBQyxrQkFBa0IsRUFBRTs0QkFDeEIsVUFBVSxDQUFDLFFBQVEsRUFBRTtnQ0FDakIsS0FBSyxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRSxDQUFDO2dDQUNyQixPQUFPLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDOzZCQUN0QyxDQUFDOzRCQUNGLFVBQVUsQ0FBQyxRQUFRLEVBQUU7Z0NBQ2pCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7NkJBQ3RDLENBQUM7eUJBQ0wsQ0FBQztxQkFDTDs4QkFVUSxLQUFLO3NCQUFiLEtBQUs7Z0JBR0csS0FBSztzQkFBYixLQUFLO2dCQUdHLE9BQU87c0JBQWYsS0FBSztnQkFHRyxRQUFRO3NCQUFoQixLQUFLO2dCQUdHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBR0csU0FBUztzQkFBakIsS0FBSztnQkFHRyxXQUFXO3NCQUFuQixLQUFLO2dCQUdHLGNBQWM7c0JBQXRCLEtBQUs7Z0JBR0csS0FBSztzQkFBYixLQUFLO2dCQUdpRCxZQUFZO3NCQUFsRSxZQUFZO3VCQUFDLG9CQUFvQixFQUFFLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRTtnQkFHUCxhQUFhO3NCQUExRCxTQUFTO3VCQUFDLGVBQWUsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUU7Z0JBR0UsYUFBYTtzQkFBMUQsU0FBUzt1QkFBQyxlQUFlLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgYW5pbWF0ZSwgc3R5bGUsIHRyYW5zaXRpb24sIHRyaWdnZXIgfSBmcm9tICdAYW5ndWxhci9hbmltYXRpb25zJztcbmltcG9ydCB7IEFmdGVyVmlld0luaXQsIENoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDaGFuZ2VEZXRlY3RvclJlZiwgQ29tcG9uZW50LCBDb250ZW50Q2hpbGQsIEVsZW1lbnRSZWYsIGluamVjdCwgSW5wdXQsIE9uQ2hhbmdlcywgVGVtcGxhdGVSZWYsIFZpZXdDaGlsZCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgUmVzaXplRGltZW5zaW9ucyB9IGZyb20gJy4uLy4uL2RpcmVjdGl2ZXMvcmVzaXplL2luZGV4JztcbmltcG9ydCB7IENvbG9yU2VydmljZSwgVGhlbWVDb2xvciB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL2NvbG9yL2luZGV4JztcbmltcG9ydCB7IFNhbmtleUxpbmssIFNhbmtleUxpbmtJbnRlcmFjdGlvbiwgU2Fua2V5TGlua1Bsb3QgfSBmcm9tICcuL2ludGVyZmFjZXMvbGluay5pbnRlcmZhY2UnO1xuaW1wb3J0IHsgU2Fua2V5Tm9kZUxpbmsgfSBmcm9tICcuL2ludGVyZmFjZXMvbm9kZS1saW5rLmludGVyZmFjZSc7XG5pbXBvcnQgeyBTYW5rZXlOb2RlIH0gZnJvbSAnLi9pbnRlcmZhY2VzL25vZGUuaW50ZXJmYWNlJztcbmltcG9ydCB7IFNhbmtleUNoYXJ0IH0gZnJvbSAnLi9zYW5rZXktY2hhcnQnO1xuaW1wb3J0IHsgU2Fua2V5Rm9jdXNNYW5hZ2VyIH0gZnJvbSAnLi9zYW5rZXktZm9jdXMtbWFuYWdlcic7XG5cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAndXgtc2Fua2V5LWNoYXJ0JyxcbiAgICB0ZW1wbGF0ZVVybDogJy4vc2Fua2V5LWNoYXJ0LmNvbXBvbmVudC5odG1sJyxcbiAgICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbiAgICB2aWV3UHJvdmlkZXJzOiBbU2Fua2V5Rm9jdXNNYW5hZ2VyXSxcbiAgICBhbmltYXRpb25zOiBbXG4gICAgICAgIHRyaWdnZXIoJ3Rvb2x0aXBBbmltYXRpb24nLCBbXG4gICAgICAgICAgICB0cmFuc2l0aW9uKCc6ZW50ZXInLCBbXG4gICAgICAgICAgICAgICAgc3R5bGUoeyBvcGFjaXR5OiAwIH0pLFxuICAgICAgICAgICAgICAgIGFuaW1hdGUoMTYwLCBzdHlsZSh7IG9wYWNpdHk6IDEgfSkpXG4gICAgICAgICAgICBdKSxcbiAgICAgICAgICAgIHRyYW5zaXRpb24oJzpsZWF2ZScsIFtcbiAgICAgICAgICAgICAgICBhbmltYXRlKDE2MCwgc3R5bGUoeyBvcGFjaXR5OiAwIH0pKVxuICAgICAgICAgICAgXSlcbiAgICAgICAgXSlcbiAgICBdLFxufSlcbmV4cG9ydCBjbGFzcyBTYW5rZXlDaGFydENvbXBvbmVudDxUPiBpbXBsZW1lbnRzIE9uQ2hhbmdlcywgQWZ0ZXJWaWV3SW5pdCB7XG4gICAgcHJpdmF0ZSByZWFkb25seSBfZm9jdXNNYW5hZ2VyID0gaW5qZWN0PFNhbmtleUZvY3VzTWFuYWdlcjxUPj4oU2Fua2V5Rm9jdXNNYW5hZ2VyKTtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NoYW5nZURldGVjdG9yID0gaW5qZWN0KENoYW5nZURldGVjdG9yUmVmKTtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NvbG9yU2VydmljZSA9IGluamVjdChDb2xvclNlcnZpY2UpO1xuXG4gICAgLyoqIERlZmluZSB0aGUgbm9kZXMgdG8gZGlzcGxheSAqL1xuICAgIEBJbnB1dCgpIG5vZGVzOiBSZWFkb25seUFycmF5PFNhbmtleU5vZGU8VD4+ID0gW107XG5cbiAgICAvKiogRGVmaW5lIHRoZSBsaW5rcyB0byBkaXNwbGF5ICovXG4gICAgQElucHV0KCkgbGlua3M6IFJlYWRvbmx5QXJyYXk8U2Fua2V5TGluaz4gPSBbXTtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGhlYWRlcnMgb2YgZWFjaCBjb2x1bW4gKi9cbiAgICBASW5wdXQoKSBjb2x1bW5zOiBzdHJpbmdbXSA9IFtdO1xuXG4gICAgLyoqIERlZmluZSB0aGUgbWluaW11bSB3aWR0aCBvZiBhIG5vZGUgKi9cbiAgICBASW5wdXQoKSBtaW5XaWR0aDogbnVtYmVyID0gMDtcblxuICAgIC8qKiBEZWZpbmUgdGhlIG1heGltdW0gd2lkdGggb2YgYSBub2RlICovXG4gICAgQElucHV0KCkgbWF4V2lkdGg6IG51bWJlciA9IEluZmluaXR5O1xuXG4gICAgLyoqIFRoZSBtaW5pbXVtIGhlaWdodCBvZiBhIG5vZGUuICovXG4gICAgQElucHV0KCkgbWluSGVpZ2h0OiBudW1iZXIgPSAwO1xuXG4gICAgLyoqIERlZmluZSB0aGUgZnVuY3Rpb24gdG8gZ2V0IHRoZSBjb250ZW50cyBvZiBhIGxpbmsgdG9vbHRpcCAqL1xuICAgIEBJbnB1dCgpIGxpbmtUb29sdGlwOiAobGluazogU2Fua2V5TGluaykgPT4gc3RyaW5nID0gdGhpcy5nZXRMaW5rVG9vbHRpcDtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGZ1bmN0aW9uIHRvIGdldCB0aGUgY29udGVudHMgb2YgYSBmYWxsb2ZmIHRvb2x0aXAgKi9cbiAgICBASW5wdXQoKSBmYWxsb2ZmVG9vbHRpcDogKGZhbGxvZmY6IG51bWJlcikgPT4gc3RyaW5nID0gdGhpcy5nZXRGYWxsb2ZmVG9vbHRpcDtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGFjdGl2ZSBjb2xvciBvZiBhIG5vZGUgKi9cbiAgICBASW5wdXQoKSBjb2xvcjogc3RyaW5nIHwgVGhlbWVDb2xvcjtcblxuICAgIC8qKiBEZWZpbmUgdGhlIHRlbXBsYXRlIG9mIHNhbmtleSBjaGFydCBub2RlcyAqL1xuICAgIEBDb250ZW50Q2hpbGQoJ3NhbmtleU5vZGVUZW1wbGF0ZScsIHsgc3RhdGljOiBmYWxzZSB9KSBub2RlVGVtcGxhdGU6IFRlbXBsYXRlUmVmPFNhbmtleU5vZGVDb250ZXh0PFQ+PjtcblxuICAgIC8qKiBBY2Nlc3MgdGhlIFNWRyBlbGVtZW50IHdoaWNoIHdpbGwgY29udGFpbiB0aGUgbGlua3MgKi9cbiAgICBAVmlld0NoaWxkKCdsaW5rQ29udGFpbmVyJywgeyBzdGF0aWM6IHRydWUgfSkgbGlua0NvbnRhaW5lcjogRWxlbWVudFJlZjtcblxuICAgIC8qKiBBY2Nlc3MgdGhlIGVsZW1lbnQgd2hpY2ggd2lsbCBjb250YWluIHRoZSBub2RlcyAqL1xuICAgIEBWaWV3Q2hpbGQoJ25vZGVDb250YWluZXInLCB7IHN0YXRpYzogdHJ1ZSB9KSBub2RlQ29udGFpbmVyOiBFbGVtZW50UmVmO1xuXG4gICAgLyoqIFN0b3JlIHRoZSB3aWR0aCBvZiB0aGUgY2hhcnQgYXJlYSAqL1xuICAgIF93aWR0aDogbnVtYmVyO1xuXG4gICAgLyoqIFN0b3JlIHRoZSBoZWlnaHQgb2YgdGhlIGNoYXJ0IGFyZWEgKi9cbiAgICBfaGVpZ2h0OiBudW1iZXI7XG5cbiAgICAvKiogRGVmaW5lIHRoZSBub2RlcyB0aGF0IHNob3VsZCBiZSByZW5kZXJlZCAqL1xuICAgIF9ub2RlczogUmVhZG9ubHlBcnJheTxTYW5rZXlOb2RlTGluazxUPj4gPSBbXTtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGNvbHVtbnMgdG8gZGlzcGxheSAqL1xuICAgIF9jb2x1bW5zOiBSZWFkb25seUFycmF5PFNhbmtleUNvbHVtbj4gPSBbXTtcblxuICAgIC8qKiBEZXRlcm1pbmUgaWYgdGhlIHRvb2x0aXAgc2hvdWxkIGJlIHZpc2libGUgb3Igbm90ICovXG4gICAgX2lzVG9vbHRpcE9wZW46IGJvb2xlYW4gPSBmYWxzZTtcblxuICAgIC8qKiBEZWZpbmUgdGhlIHBvc2l0aW9uIG9mIHRoZSB0b29sdGlwICovXG4gICAgX3Rvb2x0aXBQb3NpdGlvbjogU2Fua2V5VG9vbHRpcFBvc2l0aW9uID0geyB4OiAwLCB5OiAwIH07XG5cbiAgICAvKiogRGVmaW5lIHRoZSBjb250ZW50IHRvIGRpc3BsYXkgaW4gdGhlIHRvb2x0aXAgKi9cbiAgICBfdG9vbHRpcENvbnRlbnQ6IHN0cmluZztcblxuICAgIC8qKiBEZXRlcm1pbmUgaWYgdGhlIGNvbXBvbmVudCBpcyBpbml0aWFsaXNlZCAqL1xuICAgIHByaXZhdGUgX2lzSW5pdGlhbGlzZWQ6IGJvb2xlYW4gPSBmYWxzZTtcblxuICAgIC8qKiBTdG9yZSB0aGUgaW5zdGFuY2Ugb2YgdGhlIHNhbmtleSBsYXlvdXQgKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IF9zYW5rZXkgPSBuZXcgU2Fua2V5Q2hhcnQ8VD4oKTtcblxuICAgIG5nQWZ0ZXJWaWV3SW5pdCgpOiB2b2lkIHtcblxuICAgICAgICAvLyB2ZXJpZnkgd2UgaGF2ZSBhIG5vZGUgdGVtcGxhdGUgZGVmaW5lZCBiZWZvcmUgcHJvY2VlZGluZ1xuICAgICAgICBpZiAoIXRoaXMubm9kZVRlbXBsYXRlKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NhbmtleSBDaGFydCAtIE5vZGUgVGVtcGxhdGUgaGFzIG5vdCBiZWVuIGRlZmluZWQuJyk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBzZXQgdGhlIGluaXRpYWwgY2hhcnQgc2l6ZVxuICAgICAgICB0aGlzLl93aWR0aCA9IHRoaXMubm9kZUNvbnRhaW5lci5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRoO1xuICAgICAgICB0aGlzLl9oZWlnaHQgPSB0aGlzLm5vZGVDb250YWluZXIubmF0aXZlRWxlbWVudC5vZmZzZXRIZWlnaHQ7XG5cbiAgICAgICAgLy8gcGVyZm9ybSB0aGUgaW5pdGlhbCByZW5kZXJcbiAgICAgICAgdGhpcy5fcmVuZGVyKCk7XG5cbiAgICAgICAgLy8gbWFyayB0aGUgY29tcG9uZW50IGFzIGluaXRpYWxpc2VkXG4gICAgICAgIHRoaXMuX2lzSW5pdGlhbGlzZWQgPSB0cnVlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERldGVjdCBhbnkgY2hhbmdlcyBmcm9tIElucHV0cy4gV2UgY2FuIHNraXBcbiAgICAgKiB0aGUgZmlyc3QgZnVuY3Rpb24gY2FsbCBhcyB0aGlzIGhhcHBlbnMgYmVmb3JlXG4gICAgICogdGhlIGluaXRpYWwgcmVuZGVyIHNvIGl0IGhhcyBubyBlZmZlY3QuXG4gICAgICovXG4gICAgbmdPbkNoYW5nZXMoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLl9pc0luaXRpYWxpc2VkKSB7XG4gICAgICAgICAgICB0aGlzLl9yZW5kZXIoKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKiBSZS1yZW5kZXIgdGhlIGNoYXJ0ICovXG4gICAgX3JlbmRlcigpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5fbm9kZXMgPSB0aGlzLl9zYW5rZXlcbiAgICAgICAgICAgIC5ub2Rlcyh0aGlzLm5vZGVzKVxuICAgICAgICAgICAgLmxpbmtzKHRoaXMubGlua3MpXG4gICAgICAgICAgICAuc3BhY2luZygxNClcbiAgICAgICAgICAgIC5zaXplKHRoaXMubWluV2lkdGgsIHRoaXMubWF4V2lkdGgsIHRoaXMubWluSGVpZ2h0KVxuICAgICAgICAgICAgLndpZHRoKHRoaXMuX3dpZHRoIHx8IHRoaXMubm9kZUNvbnRhaW5lci5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRoKVxuICAgICAgICAgICAgLmhlaWdodCh0aGlzLl9oZWlnaHQgfHwgdGhpcy5ub2RlQ29udGFpbmVyLm5hdGl2ZUVsZW1lbnQub2Zmc2V0SGVpZ2h0KVxuICAgICAgICAgICAgLmxheW91dCgpO1xuXG4gICAgICAgIC8vIGVuc3VyZSB0aGUgZm9jdXMgbWFuYWdlciBoYXMgdGhlIGxhdGVzdCBub2RlIGRhdGFcbiAgICAgICAgdGhpcy5fZm9jdXNNYW5hZ2VyLnNldE5vZGVzKHRoaXMuX25vZGVzKTtcblxuICAgICAgICB0aGlzLl9jb2x1bW5zID0gdGhpcy5nZXRDb2x1bW5zKCk7XG4gICAgICAgIHRoaXMuX2NoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKTtcbiAgICB9XG5cbiAgICAvKiogVXBkYXRlIHRoZSBsYXlvdXQgd2hlbmV2ZXIgdGhlIGRpbWVuc2lvbnMgY2hhbmdlIGNoYW5nZXMgKi9cbiAgICBfb25SZXNpemUoZGltZW5zaW9uczogUmVzaXplRGltZW5zaW9ucyk6IHZvaWQge1xuICAgICAgICB0aGlzLl93aWR0aCA9IGRpbWVuc2lvbnMud2lkdGg7XG4gICAgICAgIHRoaXMuX2hlaWdodCA9IGRpbWVuc2lvbnMuaGVpZ2h0O1xuICAgICAgICB0aGlzLl9yZW5kZXIoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDb2x1bW4gY291bnQgc2hvdWxkIGJlIGJhc2VkIG9uIHRoZSBkYXRhLCBub3QgdGhlIHRpdGxlc1xuICAgICAqIGFzIHRoZXkgbWF5IG5vdCBzcGVjaWZ5IHRpdGxlcyBidXQgdGhlIG5vZGVzIHdpbGwgc3RpbGwgYmVcbiAgICAgKiByZW5kZXJlZC5cbiAgICAgKi9cbiAgICBfZ2V0Q29sdW1uQ291bnQoKTogbnVtYmVyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX25vZGVzLnJlZHVjZSgoY29sdW1uLCBub2RlKSA9PiBNYXRoLm1heChjb2x1bW4sIG5vZGUuY29sdW1uKSwgMCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0IHRoZSBTVkcgcGF0aCB0aGF0IGRlZmluZXMgdGhlIHNoYXBlIG9mIHRoZSBsaW5rXG4gICAgICovXG4gICAgX2dldFBhdGgobGluazogU2Fua2V5TGluayk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiB0aGlzLl9zYW5rZXkubGluayhsaW5rKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdGhlIGFjdGl2ZSBzdGF0ZSBvZiBhIG5vZGUgYW5kIHRoZSBpbnB1dHMgYW5kIG91dHB1dHNcbiAgICAgKiBhc3NvY2lhdGVkIHdpdGggdGhpcyBub2RlLlxuICAgICAqL1xuICAgIF9zZXROb2RlQWN0aXZlKG5vZGVMaW5rOiBTYW5rZXlOb2RlTGluazxUPiwgYWN0aXZlOiBib29sZWFuKTogdm9pZCB7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBub2RlIGFjdGl2ZSBzdGF0ZVxuICAgICAgICBub2RlTGluay5hY3RpdmUgPSBhY3RpdmU7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgZWFjaCBsaW5rXG4gICAgICAgIG5vZGVMaW5rLmlucHV0cy5mb3JFYWNoKGxpbmsgPT4gbGluay5hY3RpdmUgPSBhY3RpdmUpO1xuICAgICAgICBub2RlTGluay5vdXRwdXRzLmZvckVhY2gobGluayA9PiBsaW5rLmFjdGl2ZSA9IGFjdGl2ZSk7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgYWxsIGlucHV0IGFuZCBvdXRwdXQgbm9kZXNcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnNvdXJjZSkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuYWN0aXZlID0gYWN0aXZlKTtcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnRhcmdldCkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuYWN0aXZlID0gYWN0aXZlKTtcbiAgICAgICAgbm9kZUxpbmsub3V0cHV0cy5tYXAobGluayA9PiB0aGlzLl9zYW5rZXkuZ2V0Tm9kZUxpbmsobGluay5zb3VyY2UpKS5mb3JFYWNoKF9ub2RlID0+IF9ub2RlLmFjdGl2ZSA9IGFjdGl2ZSk7XG4gICAgICAgIG5vZGVMaW5rLm91dHB1dHMubWFwKGxpbmsgPT4gdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsudGFyZ2V0KSkuZm9yRWFjaChfbm9kZSA9PiBfbm9kZS5hY3RpdmUgPSBhY3RpdmUpO1xuXG4gICAgICAgIC8vIGVuc3VyZSB3ZSB1cGRhdGUgdGhlIHZpZXcgdG8gc2hvdyBoaWdobGlnaHRzXG4gICAgICAgIHRoaXMuX2NoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKTtcbiAgICB9XG5cblxuICAgIC8qKlxuICAgICAqIFNldCB0aGUgZm9jdXNlZCBzdGF0ZSBvZiBhIG5vZGUgYW5kIHRoZSBpbnB1dHMgYW5kIG91dHB1dHNcbiAgICAgKiBhc3NvY2lhdGVkIHdpdGggdGhpcyBub2RlLlxuICAgICAqL1xuICAgIF9zZXROb2RlRm9jdXMobm9kZUxpbms6IFNhbmtleU5vZGVMaW5rPFQ+LCBmb2N1c2VkOiBib29sZWFuLCBlbGVtZW50OiBIVE1MRGl2RWxlbWVudCk6IHZvaWQge1xuXG4gICAgICAgIC8vIHNldCB0aGUgbm9kZSBmb2N1cyBzdGF0ZVxuICAgICAgICBub2RlTGluay5mb2N1cyA9IGZvY3VzZWQ7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgZWFjaCBsaW5rXG4gICAgICAgIG5vZGVMaW5rLmlucHV0cy5mb3JFYWNoKGxpbmsgPT4gbGluay5mb2N1cyA9IGZvY3VzZWQpO1xuICAgICAgICBub2RlTGluay5vdXRwdXRzLmZvckVhY2gobGluayA9PiBsaW5rLmZvY3VzID0gZm9jdXNlZCk7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgYWxsIGlucHV0IGFuZCBvdXRwdXQgbm9kZXNcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnNvdXJjZSkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuZm9jdXMgPSBmb2N1c2VkKTtcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnRhcmdldCkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuZm9jdXMgPSBmb2N1c2VkKTtcbiAgICAgICAgbm9kZUxpbmsub3V0cHV0cy5tYXAobGluayA9PiB0aGlzLl9zYW5rZXkuZ2V0Tm9kZUxpbmsobGluay5zb3VyY2UpKS5mb3JFYWNoKF9ub2RlID0+IF9ub2RlLmZvY3VzID0gZm9jdXNlZCk7XG4gICAgICAgIG5vZGVMaW5rLm91dHB1dHMubWFwKGxpbmsgPT4gdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsudGFyZ2V0KSkuZm9yRWFjaChfbm9kZSA9PiBfbm9kZS5mb2N1cyA9IGZvY3VzZWQpO1xuXG4gICAgICAgIC8vIHdlIG5lZWQgdG8gYWRkIHRoZSBmb2N1cyBpbmRpY2F0b3IgaGVyZSBwcm9ncmFtbWF0aWNhbGx5LiBUaGUgZGVmYXVsdCBxdWFudHVtLXV4LWFzcGVjdHMgZm9jdXMgaW5kaWNhdG9yXG4gICAgICAgIC8vIHN0eWxpbmcgdXNlcyBgIWltcG9ydGFudGAgc28gb3VyIGlubGluZSBzdHlsZSBuZWVkcyB0byBhbHNvIGJlIGAhaW1wb3J0YW50YCB0byBvdmVycmlkZSB0aGlzLCBhbmQgdW5mb3J0dW5hdGVseVxuICAgICAgICAvLyB0aGVyZSBpcyBhIGtub3duIGlzc3VlIHdpdGggYE5nU3R5bGVgIGFuZCBgW3N0eWxlLnh5el1gIGJpbmRpbmdzIHByZXZlbnRpbmcgdGhlbSBmcm9tIGFkZGluZyB0aGUgYCFpbXBvcnRhbnRgXG4gICAgICAgIC8vIG1vZGlmaWVyIHNvIHdlIG11c3QgZG8gaXQgbWFudWFsbHkgKG5vdCB1c2luZyBgUmVuZGVyZXIyYCkuXG4gICAgICAgIGlmICh0aGlzLmNvbG9yKSB7XG4gICAgICAgICAgICBlbGVtZW50LnN0eWxlLnNldFByb3BlcnR5KCdib3gtc2hhZG93JywgdGhpcy5fZ2V0Rm9jdXNJbmRpY2F0b3Iobm9kZUxpbmspLCAnaW1wb3J0YW50Jyk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBlbnN1cmUgd2UgdXBkYXRlIHRoZSB2aWV3IHRvIHNob3cgaGlnaGxpZ2h0c1xuICAgICAgICB0aGlzLl9jaGFuZ2VEZXRlY3Rvci5kZXRlY3RDaGFuZ2VzKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgYSBsaW5rIGFuZCB0aGUgc291cmNlIGFuZCB0YXJnZXRcbiAgICAgKiBub2RlcyBhc3NvY2lhdGVkIHdpdGggdGhlIGxpbmtcbiAgICAgKi9cbiAgICBfc2V0TGlua0FjdGl2ZShsaW5rOiBTYW5rZXlMaW5rICYgU2Fua2V5TGlua1Bsb3QgJiBTYW5rZXlMaW5rSW50ZXJhY3Rpb24sIGFjdGl2ZTogYm9vbGVhbik6IHZvaWQge1xuICAgICAgICBsaW5rLmFjdGl2ZSA9IGFjdGl2ZTtcblxuICAgICAgICBpZiAobGluay5zb3VyY2UgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsuc291cmNlKS5hY3RpdmUgPSBhY3RpdmU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAobGluay50YXJnZXQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsudGFyZ2V0KS5hY3RpdmUgPSBhY3RpdmU7XG4gICAgICAgIH1cblxuICAgICAgICAvLyB1cGRhdGUgdGhlIHRvb2x0aXAgdmlzaWJpbGl0eVxuICAgICAgICB0aGlzLl9pc1Rvb2x0aXBPcGVuID0gYWN0aXZlO1xuXG4gICAgICAgIC8vIHVwZGF0ZSB0aGUgdG9vbHRpcCBjb250ZW50XG4gICAgICAgIHRoaXMuX3Rvb2x0aXBDb250ZW50ID0gYWN0aXZlID8gdGhpcy5saW5rVG9vbHRpcChsaW5rKSA6ICcnO1xuXG4gICAgICAgIC8vIGVuc3VyZSB3ZSB1cGRhdGUgdGhlIHZpZXcgdG8gc2hvdyBoaWdobGlnaHRzXG4gICAgICAgIHRoaXMuX2NoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUaGlzIGlzIHJlcXVpcmVkIGJlY2F1c2Ugd2Ugd2FudCB0byB0b2dnbGUgYSBjbGFzcyBiYXNlZCBvbiB0aGUgYGFjdGl2ZWBcbiAgICAgKiBwcm9wZXJ0eSBvbiBhIGxpbmssIGhvd2V2ZXIgdG9nZ2xpbmcgY2xhc3NlcyB1c2luZyBgTmdDbGFzc2Agb3IgdGhlIGNsYXNzXG4gICAgICogYmluZGluZyBzeW50YXggYFtjbGFzcy54eXpdYCBkb2VzIG5vdCB3b3JrIGluIElFIHdoZW4gYXBwbGllZCB0byBhbiBTVkdcbiAgICAgKiBlbGVtZW50LiAoaHR0cHM6Ly9naXRodWIuY29tL2FuZ3VsYXIvYW5ndWxhci9pc3N1ZXMvNjMyNylcbiAgICAgKlxuICAgICAqIFRoZSBhbHRlcm5hdGljZSBpcyB0byBiaW5kIGRpcmVjdGx5IHRvIHRoZSBgY2xhc3NgIGF0dHJpYnV0ZSBhbmQgcmV0dXJuIGFcbiAgICAgKiBzdHJpbmcgdGhhdCB3aWxsIHRvZ2dsZSB0aGUgY2xhc3MgYmFzZWQgb24gdGhlIGBhY3RpdmVgIHByb3BlcnR5LlxuICAgICAqL1xuICAgIF9nZXRMaW5rQ2xhc3MobGluazogU2Fua2V5TGluayAmIFNhbmtleUxpbmtQbG90ICYgU2Fua2V5TGlua0ludGVyYWN0aW9uKTogc3RyaW5nIHtcbiAgICAgICAgcmV0dXJuIGB1eC1zYW5rZXktY2hhcnQtbGluayAkeyhsaW5rLmFjdGl2ZSB8fCBsaW5rLmZvY3VzKSA/ICd1eC1zYW5rZXktY2hhcnQtbGluay1hY3RpdmUnIDogJyd9YDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXQgdGhlIFNWRyBwYXRoIHRoYXQgZGVmaW5lcyB0aGUgc2hhcGUgb2YgdGhlIGZhbGxvZmYgaW5kaWNhdG9yXG4gICAgICovXG4gICAgX2dldEZhbGxvZmZQYXRoKG5vZGU6IFNhbmtleU5vZGVMaW5rPFQ+KTogc3RyaW5nIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3NhbmtleS5nZXRGYWxsb2ZmUGF0aChub2RlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGYWxsb2ZmIHJlcHJlc2VudHMgdGhlIGFtb3VudCBvZiBkYXRhIHRoYXQgZG9lcyBub3QgZ2V0IHBhc3NlZCBvbixcbiAgICAgKiBmb3IgZXhhbXBsZSwgaWYgYSBub2RlIGdldHMgMSwwMDAsMDAwIGl0ZW1zIGZyb20gaW5wdXRzIGFuZCBvbmx