@ux-aspects/ux-aspects
Version:
Open source user interface framework for building modern, responsive, mobile big data applications
167 lines • 39.1 kB
JavaScript
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, inject, Input, Output } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { filter as rxFilter, takeUntil } from 'rxjs/operators';
import { TypeaheadKeyService } from '../../typeahead/index';
import { FilterRemoveAllEvent } from '../events/filter-remove-all-event';
import { FilterService } from '../filter.service';
import * as i0 from "@angular/core";
import * as i1 from "@angular/forms";
import * as i2 from "../../icon/icon.component";
import * as i3 from "../../menu/menu/menu.component";
import * as i4 from "../../menu/menu-trigger/menu-trigger.directive";
import * as i5 from "../../menu/menu-item/menu-item.component";
import * as i6 from "../../menu/menu-divider/menu-divider.component";
import * as i7 from "../../typeahead/typeahead.component";
import * as i8 from "../../../directives/safe-inner-html/safe-inner-html.directive";
import * as i9 from "@angular/common";
import * as i10 from "./filter-typeahead-highlight.pipe";
let uniqueId = 0;
export class FilterDynamicComponent {
/** Defined the closeOnBlur state for the ux-menu trigger */
set closeOnBlur(value) {
this._closeOnBlur = coerceBooleanProperty(value);
}
get closeOnBlur() {
return this._closeOnBlur;
}
/** Specify the typeahead options */
set options(options) {
this._options = options;
}
/** Get the options with the defaults for any missing options */
get options() {
return { ...this._defaultOptions, ...this._options };
}
/** Get the user provided id or fallback to a default ID */
get filterId() {
return this.id ?? `ux-filter-dynamic-${this._uniqueId}`;
}
constructor() {
this.typeaheadKeyService = inject(TypeaheadKeyService);
this._filterService = inject(FilterService);
this._changeDetector = inject(ChangeDetectorRef);
/** The unique id is used multiple times - this is to ensure we only increment it once per instance */
this._uniqueId = uniqueId++;
/** The list of possible filter options */
this.filters = [];
/** Emit when the filter menu is closed */
this.closed = new EventEmitter();
/** Generate a unique id for the typeahead */
this.typeaheadId = `ux-filter-dynamic-typeahead-${this._uniqueId}`;
/** Store the current search query */
this.query$ = new BehaviorSubject('');
/** Indicate whether or not the typeahead should be shown */
this.showTypeahead = true;
/** Store the items that should be displayed in the typeahead */
this.typeaheadItems = [];
/** Store the open state of the typeahead */
this.typeaheadOpen = false;
/** The default options */
this._defaultOptions = { placeholder: '', minCharacters: 3, maxResults: Infinity };
/** Store the user specified typeahead options */
this._options = { ...this._defaultOptions };
/** Unsubscribe from all subscriptions */
this._onDestroy = new Subject();
this._closeOnBlur = false;
// listen for remove all events in which case we should deselect event initial filters
this._filterService.events$.pipe(rxFilter(event => event instanceof FilterRemoveAllEvent), takeUntil(this._onDestroy))
.subscribe(() => this.removeFilter());
// ensure that the current selected filter is still selected when the active filters change
this._filterService.filters$.pipe(takeUntil(this._onDestroy)).subscribe(filters => {
if (this.selected && filters.indexOf(this.selected) === -1) {
this.removeFilter();
}
});
}
/** Set up the initial conditions */
ngOnInit() {
// The initially selected item should be set the the specified initial item
this.selected = this.initial;
// watch for changes to the selected filters
this._filterService.filters$.pipe(takeUntil(this._onDestroy)).subscribe(filters => {
filters.forEach(filter => {
if (this.filters.indexOf(filter) !== -1) {
this.selected = filter;
}
});
this._changeDetector.markForCheck();
});
// get the items to be displayed in the typeahead
this.typeaheadItems = this.getItems();
// hide the typeahead if the number of filters always visible equals or exceeds the
// total number of filters as there would be no additional filters to display in the typeahead
const shouldHideTypeahead = this.options?.maxIndividualItems + 1 >= this.filters.length;
if (shouldHideTypeahead) {
this.showTypeahead = false;
}
}
/** Cleanup all subscriptions */
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
/** Get the items to display in the typeahead based on the search query */
getItems() {
const query = this.query$.value.toLowerCase();
return this.filters.filter(item => item !== this.initial && item.name.toLowerCase().indexOf(query) !== -1)
.map(item => item.name)
.slice(0, this._options.maxResults);
}
/** When the dropdown is closed clear the query */
onClose() {
this.query$.next('');
}
/** If a filter needs removed, and is not the initial filter then remove it */
removeFilter() {
// check if the filter we want to remove is the initial filter
if (this.selected !== this.initial) {
this._filterService.remove(this.selected);
this.selected = this.initial;
}
// clear the search query
this.query$.next('');
this._changeDetector.markForCheck();
}
/** Select a specific filter */
selectFilter(filter) {
// clear any current filters
this.removeFilter();
// store the newly selected filter
this.selected = filter;
// store the filter in the service
this._filterService.add(this.selected);
}
/** Update typeahead items and visibility */
updateTypeahead(query) {
this.typeaheadOpen = query.length >= this._options.minCharacters;
this.typeaheadItems = this.getItems();
}
/** Select a filter from a typeahead item */
select(event) {
// find the filter with the matching name
const filter = this.filters.find(_filter => _filter.name === event.option);
if (filter) {
this.selectFilter(filter);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: FilterDynamicComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.12", type: FilterDynamicComponent, selector: "ux-filter-dynamic", inputs: { id: "id", filters: "filters", initial: "initial", closeOnBlur: "closeOnBlur", options: "options" }, outputs: { closed: "closed" }, ngImport: i0, template: "<div class=\"btn-group ux-dynamic-filter\">\n <button\n type=\"button\"\n [class.active]=\"selected !== initial\"\n class=\"filter-dropdown btn dropdown-toggle\"\n [id]=\"filterId + '-trigger'\"\n [uxMenuTriggerFor]=\"menu\"\n [closeOnBlur]=\"closeOnBlur\"\n (closed)=\"closed.emit()\"\n #trigger=\"ux-menu-trigger\"\n >\n {{ selected?.group }}\n @if (selected !== initial) {\n <span class=\"filter-header\"> ({{ selected?.name }}) </span>\n }\n <ux-icon name=\"down\"></ux-icon>\n </button>\n\n <ux-menu #menu menuClass=\"ux-dynamic-filter-menu\" (closed)=\"onClose()\">\n <!-- Initial Option -->\n @if (showTypeahead) {\n <button\n uxMenuItem\n [id]=\"initial.id || filterId + '-initial-option'\"\n (click)=\"removeFilter()\"\n (keydown.enter)=\"removeFilter()\"\n >\n <ux-icon name=\"checkmark\" [style.visibility]=\"initial === selected ? 'visible' : 'hidden'\">\n </ux-icon>\n <span class=\"filter-dropdown-title\">\n {{ initial.name }}\n </span>\n </button>\n }\n\n <!-- Selected Options -->\n @if (selected !== initial && showTypeahead) {\n <button\n uxMenuItem\n [id]=\"selected.id || filterId + '-selection'\"\n >\n <ux-icon name=\"checkmark\"></ux-icon>\n <span class=\"filter-dropdown-title\">{{ selected.name }}</span>\n </button>\n }\n\n @if (showTypeahead) {\n <ux-menu-divider></ux-menu-divider>\n }\n\n @if (showTypeahead) {\n <div class=\"typeahead-box\" role=\"none\">\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"options?.placeholder\"\n [attr.aria-activedescendant]=\"highlightedElement?.id\"\n [attr.aria-controls]=\"typeaheadId\"\n aria-autocomplete=\"list\"\n aria-multiline=\"false\"\n [ngModel]=\"query$ | async\"\n (ngModelChange)=\"query$.next($event); updateTypeahead($event)\"\n (keydown)=\"typeaheadKeyService.handleKey($event, typeahead); $event.stopPropagation()\"\n (keydown.enter)=\"$event.preventDefault()\"\n (blur)=\"typeaheadOpen = false\"\n (click)=\"$event.stopPropagation()\"\n />\n <ux-typeahead\n #typeahead\n [id]=\"typeaheadId\"\n [(open)]=\"typeaheadOpen\"\n display=\"title\"\n [selectOnEnter]=\"true\"\n [options]=\"typeaheadItems\"\n [optionTemplate]=\"filterOptionTemplate\"\n (optionSelected)=\"select($event); trigger.closeMenu($event.origin)\"\n (highlightedElementChange)=\"highlightedElement = $event\"\n >\n </ux-typeahead>\n </div>\n }\n\n @if (!showTypeahead) {\n @for (filter of filters; track filter; let index = $index) {\n <button\n type=\"button\"\n uxMenuItem\n [id]=\"filter.id || filterId + '-item-' + index\"\n (click)=\"selectFilter(filter); trigger.closeMenu('mouse')\"\n (keydown.enter)=\"selectFilter(filter); trigger.closeMenu('keyboard')\"\n >\n <ux-icon name=\"checkmark\" [style.visibility]=\"filter === selected ? 'visible' : 'hidden'\">\n </ux-icon>\n <span class=\"filter-dropdown-title\">{{ filter.name }}</span>\n </button>\n }\n }\n </ux-menu>\n </div>\n\n <ng-template #filterOptionTemplate let-option=\"option\" let-api=\"api\">\n <span\n [attr.aria-label]=\"option\"\n [uxSafeInnerHtml]=\"option | filterTypeaheadHighlight : (query$ | async)\"\n ></span>\n </ng-template>\n", dependencies: [{ kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i2.IconComponent, selector: "ux-icon", inputs: ["name", "size", "rotate", "flipHorizontal", "flipVertical"] }, { kind: "component", type: i3.MenuComponent, selector: "ux-menu", inputs: ["id", "placement", "alignment", "animate", "menuClass"], outputs: ["opening", "opened", "closing", "closed"] }, { kind: "directive", type: i4.MenuTriggerDirective, selector: "[uxMenuTriggerFor]", inputs: ["uxMenuTriggerFor", "disabled", "uxMenuParent", "closeOnBlur"], outputs: ["closed"], exportAs: ["ux-menu-trigger"] }, { kind: "component", type: i5.MenuItemComponent, selector: "[uxMenuItem]", inputs: ["disabled", "closeOnSelect", "role"], outputs: ["activate"] }, { kind: "component", type: i6.MenuDividerComponent, selector: "ux-menu-divider" }, { kind: "component", type: i7.TypeaheadComponent, selector: "ux-typeahead", inputs: ["id", "options", "filter", "open", "display", "key", "disabledOptions", "dropDirection", "maxHeight", "multiselectable", "ariaLabel", "openOnFilterChange", "pageSize", "selectFirst", "selectOnEnter", "loading", "loadingTemplate", "optionTemplate", "noOptionsTemplate", "active", "recentOptions", "recentOptionsMaxCount", "recentOptionsHeadingTemplate", "optionsHeadingTemplate"], outputs: ["openChange", "optionSelected", "highlightedChange", "highlightedElementChange", "recentOptionsChange"] }, { kind: "directive", type: i8.SafeInnerHtmlDirective, selector: "[uxSafeInnerHtml]", inputs: ["uxSafeInnerHtml"] }, { kind: "pipe", type: i9.AsyncPipe, name: "async" }, { kind: "pipe", type: i10.FilterTypeaheadHighlight, name: "filterTypeaheadHighlight" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: FilterDynamicComponent, decorators: [{
type: Component,
args: [{ selector: 'ux-filter-dynamic', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"btn-group ux-dynamic-filter\">\n <button\n type=\"button\"\n [class.active]=\"selected !== initial\"\n class=\"filter-dropdown btn dropdown-toggle\"\n [id]=\"filterId + '-trigger'\"\n [uxMenuTriggerFor]=\"menu\"\n [closeOnBlur]=\"closeOnBlur\"\n (closed)=\"closed.emit()\"\n #trigger=\"ux-menu-trigger\"\n >\n {{ selected?.group }}\n @if (selected !== initial) {\n <span class=\"filter-header\"> ({{ selected?.name }}) </span>\n }\n <ux-icon name=\"down\"></ux-icon>\n </button>\n\n <ux-menu #menu menuClass=\"ux-dynamic-filter-menu\" (closed)=\"onClose()\">\n <!-- Initial Option -->\n @if (showTypeahead) {\n <button\n uxMenuItem\n [id]=\"initial.id || filterId + '-initial-option'\"\n (click)=\"removeFilter()\"\n (keydown.enter)=\"removeFilter()\"\n >\n <ux-icon name=\"checkmark\" [style.visibility]=\"initial === selected ? 'visible' : 'hidden'\">\n </ux-icon>\n <span class=\"filter-dropdown-title\">\n {{ initial.name }}\n </span>\n </button>\n }\n\n <!-- Selected Options -->\n @if (selected !== initial && showTypeahead) {\n <button\n uxMenuItem\n [id]=\"selected.id || filterId + '-selection'\"\n >\n <ux-icon name=\"checkmark\"></ux-icon>\n <span class=\"filter-dropdown-title\">{{ selected.name }}</span>\n </button>\n }\n\n @if (showTypeahead) {\n <ux-menu-divider></ux-menu-divider>\n }\n\n @if (showTypeahead) {\n <div class=\"typeahead-box\" role=\"none\">\n <input\n type=\"text\"\n class=\"form-control\"\n [placeholder]=\"options?.placeholder\"\n [attr.aria-activedescendant]=\"highlightedElement?.id\"\n [attr.aria-controls]=\"typeaheadId\"\n aria-autocomplete=\"list\"\n aria-multiline=\"false\"\n [ngModel]=\"query$ | async\"\n (ngModelChange)=\"query$.next($event); updateTypeahead($event)\"\n (keydown)=\"typeaheadKeyService.handleKey($event, typeahead); $event.stopPropagation()\"\n (keydown.enter)=\"$event.preventDefault()\"\n (blur)=\"typeaheadOpen = false\"\n (click)=\"$event.stopPropagation()\"\n />\n <ux-typeahead\n #typeahead\n [id]=\"typeaheadId\"\n [(open)]=\"typeaheadOpen\"\n display=\"title\"\n [selectOnEnter]=\"true\"\n [options]=\"typeaheadItems\"\n [optionTemplate]=\"filterOptionTemplate\"\n (optionSelected)=\"select($event); trigger.closeMenu($event.origin)\"\n (highlightedElementChange)=\"highlightedElement = $event\"\n >\n </ux-typeahead>\n </div>\n }\n\n @if (!showTypeahead) {\n @for (filter of filters; track filter; let index = $index) {\n <button\n type=\"button\"\n uxMenuItem\n [id]=\"filter.id || filterId + '-item-' + index\"\n (click)=\"selectFilter(filter); trigger.closeMenu('mouse')\"\n (keydown.enter)=\"selectFilter(filter); trigger.closeMenu('keyboard')\"\n >\n <ux-icon name=\"checkmark\" [style.visibility]=\"filter === selected ? 'visible' : 'hidden'\">\n </ux-icon>\n <span class=\"filter-dropdown-title\">{{ filter.name }}</span>\n </button>\n }\n }\n </ux-menu>\n </div>\n\n <ng-template #filterOptionTemplate let-option=\"option\" let-api=\"api\">\n <span\n [attr.aria-label]=\"option\"\n [uxSafeInnerHtml]=\"option | filterTypeaheadHighlight : (query$ | async)\"\n ></span>\n </ng-template>\n" }]
}], ctorParameters: () => [], propDecorators: { id: [{
type: Input
}], filters: [{
type: Input
}], initial: [{
type: Input
}], closeOnBlur: [{
type: Input
}], options: [{
type: Input
}], closed: [{
type: Output
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsdGVyLWR5bmFtaWMuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvZmlsdGVycy9maWx0ZXItZHluYW1pYy9maWx0ZXItZHluYW1pYy5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvY29tcG9uZW50cy9maWx0ZXJzL2ZpbHRlci1keW5hbWljL2ZpbHRlci1keW5hbWljLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBZ0IscUJBQXFCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUM1RSxPQUFPLEVBQUUsdUJBQXVCLEVBQUUsaUJBQWlCLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFxQixNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDOUksT0FBTyxFQUFFLGVBQWUsRUFBRSxPQUFPLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDaEQsT0FBTyxFQUFFLE1BQU0sSUFBSSxRQUFRLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDL0QsT0FBTyxFQUFFLG1CQUFtQixFQUF3QixNQUFNLHVCQUF1QixDQUFDO0FBQ2xGLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLG1DQUFtQyxDQUFDO0FBQ3pFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQzs7Ozs7Ozs7Ozs7O0FBSWxELElBQUksUUFBUSxHQUFHLENBQUMsQ0FBQztBQU9qQixNQUFNLE9BQU8sc0JBQXNCO0lBbUIvQiw0REFBNEQ7SUFDNUQsSUFBYSxXQUFXLENBQUMsS0FBYztRQUNuQyxJQUFJLENBQUMsWUFBWSxHQUFHLHFCQUFxQixDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3JELENBQUM7SUFFRCxJQUFJLFdBQVc7UUFDWCxPQUFPLElBQUksQ0FBQyxZQUFZLENBQUM7SUFDN0IsQ0FBQztJ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LHlDQUF5QztRQUN6QyxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEtBQUssS0FBSyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBRTNFLElBQUksTUFBTSxFQUFFLENBQUM7WUFDVCxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzlCLENBQUM7SUFDTCxDQUFDOytHQXJMUSxzQkFBc0I7bUdBQXRCLHNCQUFzQixzTUNqQm5DLGlwSEEwR0E7OzRGRHpGYSxzQkFBc0I7a0JBTGxDLFNBQVM7K0JBQ0ksbUJBQW1CLG1CQUVaLHVCQUF1QixDQUFDLE1BQU07d0RBYXRDLEVBQUU7c0JBQVYsS0FBSztnQkFHRyxPQUFPO3NCQUFmLEtBQUs7Z0JBR0csT0FBTztzQkFBZixLQUFLO2dCQUdPLFdBQVc7c0JBQXZCLEtBQUs7Z0JBU08sT0FBTztzQkFBbkIsS0FBSztnQkFVYSxNQUFNO3NCQUF4QixNQUFNIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgQm9vbGVhbklucHV0LCBjb2VyY2VCb29sZWFuUHJvcGVydHkgfSBmcm9tICdAYW5ndWxhci9jZGsvY29lcmNpb24nO1xuaW1wb3J0IHsgQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3ksIENoYW5nZURldGVjdG9yUmVmLCBDb21wb25lbnQsIEV2ZW50RW1pdHRlciwgaW5qZWN0LCBJbnB1dCwgT25EZXN0cm95LCBPbkluaXQsIE91dHB1dCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgQmVoYXZpb3JTdWJqZWN0LCBTdWJqZWN0IH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBmaWx0ZXIgYXMgcnhGaWx0ZXIsIHRha2VVbnRpbCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7IFR5cGVhaGVhZEtleVNlcnZpY2UsIFR5cGVhaGVhZE9wdGlvbkV2ZW50IH0gZnJvbSAnLi4vLi4vdHlwZWFoZWFkL2luZGV4JztcbmltcG9ydCB7IEZpbHRlclJlbW92ZUFsbEV2ZW50IH0gZnJvbSAnLi4vZXZlbnRzL2ZpbHRlci1yZW1vdmUtYWxsLWV2ZW50JztcbmltcG9ydCB7IEZpbHRlclNlcnZpY2UgfSBmcm9tICcuLi9maWx0ZXIuc2VydmljZSc7XG5pbXBvcnQgeyBGaWx0ZXJEeW5hbWljTGlzdENvbmZpZyB9IGZyb20gJy4uL2ludGVyZmFjZXMvZmlsdGVyLWR5bmFtaWMtbGlzdC1jb25maWcuaW50ZXJmYWNlJztcbmltcG9ydCB7IEZpbHRlciB9IGZyb20gJy4uL2ludGVyZmFjZXMvZmlsdGVyLmludGVyZmFjZSc7XG5cbmxldCB1bmlxdWVJZCA9IDA7XG5cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAndXgtZmlsdGVyLWR5bmFtaWMnLFxuICAgIHRlbXBsYXRlVXJsOiAnLi9maWx0ZXItZHluYW1pYy5jb21wb25lbnQuaHRtbCcsXG4gICAgY2hhbmdlRGV0ZWN0aW9uOiBDaGFuZ2VEZXRlY3Rpb25TdHJhdGVneS5PblB1c2hcbn0pXG5leHBvcnQgY2xhc3MgRmlsdGVyRHluYW1pY0NvbXBvbmVudCBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95IHtcbiAgICByZWFkb25seSB0eXBlYWhlYWRLZXlTZXJ2aWNlID0gaW5qZWN0KFR5cGVhaGVhZEtleVNlcnZpY2UpO1xuXG4gICAgcHJpdmF0ZSByZWFkb25seSBfZmlsdGVyU2VydmljZSA9IGluamVjdChGaWx0ZXJTZXJ2aWNlKTtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX2NoYW5nZURldGVjdG9yID0gaW5qZWN0KENoYW5nZURldGVjdG9yUmVmKTtcblxuICAgIC8qKiBUaGUgdW5pcXVlIGlkIGlzIHVzZWQgbXVsdGlwbGUgdGltZXMgLSB0aGlzIGlzIHRvIGVuc3VyZSB3ZSBvbmx5IGluY3JlbWVudCBpdCBvbmNlIHBlciBpbnN0YW5jZSAqL1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX3VuaXF1ZUlkOiBudW1iZXIgPSB1bmlxdWVJZCsrO1xuXG4gICAgLyoqIERlZmluZSB0aGUgaW5wdXQgZm9yIHRoZSBjb21wb25lbnQgKi9cbiAgICBASW5wdXQoKSBpZDogc3RyaW5nO1xuXG4gICAgLyoqIFRoZSBsaXN0IG9mIHBvc3NpYmxlIGZpbHRlciBvcHRpb25zICovXG4gICAgQElucHV0KCkgZmlsdGVyczogRmlsdGVyW10gPSBbXTtcblxuICAgIC8qKiBTcGVjaWZ5IGlmIHRoZXJlIHNob3VsZCBiZSBhbiBpbml0aWFsbHkgc2VsZWN0ZWQgZmlsdGVyICovXG4gICAgQElucHV0KCkgaW5pdGlhbDogRmlsdGVyO1xuXG4gICAgLyoqIERlZmluZWQgdGhlIGNsb3NlT25CbHVyIHN0YXRlIGZvciB0aGUgdXgtbWVudSB0cmlnZ2VyICovXG4gICAgQElucHV0KCkgc2V0IGNsb3NlT25CbHVyKHZhbHVlOiBib29sZWFuKSB7XG4gICAgICAgIHRoaXMuX2Nsb3NlT25CbHVyID0gY29lcmNlQm9vbGVhblByb3BlcnR5KHZhbHVlKTtcbiAgICB9XG5cbiAgICBnZXQgY2xvc2VPbkJsdXIoKTogYm9vbGVhbiB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jbG9zZU9uQmx1cjtcbiAgICB9XG5cbiAgICAvKiogU3BlY2lmeSB0aGUgdHlwZWFoZWFkIG9wdGlvbnMgKi9cbiAgICBASW5wdXQoKSBzZXQgb3B0aW9ucyhvcHRpb25zOiBGaWx0ZXJEeW5hbWljTGlzdENvbmZpZykge1xuICAgICAgICB0aGlzLl9vcHRpb25zID0gb3B0aW9ucztcbiAgICB9XG5cbiAgICAvKiogR2V0IHRoZSBvcHRpb25zIHdpdGggdGhlIGRlZmF1bHRzIGZvciBhbnkgbWlzc2luZyBvcHRpb25zICovXG4gICAgZ2V0IG9wdGlvbnMoKTogRmlsdGVyRHluYW1pY0xpc3RDb25maWcge1xuICAgICAgICByZXR1cm4geyAuLi50aGlzLl9kZWZhdWx0T3B0aW9ucywgLi4udGhpcy5fb3B0aW9ucyB9O1xuICAgIH1cblxuICAgIC8qKiBFbWl0IHdoZW4gdGhlIGZpbHRlciBtZW51IGlzIGNsb3NlZCAqL1xuICAgIEBPdXRwdXQoKSByZWFkb25seSBjbG9zZWQgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG5cbiAgICAvKiogR2VuZXJhdGUgYSB1bmlxdWUgaWQgZm9yIHRoZSB0eXBlYWhlYWQgKi9cbiAgICB0eXBlYWhlYWRJZDogc3RyaW5nID0gYHV4LWZpbHRlci1keW5hbWljLXR5cGVhaGVhZC0keyB0aGlzLl91bmlxdWVJZCB9YDtcblxuICAgIC8qKiBTdG9yZSB0aGUgY3VycmVudCBzZWFyY2ggcXVlcnkgKi9cbiAgICBxdWVyeSQgPSBuZXcgQmVoYXZpb3JTdWJqZWN0PHN0cmluZz4oJycpO1xuXG4gICAgLyoqIFN0b3JlIHRoZSBzZWxlY3RlZCBmaWx0ZXIgKi9cbiAgICBzZWxlY3RlZDogRmlsdGVyO1xuXG4gICAgLyoqIEluZGljYXRlIHdoZXRoZXIgb3Igbm90IHRoZSB0eXBlYWhlYWQgc2hvdWxkIGJlIHNob3duICovXG4gICAgc2hvd1R5cGVhaGVhZDogYm9vbGVhbiA9IHRydWU7XG5cbiAgICAvKiogU3RvcmUgdGhlIGl0ZW1zIHRoYXQgc2hvdWxkIGJlIGRpc3BsYXllZCBpbiB0aGUgdHlwZWFoZWFkICovXG4gICAgdHlwZWFoZWFkSXRlbXM6IHN0cmluZ1tdID0gW107XG5cbiAgICAvKiogU3RvcmUgdGhlIGN1cnJlbnRseSBoaWdobGlnaHRlZCBlbGVtZW50ICovXG4gICAgaGlnaGxpZ2h0ZWRFbGVtZW50OiBIVE1MRWxlbWVudDtcblxuICAgIC8qKiBTdG9yZSB0aGUgb3BlbiBzdGF0ZSBvZiB0aGUgdHlwZWFoZWFkICovXG4gICAgdHlwZWFoZWFkT3BlbjogYm9vbGVhbiA9IGZhbHNlO1xuXG4gICAgLyoqIEdldCB0aGUgdXNlciBwcm92aWRlZCBpZCBvciBmYWxsYmFjayB0byBhIGRlZmF1bHQgSUQgKi9cbiAgICBnZXQgZmlsdGVySWQoKTogc3RyaW5nIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuaWQgPz8gYHV4LWZpbHRlci1keW5hbWljLSR7IHRoaXMuX3VuaXF1ZUlkIH1gO1xuICAgIH1cblxuICAgIC8qKiBUaGUgZGVmYXVsdCBvcHRpb25zICovXG4gICAgcHJpdmF0ZSByZWFkb25seSBfZGVmYXVsdE9wdGlvbnM6IEZpbHRlckR5bmFtaWNMaXN0Q29uZmlnID0geyBwbGFjZWhvbGRlcjogJycsIG1pbkNoYXJhY3RlcnM6IDMsIG1heFJlc3VsdHM6IEluZmluaXR5IH07XG5cbiAgICAvKiogU3RvcmUgdGhlIHVzZXIgc3BlY2lmaWVkIHR5cGVhaGVhZCBvcHRpb25zICovXG4gICAgcHJpdmF0ZSBfb3B0aW9uczogRmlsdGVyRHluYW1pY0xpc3RDb25maWcgPSB7IC4uLnRoaXMuX2RlZmF1bHRPcHRpb25zIH07XG5cbiAgICAvKiogVW5zdWJzY3JpYmUgZnJvbSBhbGwgc3Vic2NyaXB0aW9ucyAqL1xuICAgIHByaXZhdGUgcmVhZG9ubHkgX29uRGVzdHJveSA9IG5ldyBTdWJqZWN0PHZvaWQ+KCk7XG4gICAgcHJpdmF0ZSBfY2xvc2VPbkJsdXI6IGJvb2xlYW4gPSBmYWxzZTtcblxuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICAvLyBsaXN0ZW4gZm9yIHJlbW92ZSBhbGwgZXZlbnRzIGluIHdoaWNoIGNhc2Ugd2Ugc2hvdWxkIGRlc2VsZWN0IGV2ZW50IGluaXRpYWwgZmlsdGVyc1xuICAgICAgICB0aGlzLl9maWx0ZXJTZXJ2aWNlLmV2ZW50cyQucGlwZShyeEZpbHRlcihldmVudCA9PiBldmVudCBpbnN0YW5jZW9mIEZpbHRlclJlbW92ZUFsbEV2ZW50KSwgdGFrZVVudGlsKHRoaXMuX29uRGVzdHJveSkpXG4gICAgICAgICAgICAuc3Vic2NyaWJlKCgpID0+IHRoaXMucmVtb3ZlRmlsdGVyKCkpO1xuXG4gICAgICAgIC8vIGVuc3VyZSB0aGF0IHRoZSBjdXJyZW50IHNlbGVjdGVkIGZpbHRlciBpcyBzdGlsbCBzZWxlY3RlZCB3aGVuIHRoZSBhY3RpdmUgZmlsdGVycyBjaGFuZ2VcbiAgICAgICAgdGhpcy5fZmlsdGVyU2VydmljZS5maWx0ZXJzJC5waXBlKHRha2VVbnRpbCh0aGlzLl9vbkRlc3Ryb3kpKS5zdWJzY3JpYmUoZmlsdGVycyA9PiB7XG4gICAgICAgICAgICBpZiAodGhpcy5zZWxlY3RlZCAmJiBmaWx0ZXJzLmluZGV4T2YodGhpcy5zZWxlY3RlZCkgPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5yZW1vdmVGaWx0ZXIoKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgLyoqIFNldCB1cCB0aGUgaW5pdGlhbCBjb25kaXRpb25zICovXG4gICAgbmdPbkluaXQoKTogdm9pZCB7XG5cbiAgICAgICAgLy8gVGhlIGluaXRpYWxseSBzZWxlY3RlZCBpdGVtIHNob3VsZCBiZSBzZXQgdGhlIHRoZSBzcGVjaWZpZWQgaW5pdGlhbCBpdGVtXG4gICAgICAgIHRoaXMuc2VsZWN0ZWQgPSB0aGlzLmluaXRpYWw7XG5cbiAgICAgICAgLy8gd2F0Y2ggZm9yIGNoYW5nZXMgdG8gdGhlIHNlbGVjdGVkIGZpbHRlcnNcbiAgICAgICAgdGhpcy5fZmlsdGVyU2VydmljZS5maWx0ZXJzJC5waXBlKHRha2VVbnRpbCh0aGlzLl9vbkRlc3Ryb3kpKS5zdWJzY3JpYmUoZmlsdGVycyA9PiB7XG4gICAgICAgICAgICBmaWx0ZXJzLmZvckVhY2goZmlsdGVyID0+IHtcbiAgICAgICAgICAgICAgICBpZiAodGhpcy5maWx0ZXJzLmluZGV4T2YoZmlsdGVyKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5zZWxlY3RlZCA9IGZpbHRlcjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgdGhpcy5fY2hhbmdlRGV0ZWN0b3IubWFya0ZvckNoZWNrKCk7XG4gICAgICAgIH0pO1xuXG4gICAgICAgIC8vIGdldCB0aGUgaXRlbXMgdG8gYmUgZGlzcGxheWVkIGluIHRoZSB0eXBlYWhlYWRcbiAgICAgICAgdGhpcy50eXBlYWhlYWRJdGVtcyA9IHRoaXMuZ2V0SXRlbXMoKTtcblxuICAgICAgICAvLyBoaWRlIHRoZSB0eXBlYWhlYWQgaWYgdGhlIG51bWJlciBvZiBmaWx0ZXJzIGFsd2F5cyB2aXNpYmxlIGVxdWFscyBvciBleGNlZWRzIHRoZVxuICAgICAgICAvLyB0b3RhbCBudW1iZXIgb2YgZmlsdGVycyBhcyB0aGVyZSB3b3VsZCBiZSBubyBhZGRpdGlvbmFsIGZpbHRlcnMgdG8gZGlzcGxheSBpbiB0aGUgdHlwZWFoZWFkXG4gICAgICAgIGNvbnN0IHNob3VsZEhpZGVUeXBlYWhlYWQgPSB0aGlzLm9wdGlvbnM/Lm1heEluZGl2aWR1YWxJdGVtcyArIDEgPj0gdGhpcy5maWx0ZXJzLmxlbmd0aDtcblxuICAgICAgICBpZiAoc2hvdWxkSGlkZVR5cGVhaGVhZCkge1xuICAgICAgICAgICAgdGhpcy5zaG93VHlwZWFoZWFkID0gZmFsc2U7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvKiogQ2xlYW51cCBhbGwgc3Vic2NyaXB0aW9ucyAqL1xuICAgIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgICAgICB0aGlzLl9vbkRlc3Ryb3kubmV4dCgpO1xuICAgICAgICB0aGlzLl9vbkRlc3Ryb3kuY29tcGxldGUoKTtcbiAgICB9XG5cbiAgICAvKiogR2V0IHRoZSBpdGVtcyB0byBkaXNwbGF5IGluIHRoZSB0eXBlYWhlYWQgYmFzZWQgb24gdGhlIHNlYXJjaCBxdWVyeSAqL1xuICAgIGdldEl0ZW1zKCk6IHN0cmluZ1tdIHtcbiAgICAgICAgY29uc3QgcXVlcnkgPSB0aGlzLnF1ZXJ5JC52YWx1ZS50b0xvd2VyQ2FzZSgpO1xuXG4gICAgICAgIHJldHVybiB0aGlzLmZpbHRlcnMuZmlsdGVyKGl0ZW0gPT4gaXRlbSAhPT0gdGhpcy5pbml0aWFsICYmIGl0ZW0ubmFtZS50b0xvd2VyQ2FzZSgpLmluZGV4T2YocXVlcnkpICE9PSAtMSlcbiAgICAgICAgICAgIC5tYXAoaXRlbSA9PiBpdGVtLm5hbWUpXG4gICAgICAgICAgICAuc2xpY2UoMCwgdGhpcy5fb3B0aW9ucy5tYXhSZXN1bHRzKTtcbiAgICB9XG5cbiAgICAvKiogV2hlbiB0aGUgZHJvcGRvd24gaXMgY2xvc2VkIGNsZWFyIHRoZSBxdWVyeSAqL1xuICAgIG9uQ2xvc2UoKTogdm9pZCB7XG4gICAgICAgIHRoaXMucXVlcnkkLm5leHQoJycpO1xuICAgIH1cblxuICAgIC8qKiBJZiBhIGZpbHRlciBuZWVkcyByZW1vdmVkLCBhbmQgaXMgbm90IHRoZSBpbml0aWFsIGZpbHRlciB0aGVuIHJlbW92ZSBpdCAqL1xuICAgIHJlbW92ZUZpbHRlcigpOiB2b2lkIHtcblxuICAgICAgICAvLyBjaGVjayBpZiB0aGUgZmlsdGVyIHdlIHdhbnQgdG8gcmVtb3ZlIGlzIHRoZSBpbml0aWFsIGZpbHRlclxuICAgICAgICBpZiAodGhpcy5zZWxlY3RlZCAhPT0gdGhpcy5pbml0aWFsKSB7XG4gICAgICAgICAgICB0aGlzLl9maWx0ZXJTZXJ2aWNlLnJlbW92ZSh0aGlzLnNlbGVjdGVkKTtcbiAgICAgICAgICAgIHRoaXMuc2VsZWN0ZWQgPSB0aGlzLmluaXRpYWw7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBjbGVhciB0aGUgc2VhcmNoIHF1ZXJ5XG4gICAgICAgIHRoaXMucXVlcnkkLm5leHQoJycpO1xuXG4gICAgICAgIHRoaXMuX2NoYW5nZURldGVjdG9yLm1hcmtGb3JDaGVjaygpO1xuICAgIH1cblxuICAgIC8qKiBTZWxlY3QgYSBzcGVjaWZpYyBmaWx0ZXIgKi9cbiAgICBzZWxlY3RGaWx0ZXIoZmlsdGVyOiBGaWx0ZXIpOiB2b2lkIHtcblxuICAgICAgICAvLyBjbGVhciBhbnkgY3VycmVudCBmaWx0ZXJzXG4gICAgICAgIHRoaXMucmVtb3ZlRmlsdGVyKCk7XG5cbiAgICAgICAgLy8gc3RvcmUgdGhlIG5ld2x5IHNlbGVjdGVkIGZpbHRlclxuICAgICAgICB0aGlzLnNlbGVjdGVkID0gZmlsdGVyO1xuXG4gICAgICAgIC8vIHN0b3JlIHRoZSBmaWx0ZXIgaW4gdGhlIHNlcnZpY2VcbiAgICAgICAgdGhpcy5fZmlsdGVyU2VydmljZS5hZGQodGhpcy5zZWxlY3RlZCk7XG4gICAgfVxuXG4gICAgLyoqIFVwZGF0ZSB0eXBlYWhlYWQgaXRlbXMgYW5kIHZpc2liaWxpdHkgKi9cbiAgICB1cGRhdGVUeXBlYWhlYWQocXVlcnk6IHN0cmluZyk6IHZvaWQge1xuICAgICAgICB0aGlzLnR5cGVhaGVhZE9wZW4gPSBxdWVyeS5sZW5ndGggPj0gdGhpcy5fb3B0aW9ucy5taW5DaGFyYWN0ZXJzO1xuICAgICAgICB0aGlzLnR5cGVhaGVhZEl0ZW1zID0gdGhpcy5nZXRJdGVtcygpO1xuICAgIH1cblxuICAgIC8qKiBTZWxlY3QgYSBmaWx0ZXIgZnJvbSBhIHR5cGVhaGVhZCBpdGVtICovXG4gICAgc2VsZWN0KGV2ZW50OiBUeXBlYWhlYWRPcHRpb25FdmVudCk6IHZvaWQge1xuICAgICAgICAvLyBmaW5kIHRoZSBmaWx0ZXIgd2l0aCB0aGUgbWF0Y2hpbmcgbmFtZVxuICAgICAgICBjb25zdCBmaWx0ZXIgPSB0aGlzLmZpbHRlcnMuZmluZChfZmlsdGVyID0+IF9maWx0ZXIubmFtZSA9PT0gZXZlbnQub3B0aW9uKTtcblxuICAgICAgICBpZiAoZmlsdGVyKSB7XG4gICAgICAgICAgICB0aGlzLnNlbGVjdEZpbHRlcihmaWx0ZXIpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgc3RhdGljIG5nQWNjZXB0SW5wdXRUeXBlX2Nsb3NlT25CbHVyOiBCb29sZWFuSW5wdXQ7XG59XG4iLCI8ZGl2IGNsYXNzPVwiYnRuLWdyb3VwIHV4LWR5bmFtaWMtZmlsdGVyXCI+XG4gIDxidXR0b25cbiAgICB0eXBlPVwiYnV0dG9uXCJcbiAgICBbY2xhc3MuYWN0aXZlXT1cInNlbGVjdGVkICE9PSBpbml0aWFsXCJcbiAgICBjbGFzcz1cImZpbHRlci1kcm9wZG93biBidG4gZHJvcGRvd24tdG9nZ2xlXCJcbiAgICBbaWRdPVwiZmlsdGVySWQgKyAnLXRyaWdnZXInXCJcbiAgICBbdXhNZW51VHJpZ2dlckZvcl09XCJtZW51XCJcbiAgICBbY2xvc2VPbkJsdXJdPVwiY2xvc2VPbkJsdXJcIlxuICAgIChjbG9zZWQpPVwiY2xvc2VkLmVtaXQoKVwiXG4gICAgI3RyaWdnZXI9XCJ1eC1tZW51LXRyaWdnZXJcIlxuICAgID5cbiAgICB7eyBzZWxlY3RlZD8uZ3JvdXAgfX1cbiAgICBAaWYgKHNlbGVjdGVkICE9PSBpbml0aWFsKSB7XG4gICAgICA8c3BhbiBjbGFzcz1cImZpbHRlci1oZWFkZXJcIj4gKHt7IHNlbGVjdGVkPy5uYW1lIH19KSA8L3NwYW4+XG4gICAgfVxuICAgIDx1eC1pY29uIG5hbWU9XCJkb3duXCI+PC91eC1pY29uPlxuICA8L2J1dHRvbj5cblxuICA8dXgtbWVudSAjbWVudSBtZW51Q2xhc3M9XCJ1eC1keW5hbWljLWZpbHRlci1tZW51XCIgKGNsb3NlZCk9XCJvbkNsb3NlKClcIj5cbiAgICA8IS0tIEluaXRpYWwgT3B0aW9uIC0tPlxuICAgIEBpZiAoc2hvd1R5cGVhaGVhZCkge1xuICAgICAgPGJ1dHRvblxuICAgICAgICB1eE1lbnVJdGVtXG4gICAgICAgIFtpZF09XCJpbml0aWFsLmlkIHx8IGZpbHRlcklkICsgJy1pbml0aWFsLW9wdGlvbidcIlxuICAgICAgICAoY2xpY2spPVwicmVtb3ZlRmlsdGVyKClcIlxuICAgICAgICAoa2V5ZG93bi5lbnRlcik9XCJyZW1vdmVGaWx0ZXIoKVwiXG4gICAgICAgID5cbiAgICAgICAgPHV4LWljb24gbmFtZT1cImNoZWNrbWFya1wiIFtzdHlsZS52aXNpYmlsaXR5XT1cImluaXRpYWwgPT09IHNlbGVjdGVkID8gJ3Zpc2libGUnIDogJ2hpZGRlbidcIj5cbiAgICAgICAgPC91eC1pY29uPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZpbHRlci1kcm9wZG93bi10aXRsZVwiPlxuICAgICAgICAgIHt7IGluaXRpYWwubmFtZSB9fVxuICAgICAgICA8L3NwYW4+XG4gICAgICA8L2J1dHRvbj5cbiAgICB9XG5cbiAgICA8IS0tIFNlbGVjdGVkIE9wdGlvbnMgLS0+XG4gICAgQGlmIChzZWxlY3RlZCAhPT0gaW5pdGlhbCAmJiBzaG93VHlwZWFoZWFkKSB7XG4gICAgICA8YnV0dG9uXG4gICAgICAgIHV4TWVudUl0ZW1cbiAgICAgICAgW2lkXT1cInNlbGVjdGVkLmlkIHx8IGZpbHRlcklkICsgJy1zZWxlY3Rpb24nXCJcbiAgICAgICAgPlxuICAgICAgICA8dXgtaWNvbiBuYW1lPVwiY2hlY2ttYXJrXCI+PC91eC1pY29uPlxuICAgICAgICA8c3BhbiBjbGFzcz1cImZpbHRlci1kcm9wZG93bi10aXRsZVwiPnt7IHNlbGVjdGVkLm5hbWUgfX08L3NwYW4+XG4gICAgICA8L2J1dHRvbj5cbiAgICB9XG5cbiAgICBAaWYgKHNob3dUeXBlYWhlYWQpIHtcbiAgICAgIDx1eC1tZW51LWRpdmlkZXI+PC91eC1tZW51LWRpdmlkZXI+XG4gICAgfVxuXG4gICAgQGlmIChzaG93VHlwZWFoZWFkKSB7XG4gICAgICA8ZGl2IGNsYXNzPVwidHlwZWFoZWFkLWJveFwiIHJvbGU9XCJub25lXCI+XG4gICAgICAgIDxpbnB1dFxuICAgICAgICAgIHR5cGU9XCJ0ZXh0XCJcbiAgICAgICAgICBjbGFzcz1cImZvcm0tY29udHJvbFwiXG4gICAgICAgICAgW3BsYWNlaG9sZGVyXT1cIm9wdGlvbnM/LnBsYWNlaG9sZGVyXCJcbiAgICAgICAgICBbYXR0ci5hcmlhLWFjdGl2ZWRlc2NlbmRhbnRdPVwiaGlnaGxpZ2h0ZWRFbGVtZW50Py5pZFwiXG4gICAgICAgICAgW2F0dHIuYXJpYS1jb250cm9sc109XCJ0eXBlYWhlYWRJZFwiXG4gICAgICAgICAgYXJpYS1hdXRvY29tcGxldGU9XCJsaXN0XCJcbiAgICAgICAgICBhcmlhLW11bHRpbGluZT1cImZhbHNlXCJcbiAgICAgICAgICBbbmdNb2RlbF09XCJxdWVyeSQgfCBhc3luY1wiXG4gICAgICAgICAgKG5nTW9kZWxDaGFuZ2UpPVwicXVlcnkkLm5leHQoJGV2ZW50KTsgdXBkYXRlVHlwZWFoZWFkKCRldmVudClcIlxuICAgICAgICAgIChrZXlkb3duKT1cInR5cGVhaGVhZEtleVNlcnZpY2UuaGFuZGxlS2V5KCRldmVudCwgdHlwZWFoZWFkKTsgJGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpXCJcbiAgICAgICAgICAoa2V5ZG93bi5lbnRlcik9XCIkZXZlbnQucHJldmVudERlZmF1bHQoKVwiXG4gICAgICAgICAgKGJsdXIpPVwidHlwZWFoZWFkT3BlbiA9IGZhbHNlXCJcbiAgICAgICAgICAoY2xpY2spPVwiJGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpXCJcbiAgICAgICAgICAvPlxuICAgICAgICAgIDx1eC10eXBlYWhlYWRcbiAgICAgICAgICAgICN0eXBlYWhlYWRcbiAgICAgICAgICAgIFtpZF09XCJ0eXBlYWhlYWRJZFwiXG4gICAgICAgICAgICBbKG9wZW4pXT1cInR5cGVhaGVhZE9wZW5cIlxuICAgICAgICAgICAgZGlzcGxheT1cInRpdGxlXCJcbiAgICAgICAgICAgIFtzZWxlY3RPbkVudGVyXT1cInRydWVcIlxuICAgICAgICAgICAgW29wdGlvbnNdPVwidHlwZWFoZWFkSXRlbXNcIlxuICAgICAgICAgICAgW29wdGlvblRlbXBsYXRlXT1cImZpbHRlck9wdGlvblRlbXBsYXRlXCJcbiAgICAgICAgICAgIChvcHRpb25TZWxlY3RlZCk9XCJzZWxlY3QoJGV2ZW50KTsgdHJpZ2dlci5jbG9zZU1lbnUoJGV2ZW50Lm9yaWdpbilcIlxuICAgICAgICAgICAgKGhpZ2hsaWdodGVkRWxlbWVudENoYW5nZSk9XCJoaWdobGlnaHRlZEVsZW1lbnQgPSAkZXZlbnRcIlxuICAgICAgICAgICAgPlxuICAgICAgICAgIDwvdXgtdHlwZWFoZWFkPlxuICAgICAgICA8L2Rpdj5cbiAgICAgIH1cblxuICAgICAgQGlmICghc2hvd1R5cGVhaGVhZCkge1xuICAgICAgICBAZm9yIChmaWx0ZXIgb2YgZmlsdGVyczsgdHJhY2sgZmlsdGVyOyBsZXQgaW5kZXggPSAkaW5kZXgpIHtcbiAgICAgICAgICA8YnV0dG9uXG4gICAgICAgICAgICB0eXBlPVwiYnV0dG9uXCJcbiAgICAgICAgICAgIHV4TWVudUl0ZW1cbiAgICAgICAgICAgIFtpZF09XCJmaWx0ZXIuaWQgfHwgZmlsdGVySWQgKyAnLWl0ZW0tJyArIGluZGV4XCJcbiAgICAgICAgICAgIChjbGljayk9XCJzZWxlY3RGaWx0ZXIoZmlsdGVyKTsgdHJpZ2dlci5jbG9zZU1lbnUoJ21vdXNlJylcIlxuICAgICAgICAgICAgKGtleWRvd24uZW50ZXIpPVwic2VsZWN0RmlsdGVyKGZpbHRlcik7IHRyaWdnZXIuY2xvc2VNZW51KCdrZXlib2FyZCcpXCJcbiAgICAgICAgICAgID5cbiAgICAgICAgICAgIDx1eC1pY29uIG5hbWU9XCJjaGVja21hcmtcIiBbc3R5bGUudmlzaWJpbGl0eV09XCJmaWx0ZXIgPT09IHNlbGVjdGVkID8gJ3Zpc2libGUnIDogJ2hpZGRlbidcIj5cbiAgICAgICAgICAgIDwvdXgtaWNvbj5cbiAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwiZmlsdGVyLWRyb3Bkb3duLXRpdGxlXCI+e3sgZmlsdGVyLm5hbWUgfX08L3NwYW4+XG4gICAgICAgICAgPC9idXR0b24+XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICA8L3V4LW1lbnU+XG4gIDwvZGl2PlxuXG4gIDxuZy10ZW1wbGF0ZSAjZmlsdGVyT3B0aW9uVGVtcGxhdGUgbGV0LW9wdGlvbj1cIm9wdGlvblwiIGxldC1hcGk9XCJhcGlcIj5cbiAgICA8c3BhblxuICAgICAgW2F0dHIuYXJpYS1sYWJlbF09XCJvcHRpb25cIlxuICAgICAgW3V4U2FmZUlubmVySHRtbF09XCJvcHRpb24gfCBmaWx0ZXJUeXBlYWhlYWRIaWdobGlnaHQgOiAocXVlcnkkIHwgYXN5bmMpXCJcbiAgICA+PC9zcGFuPlxuICA8L25nLXRlbXBsYXRlPlxuIl19