@ux-aspects/ux-aspects
Version:
Open source user interface framework for building modern, responsive, mobile big data applications
333 lines • 60.3 kB
JavaScript
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Fua2V5LWNoYXJ0LmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jb21wb25lbnRzL3NhbmtleS1jaGFydC9zYW5rZXktY2hhcnQuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvc2Fua2V5LWNoYXJ0L3NhbmtleS1jaGFydC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDMUUsT0FBTyxFQUFpQix1QkFBdUIsRUFBRSxpQkFBaUIsRUFBRSxTQUFTLEVBQUUsWUFBWSxFQUFjLE1BQU0sRUFBRSxLQUFLLEVBQTBCLFNBQVMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUVqTCxPQUFPLEVBQUUsWUFBWSxFQUFFLFVBQVUsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBSXRFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUM3QyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQzs7Ozs7OztBQW1CNUQsTUFBTSxPQUFPLG9CQUFvQjtJQWpCakM7UUFrQnFCLGtCQUFhLEdBQUcsTUFBTSxDQUF3QixrQkFBa0IsQ0FBQyxDQUFDO1FBRWxFLG9CQUFlLEdBQUcsTUFBTSxDQUFDLGlCQUFpQixDQUFDLENBQUM7UUFFNUMsa0JBQWEsR0FBRyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7UUFFdEQsa0NBQWtDO1FBQ3pCLFVBQUssR0FBaUMsRUFBRSxDQUFDO1FBRWxELGtDQUFrQztRQUN6QixVQUFLLEdBQThCLEVBQUUsQ0FBQztRQUUvQyx3Q0FBd0M7UUFDL0IsWUFBTyxHQUFhLEVBQUUsQ0FBQztRQUVoQyx5Q0FBeUM7UUFDaEMsYUFBUSxHQUFXLENBQUMsQ0FBQztRQUU5Qix5Q0FB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O2FBQ0wsQ0FBQztTQUNMOzs0RkFFUSxvQkFBb0I7a0JBakJoQyxTQUFTOytCQUNJLGlCQUFpQixtQkFFVix1QkFBdUIsQ0FBQyxNQUFNLGlCQUNoQyxDQUFDLGtCQUFrQixDQUFDLGNBQ3ZCO3dCQUNSLE9BQU8sQ0FBQyxrQkFBa0IsRUFBRTs0QkFDeEIsVUFBVSxDQUFDLFFBQVEsRUFBRTtnQ0FDakIsS0FBSyxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRSxDQUFDO2dDQUNyQixPQUFPLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxFQUFFLE9BQU8sRUFBRSxDQUFDLEVBQUUsQ0FBQyxDQUFDOzZCQUN0QyxDQUFDOzRCQUNGLFVBQVUsQ0FBQyxRQUFRLEVBQUU7Z0NBQ2pCLE9BQU8sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUM7NkJBQ3RDLENBQUM7eUJBQ0wsQ0FBQztxQkFDTDs4QkFVUSxLQUFLO3NCQUFiLEtBQUs7Z0JBR0csS0FBSztzQkFBYixLQUFLO2dCQUdHLE9BQU87c0JBQWYsS0FBSztnQkFHRyxRQUFRO3NCQUFoQixLQUFLO2dCQUdHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBR0csU0FBUztzQkFBakIsS0FBSztnQkFHRyxXQUFXO3NCQUFuQixLQUFLO2dCQUdHLGNBQWM7c0JBQXRCLEtBQUs7Z0JBR0csS0FBSztzQkFBYixLQUFLO2dCQUdpRCxZQUFZO3NCQUFsRSxZQUFZO3VCQUFDLG9CQUFvQixFQUFFLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRTtnQkFHUCxhQUFhO3NCQUExRCxTQUFTO3VCQUFDLGVBQWUsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUU7Z0JBR0UsYUFBYTtzQkFBMUQsU0FBUzt1QkFBQyxlQUFlLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgYW5pbWF0ZSwgc3R5bGUsIHRyYW5zaXRpb24sIHRyaWdnZXIgfSBmcm9tICdAYW5ndWxhci9hbmltYXRpb25zJztcbmltcG9ydCB7IEFmdGVyVmlld0luaXQsIENoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDaGFuZ2VEZXRlY3RvclJlZiwgQ29tcG9uZW50LCBDb250ZW50Q2hpbGQsIEVsZW1lbnRSZWYsIGluamVjdCwgSW5wdXQsIE9uQ2hhbmdlcywgVGVtcGxhdGVSZWYsIFZpZXdDaGlsZCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgUmVzaXplRGltZW5zaW9ucyB9IGZyb20gJy4uLy4uL2RpcmVjdGl2ZXMvcmVzaXplL2luZGV4JztcbmltcG9ydCB7IENvbG9yU2VydmljZSwgVGhlbWVDb2xvciB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL2NvbG9yL2luZGV4JztcbmltcG9ydCB7IFNhbmtleUxpbmssIFNhbmtleUxpbmtJbnRlcmFjdGlvbiwgU2Fua2V5TGlua1Bsb3QgfSBmcm9tICcuL2ludGVyZmFjZXMvbGluay5pbnRlcmZhY2UnO1xuaW1wb3J0IHsgU2Fua2V5Tm9kZUxpbmsgfSBmcm9tICcuL2ludGVyZmFjZXMvbm9kZS1saW5rLmludGVyZmFjZSc7XG5pbXBvcnQgeyBTYW5rZXlOb2RlIH0gZnJvbSAnLi9pbnRlcmZhY2VzL25vZGUuaW50ZXJmYWNlJztcbmltcG9ydCB7IFNhbmtleUNoYXJ0IH0gZnJvbSAnLi9zYW5rZXktY2hhcnQnO1xuaW1wb3J0IHsgU2Fua2V5Rm9jdXNNYW5hZ2VyIH0gZnJvbSAnLi9zYW5rZXktZm9jdXMtbWFuYWdlcic7XG5cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAndXgtc2Fua2V5LWNoYXJ0JyxcbiAgICB0ZW1wbGF0ZVVybDogJy4vc2Fua2V5LWNoYXJ0LmNvbXBvbmVudC5odG1sJyxcbiAgICBjaGFuZ2VEZXRlY3Rpb246IENoYW5nZURldGVjdGlvblN0cmF0ZWd5Lk9uUHVzaCxcbiAgICB2aWV3UHJvdmlkZXJzOiBbU2Fua2V5Rm9jdXNNYW5hZ2VyXSxcbiAgICBhbmltYXRpb25zOiBbXG4gICAgICAgIHRyaWdnZXIoJ3Rvb2x0aXBBbmltYXRpb24nLCBbXG4gICAgICAgICAgICB0cmFuc2l0aW9uKCc6ZW50ZXInLCBbXG4gICAgICAgICAgICAgICAgc3R5bGUoeyBvcGFjaXR5OiAwIH0pLFxuICAgICAgICAgICAgICAgIGFuaW1hdGUoMTYwLCBzdHlsZSh7IG9wYWNpdHk6IDEgfSkpXG4gICAgICAgICAgICBdKSxcbiAgICAgICAgICAgIHRyYW5zaXRpb24oJzpsZWF2ZScsIFtcbiAgICAgICAgICAgICAgICBhbmltYXRlKDE2MCwgc3R5bGUoeyBvcGFjaXR5OiAwIH0pKVxuICAgICAgICAgICAgXSlcbiAgICAgICAgXSlcbiAgICBdLFxufSlcbmV4cG9ydCBjbGFzcyBTYW5rZXlDaGFydENvbXBvbmVudDxUPiBpbXBsZW1lbnRzIE9uQ2hhbmdlcywgQWZ0ZXJWaWV3SW5pdCB7XG4gICAgcHJpdmF0ZSByZWFkb25seSBfZm9jdXNNYW5hZ2VyID0gaW5qZWN0PFNhbmtleUZvY3VzTWFuYWdlcjxUPj4oU2Fua2V5Rm9jdXNNYW5hZ2VyKTtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NoYW5nZURldGVjdG9yID0gaW5qZWN0KENoYW5nZURldGVjdG9yUmVmKTtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NvbG9yU2VydmljZSA9IGluamVjdChDb2xvclNlcnZpY2UpO1xuXG4gICAgLyoqIERlZmluZSB0aGUgbm9kZXMgdG8gZGlzcGxheSAqL1xuICAgIEBJbnB1dCgpIG5vZGVzOiBSZWFkb25seUFycmF5PFNhbmtleU5vZGU8VD4+ID0gW107XG5cbiAgICAvKiogRGVmaW5lIHRoZSBsaW5rcyB0byBkaXNwbGF5ICovXG4gICAgQElucHV0KCkgbGlua3M6IFJlYWRvbmx5QXJyYXk8U2Fua2V5TGluaz4gPSBbXTtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGhlYWRlcnMgb2YgZWFjaCBjb2x1bW4gKi9cbiAgICBASW5wdXQoKSBjb2x1bW5zOiBzdHJpbmdbXSA9IFtdO1xuXG4gICAgLyoqIERlZmluZSB0aGUgbWluaW11bSB3aWR0aCBvZiBhIG5vZGUgKi9cbiAgICBASW5wdXQoKSBtaW5XaWR0aDogbnVtYmVyID0gMDtcblxuICAgIC8qKiBEZWZpbmUgdGhlIG1heGltdW0gd2lkdGggb2YgYSBub2RlICovXG4gICAgQElucHV0KCkgbWF4V2lkdGg6IG51bWJlciA9IEluZmluaXR5O1xuXG4gICAgLyoqIFRoZSBtaW5pbXVtIGhlaWdodCBvZiBhIG5vZGUuICovXG4gICAgQElucHV0KCkgbWluSGVpZ2h0OiBudW1iZXIgPSAwO1xuXG4gICAgLyoqIERlZmluZSB0aGUgZnVuY3Rpb24gdG8gZ2V0IHRoZSBjb250ZW50cyBvZiBhIGxpbmsgdG9vbHRpcCAqL1xuICAgIEBJbnB1dCgpIGxpbmtUb29sdGlwOiAobGluazogU2Fua2V5TGluaykgPT4gc3RyaW5nID0gdGhpcy5nZXRMaW5rVG9vbHRpcDtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGZ1bmN0aW9uIHRvIGdldCB0aGUgY29udGVudHMgb2YgYSBmYWxsb2ZmIHRvb2x0aXAgKi9cbiAgICBASW5wdXQoKSBmYWxsb2ZmVG9vbHRpcDogKGZhbGxvZmY6IG51bWJlcikgPT4gc3RyaW5nID0gdGhpcy5nZXRGYWxsb2ZmVG9vbHRpcDtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGFjdGl2ZSBjb2xvciBvZiBhIG5vZGUgKi9cbiAgICBASW5wdXQoKSBjb2xvcjogc3RyaW5nIHwgVGhlbWVDb2xvcjtcblxuICAgIC8qKiBEZWZpbmUgdGhlIHRlbXBsYXRlIG9mIHNhbmtleSBjaGFydCBub2RlcyAqL1xuICAgIEBDb250ZW50Q2hpbGQoJ3NhbmtleU5vZGVUZW1wbGF0ZScsIHsgc3RhdGljOiBmYWxzZSB9KSBub2RlVGVtcGxhdGU6IFRlbXBsYXRlUmVmPFNhbmtleU5vZGVDb250ZXh0PFQ+PjtcblxuICAgIC8qKiBBY2Nlc3MgdGhlIFNWRyBlbGVtZW50IHdoaWNoIHdpbGwgY29udGFpbiB0aGUgbGlua3MgKi9cbiAgICBAVmlld0NoaWxkKCdsaW5rQ29udGFpbmVyJywgeyBzdGF0aWM6IHRydWUgfSkgbGlua0NvbnRhaW5lcjogRWxlbWVudFJlZjtcblxuICAgIC8qKiBBY2Nlc3MgdGhlIGVsZW1lbnQgd2hpY2ggd2lsbCBjb250YWluIHRoZSBub2RlcyAqL1xuICAgIEBWaWV3Q2hpbGQoJ25vZGVDb250YWluZXInLCB7IHN0YXRpYzogdHJ1ZSB9KSBub2RlQ29udGFpbmVyOiBFbGVtZW50UmVmO1xuXG4gICAgLyoqIFN0b3JlIHRoZSB3aWR0aCBvZiB0aGUgY2hhcnQgYXJlYSAqL1xuICAgIF93aWR0aDogbnVtYmVyO1xuXG4gICAgLyoqIFN0b3JlIHRoZSBoZWlnaHQgb2YgdGhlIGNoYXJ0IGFyZWEgKi9cbiAgICBfaGVpZ2h0OiBudW1iZXI7XG5cbiAgICAvKiogRGVmaW5lIHRoZSBub2RlcyB0aGF0IHNob3VsZCBiZSByZW5kZXJlZCAqL1xuICAgIF9ub2RlczogUmVhZG9ubHlBcnJheTxTYW5rZXlOb2RlTGluazxUPj4gPSBbXTtcblxuICAgIC8qKiBEZWZpbmUgdGhlIGNvbHVtbnMgdG8gZGlzcGxheSAqL1xuICAgIF9jb2x1bW5zOiBSZWFkb25seUFycmF5PFNhbmtleUNvbHVtbj4gPSBbXTtcblxuICAgIC8qKiBEZXRlcm1pbmUgaWYgdGhlIHRvb2x0aXAgc2hvdWxkIGJlIHZpc2libGUgb3Igbm90ICovXG4gICAgX2lzVG9vbHRpcE9wZW46IGJvb2xlYW4gPSBmYWxzZTtcblxuICAgIC8qKiBEZWZpbmUgdGhlIHBvc2l0aW9uIG9mIHRoZSB0b29sdGlwICovXG4gICAgX3Rvb2x0aXBQb3NpdGlvbjogU2Fua2V5VG9vbHRpcFBvc2l0aW9uID0geyB4OiAwLCB5OiAwIH07XG5cbiAgICAvKiogRGVmaW5lIHRoZSBjb250ZW50IHRvIGRpc3BsYXkgaW4gdGhlIHRvb2x0aXAgKi9cbiAgICBfdG9vbHRpcENvbnRlbnQ6IHN0cmluZztcblxuICAgIC8qKiBEZXRlcm1pbmUgaWYgdGhlIGNvbXBvbmVudCBpcyBpbml0aWFsaXNlZCAqL1xuICAgIHByaXZhdGUgX2lzSW5pdGlhbGlzZWQ6IGJvb2xlYW4gPSBmYWxzZTtcblxuICAgIC8qKiBTdG9yZSB0aGUgaW5zdGFuY2Ugb2YgdGhlIHNhbmtleSBsYXlvdXQgKi9cbiAgICBwcml2YXRlIHJlYWRvbmx5IF9zYW5rZXkgPSBuZXcgU2Fua2V5Q2hhcnQ8VD4oKTtcblxuICAgIG5nQWZ0ZXJWaWV3SW5pdCgpOiB2b2lkIHtcblxuICAgICAgICAvLyB2ZXJpZnkgd2UgaGF2ZSBhIG5vZGUgdGVtcGxhdGUgZGVmaW5lZCBiZWZvcmUgcHJvY2VlZGluZ1xuICAgICAgICBpZiAoIXRoaXMubm9kZVRlbXBsYXRlKSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NhbmtleSBDaGFydCAtIE5vZGUgVGVtcGxhdGUgaGFzIG5vdCBiZWVuIGRlZmluZWQuJyk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBzZXQgdGhlIGluaXRpYWwgY2hhcnQgc2l6ZVxuICAgICAgICB0aGlzLl93aWR0aCA9IHRoaXMubm9kZUNvbnRhaW5lci5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRoO1xuICAgICAgICB0aGlzLl9oZWlnaHQgPSB0aGlzLm5vZGVDb250YWluZXIubmF0aXZlRWxlbWVudC5vZmZzZXRIZWlnaHQ7XG5cbiAgICAgICAgLy8gcGVyZm9ybSB0aGUgaW5pdGlhbCByZW5kZXJcbiAgICAgICAgdGhpcy5fcmVuZGVyKCk7XG5cbiAgICAgICAgLy8gbWFyayB0aGUgY29tcG9uZW50IGFzIGluaXRpYWxpc2VkXG4gICAgICAgIHRoaXMuX2lzSW5pdGlhbGlzZWQgPSB0cnVlO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIERldGVjdCBhbnkgY2hhbmdlcyBmcm9tIElucHV0cy4gV2UgY2FuIHNraXBcbiAgICAgKiB0aGUgZmlyc3QgZnVuY3Rpb24gY2FsbCBhcyB0aGlzIGhhcHBlbnMgYmVmb3JlXG4gICAgICogdGhlIGluaXRpYWwgcmVuZGVyIHNvIGl0IGhhcyBubyBlZmZlY3QuXG4gICAgICovXG4gICAgbmdPbkNoYW5nZXMoKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLl9pc0luaXRpYWxpc2VkKSB7XG4gICAgICAgICAgICB0aGlzLl9yZW5kZXIoKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8qKiBSZS1yZW5kZXIgdGhlIGNoYXJ0ICovXG4gICAgX3JlbmRlcigpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5fbm9kZXMgPSB0aGlzLl9zYW5rZXlcbiAgICAgICAgICAgIC5ub2Rlcyh0aGlzLm5vZGVzKVxuICAgICAgICAgICAgLmxpbmtzKHRoaXMubGlua3MpXG4gICAgICAgICAgICAuc3BhY2luZygxNClcbiAgICAgICAgICAgIC5zaXplKHRoaXMubWluV2lkdGgsIHRoaXMubWF4V2lkdGgsIHRoaXMubWluSGVpZ2h0KVxuICAgICAgICAgICAgLndpZHRoKHRoaXMuX3dpZHRoIHx8IHRoaXMubm9kZUNvbnRhaW5lci5uYXRpdmVFbGVtZW50Lm9mZnNldFdpZHRoKVxuICAgICAgICAgICAgLmhlaWdodCh0aGlzLl9oZWlnaHQgfHwgdGhpcy5ub2RlQ29udGFpbmVyLm5hdGl2ZUVsZW1lbnQub2Zmc2V0SGVpZ2h0KVxuICAgICAgICAgICAgLmxheW91dCgpO1xuXG4gICAgICAgIC8vIGVuc3VyZSB0aGUgZm9jdXMgbWFuYWdlciBoYXMgdGhlIGxhdGVzdCBub2RlIGRhdGFcbiAgICAgICAgdGhpcy5fZm9jdXNNYW5hZ2VyLnNldE5vZGVzKHRoaXMuX25vZGVzKTtcblxuICAgICAgICB0aGlzLl9jb2x1bW5zID0gdGhpcy5nZXRDb2x1bW5zKCk7XG4gICAgICAgIHRoaXMuX2NoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKTtcbiAgICB9XG5cbiAgICAvKiogVXBkYXRlIHRoZSBsYXlvdXQgd2hlbmV2ZXIgdGhlIGRpbWVuc2lvbnMgY2hhbmdlIGNoYW5nZXMgKi9cbiAgICBfb25SZXNpemUoZGltZW5zaW9uczogUmVzaXplRGltZW5zaW9ucyk6IHZvaWQge1xuICAgICAgICB0aGlzLl93aWR0aCA9IGRpbWVuc2lvbnMud2lkdGg7XG4gICAgICAgIHRoaXMuX2hlaWdodCA9IGRpbWVuc2lvbnMuaGVpZ2h0O1xuICAgICAgICB0aGlzLl9yZW5kZXIoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBDb2x1bW4gY291bnQgc2hvdWxkIGJlIGJhc2VkIG9uIHRoZSBkYXRhLCBub3QgdGhlIHRpdGxlc1xuICAgICAqIGFzIHRoZXkgbWF5IG5vdCBzcGVjaWZ5IHRpdGxlcyBidXQgdGhlIG5vZGVzIHdpbGwgc3RpbGwgYmVcbiAgICAgKiByZW5kZXJlZC5cbiAgICAgKi9cbiAgICBfZ2V0Q29sdW1uQ291bnQoKTogbnVtYmVyIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX25vZGVzLnJlZHVjZSgoY29sdW1uLCBub2RlKSA9PiBNYXRoLm1heChjb2x1bW4sIG5vZGUuY29sdW1uKSwgMCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogR2V0IHRoZSBTVkcgcGF0aCB0aGF0IGRlZmluZXMgdGhlIHNoYXBlIG9mIHRoZSBsaW5rXG4gICAgICovXG4gICAgX2dldFBhdGgobGluazogU2Fua2V5TGluayk6IHN0cmluZyB7XG4gICAgICAgIHJldHVybiB0aGlzLl9zYW5rZXkubGluayhsaW5rKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBTZXQgdGhlIGFjdGl2ZSBzdGF0ZSBvZiBhIG5vZGUgYW5kIHRoZSBpbnB1dHMgYW5kIG91dHB1dHNcbiAgICAgKiBhc3NvY2lhdGVkIHdpdGggdGhpcyBub2RlLlxuICAgICAqL1xuICAgIF9zZXROb2RlQWN0aXZlKG5vZGVMaW5rOiBTYW5rZXlOb2RlTGluazxUPiwgYWN0aXZlOiBib29sZWFuKTogdm9pZCB7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBub2RlIGFjdGl2ZSBzdGF0ZVxuICAgICAgICBub2RlTGluay5hY3RpdmUgPSBhY3RpdmU7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgZWFjaCBsaW5rXG4gICAgICAgIG5vZGVMaW5rLmlucHV0cy5mb3JFYWNoKGxpbmsgPT4gbGluay5hY3RpdmUgPSBhY3RpdmUpO1xuICAgICAgICBub2RlTGluay5vdXRwdXRzLmZvckVhY2gobGluayA9PiBsaW5rLmFjdGl2ZSA9IGFjdGl2ZSk7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgYWxsIGlucHV0IGFuZCBvdXRwdXQgbm9kZXNcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnNvdXJjZSkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuYWN0aXZlID0gYWN0aXZlKTtcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnRhcmdldCkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuYWN0aXZlID0gYWN0aXZlKTtcbiAgICAgICAgbm9kZUxpbmsub3V0cHV0cy5tYXAobGluayA9PiB0aGlzLl9zYW5rZXkuZ2V0Tm9kZUxpbmsobGluay5zb3VyY2UpKS5mb3JFYWNoKF9ub2RlID0+IF9ub2RlLmFjdGl2ZSA9IGFjdGl2ZSk7XG4gICAgICAgIG5vZGVMaW5rLm91dHB1dHMubWFwKGxpbmsgPT4gdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsudGFyZ2V0KSkuZm9yRWFjaChfbm9kZSA9PiBfbm9kZS5hY3RpdmUgPSBhY3RpdmUpO1xuXG4gICAgICAgIC8vIGVuc3VyZSB3ZSB1cGRhdGUgdGhlIHZpZXcgdG8gc2hvdyBoaWdobGlnaHRzXG4gICAgICAgIHRoaXMuX2NoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKTtcbiAgICB9XG5cblxuICAgIC8qKlxuICAgICAqIFNldCB0aGUgZm9jdXNlZCBzdGF0ZSBvZiBhIG5vZGUgYW5kIHRoZSBpbnB1dHMgYW5kIG91dHB1dHNcbiAgICAgKiBhc3NvY2lhdGVkIHdpdGggdGhpcyBub2RlLlxuICAgICAqL1xuICAgIF9zZXROb2RlRm9jdXMobm9kZUxpbms6IFNhbmtleU5vZGVMaW5rPFQ+LCBmb2N1c2VkOiBib29sZWFuLCBlbGVtZW50OiBIVE1MRGl2RWxlbWVudCk6IHZvaWQge1xuXG4gICAgICAgIC8vIHNldCB0aGUgbm9kZSBmb2N1cyBzdGF0ZVxuICAgICAgICBub2RlTGluay5mb2N1cyA9IGZvY3VzZWQ7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgZWFjaCBsaW5rXG4gICAgICAgIG5vZGVMaW5rLmlucHV0cy5mb3JFYWNoKGxpbmsgPT4gbGluay5mb2N1cyA9IGZvY3VzZWQpO1xuICAgICAgICBub2RlTGluay5vdXRwdXRzLmZvckVhY2gobGluayA9PiBsaW5rLmZvY3VzID0gZm9jdXNlZCk7XG5cbiAgICAgICAgLy8gc2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgYWxsIGlucHV0IGFuZCBvdXRwdXQgbm9kZXNcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnNvdXJjZSkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuZm9jdXMgPSBmb2N1c2VkKTtcbiAgICAgICAgbm9kZUxpbmsuaW5wdXRzLm1hcChsaW5rID0+IHRoaXMuX3NhbmtleS5nZXROb2RlTGluayhsaW5rLnRhcmdldCkpLmZvckVhY2goX25vZGUgPT4gX25vZGUuZm9jdXMgPSBmb2N1c2VkKTtcbiAgICAgICAgbm9kZUxpbmsub3V0cHV0cy5tYXAobGluayA9PiB0aGlzLl9zYW5rZXkuZ2V0Tm9kZUxpbmsobGluay5zb3VyY2UpKS5mb3JFYWNoKF9ub2RlID0+IF9ub2RlLmZvY3VzID0gZm9jdXNlZCk7XG4gICAgICAgIG5vZGVMaW5rLm91dHB1dHMubWFwKGxpbmsgPT4gdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsudGFyZ2V0KSkuZm9yRWFjaChfbm9kZSA9PiBfbm9kZS5mb2N1cyA9IGZvY3VzZWQpO1xuXG4gICAgICAgIC8vIHdlIG5lZWQgdG8gYWRkIHRoZSBmb2N1cyBpbmRpY2F0b3IgaGVyZSBwcm9ncmFtbWF0aWNhbGx5LiBUaGUgZGVmYXVsdCBxdWFudHVtLXV4LWFzcGVjdHMgZm9jdXMgaW5kaWNhdG9yXG4gICAgICAgIC8vIHN0eWxpbmcgdXNlcyBgIWltcG9ydGFudGAgc28gb3VyIGlubGluZSBzdHlsZSBuZWVkcyB0byBhbHNvIGJlIGAhaW1wb3J0YW50YCB0byBvdmVycmlkZSB0aGlzLCBhbmQgdW5mb3J0dW5hdGVseVxuICAgICAgICAvLyB0aGVyZSBpcyBhIGtub3duIGlzc3VlIHdpdGggYE5nU3R5bGVgIGFuZCBgW3N0eWxlLnh5el1gIGJpbmRpbmdzIHByZXZlbnRpbmcgdGhlbSBmcm9tIGFkZGluZyB0aGUgYCFpbXBvcnRhbnRgXG4gICAgICAgIC8vIG1vZGlmaWVyIHNvIHdlIG11c3QgZG8gaXQgbWFudWFsbHkgKG5vdCB1c2luZyBgUmVuZGVyZXIyYCkuXG4gICAgICAgIGlmICh0aGlzLmNvbG9yKSB7XG4gICAgICAgICAgICBlbGVtZW50LnN0eWxlLnNldFByb3BlcnR5KCdib3gtc2hhZG93JywgdGhpcy5fZ2V0Rm9jdXNJbmRpY2F0b3Iobm9kZUxpbmspLCAnaW1wb3J0YW50Jyk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBlbnN1cmUgd2UgdXBkYXRlIHRoZSB2aWV3IHRvIHNob3cgaGlnaGxpZ2h0c1xuICAgICAgICB0aGlzLl9jaGFuZ2VEZXRlY3Rvci5kZXRlY3RDaGFuZ2VzKCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogU2V0IHRoZSBhY3RpdmUgc3RhdGUgb2YgYSBsaW5rIGFuZCB0aGUgc291cmNlIGFuZCB0YXJnZXRcbiAgICAgKiBub2RlcyBhc3NvY2lhdGVkIHdpdGggdGhlIGxpbmtcbiAgICAgKi9cbiAgICBfc2V0TGlua0FjdGl2ZShsaW5rOiBTYW5rZXlMaW5rICYgU2Fua2V5TGlua1Bsb3QgJiBTYW5rZXlMaW5rSW50ZXJhY3Rpb24sIGFjdGl2ZTogYm9vbGVhbik6IHZvaWQge1xuICAgICAgICBsaW5rLmFjdGl2ZSA9IGFjdGl2ZTtcblxuICAgICAgICBpZiAobGluay5zb3VyY2UgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsuc291cmNlKS5hY3RpdmUgPSBhY3RpdmU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAobGluay50YXJnZXQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgdGhpcy5fc2Fua2V5LmdldE5vZGVMaW5rKGxpbmsudGFyZ2V0KS5hY3RpdmUgPSBhY3RpdmU7XG4gICAgICAgIH1cblxuICAgICAgICAvLyB1cGRhdGUgdGhlIHRvb2x0aXAgdmlzaWJpbGl0eVxuICAgICAgICB0aGlzLl9pc1Rvb2x0aXBPcGVuID0gYWN0aXZlO1xuXG4gICAgICAgIC8vIHVwZGF0ZSB0aGUgdG9vbHRpcCBjb250ZW50XG4gICAgICAgIHRoaXMuX3Rvb2x0aXBDb250ZW50ID0gYWN0aXZlID8gdGhpcy5saW5rVG9vbHRpcChsaW5rKSA6ICcnO1xuXG4gICAgICAgIC8vIGVuc3VyZSB3ZSB1cGRhdGUgdGhlIHZpZXcgdG8gc2hvdyBoaWdobGlnaHRzXG4gICAgICAgIHRoaXMuX2NoYW5nZURldGVjdG9yLmRldGVjdENoYW5nZXMoKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUaGlzIGlzIHJlcXVpcmVkIGJlY2F1c2Ugd2Ugd2FudCB0byB0b2dnbGUgYSBjbGFzcyBiYXNlZCBvbiB0aGUgYGFjdGl2ZWBcbiAgICAgKiBwcm9wZXJ0eSBvbiBhIGxpbmssIGhvd2V2ZXIgdG9nZ2xpbmcgY2xhc3NlcyB1c2luZyBgTmdDbGFzc2Agb3IgdGhlIGNsYXNzXG4gICAgICogYmluZGluZyBzeW50YXggYFtjbGFzcy54eXpdYCBkb2VzIG5vdCB3b3JrIGluIElFIHdoZW4gYXBwbGllZCB0byBhbiBTVkdcbiAgICAgKiBlbGVtZW50LiAoaHR0cHM6Ly9naXRodWIuY29tL2FuZ3VsYXIvYW5ndWxhci9pc3N1ZXMvNjMyNylcbiAgICAgKlxuICAgICAqIFRoZSBhbHRlcm5hdGljZSBpcyB0byBiaW5kIGRpcmVjdGx5IHRvIHRoZSBgY2xhc3NgIGF0dHJpYnV0ZSBhbmQgcmV0dXJuIGFcbiAgICAgKiBzdHJpbmcgdGhhdCB3aWxsIHRvZ2dsZSB0aGUgY2xhc3MgYmFzZWQgb24gdGhlIGBhY3RpdmVgIHByb3BlcnR5LlxuICAgICAqL1xuICAgIF9nZXRMaW5rQ2xhc3MobGluazogU2Fua2V5TGluayAmIFNhbmtleUxpbmtQbG90ICYgU2Fua2V5TGlua0ludGVyYWN0aW9uKTogc3RyaW5nIHtcbiAgICAgICAgcmV0dXJuIGB1eC1zYW5rZXktY2hhcnQtbGluayAkeyhsaW5rLmFjdGl2ZSB8fCBsaW5rLmZvY3VzKSA/ICd1eC1zYW5rZXktY2hhcnQtbGluay1hY3RpdmUnIDogJyd9YDtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBHZXQgdGhlIFNWRyBwYXRoIHRoYXQgZGVmaW5lcyB0aGUgc2hhcGUgb2YgdGhlIGZhbGxvZmYgaW5kaWNhdG9yXG4gICAgICovXG4gICAgX2dldEZhbGxvZmZQYXRoKG5vZGU6IFNhbmtleU5vZGVMaW5rPFQ+KTogc3RyaW5nIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX3NhbmtleS5nZXRGYWxsb2ZmUGF0aChub2RlKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGYWxsb2ZmIHJlcHJlc2VudHMgdGhlIGFtb3VudCBvZiBkYXRhIHRoYXQgZG9lcyBub3QgZ2V0IHBhc3NlZCBvbixcbiAgICAgKiBmb3IgZXhhbXBsZSwgaWYgYSBub2RlIGdldHMgMSwwMDAsMDAwIGl0ZW1zIGZyb20gaW5wdXRzIGFuZCBvbmx