@nova-ui/charts
Version:
Nova Charts is a library created to provide potential consumers with solutions for various data visualizations that conform with the Nova Design Language. It's designed to solve common patterns identified by UX designers, but also be very flexible so that
201 lines • 32.7 kB
JavaScript
// © 2022 SolarWinds Worldwide, LLC. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import { ChangeDetectorRef, Component, ElementRef, Input, QueryList, ViewChildren, } from "@angular/core";
import { forceCollide, forceSimulation, } from "d3";
import { select } from "d3";
import each from "lodash/each";
import isEqual from "lodash/isEqual";
import isNil from "lodash/isNil";
import { Subject } from "rxjs";
import { takeUntil } from "rxjs/operators";
import { ChartTooltipDirective } from "./chart-tooltip.directive";
import { ChartTooltipsPlugin } from "../core/plugins/tooltips/chart-tooltips-plugin";
import * as i0 from "@angular/core";
import * as i1 from "@angular/common";
import * as i2 from "./chart-tooltip.directive";
export class ChartTooltipsComponent {
changeDetector;
plugin;
template;
tooltips;
openTooltips = new Subject();
closeTooltips = new Subject();
unsubscribe$ = new Subject();
simulation;
// index we use for fast access of tooltip directives by seriesId
tooltipDirectivesIndex = {};
closePending = false;
isOpen = false;
openTimeout;
collisionTimeout;
closeTimeout;
constructor(changeDetector) {
this.changeDetector = changeDetector;
}
ngOnChanges(changes) {
if (!(changes["plugin"] && changes["plugin"].currentValue)) {
return;
}
this.unsubscribe$.next();
this.plugin.showSubject
.pipe(takeUntil(this.unsubscribe$))
.subscribe(() => this.handleOpen());
this.plugin.hideSubject
.pipe(takeUntil(this.unsubscribe$))
.subscribe(() => this.handleClose());
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
trackByFn(index, item) {
return item.value.seriesId;
}
handleOpen() {
this.changeDetector.detectChanges();
const currentTooltipDirectivesIndex = {};
this.tooltips.forEach((tooltip) => {
// this is how we identify which series does the tooltip belong to
const seriesId = tooltip.elementRef.nativeElement.getAttribute("series-id") ??
undefined;
if (!seriesId) {
throw new Error("SeriesId is not defined");
}
currentTooltipDirectivesIndex[seriesId] = tooltip;
});
const directivesChanged = !isEqual(currentTooltipDirectivesIndex, this.tooltipDirectivesIndex);
if (this.closePending || directivesChanged || !this.isOpen) {
clearTimeout(this.openTimeout);
this.openTimeout = window.setTimeout(() => {
this.openTooltips.next();
clearTimeout(this.collisionTimeout);
this.collisionTimeout = window.setTimeout(() => {
this.avoidTooltipCollisions();
this.isOpen = true;
});
});
}
else {
this.openTooltips.next();
this.avoidTooltipCollisions();
this.isOpen = true;
}
}
handleClose() {
if (this.simulation) {
this.simulation.stop();
}
this.closePending = true;
clearTimeout(this.closeTimeout);
this.closeTimeout = window.setTimeout(() => {
this.closeTooltips.next();
this.closePending = false;
this.isOpen = false;
});
}
/**
* Runs the D3 forceCollide based tooltip collision avoidance algorithm
*/
avoidTooltipCollisions() {
// extracted tooltip positions from tooltip directives
const tooltipPositions = {};
this.tooltips.forEach((tooltip) => {
const element = tooltip.getOverlayElement();
// this is how we identify which series does the tooltip belong to
const seriesId = tooltip.elementRef.nativeElement.getAttribute("series-id") ??
undefined;
if (!seriesId) {
throw new Error("SeriesId is not defined");
}
this.tooltipDirectivesIndex[seriesId] = tooltip;
tooltipPositions[seriesId] = {
x: element.offsetLeft,
y: element.offsetTop,
width: element.clientWidth,
height: element.clientHeight,
};
});
// if there was a previous simulation, then stop it before running a new one
if (this.simulation) {
this.simulation.stop();
}
this.simulation = this.startSimulation(this.tooltipDirectivesIndex, tooltipPositions);
}
/**
* Starts the force simulation to avoid tooltip overlap
*
* @param tooltipIndex
* @param tooltipPositions
*/
startSimulation(tooltipIndex, tooltipPositions) {
const nodes = Object.keys(tooltipPositions).map((seriesId) => {
const position = tooltipPositions[seriesId];
if (isNil(position.height) || isNil(position.width)) {
throw new Error("Position height or width are not defined");
}
const props = {
seriesId: seriesId,
x: position.x,
y: position.y,
radius: this.isVertical()
? position.height / 2
: position.width / 2,
};
if (this.isVertical()) {
props.fx = position.x;
}
else {
props.fy = position.y;
}
return props;
});
// TODO: these numbers are only based on playing with the library for the while, it's not fine tuned at all
const collisionForce = forceCollide((node) => node.radius)
.strength(0.5)
.iterations(20);
const simulation = forceSimulation(nodes)
.alphaDecay(0.3)
.force("collisionForce", collisionForce);
simulation.on("tick", () => {
each(nodes, (node) => {
const tooltip = tooltipIndex[node.seriesId];
select(tooltip.getOverlayElement()).style(this.isVertical() ? "top" : "left", (this.isVertical() ? node.y : node.x) + "px");
});
});
return simulation;
}
isVertical() {
return this.plugin.orientation === "right";
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChartTooltipsComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: ChartTooltipsComponent, selector: "nui-chart-tooltips", inputs: { plugin: "plugin", template: "template" }, viewQueries: [{ propertyName: "tooltips", predicate: ChartTooltipDirective, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div *ngIf=\"plugin && plugin.dataPoints\">\n <div *ngFor=\"let item of plugin.dataPoints | keyvalue; trackBy: trackByFn\">\n <ng-template #tooltipTemplate>\n <ng-container\n *ngTemplateOutlet=\"template; context: { dataPoint: item.value }\"\n ></ng-container>\n </ng-template>\n\n <div\n nuiChartTooltip\n *ngIf=\"plugin.dataPointPositions[item.key]\"\n [attr.series-id]=\"item.key\"\n [template]=\"tooltipTemplate\"\n [openRemoteControl]=\"openTooltips\"\n [closeRemoteControl]=\"closeTooltips\"\n [positions]=\"plugin.dataPointPositions[item.key].overlayPositions\"\n class=\"chart-tooltips__popover\"\n [style.left.px]=\"plugin.dataPointPositions[item.key].x\"\n [style.top.px]=\"plugin.dataPointPositions[item.key].y\"\n [style.height.px]=\"plugin.dataPointPositions[item.key].height\"\n [style.width.px]=\"plugin.dataPointPositions[item.key].width\"\n ></div>\n </div>\n</div>\n", styles: [".chart-tooltips__popover{position:absolute;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.ChartTooltipDirective, selector: "[nuiChartTooltip]", inputs: ["template", "openRemoteControl", "closeRemoteControl", "positions"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChartTooltipsComponent, decorators: [{
type: Component,
args: [{ selector: "nui-chart-tooltips", template: "<div *ngIf=\"plugin && plugin.dataPoints\">\n <div *ngFor=\"let item of plugin.dataPoints | keyvalue; trackBy: trackByFn\">\n <ng-template #tooltipTemplate>\n <ng-container\n *ngTemplateOutlet=\"template; context: { dataPoint: item.value }\"\n ></ng-container>\n </ng-template>\n\n <div\n nuiChartTooltip\n *ngIf=\"plugin.dataPointPositions[item.key]\"\n [attr.series-id]=\"item.key\"\n [template]=\"tooltipTemplate\"\n [openRemoteControl]=\"openTooltips\"\n [closeRemoteControl]=\"closeTooltips\"\n [positions]=\"plugin.dataPointPositions[item.key].overlayPositions\"\n class=\"chart-tooltips__popover\"\n [style.left.px]=\"plugin.dataPointPositions[item.key].x\"\n [style.top.px]=\"plugin.dataPointPositions[item.key].y\"\n [style.height.px]=\"plugin.dataPointPositions[item.key].height\"\n [style.width.px]=\"plugin.dataPointPositions[item.key].width\"\n ></div>\n </div>\n</div>\n", styles: [".chart-tooltips__popover{position:absolute;pointer-events:none}\n"] }]
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }], propDecorators: { plugin: [{
type: Input
}], template: [{
type: Input
}], tooltips: [{
type: ViewChildren,
args: [ChartTooltipDirective]
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hhcnQtdG9vbHRpcHMuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NoYXJ0LXRvb2x0aXBzL2NoYXJ0LXRvb2x0aXBzLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uL3NyYy9jaGFydC10b29sdGlwcy9jaGFydC10b29sdGlwcy5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSx5REFBeUQ7QUFDekQsRUFBRTtBQUNGLCtFQUErRTtBQUMvRSw0RUFBNEU7QUFDNUUsOEVBQThFO0FBQzlFLCtFQUErRTtBQUMvRSw4RUFBOEU7QUFDOUUsNERBQTREO0FBQzVELEVBQUU7QUFDRiw2RUFBNkU7QUFDN0UsdURBQXVEO0FBQ3ZELEVBQUU7QUFDRiw2RUFBNkU7QUFDN0UsNEVBQTRFO0FBQzVFLCtFQUErRTtBQUMvRSwwRUFBMEU7QUFDMUUsaUZBQWlGO0FBQ2pGLDZFQUE2RTtBQUM3RSxpQkFBaUI7QUFFakIsT0FBTyxFQUNILGlCQUFpQixFQUNqQixTQUFTLEVBQ1QsVUFBVSxFQUNWLEtBQUssRUFHTCxTQUFTLEVBRVQsWUFBWSxHQUNmLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFDSCxZQUFZLEVBQ1osZUFBZSxHQUdsQixNQUFNLElBQUksQ0FBQztBQUNaLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDNUIsT0FBTyxJQUFJLE1BQU0sYUFBYSxDQUFDO0FBQy9CLE9BQU8sT0FBTyxNQUFNLGdCQUFnQixDQUFDO0FBQ3JDLE9BQU8sS0FBSyxNQUFNLGNBQWMsQ0FBQztBQUNqQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQy9CLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUUzQyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUVsRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxn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QUFVLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FDL0MsQ0FBQztZQUNOLENBQUMsQ0FBQyxDQUFDO1FBQ1AsQ0FBQyxDQUFDLENBQUM7UUFFSCxPQUFPLFVBQVUsQ0FBQztJQUN0QixDQUFDO0lBRU8sVUFBVTtRQUNkLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLEtBQUssT0FBTyxDQUFDO0lBQy9DLENBQUM7d0dBNU1RLHNCQUFzQjs0RkFBdEIsc0JBQXNCLDJJQUtqQixxQkFBcUIscUVDL0R2Qyxpa0NBd0JBOzs0RkRrQ2Esc0JBQXNCO2tCQUxsQyxTQUFTOytCQUNJLG9CQUFvQjtzRkFLckIsTUFBTTtzQkFBZCxLQUFLO2dCQUVHLFFBQVE7c0JBQWhCLEtBQUs7Z0JBR04sUUFBUTtzQkFEUCxZQUFZO3VCQUFDLHFCQUFxQiIsInNvdXJjZXNDb250ZW50IjpbIi8vIMKpIDIwMjIgU29sYXJXaW5kcyBXb3JsZHdpZGUsIExMQy4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbi8vXG4vLyBQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5XG4vLyAgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgXCJTb2Z0d2FyZVwiKSwgdG9cbi8vICBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZVxuLy8gIHJpZ2h0cyB0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vclxuLy8gIHNlbGwgY29waWVzIG9mIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdCBwZXJzb25zIHRvIHdob20gdGhlIFNvZnR3YXJlIGlzXG4vLyAgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczpcbi8vXG4vLyBUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZCBpblxuLy8gIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1Jcbi8vICBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSxcbi8vICBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBTkQgTk9OSU5GUklOR0VNRU5ULiBJTiBOTyBFVkVOVCBTSEFMTCBUSEVcbi8vICBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSXG4vLyAgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSxcbi8vICBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBUSEUgVVNFIE9SIE9USEVSIERFQUxJTkdTIElOXG4vLyAgVEhFIFNPRlRXQVJFLlxuXG5pbXBvcnQge1xuICAgIENoYW5nZURldGVjdG9yUmVmLFxuICAgIENvbXBvbmVudCxcbiAgICBFbGVtZW50UmVmLFxuICAgIElucHV0LFxuICAgIE9uQ2hhbmdlcyxcbiAgICBPbkRlc3Ryb3ksXG4gICAgUXVlcnlMaXN0LFxuICAgIFNpbXBsZUNoYW5nZXMsXG4gICAgVmlld0NoaWxkcmVuLFxufSBmcm9tIFwiQGFuZ3VsYXIvY29yZVwiO1xuaW1wb3J0IHtcbiAgICBmb3JjZUNvbGxpZGUsXG4gICAgZm9yY2VTaW11bGF0aW9uLFxuICAgIFNpbXVsYXRpb24sXG4gICAgU2ltdWxhdGlvbk5vZGVEYXR1bSxcbn0gZnJvbSBcImQzXCI7XG5pbXBvcnQgeyBzZWxlY3QgfSBmcm9tIFwiZDNcIjtcbmltcG9ydCBlYWNoIGZyb20gXCJsb2Rhc2gvZWFjaFwiO1xuaW1wb3J0IGlzRXF1YWwgZnJvbSBcImxvZGFzaC9pc0VxdWFsXCI7XG5pbXBvcnQgaXNOaWwgZnJvbSBcImxvZGFzaC9pc05pbFwiO1xuaW1wb3J0IHsgU3ViamVjdCB9IGZyb20gXCJyeGpzXCI7XG5pbXBvcnQgeyB0YWtlVW50aWwgfSBmcm9tIFwicnhqcy9vcGVyYXRvcnNcIjtcblxuaW1wb3J0IHsgQ2hhcnRUb29sdGlwRGlyZWN0aXZlIH0gZnJvbSBcIi4vY2hhcnQtdG9vbHRpcC5kaXJlY3RpdmVcIjtcbmltcG9ydCB7IElQb3NpdGlvbiB9IGZyb20gXCIuLi9jb3JlL2NvbW1vbi90eXBlc1wiO1xuaW1wb3J0IHsgQ2hhcnRUb29sdGlwc1BsdWdpbiB9IGZyb20gXCIuLi9jb3JlL3BsdWdpbnMvdG9vbHRpcHMvY2hhcnQtdG9vbHRpcHMtcGx1Z2luXCI7XG5cbmludGVyZmFjZSBJVG9vbHRpcE5vZGUgZXh0ZW5kcyBTaW11bGF0aW9uTm9kZURhdHVtIHtcbiAgICBzZXJpZXNJZDogc3RyaW5nO1xuICAgIHJhZGl1czogbnVtYmVyO1xufVxuXG5AQ29tcG9uZW50KHtcbiAgICBzZWxlY3RvcjogXCJudWktY2hhcnQtdG9vbHRpcHNcIixcbiAgICB0ZW1wbGF0ZVVybDogXCIuL2NoYXJ0LXRvb2x0aXBzLmNvbXBvbmVudC5odG1sXCIsXG4gICAgc3R5bGVVcmxzOiBbXCIuL2NoYXJ0LXRvb2x0aXBzLmNvbXBvbmVudC5sZXNzXCJdLFxufSlcbmV4cG9ydCBjbGFzcyBDaGFydFRvb2x0aXBzQ29tcG9uZW50IGltcGxlbWVudHMgT25DaGFuZ2VzLCBPbkRlc3Ryb3kge1xuICAgIEBJbnB1dCgpIHBsdWdpbjogQ2hhcnRUb29sdGlwc1BsdWdpbjtcblxuICAgIEBJbnB1dCgpIHRlbXBsYXRlOiBFbGVtZW50UmVmO1xuXG4gICAgQFZpZXdDaGlsZHJlbihDaGFydFRvb2x0aXBEaXJlY3RpdmUpXG4gICAgdG9vbHRpcHM6IFF1ZXJ5TGlzdDxDaGFydFRvb2x0aXBEaXJlY3RpdmU+O1xuXG4gICAgcHVibGljIG9wZW5Ub29sdGlwcyA9IG5ldyBTdWJqZWN0PHZvaWQ+KCk7XG4gICAgcHVibGljIGNsb3NlVG9vbHRpcHMgPSBuZXcgU3ViamVjdDx2b2lkPigpO1xuXG4gICAgcHJpdmF0ZSB1bnN1YnNjcmliZSQgPSBuZXcgU3ViamVjdDx2b2lkPigpO1xuICAgIHByaXZhdGUgc2ltdWxhdGlvbjogU2ltdWxhdGlvbjxJVG9vbHRpcE5vZGUsIHVuZGVmaW5lZD47XG4gICAgLy8gaW5kZXggd2UgdXNlIGZvciBmYXN0IGFjY2VzcyBvZiB0b29sdGlwIGRpcmVjdGl2ZXMgYnkgc2VyaWVzSWRcbiAgICBwcml2YXRlIHRvb2x0aXBEaXJlY3RpdmVzSW5kZXg6IHtcbiAgICAgICAgW3Nlcmllc0lkOiBzdHJpbmddOiBDaGFydFRvb2x0aXBEaXJlY3RpdmU7XG4gICAgfSA9IHt9O1xuICAgIHByaXZhdGUgY2xvc2VQZW5kaW5nID0gZmFsc2U7XG4gICAgcHJpdmF0ZSBpc09wZW4gPSBmYWxzZTtcbiAgICBwcml2YXRlIG9wZW5UaW1lb3V0OiBudW1iZXI7XG4gICAgcHJpdmF0ZSBjb2xsaXNpb25UaW1lb3V0OiBudW1iZXI7XG4gICAgcHJpdmF0ZSBjbG9zZVRpbWVvdXQ6IG51bWJlcjtcblxuICAgIGNvbnN0cnVjdG9yKHByaXZhdGUgY2hhbmdlRGV0ZWN0b3I6IENoYW5nZURldGVjdG9yUmVmKSB7fVxuXG4gICAgcHVibGljIG5nT25DaGFuZ2VzKGNoYW5nZXM6IFNpbXBsZUNoYW5nZXMpOiB2b2lkIHtcbiAgICAgICAgaWYgKCEoY2hhbmdlc1tcInBsdWdpblwiXSAmJiBjaGFuZ2VzW1wicGx1Z2luXCJdLmN1cnJlbnRWYWx1ZSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMudW5zdWJzY3JpYmUkLm5leHQoKTtcblxuICAgICAgICB0aGlzLnBsdWdpbi5zaG93U3ViamVjdFxuICAgICAgICAgICAgLnBpcGUodGFrZVVudGlsKHRoaXMudW5zdWJzY3JpYmUkKSlcbiAgICAgICAgICAgIC5zdWJzY3JpYmUoKCkgPT4gdGhpcy5oYW5kbGVPcGVuKCkpO1xuICAgICAgICB0aGlzLnBsdWdpbi5oaWRlU3ViamVjdFxuICAgICAgICAgICAgLnBpcGUodGFrZVVudGlsKHRoaXMudW5zdWJzY3JpYmUkKSlcbiAgICAgICAgICAgIC5zdWJzY3JpYmUoKCkgPT4gdGhpcy5oYW5kbGVDbG9zZSgpKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgbmdPbkRlc3Ryb3koKTogdm9pZCB7XG4gICAgICAgIHRoaXMudW5zdWJzY3JpYmUkLm5leHQoKTtcbiAgICAgICAgdGhpcy51bnN1YnNjcmliZSQuY29tcGxldGUoKTtcbiAgICB9XG5cbiAgICBwdWJsaWMgdHJhY2tCeUZuKGluZGV4OiBudW1iZXIsIGl0ZW06IGFueSk6IHN0cmluZyB8IHVuZGVmaW5lZCB7XG4gICAgICAgIHJldHVybiBpdGVtLnZhbHVlLnNlcmllc0lkO1xuICAgIH1cblxuICAgIHByaXZhdGUgaGFuZGxlT3BlbigpIHtcbiAgICAgICAgdGhpcy5jaGFuZ2VEZXRlY3Rvci5kZXRlY3RDaGFuZ2VzKCk7XG5cbiAgICAgICAgY29uc3QgY3VycmVudFRvb2x0aXBEaXJlY3RpdmVzSW5kZXg6IHtcbiAgICAgICAgICAgIFtzZXJpZXNJZDogc3RyaW5nXTogQ2hhcnRUb29sdGlwRGlyZWN0aXZlO1xuICAgICAgICB9ID0ge307XG4gICAgICAgIHRoaXMudG9vbHRpcHMuZm9yRWFjaCgodG9vbHRpcCkgPT4ge1xuICAgICAgICAgICAgLy8gdGhpcyBpcyBob3cgd2UgaWRlbnRpZnkgd2hpY2ggc2VyaWVzIGRvZXMgdGhlIHRvb2x0aXAgYmVsb25nIHRvXG4gICAgICAgICAgICBjb25zdCBzZXJpZXNJZDogc3RyaW5nIHwgdW5kZWZpbmVkID1cbiAgICAgICAgICAgICAgICB0b29sdGlwLmVsZW1lbnRSZWYubmF0aXZlRWxlbWVudC5nZXRBdHRyaWJ1dGUoXCJzZXJpZXMtaWRcIikgPz9cbiAgICAgICAgICAgICAgICB1bmRlZmluZWQ7XG5cbiAgICAgICAgICAgIGlmICghc2VyaWVzSWQpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJTZXJpZXNJZCBpcyBub3QgZGVmaW5lZFwiKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY3VycmVudFRvb2x0aXBEaXJlY3RpdmVzSW5kZXhbc2VyaWVzSWRdID0gdG9vbHRpcDtcbiAgICAgICAgfSk7XG5cbiAgICAgICAgY29uc3QgZGlyZWN0aXZlc0NoYW5nZWQgPSAhaXNFcXVhbChcbiAgICAgICAgICAgIGN1cnJlbnRUb29sdGlwRGlyZWN0aXZlc0luZGV4LFxuICAgICAgICAgICAgdGhpcy50b29sdGlwRGlyZWN0aXZlc0luZGV4XG4gICAgICAgICk7XG4gICAgICAgIGlmICh0aGlzLmNsb3NlUGVuZGluZyB8fCBkaXJlY3RpdmVzQ2hhbmdlZCB8fCAhdGhpcy5pc09wZW4pIHtcbiAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aGlzLm9wZW5UaW1lb3V0KTtcblxuICAgICAgICAgICAgdGhpcy5vcGVuVGltZW91dCA9IHdpbmRvdy5zZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wZW5Ub29sdGlwcy5uZXh0KCk7XG4gICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuY29sbGlzaW9uVGltZW91dCk7XG5cbiAgICAgICAgICAgICAgICB0aGlzLmNvbGxpc2lvblRpbWVvdXQgPSB3aW5kb3cuc2V0VGltZW91dCgoKSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuYXZvaWRUb29sdGlwQ29sbGlzaW9ucygpO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmlzT3BlbiA9IHRydWU7XG4gICAgICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMub3BlblRvb2x0aXBzLm5leHQoKTtcbiAgICAgICAgICAgIHRoaXMuYXZvaWRUb29sdGlwQ29sbGlzaW9ucygpO1xuICAgICAgICAgICAgdGhpcy5pc09wZW4gPSB0cnVlO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcHJpdmF0ZSBoYW5kbGVDbG9zZSgpIHtcbiAgICAgICAgaWYgKHRoaXMuc2ltdWxhdGlvbikge1xuICAgICAgICAgICAgdGhpcy5zaW11bGF0aW9uLnN0b3AoKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuY2xvc2VQZW5kaW5nID0gdHJ1ZTtcbiAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuY2xvc2VUaW1lb3V0KTtcblxuICAgICAgICB0aGlzLmNsb3NlVGltZW91dCA9IHdpbmRvdy5zZXRUaW1lb3V0KCgpID0+IHtcbiAgICAgICAgICAgIHRoaXMuY2xvc2VUb29sdGlwcy5uZXh0KCk7XG4gICAgICAgICAgICB0aGlzLmNsb3NlUGVuZGluZyA9IGZhbHNlO1xuICAgICAgICAgICAgdGhpcy5pc09wZW4gPSBmYWxzZTtcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogUnVucyB0aGUgRDMgZm9yY2VDb2xsaWRlIGJhc2VkIHRvb2x0aXAgY29sbGlzaW9uIGF2b2lkYW5jZSBhbGdvcml0aG1cbiAgICAgKi9cbiAgICBwcml2YXRlIGF2b2lkVG9vbHRpcENvbGxpc2lvbnMoKSB7XG4gICAgICAgIC8vIGV4dHJhY3RlZCB0b29sdGlwIHBvc2l0aW9ucyBmcm9tIHRvb2x0aXAgZGlyZWN0aXZlc1xuICAgICAgICBjb25zdCB0b29sdGlwUG9zaXRpb25zOiB7IFtzZXJpZXNJZDogc3RyaW5nXTogSVBvc2l0aW9uIH0gPSB7fTtcblxuICAgICAgICB0aGlzLnRvb2x0aXBzLmZvckVhY2goKHRvb2x0aXApID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGVsZW1lbnQgPSB0b29sdGlwLmdldE92ZXJsYXlFbGVtZW50KCk7XG5cbiAgICAgICAgICAgIC8vIHRoaXMgaXMgaG93IHdlIGlkZW50aWZ5IHdoaWNoIHNlcmllcyBkb2VzIHRoZSB0b29sdGlwIGJlbG9uZyB0b1xuICAgICAgICAgICAgY29uc3Qgc2VyaWVzSWQ6IHN0cmluZyB8IHVuZGVmaW5lZCA9XG4gICAgICAgICAgICAgICAgdG9vbHRpcC5lbGVtZW50UmVmLm5hdGl2ZUVsZW1lbnQuZ2V0QXR0cmlidXRlKFwic2VyaWVzLWlkXCIpID8/XG4gICAgICAgICAgICAgICAgdW5kZWZpbmVkO1xuXG4gICAgICAgICAgICBpZiAoIXNlcmllc0lkKSB7XG4gICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiU2VyaWVzSWQgaXMgbm90IGRlZmluZWRcIik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMudG9vbHRpcERpcmVjdGl2ZXNJbmRleFtzZXJpZXNJZF0gPSB0b29sdGlwO1xuXG4gICAgICAgICAgICB0b29sdGlwUG9zaXRpb25zW3Nlcmllc0lkXSA9IHtcbiAgICAgICAgICAgICAgICB4OiBlbGVtZW50Lm9mZnNldExlZnQsXG4gICAgICAgICAgICAgICAgeTogZWxlbWVudC5vZmZzZXRUb3AsXG4gICAgICAgICAgICAgICAgd2lkdGg6IGVsZW1lbnQuY2xpZW50V2lkdGgsXG4gICAgICAgICAgICAgICAgaGVpZ2h0OiBlbGVtZW50LmNsaWVudEhlaWdodCxcbiAgICAgICAgICAgIH07XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIGlmIHRoZXJlIHdhcyBhIHByZXZpb3VzIHNpbXVsYXRpb24sIHRoZW4gc3RvcCBpdCBiZWZvcmUgcnVubmluZyBhIG5ldyBvbmVcbiAgICAgICAgaWYgKHRoaXMuc2ltdWxhdGlvbikge1xuICAgICAgICAgICAgdGhpcy5zaW11bGF0aW9uLnN0b3AoKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc2ltdWxhdGlvbiA9IHRoaXMuc3RhcnRTaW11bGF0aW9uKFxuICAgICAgICAgICAgdGhpcy50b29sdGlwRGlyZWN0aXZlc0luZGV4LFxuICAgICAgICAgICAgdG9vbHRpcFBvc2l0aW9uc1xuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFN0YXJ0cyB0aGUgZm9yY2Ugc2ltdWxhdGlvbiB0byBhdm9pZCB0b29sdGlwIG92ZXJsYXBcbiAgICAgKlxuICAgICAqIEBwYXJhbSB0b29sdGlwSW5kZXhcbiAgICAgKiBAcGFyYW0gdG9vbHRpcFBvc2l0aW9uc1xuICAgICAqL1xuICAgIHByaXZhdGUgc3RhcnRTaW11bGF0aW9uKFxuICAgICAgICB0b29sdGlwSW5kZXg6IHsgW3A6IHN0cmluZ106IENoYXJ0VG9vbHRpcERpcmVjdGl2ZSB9LFxuICAgICAgICB0b29sdGlwUG9zaXRpb25zOiB7IFtzZXJpZXNJZDogc3RyaW5nXTogSVBvc2l0aW9uIH1cbiAgICApIHtcbiAgICAgICAgY29uc3Qgbm9kZXMgPSBPYmplY3Qua2V5cyh0b29sdGlwUG9zaXRpb25zKS5tYXAoKHNlcmllc0lkKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBwb3NpdGlvbiA9IHRvb2x0aXBQb3NpdGlvbnNbc2VyaWVzSWRdO1xuXG4gICAgICAgICAgICBpZiAoaXNOaWwocG9zaXRpb24uaGVpZ2h0KSB8fCBpc05pbChwb3NpdGlvbi53aWR0aCkpIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJQb3NpdGlvbiBoZWlnaHQgb3Igd2lkdGggYXJlIG5vdCBkZWZpbmVkXCIpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBwcm9wczogYW55ID0ge1xuICAgICAgICAgICAgICAgIHNlcmllc0lkOiBzZXJpZXNJZCxcbiAgICAgICAgICAgICAgICB4OiBwb3NpdGlvbi54LFxuICAgICAgICAgICAgICAgIHk6IHBvc2l0aW9uLnksXG4gICAgICAgICAgICAgICAgcmFkaXVzOiB0aGlzLmlzVmVydGljYWwoKVxuICAgICAgICAgICAgICAgICAgICA/IHBvc2l0aW9uLmhlaWdodCAvIDJcbiAgICAgICAgICAgICAgICAgICAgOiBwb3NpdGlvbi53aWR0aCAvIDIsXG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgaWYgKHRoaXMuaXNWZXJ0aWNhbCgpKSB7XG4gICAgICAgICAgICAgICAgcHJvcHMuZnggPSBwb3NpdGlvbi54O1xuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBwcm9wcy5meSA9IHBvc2l0aW9uLnk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXR1cm4gcHJvcHM7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIFRPRE86IHRoZXNlIG51bWJlcnMgYXJlIG9ubHkgYmFzZWQgb24gcGxheWluZyB3aXRoIHRoZSBsaWJyYXJ5IGZvciB0aGUgd2hpbGUsIGl0J3Mgbm90IGZpbmUgdHVuZWQgYXQgYWxsXG4gICAgICAgIGNvbnN0IGNvbGxpc2lvbkZvcmNlID0gZm9yY2VDb2xsaWRlKChub2RlOiBhbnkpID0+IG5vZGUucmFkaXVzKVxuICAgICAgICAgICAgLnN0cmVuZ3RoKDAuNSlcbiAgICAgICAgICAgIC5pdGVyYXRpb25zKDIwKTtcblxuICAgICAgICBjb25zdCBzaW11bGF0aW9uID0gZm9yY2VTaW11bGF0aW9uKG5vZGVzKVxuICAgICAgICAgICAgLmFscGhhRGVjYXkoMC4zKVxuICAgICAgICAgICAgLmZvcmNlKFwiY29sbGlzaW9uRm9yY2VcIiwgY29sbGlzaW9uRm9yY2UpO1xuXG4gICAgICAgIHNpbXVsYXRpb24ub24oXCJ0aWNrXCIsICgpID0+IHtcbiAgICAgICAgICAgIGVhY2gobm9kZXMsIChub2RlOiBJVG9vbHRpcE5vZGUpID0+IHtcbiAgICAgICAgICAgICAgICBjb25zdCB0b29sdGlwID0gdG9vbHRpcEluZGV4W25vZGUuc2VyaWVzSWRdO1xuXG4gICAgICAgICAgICAgICAgc2VsZWN0KHRvb2x0aXAuZ2V0T3ZlcmxheUVsZW1lbnQoKSkuc3R5bGUoXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuaXNWZXJ0aWNhbCgpID8gXCJ0b3BcIiA6IFwibGVmdFwiLFxuICAgICAgICAgICAgICAgICAgICAodGhpcy5pc1ZlcnRpY2FsKCkgPyBub2RlLnkgOiBub2RlLngpICsgXCJweFwiXG4gICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgIH0pO1xuICAgICAgICB9KTtcblxuICAgICAgICByZXR1cm4gc2ltdWxhdGlvbjtcbiAgICB9XG5cbiAgICBwcml2YXRlIGlzVmVydGljYWwoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnBsdWdpbi5vcmllbnRhdGlvbiA9PT0gXCJyaWdodFwiO1xuICAgIH1cbn1cbiIsIjxkaXYgKm5nSWY9XCJwbHVnaW4gJiYgcGx1Z2luLmRhdGFQb2ludHNcIj5cbiAgICA8ZGl2ICpuZ0Zvcj1cImxldCBpdGVtIG9mIHBsdWdpbi5kYXRhUG9pbnRzIHwga2V5dmFsdWU7IHRyYWNrQnk6IHRyYWNrQnlGblwiPlxuICAgICAgICA8bmctdGVtcGxhdGUgI3Rvb2x0aXBUZW1wbGF0ZT5cbiAgICAgICAgICAgIDxuZy1jb250YWluZXJcbiAgICAgICAgICAgICAgICAqbmdUZW1wbGF0ZU91dGxldD1cInRlbXBsYXRlOyBjb250ZXh0OiB7IGRhdGFQb2ludDogaXRlbS52YWx1ZSB9XCJcbiAgICAgICAgICAgID48L25nLWNvbnRhaW5lcj5cbiAgICAgICAgPC9uZy10ZW1wbGF0ZT5cblxuICAgICAgICA8ZGl2XG4gICAgICAgICAgICBudWlDaGFydFRvb2x0aXBcbiAgICAgICAgICAgICpuZ0lmPVwicGx1Z2luLmRhdGFQb2ludFBvc2l0aW9uc1tpdGVtLmtleV1cIlxuICAgICAgICAgICAgW2F0dHIuc2VyaWVzLWlkXT1cIml0ZW0ua2V5XCJcbiAgICAgICAgICAgIFt0ZW1wbGF0ZV09XCJ0b29sdGlwVGVtcGxhdGVcIlxuICAgICAgICAgICAgW29wZW5SZW1vdGVDb250cm9sXT1cIm9wZW5Ub29sdGlwc1wiXG4gICAgICAgICAgICBbY2xvc2VSZW1vdGVDb250cm9sXT1cImNsb3NlVG9vbHRpcHNcIlxuICAgICAgICAgICAgW3Bvc2l0aW9uc109XCJwbHVnaW4uZGF0YVBvaW50UG9zaXRpb25zW2l0ZW0ua2V5XS5vdmVybGF5UG9zaXRpb25zXCJcbiAgICAgICAgICAgIGNsYXNzPVwiY2hhcnQtdG9vbHRpcHNfX3BvcG92ZXJcIlxuICAgICAgICAgICAgW3N0eWxlLmxlZnQucHhdPVwicGx1Z2luLmRhdGFQb2ludFBvc2l0aW9uc1tpdGVtLmtleV0ueFwiXG4gICAgICAgICAgICBbc3R5bGUudG9wLnB4XT1cInBsdWdpbi5kYXRhUG9pbnRQb3NpdGlvbnNbaXRlbS5rZXldLnlcIlxuICAgICAgICAgICAgW3N0eWxlLmhlaWdodC5weF09XCJwbHVnaW4uZGF0YVBvaW50UG9zaXRpb25zW2l0ZW0ua2V5XS5oZWlnaHRcIlxuICAgICAgICAgICAgW3N0eWxlLndpZHRoLnB4XT1cInBsdWdpbi5kYXRhUG9pbnRQb3NpdGlvbnNbaXRlbS5rZXldLndpZHRoXCJcbiAgICAgICAgPjwvZGl2PlxuICAgIDwvZGl2PlxuPC9kaXY+XG4iXX0=