@ux-aspects/ux-aspects
Version:
Open source user interface framework for building modern, responsive, mobile big data applications
216 lines • 41 kB
JavaScript
import { Component, EventEmitter, HostBinding, HostListener, Input, Output, ViewChild } from '@angular/core';
import { BehaviorSubject, Subject, combineLatest } from 'rxjs';
import { pairwise, takeUntil } from 'rxjs/operators';
import { TabbableListDirective } from '../../directives/accessibility/index';
import { ColorPickerColor } from './color-picker-color';
import * as i0 from "@angular/core";
import * as i1 from "../../directives/accessibility/focus-indicator/default-focus-indicator.directive";
import * as i2 from "../../directives/accessibility/focus-indicator/focus-indicator.directive";
import * as i3 from "../../directives/accessibility/tabbable-list/tabbable-list.directive";
import * as i4 from "../../directives/accessibility/tabbable-list/tabbable-list-item.directive";
import * as i5 from "../../directives/accessibility/contrast-ratio/color-contrast.directive";
import * as i6 from "@angular/forms";
import * as i7 from "../tooltip/tooltip.directive";
import * as i8 from "../icon/icon.component";
import * as i9 from "@angular/common";
// Values corresponding to stylesheet
const BUTTON_MARGIN = 8;
const BUTTON_WIDTHS = {
'sm': 26,
'md': 32,
'lg': 40
};
let uniqueId = 0;
export class ColorPickerComponent {
constructor() {
this.id = `ux-color-picker-${uniqueId++}`;
/** The style of the color swatch buttons. */
this.buttonStyle = 'circle';
/** Whether to show tooltips above the color swatch buttons. These contain the color name if provided; otherwise the color hex/RGBA value. */
this.showTooltips = false;
/** Whether to show the hex/RGBA input panel. */
this.showInput = false;
/** The default input mode to display in the input panel. The user can switch modes using the toggle button. */
this.inputMode = 'hex';
/** Defines a function that returns an aria-label for ColorPickerColor. */
this.colorAriaLabel = this.getColorAriaLabel;
/** Defines a function that returns an aria-label for the button that switches input modes. */
this.switchModeAriaLabel = this.getSwitchModeAriaLabel;
/** Define a function that returns an aria-label for the input control. */
this.inputAriaLabel = this.getInputAriaLabel;
/** Emitted when the user changes the selected color, either by clicking a color swatch button, or entering a valid color value into the input panel text field. */
this.selectedChange = new EventEmitter();
/** Emitted when the user changes the colour input mode */
this.inputModeChange = new EventEmitter();
/** Emitted when the user presses enter in the input panel text field. This can be used to commit a color change and/or close a popup. */
this.inputSubmit = new EventEmitter();
this.cssWidth = 'auto';
this.colors = [];
this.selected$ = new BehaviorSubject(null);
this.columns$ = new BehaviorSubject(-1);
this.buttonSize$ = new BehaviorSubject('md');
this.inputPatterns = {
'hex': /^#(?:[\da-fA-F]{3}){1,2}$/,
'rgba': /^(?:rgb\(\d{1,3},\s*\d{1,3},\s*\d{1,3}\))|(?:rgba\(\d{1,3},\s*\d{1,3},\s*\d{1,3},\s*\d(\.\d+)?\))$/
};
this._onDestroy = new Subject();
}
/**
* The collection of colors to display in the color swatch.
*
* Colors can be specified either as a string, which is the hex or RGBA value of the color; or as a `ColorPickerColor` object,
* which allows a name to be associated with the color. See below for details of the `ColorPickerColor` class.
* This property is either a one-dimensional or two-dimensional array. If a two-dimensional array is provided,
* the colors will be split into rows, providing more control over the appearance of the swatch.
*/
set inputColors(colors) {
let normalizedColors;
// If it's a 1d array, convert it to 2d
if (colors.length === 0 || !Array.isArray(colors[0])) {
normalizedColors = [colors];
}
else {
normalizedColors = colors;
}
// Convert any string colors to ColorPickerColor
this.colors = normalizedColors.map(row => {
return row.map(color => color instanceof ColorPickerColor ? color : new ColorPickerColor(color, color));
});
}
/**
* The currently selected color. If this is one of the `colors` in the colors collection, it will be visually
* highlighted in the swatch. It will also be shown in the input panel, if enabled (see showInput).
* Note that this will always be a `ColorPickerColor` object, even if plain strings are provided to the colors property.
* See below for details of the `ColorPickerColor` class.
*/
set selected(selected) {
this.selected$.next(selected);
}
/**
* The number of columns to display in the color swatch. Set this to -1 if the width should be specified by a stylesheet
* instead, e.g. to provide a responsive layout.
*/
set columns(columns) {
this.columns$.next(columns);
}
/** The size of the color swatch buttons. Three size variants are currently supported. */
set buttonSize(buttonSize) {
this.buttonSize$.next(buttonSize);
}
ngOnInit() {
// Skip emitting the initial selectedChange
this.selected$.pipe(pairwise(), takeUntil(this._onDestroy)).subscribe(([prev, curr]) => {
if (prev) {
this.selectedChange.emit(curr);
}
});
// Set the width based on column count and button size
combineLatest([this.columns$, this.buttonSize$])
.pipe(takeUntil(this._onDestroy))
.subscribe(([columns, buttonSize]) => {
if (columns > 0) {
const w = columns * (BUTTON_WIDTHS[buttonSize] + (2 * BUTTON_MARGIN));
this.cssWidth = `${w}px`;
}
else {
this.cssWidth = 'auto';
}
});
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
onFocus() {
// Forward focus to the color grid
this.tabbableList.focusTabbableItem();
}
updateColorValue(input, mode) {
if (this.inputPatterns[mode].test(input)) {
this.selected$.next(new ColorPickerColor('Custom', input, mode));
}
}
toggleColorEntryType() {
// update the input mode
this.inputMode = (this.inputMode === 'hex') ? 'rgba' : 'hex';
// emit the new input mode
this.inputModeChange.emit(this.inputMode);
// get the current color value if there is one
const color = this.selected$.value;
// if there is no selected color property then skip
if (color) {
// get the new color value
const newColor = this.inputMode === 'hex' ? color.hex : color.rgba;
// update the selected color value based on the input mode
this.updateColorValue(newColor, this.inputMode);
// forcibly update the validation status of ngModel to prevent any
// incorrect error states in the underlying form control
// (running change detection will not suffice)
this.inputFormControl.control.setValue(newColor);
}
}
getColorAriaLabel(color) {
return `Select color ${color.name}`;
}
getSwitchModeAriaLabel(mode) {
return `Switch input mode to ${mode === 'hex' ? 'RGBA' : 'hex'}`;
}
getInputAriaLabel(mode) {
return `Edit ${mode} color value`;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: ColorPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.12", type: ColorPickerComponent, selector: "ux-color-picker", inputs: { id: "id", inputColors: ["colors", "inputColors"], selected: "selected", columns: "columns", buttonStyle: "buttonStyle", buttonSize: "buttonSize", showTooltips: "showTooltips", showInput: "showInput", inputMode: "inputMode", colorAriaLabel: "colorAriaLabel", switchModeAriaLabel: "switchModeAriaLabel", inputAriaLabel: "inputAriaLabel" }, outputs: { selectedChange: "selectedChange", inputModeChange: "inputModeChange", inputSubmit: "inputSubmit" }, host: { attributes: { "tabindex": "0" }, listeners: { "focus": "onFocus($event)" }, properties: { "attr.id": "this.id", "style.width": "this.cssWidth" } }, viewQueries: [{ propertyName: "inputFormControl", first: true, predicate: ["inputField"], descendants: true }, { propertyName: "tabbableList", first: true, predicate: TabbableListDirective, descendants: true }], exportAs: ["ux-color-picker"], ngImport: i0, template: "<div class=\"ux-color-picker\" uxTabbableList direction=\"horizontal\">\n <div class=\"ux-color-picker-swatch\">\n @for (row of colors; track row) {\n <div class=\"ux-color-picker-swatch-row\">\n @for (color of row; track color) {\n <div\n #colorPickerColor\n class=\"ux-color-picker-color\"\n [class.ux-small]=\"(buttonSize$ | async) === 'sm'\"\n [class.ux-large]=\"(buttonSize$ | async) === 'lg'\"\n [class.ux-circle]=\"buttonStyle === 'circle'\"\n [class.ux-selected]=\"color === (selected$ | async)\">\n <button type=\"button\"\n uxFocusIndicator\n [attr.aria-label]=\"colorAriaLabel(color)\"\n [attr.aria-selected]=\"color === (selected$ | async)\"\n class=\"btn btn-icon\"\n [uxColorContrast]=\"color.hex\"\n [style.background-color]=\"color.rgba\"\n (click)=\"selected$.next(color)\"\n uxTabbableListItem\n [uxTooltip]=\"color.name\"\n [tooltipDisabled]=\"!showTooltips\">\n <ux-icon name=\"checkmark\"></ux-icon>\n </button>\n </div>\n }\n </div>\n }\n </div>\n\n @if (showInput) {\n <div class=\"ux-color-picker-input-panel\">\n <div class=\"ux-color-picker-input-header\">\n <div class=\"ux-color-picker-preview\"\n [style.background-color]=\"(selected$ | async).rgba\"\n [class.ux-circle]=\"buttonStyle === 'circle'\">\n </div>\n @if (inputMode === 'hex') {\n <label attr.for=\"{{ id }}-input-field\">HEX</label>\n }\n @if (inputMode === 'rgba') {\n <label attr.for=\"{{ id }}-input-field\">RGBA</label>\n }\n <button type=\"button\"\n [attr.aria-label]=\"switchModeAriaLabel(inputMode)\"\n class=\"btn btn-link btn-icon button-secondary ux-color-picker-input-toggle\"\n (click)=\"toggleColorEntryType(); $event.stopPropagation()\">\n <ux-icon name=\"chevron-right\"></ux-icon>\n </button>\n </div>\n <div class=\"ux-color-picker-input\"\n [class.has-error]=\"inputField.errors\"\n [class.has-feedback]=\"inputField.errors\">\n <input type=\"text\"\n attr.id=\"{{id}}-input-field\"\n [attr.aria-description]=\"inputAriaLabel(inputMode)\"\n class=\"form-control\"\n #inputField=\"ngModel\"\n [ngModel]=\"(selected$ | async)[inputMode]\"\n (ngModelChange)=\"updateColorValue($event, inputMode)\"\n [pattern]=\"inputPatterns[inputMode].source\"\n (keyup.enter)=\"inputSubmit.emit()\">\n <ux-icon class=\"form-control-feedback\" name=\"alert\"></ux-icon>\n </div>\n </div>\n }\n </div>\n", dependencies: [{ kind: "directive", type: i1.DefaultFocusIndicatorDirective, selector: ".btn:not([uxFocusIndicator]):not([uxMenuNavigationToggle]):not([uxMenuTriggerFor]), a[href]:not([uxFocusIndicator]):not([uxMenuNavigationToggle]):not([uxMenuTriggerFor])" }, { kind: "directive", type: i2.FocusIndicatorDirective, selector: "[uxFocusIndicator]", inputs: ["checkChildren", "mouseFocusIndicator", "touchFocusIndicator", "keyboardFocusIndicator", "programmaticFocusIndicator"], outputs: ["indicator"], exportAs: ["ux-focus-indicator"] }, { kind: "directive", type: i3.TabbableListDirective, selector: "[uxTabbableList]", inputs: ["direction", "wrap", "focusOnShow", "returnFocus", "hierarchy", "allowAltModifier", "allowCtrlModifier", "allowBoundaryKeys"], exportAs: ["ux-tabbable-list"] }, { kind: "directive", type: i4.TabbableListItemDirective, selector: "[uxTabbableListItem]", inputs: ["parent", "rank", "disabled", "expanded", "key"], outputs: ["expandedChange", "activated"], exportAs: ["ux-tabbable-list-item"] }, { kind: "directive", type: i5.ColorContrastDirective, selector: "[uxColorContrast]", inputs: ["uxColorContrast", "lightColor", "darkColor"] }, { kind: "directive", type: i6.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: i6.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i6.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i6.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i7.TooltipDirective, selector: "[uxTooltip]", inputs: ["uxTooltip", "tooltipDisabled", "tooltipClass", "tooltipRole", "tooltipContext", "tooltipDelay", "isOpen", "placement", "fallbackPlacement", "alignment", "showTriggers", "hideTriggers"], outputs: ["shown", "hidden", "isOpenChange"], exportAs: ["ux-tooltip"] }, { kind: "component", type: i8.IconComponent, selector: "ux-icon", inputs: ["name", "size", "rotate", "flipHorizontal", "flipVertical"] }, { kind: "pipe", type: i9.AsyncPipe, name: "async" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: ColorPickerComponent, decorators: [{
type: Component,
args: [{ selector: 'ux-color-picker', exportAs: 'ux-color-picker', host: {
'tabindex': '0'
}, template: "<div class=\"ux-color-picker\" uxTabbableList direction=\"horizontal\">\n <div class=\"ux-color-picker-swatch\">\n @for (row of colors; track row) {\n <div class=\"ux-color-picker-swatch-row\">\n @for (color of row; track color) {\n <div\n #colorPickerColor\n class=\"ux-color-picker-color\"\n [class.ux-small]=\"(buttonSize$ | async) === 'sm'\"\n [class.ux-large]=\"(buttonSize$ | async) === 'lg'\"\n [class.ux-circle]=\"buttonStyle === 'circle'\"\n [class.ux-selected]=\"color === (selected$ | async)\">\n <button type=\"button\"\n uxFocusIndicator\n [attr.aria-label]=\"colorAriaLabel(color)\"\n [attr.aria-selected]=\"color === (selected$ | async)\"\n class=\"btn btn-icon\"\n [uxColorContrast]=\"color.hex\"\n [style.background-color]=\"color.rgba\"\n (click)=\"selected$.next(color)\"\n uxTabbableListItem\n [uxTooltip]=\"color.name\"\n [tooltipDisabled]=\"!showTooltips\">\n <ux-icon name=\"checkmark\"></ux-icon>\n </button>\n </div>\n }\n </div>\n }\n </div>\n\n @if (showInput) {\n <div class=\"ux-color-picker-input-panel\">\n <div class=\"ux-color-picker-input-header\">\n <div class=\"ux-color-picker-preview\"\n [style.background-color]=\"(selected$ | async).rgba\"\n [class.ux-circle]=\"buttonStyle === 'circle'\">\n </div>\n @if (inputMode === 'hex') {\n <label attr.for=\"{{ id }}-input-field\">HEX</label>\n }\n @if (inputMode === 'rgba') {\n <label attr.for=\"{{ id }}-input-field\">RGBA</label>\n }\n <button type=\"button\"\n [attr.aria-label]=\"switchModeAriaLabel(inputMode)\"\n class=\"btn btn-link btn-icon button-secondary ux-color-picker-input-toggle\"\n (click)=\"toggleColorEntryType(); $event.stopPropagation()\">\n <ux-icon name=\"chevron-right\"></ux-icon>\n </button>\n </div>\n <div class=\"ux-color-picker-input\"\n [class.has-error]=\"inputField.errors\"\n [class.has-feedback]=\"inputField.errors\">\n <input type=\"text\"\n attr.id=\"{{id}}-input-field\"\n [attr.aria-description]=\"inputAriaLabel(inputMode)\"\n class=\"form-control\"\n #inputField=\"ngModel\"\n [ngModel]=\"(selected$ | async)[inputMode]\"\n (ngModelChange)=\"updateColorValue($event, inputMode)\"\n [pattern]=\"inputPatterns[inputMode].source\"\n (keyup.enter)=\"inputSubmit.emit()\">\n <ux-icon class=\"form-control-feedback\" name=\"alert\"></ux-icon>\n </div>\n </div>\n }\n </div>\n" }]
}], propDecorators: { id: [{
type: Input
}, {
type: HostBinding,
args: ['attr.id']
}], inputColors: [{
type: Input,
args: ['colors']
}], selected: [{
type: Input
}], columns: [{
type: Input
}], buttonStyle: [{
type: Input
}], buttonSize: [{
type: Input
}], showTooltips: [{
type: Input
}], showInput: [{
type: Input
}], inputMode: [{
type: Input
}], colorAriaLabel: [{
type: Input
}], switchModeAriaLabel: [{
type: Input
}], inputAriaLabel: [{
type: Input
}], selectedChange: [{
type: Output
}], inputModeChange: [{
type: Output
}], inputSubmit: [{
type: Output
}], cssWidth: [{
type: HostBinding,
args: ['style.width']
}], inputFormControl: [{
type: ViewChild,
args: ['inputField', { static: false }]
}], tabbableList: [{
type: ViewChild,
args: [TabbableListDirective]
}], onFocus: [{
type: HostListener,
args: ['focus', ['$event']]
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29sb3ItcGlja2VyLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9jb21wb25lbnRzL2NvbG9yLXBpY2tlci9jb2xvci1waWNrZXIuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvY29sb3ItcGlja2VyL2NvbG9yLXBpY2tlci5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLFlBQVksRUFBRSxXQUFXLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBcUIsTUFBTSxFQUFFLFNBQVMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUVoSSxPQUFPLEVBQUUsZUFBZSxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDL0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNyRCxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxzQ0FBc0MsQ0FBQztBQUM3RSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQzs7Ozs7Ozs7Ozs7QUFHeEQscUNBQXFDO0FBQ3JDLE1BQU0sYUFBYSxHQUFHLENBQUMsQ0FBQztBQUN4QixNQUFNLGFBQWEsR0FBRztJQUNsQixJQUFJLEVBQUUsRUFBRTtJQUNSLElBQUksRUFBRSxFQUFFO0lBQ1IsSUFBSSxFQUFFLEVBQUU7Q0FDWCxDQUFDO0FBRUYsSUFBSSxRQUFRLEdBQUcsQ0FBQyxDQUFDO0FBVWpCLE1BQU0sT0FBTyxvQkFBb0I7SUFSakM7UUFZSSxPQUFFLEdBQVcsbUJBQW1CLFFBQVEsRUFBRSxFQUFFLENBQUM7UUFnRDdDLDZDQUE2QztRQUU3QyxnQkFBVyxHQUEyQixRQUFRLENBQUM7UUFRL0MsNklBQTZJO1FBRTdJLGlCQUFZLEdBQVksS0FBSyxDQUFDO1FBRTlCLGdEQUFnRDtRQUVoRCxjQUFT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b0VBOzs0RkQxQ2Esb0JBQW9CO2tCQVJoQyxTQUFTOytCQUNJLGlCQUFpQixZQUNqQixpQkFBaUIsUUFFckI7d0JBQ0YsVUFBVSxFQUFFLEdBQUc7cUJBQ2xCOzhCQU1ELEVBQUU7c0JBRkQsS0FBSzs7c0JBQ0wsV0FBVzt1QkFBQyxTQUFTO2dCQVlsQixXQUFXO3NCQURkLEtBQUs7dUJBQUMsUUFBUTtnQkF5QlgsUUFBUTtzQkFEWCxLQUFLO2dCQVVGLE9BQU87c0JBRFYsS0FBSztnQkFPTixXQUFXO3NCQURWLEtBQUs7Z0JBS0YsVUFBVTtzQkFEYixLQUFLO2dCQU9OLFlBQVk7c0JBRFgsS0FBSztnQkFLTixTQUFTO3NCQURSLEtBQUs7Z0JBS04sU0FBUztzQkFEUixLQUFLO2dCQUtOLGNBQWM7c0JBRGIsS0FBSztnQkFLTixtQkFBbUI7c0JBRGxCLEtBQUs7Z0JBS04sY0FBYztzQkFEYixLQUFLO2dCQUtOLGNBQWM7c0JBRGIsTUFBTTtnQkFLUCxlQUFlO3NCQURkLE1BQU07Z0JBS1AsV0FBVztzQkFEVixNQUFNO2dCQUlQLFFBQVE7c0JBRFAsV0FBVzt1QkFBQyxhQUFhO2dCQWFrQixnQkFBZ0I7c0JBQTNELFNBQVM7dUJBQUMsWUFBWSxFQUFFLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRTtnQkFDUixZQUFZO3NCQUE3QyxTQUFTO3VCQUFDLHFCQUFxQjtnQkFnQ2hDLE9BQU87c0JBRE4sWUFBWTt1QkFBQyxPQUFPLEVBQUUsQ0FBQyxRQUFRLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIEV2ZW50RW1pdHRlciwgSG9zdEJpbmRpbmcsIEhvc3RMaXN0ZW5lciwgSW5wdXQsIE9uRGVzdHJveSwgT25Jbml0LCBPdXRwdXQsIFZpZXdDaGlsZCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgTmdNb2RlbCB9IGZyb20gJ0Bhbmd1bGFyL2Zvcm1zJztcbmltcG9ydCB7IEJlaGF2aW9yU3ViamVjdCwgU3ViamVjdCwgY29tYmluZUxhdGVzdCB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHsgcGFpcndpc2UsIHRha2VVbnRpbCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7IFRhYmJhYmxlTGlzdERpcmVjdGl2ZSB9IGZyb20gJy4uLy4uL2RpcmVjdGl2ZXMvYWNjZXNzaWJpbGl0eS9pbmRleCc7XG5pbXBvcnQgeyBDb2xvclBpY2tlckNvbG9yIH0gZnJvbSAnLi9jb2xvci1waWNrZXItY29sb3InO1xuaW1wb3J0IHsgQ29sb3JQaWNrZXJCdXR0b25TaXplLCBDb2xvclBpY2tlckJ1dHRvblN0eWxlLCBDb2xvclBpY2tlcklucHV0Q29sb3JzLCBDb2xvclBpY2tlcklucHV0TW9kZSB9IGZyb20gJy4vY29sb3ItcGlja2VyLnR5cGUnO1xuXG4vLyBWYWx1ZXMgY29ycmVzcG9uZGluZyB0byBzdHlsZXNoZWV0XG5jb25zdCBCVVRUT05fTUFSR0lOID0gODtcbmNvbnN0IEJVVFRPTl9XSURUSFMgPSB7XG4gICAgJ3NtJzogMjYsXG4gICAgJ21kJzogMzIsXG4gICAgJ2xnJzogNDBcbn07XG5cbmxldCB1bmlxdWVJZCA9IDA7XG5cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAndXgtY29sb3ItcGlja2VyJyxcbiAgICBleHBvcnRBczogJ3V4LWNvbG9yLXBpY2tlcicsXG4gICAgdGVtcGxhdGVVcmw6ICdjb2xvci1waWNrZXIuY29tcG9uZW50Lmh0bWwnLFxuICAgIGhvc3Q6IHtcbiAgICAgICAgJ3RhYmluZGV4JzogJzAnXG4gICAgfVxufSlcbmV4cG9ydCBjbGFzcyBDb2xvclBpY2tlckNvbXBvbmVudCBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95IHtcblxuICAgIEBJbnB1dCgpXG4gICAgQEhvc3RCaW5kaW5nKCdhdHRyLmlkJylcbiAgICBpZDogc3RyaW5nID0gYHV4LWNvbG9yLXBpY2tlci0ke3VuaXF1ZUlkKyt9YDtcblxuICAgIC8qKlxuICAgICAqIFRoZSBjb2xsZWN0aW9uIG9mIGNvbG9ycyB0byBkaXNwbGF5IGluIHRoZSBjb2xvciBzd2F0Y2guXG4gICAgICpcbiAgICAgKiBDb2xvcnMgY2FuIGJlIHNwZWNpZmllZCBlaXRoZXIgYXMgYSBzdHJpbmcsIHdoaWNoIGlzIHRoZSBoZXggb3IgUkdCQSB2YWx1ZSBvZiB0aGUgY29sb3I7IG9yIGFzIGEgYENvbG9yUGlja2VyQ29sb3JgIG9iamVjdCxcbiAgICAgKiB3aGljaCBhbGxvd3MgYSBuYW1lIHRvIGJlIGFzc29jaWF0ZWQgd2l0aCB0aGUgY29sb3IuIFNlZSBiZWxvdyBmb3IgZGV0YWlscyBvZiB0aGUgYENvbG9yUGlja2VyQ29sb3JgIGNsYXNzLlxuICAgICAqIFRoaXMgcHJvcGVydHkgaXMgZWl0aGVyIGEgb25lLWRpbWVuc2lvbmFsIG9yIHR3by1kaW1lbnNpb25hbCBhcnJheS4gSWYgYSB0d28tZGltZW5zaW9uYWwgYXJyYXkgaXMgcHJvdmlkZWQsXG4gICAgICogdGhlIGNvbG9ycyB3aWxsIGJlIHNwbGl0IGludG8gcm93cywgcHJvdmlkaW5nIG1vcmUgY29udHJvbCBvdmVyIHRoZSBhcHBlYXJhbmNlIG9mIHRoZSBzd2F0Y2guXG4gICAgICovXG4gICAgQElucHV0KCdjb2xvcnMnKVxuICAgIHNldCBpbnB1dENvbG9ycyhjb2xvcnM6IENvbG9yUGlja2VySW5wdXRDb2xvcnNbXSB8IENvbG9yUGlja2VySW5wdXRDb2xvcnNbXVtdKSB7XG5cbiAgICAgICAgbGV0IG5vcm1hbGl6ZWRDb2xvcnM6IENvbG9yUGlja2VySW5wdXRDb2xvcnNbXVtdO1xuXG4gICAgICAgIC8vIElmIGl0J3MgYSAxZCBhcnJheSwgY29udmVydCBpdCB0byAyZFxuICAgICAgICBpZiAoY29sb3JzLmxlbmd0aCA9PT0gMCB8fCAhQXJyYXkuaXNBcnJheShjb2xvcnNbMF0pKSB7XG4gICAgICAgICAgICBub3JtYWxpemVkQ29sb3JzID0gW2NvbG9ycyBhcyBDb2xvclBpY2tlcklucHV0Q29sb3JzW11dO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgbm9ybWFsaXplZENvbG9ycyA9IGNvbG9ycyBhcyBDb2xvclBpY2tlcklucHV0Q29sb3JzW11bXTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIENvbnZlcnQgYW55IHN0cmluZyBjb2xvcnMgdG8gQ29sb3JQaWNrZXJDb2xvclxuICAgICAgICB0aGlzLmNvbG9ycyA9IG5vcm1hbGl6ZWRDb2xvcnMubWFwKHJvdyA9PiB7XG4gICAgICAgICAgICByZXR1cm4gcm93Lm1hcChjb2xvciA9PiBjb2xvciBpbnN0YW5jZW9mIENvbG9yUGlja2VyQ29sb3IgPyBjb2xvciA6IG5ldyBDb2xvclBpY2tlckNvbG9yKGNvbG9yLCBjb2xvcikpO1xuICAgICAgICB9KTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBUaGUgY3VycmVudGx5IHNlbGVjdGVkIGNvbG9yLiBJZiB0aGlzIGlzIG9uZSBvZiB0aGUgYGNvbG9yc2AgaW4gdGhlIGNvbG9ycyBjb2xsZWN0aW9uLCBpdCB3aWxsIGJlIHZpc3VhbGx5XG4gICAgICogaGlnaGxpZ2h0ZWQgaW4gdGhlIHN3YXRjaC4gSXQgd2lsbCBhbHNvIGJlIHNob3duIGluIHRoZSBpbnB1dCBwYW5lbCwgaWYgZW5hYmxlZCAoc2VlIHNob3dJbnB1dCkuXG4gICAgICogTm90ZSB0aGF0IHRoaXMgd2lsbCBhbHdheXMgYmUgYSBgQ29sb3JQaWNrZXJDb2xvcmAgb2JqZWN0LCBldmVuIGlmIHBsYWluIHN0cmluZ3MgYXJlIHByb3ZpZGVkIHRvIHRoZSBjb2xvcnMgcHJvcGVydHkuXG4gICAgICogU2VlIGJlbG93IGZvciBkZXRhaWxzIG9mIHRoZSBgQ29sb3JQaWNrZXJDb2xvcmAgY2xhc3MuXG4gICAgICovXG4gICAgQElucHV0KClcbiAgICBzZXQgc2VsZWN0ZWQoc2VsZWN0ZWQ6IENvbG9yUGlja2VyQ29sb3IpIHtcbiAgICAgICAgdGhpcy5zZWxlY3RlZCQubmV4dChzZWxlY3RlZCk7XG4gICAgfVxuXG4gICAgLyoqXG4gICAgICogVGhlIG51bWJlciBvZiBjb2x1bW5zIHRvIGRpc3BsYXkgaW4gdGhlIGNvbG9yIHN3YXRjaC4gU2V0IHRoaXMgdG8gLTEgaWYgdGhlIHdpZHRoIHNob3VsZCBiZSBzcGVjaWZpZWQgYnkgYSBzdHlsZXNoZWV0XG4gICAgICogaW5zdGVhZCwgZS5nLiB0byBwcm92aWRlIGEgcmVzcG9uc2l2ZSBsYXlvdXQuXG4gICAgICovXG4gICAgQElucHV0KClcbiAgICBzZXQgY29sdW1ucyhjb2x1bW5zOiBudW1iZXIpIHtcbiAgICAgICAgdGhpcy5jb2x1bW5zJC5uZXh0KGNvbHVtbnMpO1xuICAgIH1cblxuICAgIC8qKiBUaGUgc3R5bGUgb2YgdGhlIGNvbG9yIHN3YXRjaCBidXR0b25zLiAqL1xuICAgIEBJbnB1dCgpXG4gICAgYnV0dG9uU3R5bGU6IENvbG9yUGlja2VyQnV0dG9uU3R5bGUgPSAnY2lyY2xlJztcblxuICAgIC8qKiBUaGUgc2l6ZSBvZiB0aGUgY29sb3Igc3dhdGNoIGJ1dHRvbnMuIFRocmVlIHNpemUgdmFyaWFudHMgYXJlIGN1cnJlbnRseSBzdXBwb3J0ZWQuICovXG4gICAgQElucHV0KClcbiAgICBzZXQgYnV0dG9uU2l6ZShidXR0b25TaXplOiBDb2xvclBpY2tlckJ1dHRvblNpemUpIHtcbiAgICAgICAgdGhpcy5idXR0b25TaXplJC5uZXh0KGJ1dHRvblNpemUpO1xuICAgIH1cblxuICAgIC8qKiBXaGV0aGVyIHRvIHNob3cgdG9vbHRpcHMgYWJvdmUgdGhlIGNvbG9yIHN3YXRjaCBidXR0b25zLiBUaGVzZSBjb250YWluIHRoZSBjb2xvciBuYW1lIGlmIHByb3ZpZGVkOyBvdGhlcndpc2UgdGhlIGNvbG9yIGhleC9SR0JBIHZhbHVlLiAqL1xuICAgIEBJbnB1dCgpXG4gICAgc2hvd1Rvb2x0aXBzOiBib29sZWFuID0gZmFsc2U7XG5cbiAgICAvKiogV2hldGhlciB0byBzaG93IHRoZSBoZXgvUkdCQSBpbnB1dCBwYW5lbC4gKi9cbiAgICBASW5wdXQoKVxuICAgIHNob3dJbnB1dDogYm9vbGVhbiA9IGZhbHNlO1xuXG4gICAgLyoqIFRoZSBkZWZhdWx0IGlucHV0IG1vZGUgdG8gZGlzcGxheSBpbiB0aGUgaW5wdXQgcGFuZWwuIFRoZSB1c2VyIGNhbiBzd2l0Y2ggbW9kZXMgdXNpbmcgdGhlIHRvZ2dsZSBidXR0b24uICovXG4gICAgQElucHV0KClcbiAgICBpbnB1dE1vZGU6IENvbG9yUGlja2VySW5wdXRNb2RlID0gJ2hleCc7XG5cbiAgICAvKiogRGVmaW5lcyBhIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBhbiBhcmlhLWxhYmVsIGZvciBDb2xvclBpY2tlckNvbG9yLiAqL1xuICAgIEBJbnB1dCgpXG4gICAgY29sb3JBcmlhTGFiZWw6IChjb2xvcjogQ29sb3JQaWNrZXJDb2xvcikgPT4gc3RyaW5nID0gdGhpcy5nZXRDb2xvckFyaWFMYWJlbDtcblxuICAgIC8qKiBEZWZpbmVzIGEgZnVuY3Rpb24gdGhhdCByZXR1cm5zIGFuIGFyaWEtbGFiZWwgZm9yIHRoZSBidXR0b24gdGhhdCBzd2l0Y2hlcyBpbnB1dCBtb2Rlcy4gKi9cbiAgICBASW5wdXQoKVxuICAgIHN3aXRjaE1vZGVBcmlhTGFiZWw6IChtb2RlOiBDb2xvclBpY2tlcklucHV0TW9kZSkgPT4gc3RyaW5nID0gdGhpcy5nZXRTd2l0Y2hNb2RlQXJpYUxhYmVsO1xuXG4gICAgLyoqIERlZmluZSBhIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBhbiBhcmlhLWxhYmVsIGZvciB0aGUgaW5wdXQgY29udHJvbC4gKi9cbiAgICBASW5wdXQoKVxuICAgIGlucHV0QXJpYUxhYmVsOiAobW9kZTogQ29sb3JQaWNrZXJJbnB1dE1vZGUpID0+IHN0cmluZyA9IHRoaXMuZ2V0SW5wdXRBcmlhTGFiZWw7XG5cbiAgICAvKiogRW1pdHRlZCB3aGVuIHRoZSB1c2VyIGNoYW5nZXMgdGhlIHNlbGVjdGVkIGNvbG9yLCBlaXRoZXIgYnkgY2xpY2tpbmcgYSBjb2xvciBzd2F0Y2ggYnV0dG9uLCBvciBlbnRlcmluZyBhIHZhbGlkIGNvbG9yIHZhbHVlIGludG8gdGhlIGlucHV0IHBhbmVsIHRleHQgZmllbGQuICovXG4gICAgQE91dHB1dCgpXG4gICAgc2VsZWN0ZWRDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPENvbG9yUGlja2VyQ29sb3I+KCk7XG5cbiAgICAvKiogRW1pdHRlZCB3aGVuIHRoZSB1c2VyIGNoYW5nZXMgdGhlIGNvbG91ciBpbnB1dCBtb2RlICovXG4gICAgQE91dHB1dCgpXG4gICAgaW5wdXRNb2RlQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxDb2xvclBpY2tlcklucHV0TW9kZT4oKTtcblxuICAgIC8qKiBFbWl0dGVkIHdoZW4gdGhlIHVzZXIgcHJlc3NlcyBlbnRlciBpbiB0aGUgaW5wdXQgcGFuZWwgdGV4dCBmaWVsZC4gVGhpcyBjYW4gYmUgdXNlZCB0byBjb21taXQgYSBjb2xvciBjaGFuZ2UgYW5kL29yIGNsb3NlIGEgcG9wdXAuICovXG4gICAgQE91dHB1dCgpXG4gICAgaW5wdXRTdWJtaXQgPSBuZXcgRXZlbnRFbWl0dGVyPHZvaWQ+KCk7XG5cbiAgICBASG9zdEJpbmRpbmcoJ3N0eWxlLndpZHRoJylcbiAgICBjc3NXaWR0aCA9ICdhdXRvJztcblxuICAgIGNvbG9yczogQ29sb3JQaWNrZXJDb2xvcltdW10gPSBbXTtcbiAgICBzZWxlY3RlZCQgPSBuZXcgQmVoYXZpb3JTdWJqZWN0PENvbG9yUGlja2VyQ29sb3I+KG51bGwpO1xuICAgIGNvbHVtbnMkID0gbmV3IEJlaGF2aW9yU3ViamVjdDxudW1iZXI+KC0xKTtcbiAgICBidXR0b25TaXplJCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8Q29sb3JQaWNrZXJCdXR0b25TaXplPignbWQnKTtcbiAgICBpbnB1dFBhdHRlcm5zID0ge1xuICAgICAgICAnaGV4JzogL14jKD86W1xcZGEtZkEtRl17M30pezEsMn0kLyxcbiAgICAgICAgJ3JnYmEnOiAvXig/OnJnYlxcKFxcZHsxLDN9LFxccypcXGR7MSwzfSxcXHMqXFxkezEsM31cXCkpfCg/OnJnYmFcXChcXGR7MSwzfSxcXHMqXFxkezEsM30sXFxzKlxcZHsxLDN9LFxccypcXGQoXFwuXFxkKyk/XFwpKSQvXG4gICAgfTtcblxuICAgIC8qKiBBY2Nlc3MgdGhlIG5nTW9kZWwgaW5zdGFuY2Ugb2YgdGhlIGlucHV0IGZpZWxkICovXG4gICAgQFZpZXdDaGlsZCgnaW5wdXRGaWVsZCcsIHsgc3RhdGljOiBmYWxzZSB9KSBpbnB1dEZvcm1Db250cm9sOiBOZ01vZGVsO1xuICAgIEBWaWV3Q2hpbGQoVGFiYmFibGVMaXN0RGlyZWN0aXZlKSB0YWJiYWJsZUxpc3Q6IFRhYmJhYmxlTGlzdERpcmVjdGl2ZTtcblxuICAgIHByaXZhdGUgcmVhZG9ubHkgX29uRGVzdHJveSA9IG5ldyBTdWJqZWN0KCk7XG5cbiAgICBuZ09uSW5pdCgpOiB2b2lkIHtcblxuICAgICAgICAvLyBTa2lwIGVtaXR0aW5nIHRoZSBpbml0aWFsIHNlbGVjdGVkQ2hhbmdlXG4gICAgICAgIHRoaXMuc2VsZWN0ZWQkLnBpcGUocGFpcndpc2UoKSwgdGFrZVVudGlsKHRoaXMuX29uRGVzdHJveSkpLnN1YnNjcmliZSgoW3ByZXYsIGN1cnJdKSA9PiB7XG4gICAgICAgICAgICBpZiAocHJldikge1xuICAgICAgICAgICAgICAgIHRoaXMuc2VsZWN0ZWRDaGFuZ2UuZW1pdChjdXJyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSk7XG5cbiAgICAgICAgLy8gU2V0IHRoZSB3aWR0aCBiYXNlZCBvbiBjb2x1bW4gY291bnQgYW5kIGJ1dHRvbiBzaXplXG4gICAgICAgIGNvbWJpbmVMYXRlc3QoW3RoaXMuY29sdW1ucyQsIHRoaXMuYnV0dG9uU2l6ZSRdKVxuICAgICAgICAgICAgLnBpcGUodGFrZVVudGlsKHRoaXMuX29uRGVzdHJveSkpXG4gICAgICAgICAgICAuc3Vic2NyaWJlKChbY29sdW1ucywgYnV0dG9uU2l6ZV0pID0+IHtcbiAgICAgICAgICAgICAgICBpZiAoY29sdW1ucyA+IDApIHtcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgdyA9IGNvbHVtbnMgKiAoQlVUVE9OX1dJRFRIU1tidXR0b25TaXplXSArICgyICogQlVUVE9OX01BUkdJTikpO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmNzc1dpZHRoID0gYCR7d31weGA7XG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5jc3NXaWR0aCA9ICdhdXRvJztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9KTtcbiAgICB9XG5cbiAgICBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5fb25EZXN0cm95Lm5leHQoKTtcbiAgICAgICAgdGhpcy5fb25EZXN0cm95LmNvbXBsZXRlKCk7XG4gICAgfVxuXG4gICAgQEhvc3RMaXN0ZW5lcignZm9jdXMnLCBbJyRldmVudCddKVxuICAgIG9uRm9jdXMoKSB7XG4gICAgICAgIC8vIEZvcndhcmQgZm9jdXMgdG8gdGhlIGNvbG9yIGdyaWRcbiAgICAgICAgdGhpcy50YWJiYWJsZUxpc3QuZm9jdXNUYWJiYWJsZUl0ZW0oKTtcbiAgICB9XG5cbiAgICB1cGRhdGVDb2xvclZhbHVlKGlucHV0OiBzdHJpbmcsIG1vZGU6IENvbG9yUGlja2VySW5wdXRNb2RlKTogdm9pZCB7XG4gICAgICAgIGlmICh0aGlzLmlucHV0UGF0dGVybnNbbW9kZV0udGVzdChpbnB1dCkpIHtcbiAgICAgICAgICAgIHRoaXMuc2VsZWN0ZWQkLm5leHQobmV3IENvbG9yUGlja2VyQ29sb3IoJ0N1c3RvbScsIGlucHV0LCBtb2RlKSk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICB0b2dnbGVDb2xvckVudHJ5VHlwZSgpOiB2b2lkIHtcblxuICAgICAgICAvLyB1cGRhdGUgdGhlIGlucHV0IG1vZGVcbiAgICAgICAgdGhpcy5pbnB1dE1vZGUgPSAodGhpcy5pbnB1dE1vZGUgPT09ICdoZXgnKSA/ICdyZ2JhJyA6ICdoZXgnO1xuXG4gICAgICAgIC8vIGVtaXQgdGhlIG5ldyBpbnB1dCBtb2RlXG4gICAgICAgIHRoaXMuaW5wdXRNb2RlQ2hhbmdlLmVtaXQodGhpcy5pbnB1dE1vZGUpO1xuXG4gICAgICAgIC8vIGdldCB0aGUgY3VycmVudCBjb2xvciB2YWx1ZSBpZiB0aGVyZSBpcyBvbmVcbiAgICAgICAgY29uc3QgY29sb3IgPSB0aGlzLnNlbGVjdGVkJC52YWx1ZTtcblxuICAgICAgICAvLyBpZiB0aGVyZSBpcyBubyBzZWxlY3RlZCBjb2xvciBwcm9wZXJ0eSB0aGVuIHNraXBcbiAgICAgICAgaWYgKGNvbG9yKSB7XG5cbiAgICAgICAgICAgIC8vIGdldCB0aGUgbmV3IGNvbG9yIHZhbHVlXG4gICAgICAgICAgICBjb25zdCBuZXdDb2xvciA9IHRoaXMuaW5wdXRNb2RlID09PSAnaGV4JyA/IGNvbG9yLmhleCA6IGNvbG9yLnJnYmE7XG5cbiAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgc2VsZWN0ZWQgY29sb3IgdmFsdWUgYmFzZWQgb24gdGhlIGlucHV0IG1vZGVcbiAgICAgICAgICAgIHRoaXMudXBkYXRlQ29sb3JWYWx1ZShuZXdDb2xvciwgdGhpcy5pbnB1dE1vZGUpO1xuXG4gICAgICAgICAgICAvLyBmb3JjaWJseSB1cGRhdGUgdGhlIHZhbGlkYXRpb24gc3RhdHVzIG9mIG5nTW9kZWwgdG8gcHJldmVudCBhbnlcbiAgICAgICAgICAgIC8vIGluY29ycmVjdCBlcnJvciBzdGF0ZXMgaW4gdGhlIHVuZGVybHlpbmcgZm9ybSBjb250cm9sXG4gICAgICAgICAgICAvLyAocnVubmluZyBjaGFuZ2UgZGV0ZWN0aW9uIHdpbGwgbm90IHN1ZmZpY2UpXG4gICAgICAgICAgICB0aGlzLmlucHV0Rm9ybUNvbnRyb2wuY29udHJvbC5zZXRWYWx1ZShuZXdDb2xvcik7XG4gICAgICAgIH1cblxuICAgIH1cblxuICAgIHByaXZhdGUgZ2V0Q29sb3JBcmlhTGFiZWwoY29sb3I6IENvbG9yUGlja2VyQ29sb3IpOiBzdHJpbmcge1xuICAgICAgICByZXR1cm4gYFNlbGVjdCBjb2xvciAke2NvbG9yLm5hbWV9YDtcbiAgICB9XG5cbiAgICBwcml2YXRlIGdldFN3aXRjaE1vZGVBcmlhTGFiZWwobW9kZTogQ29sb3JQaWNrZXJJbnB1dE1vZGUpOiBzdHJpbmcge1xuICAgICAgICByZXR1cm4gYFN3aXRjaCBpbnB1dCBtb2RlIHRvICR7bW9kZSA9PT0gJ2hleCcgPyAnUkdCQScgOiAnaGV4J31gO1xuICAgIH1cblxuICAgIHByaXZhdGUgZ2V0SW5wdXRBcmlhTGFiZWwobW9kZTogQ29sb3JQaWNrZXJJbnB1dE1vZGUpOiBzdHJpbmcge1xuICAgICAgICByZXR1cm4gYEVkaXQgJHttb2RlfSBjb2xvciB2YWx1ZWA7XG4gICAgfVxufVxuIiwiPGRpdiBjbGFzcz1cInV4LWNvbG9yLXBpY2tlclwiIHV4VGFiYmFibGVMaXN0IGRpcmVjdGlvbj1cImhvcml6b250YWxcIj5cbiAgPGRpdiBjbGFzcz1cInV4LWNvbG9yLXBpY2tlci1zd2F0Y2hcIj5cbiAgICBAZm9yIChyb3cgb2YgY29sb3JzOyB0cmFjayByb3cpIHtcbiAgICAgIDxkaXYgY2xhc3M9XCJ1eC1jb2xvci1waWNrZXItc3dhdGNoLXJvd1wiPlxuICAgICAgICBAZm9yIChjb2xvciBvZiByb3c7IHRyYWNrIGNvbG9yKSB7XG4gICAgICAgICAgPGRpdlxuICAgICAgICAgICAgI2NvbG9yUGlja2VyQ29sb3JcbiAgICAgICAgICAgIGNsYXNzPVwidXgtY29sb3ItcGlja2VyLWNvbG9yXCJcbiAgICAgICAgICAgIFtjbGFzcy51eC1zbWFsbF09XCIoYnV0dG9uU2l6ZSQgfCBhc3luYykgPT09ICdzbSdcIlxuICAgICAgICAgICAgW2NsYXNzLnV4LWxhcmdlXT1cIihidXR0b25TaXplJCB8IGFzeW5jKSA9PT0gJ2xnJ1wiXG4gICAgICAgICAgICBbY2xhc3MudXgtY2lyY2xlXT1cImJ1dHRvblN0eWxlID09PSAnY2lyY2xlJ1wiXG4gICAgICAgICAgICBbY2xhc3MudXgtc2VsZWN0ZWRdPVwiY29sb3IgPT09IChzZWxlY3RlZCQgfCBhc3luYylcIj5cbiAgICAgICAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiXG4gICAgICAgICAgICAgIHV4Rm9jdXNJbmRpY2F0b3JcbiAgICAgICAgICAgICAgW2F0dHIuYXJpYS1sYWJlbF09XCJjb2xvckFyaWFMYWJlbChjb2xvcilcIlxuICAgICAgICAgICAgICBbYXR0ci5hcmlhLXNlbGVjdGVkXT1cImNvbG9yID09PSAoc2VsZWN0ZWQkIHwgYXN5bmMpXCJcbiAgICAgICAgICAgICAgY2xhc3M9XCJidG4gYnRuLWljb25cIlxuICAgICAgICAgICAgICBbdXhDb2xvckNvbnRyYXN0XT1cImNvbG9yLmhleFwiXG4gICAgICAgICAgICAgIFtzdHlsZS5iYWNrZ3JvdW5kLWNvbG9yXT1cImNvbG9yLnJnYmFcIlxuICAgICAgICAgICAgICAoY2xpY2spPVwic2VsZWN0ZWQkLm5leHQoY29sb3IpXCJcbiAgICAgICAgICAgICAgdXhUYWJiYWJsZUxpc3RJdGVtXG4gICAgICAgICAgICAgIFt1eFRvb2x0aXBdPVwiY29sb3IubmFtZVwiXG4gICAgICAgICAgICAgIFt0b29sdGlwRGlzYWJsZWRdPVwiIXNob3dUb29sdGlwc1wiPlxuICAgICAgICAgICAgICA8dXgtaWNvbiBuYW1lPVwiY2hlY2ttYXJrXCI+PC91eC1pY29uPlxuICAgICAgICAgICAgPC9idXR0b24+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgIH1cbiAgICAgIDwvZGl2PlxuICAgIH1cbiAgPC9kaXY+XG5cbiAgQGlmIChzaG93SW5wdXQpIHtcbiAgICA8ZGl2IGNsYXNzPVwidXgtY29sb3ItcGlja2VyLWlucHV0LXBhbmVsXCI+XG4gICAgICA8ZGl2IGNsYXNzPVwidXgtY29sb3ItcGlja2VyLWlucHV0LWhlYWRlclwiPlxuICAgICAgICA8ZGl2IGNsYXNzPVwidXgtY29sb3ItcGlja2VyLXByZXZpZXdcIlxuICAgICAgICAgIFtzdHlsZS5iYWNrZ3JvdW5kLWNvbG9yXT1cIihzZWxlY3RlZCQgfCBhc3luYykucmdiYVwiXG4gICAgICAgICAgW2NsYXNzLnV4LWNpcmNsZV09XCJidXR0b25TdHlsZSA9PT0gJ2NpcmNsZSdcIj5cbiAgICAgICAgPC9kaXY+XG4gICAgICAgIEBpZiAoaW5wdXRNb2RlID09PSAnaGV4Jykge1xuICAgICAgICAgIDxsYWJlbCBhdHRyLmZvcj1cInt7IGlkIH19LWlucHV0LWZpZWxkXCI+SEVYPC9sYWJlbD5cbiAgICAgICAgfVxuICAgICAgICBAaWYgKGlucHV0TW9kZSA9PT0gJ3JnYmEnKSB7XG4gICAgICAgICAgPGxhYmVsIGF0dHIuZm9yPVwie3sgaWQgfX0taW5wdXQtZmllbGRcIj5SR0JBPC9sYWJlbD5cbiAgICAgICAgfVxuICAgICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIlxuICAgICAgICAgIFthdHRyLmFyaWEtbGFiZWxdPVwic3dpdGNoTW9kZUFyaWFMYWJlbChpbnB1dE1vZGUpXCJcbiAgICAgICAgICBjbGFzcz1cImJ0biBidG4tbGluayBidG4taWNvbiBidXR0b24tc2Vjb25kYXJ5IHV4LWNvbG9yLXBpY2tlci1pbnB1dC10b2dnbGVcIlxuICAgICAgICAgIChjbGljayk9XCJ0b2dnbGVDb2xvckVudHJ5VHlwZSgpOyAkZXZlbnQuc3RvcFByb3BhZ2F0aW9uKClcIj5cbiAgICAgICAgICA8dXgtaWNvbiBuYW1lPVwiY2hldnJvbi1yaWdodFwiPjwvdXgtaWNvbj5cbiAgICAgICAgPC9idXR0b24+XG4gICAgICA8L2Rpdj5cbiAgICAgIDxkaXYgY2xhc3M9XCJ1eC1jb2xvci1waWNrZXItaW5wdXRcIlxuICAgICAgICBbY2xhc3MuaGFzLWVycm9yXT1cImlucHV0RmllbGQuZXJyb3JzXCJcbiAgICAgICAgW2NsYXNzLmhhcy1mZWVkYmFja109XCJpbnB1dEZpZWxkLmVycm9yc1wiPlxuICAgICAgICA8aW5wdXQgdHlwZT1cInRleHRcIlxuICAgICAgICAgIGF0dHIuaWQ9XCJ7e2lkfX0taW5wdXQtZmllbGRcIlxuICAgICAgICAgIFthdHRyLmFyaWEtZGVzY3JpcHRpb25dPVwiaW5wdXRBcmlhTGFiZWwoaW5wdXRNb2RlKVwiXG4gICAgICAgICAgY2xhc3M9XCJmb3JtLWNvbnRyb2xcIlxuICAgICAgICAgICNpbnB1dEZpZWxkPVwibmdNb2RlbFwiXG4gICAgICAgICAgW25nTW9kZWxdPVwiKHNlbGVjdGVkJCB8IGFzeW5jKVtpbnB1dE1vZGVdXCJcbiAgICAgICAgICAobmdNb2RlbENoYW5nZSk9XCJ1cGRhdGVDb2xvclZhbHVlKCRldmVudCwgaW5wdXRNb2RlKVwiXG4gICAgICAgICAgW3BhdHRlcm5dPVwiaW5wdXRQYXR0ZXJuc1tpbnB1dE1vZGVdLnNvdXJjZVwiXG4gICAgICAgICAgKGtleXVwLmVudGVyKT1cImlucHV0U3VibWl0LmVtaXQoKVwiPlxuICAgICAgICAgIDx1eC1pY29uIGNsYXNzPVwiZm9ybS1jb250cm9sLWZlZWRiYWNrXCIgbmFtZT1cImFsZXJ0XCI+PC91eC1pY29uPlxuICAgICAgICA8L2Rpdj5cbiAgICAgIDwvZGl2PlxuICAgIH1cbiAgPC9kaXY+XG4iXX0=