@ux-aspects/ux-aspects
Version:
Open source user interface framework for building modern, responsive, mobile big data applications
117 lines • 38.7 kB
JavaScript
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { Subject, timer } from 'rxjs';
import { switchMap, takeUntil } from 'rxjs/operators';
import { SliderSize } from '../../../slider/index';
import { MediaPlayerService } from '../../media-player.service';
import * as i0 from "@angular/core";
import * as i1 from "../../../../directives/accessibility/focus-indicator/focus-indicator.directive";
import * as i2 from "../../../../directives/accessibility/focus-within/focus-within.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/click-outside/click-outside.directive";
import * as i6 from "../../../icon/icon.component";
import * as i7 from "../../../slider/slider.component";
import * as i8 from "../../../tooltip/tooltip.directive";
import * as i9 from "@angular/common";
let uniqueId = 1;
export class MediaPlayerControlsExtensionComponent {
constructor() {
this.mediaPlayerService = inject(MediaPlayerService);
this.volumeActive = false;
this.volumeFocus = false;
this.returnFocus = true;
this.subtitlesId = `ux-media-player-subtitle-popover-${uniqueId++}`;
this.subtitlesOpen = false;
this.mouseEnterVolume = new Subject();
this.mouseLeaveVolume = new Subject();
this.options = {
handles: {
aria: {
thumb: 'Volume'
}
},
track: {
colors: {
lower: '#666'
},
height: SliderSize.Narrow,
ticks: {
major: {
show: false
},
minor: {
show: false
}
}
}
};
this._volume = 100;
this._previousVolume = 100;
this._onDestroy = new Subject();
}
get volume() {
return this._volume;
}
set volume(value) {
if (value === 0 && this._volume !== 0) {
this._previousVolume = this._volume;
}
this._volume = Math.min(Math.max(value, 0), 100);
this.mediaPlayerService.volume = this._volume / 100;
}
ngOnInit() {
this.mediaPlayerService.volumeChangeEvent.pipe(takeUntil(this._onDestroy)).subscribe(volume => this.volume = volume * 100);
this.mediaPlayerService.initEvent.pipe(takeUntil(this._onDestroy)).subscribe(() => this.volume = this.mediaPlayerService.volume * 100);
this.mouseEnterVolume.pipe(takeUntil(this._onDestroy)).subscribe(() => this.volumeActive = true);
this.mouseLeaveVolume.pipe(switchMap(() => timer(1500).pipe(takeUntil(this.mouseEnterVolume))), takeUntil(this._onDestroy)).subscribe(() => this.volumeActive = false);
}
ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}
toggleMute() {
this.volume = this.volume === 0 ? this._previousVolume : 0;
}
goToStart() {
this.mediaPlayerService.currentTime = 0;
}
goToEnd() {
this.mediaPlayerService.currentTime = this.mediaPlayerService.duration;
}
isSubtitleActive() {
for (let idx = 0; idx < this.mediaPlayerService.textTracks.length; idx++) {
if (this.mediaPlayerService.textTracks[idx].mode === 'showing') {
return true;
}
}
return false;
}
setSubtitleTrack(track) {
// hide all tracks
this.mediaPlayerService.hideSubtitleTracks();
// set the position of the subtitle track
for (let idx = 0; idx < track.cues.length; idx++) {
const cue = track.cues[idx];
cue.line = -3;
}
// activate the selected one
track.mode = 'showing';
}
getSubtitleTrack() {
for (let idx = 0; idx < this.mediaPlayerService.textTracks.length; idx++) {
if (this.mediaPlayerService.textTracks[idx].mode === 'showing') {
return this.mediaPlayerService.textTracks[idx].label;
}
}
return this.mediaPlayerService.noSubtitlesAriaLabel;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: MediaPlayerControlsExtensionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.12", type: MediaPlayerControlsExtensionComponent, selector: "ux-media-player-controls", host: { properties: { "class.quiet": "mediaPlayerService.quietMode || mediaPlayerService.fullscreen" } }, ngImport: i0, template: "<div class=\"volume-container\">\n\n <div class=\"volume-slider-container\"\n #volumeContainer\n [class.active]=\"volumeActive || volumeFocus\"\n (mouseenter)=\"mouseEnterVolume.next()\"\n (mouseleave)=\"mouseLeaveVolume.next()\"\n (uxFocusWithin)=\"volumeFocus = true\"\n (uxBlurWithin)=\"volumeFocus = false\">\n\n <button #volumeIcon\n uxFocusIndicator\n type=\"button\"\n class=\"volume-slider-icon\"\n [attr.aria-label]=\"mediaPlayerService.muteAriaLabel(volume)\"\n [uxTooltip]=\"muteTooltip\"\n [showTriggers]=\"['mouseenter']\"\n [hideTriggers]=\"['mouseleave']\"\n (click)=\"toggleMute()\"\n (mouseup)=\"volumeIcon.blur()\">\n\n @if (volume === 0) {\n <ux-icon name=\"volume-mute\"></ux-icon>\n }\n @if (volume > 0 && volume <= 70) {\n <ux-icon name=\"volume-low\"></ux-icon>\n }\n @if (volume > 70) {\n <ux-icon name=\"volume\"></ux-icon>\n }\n </button>\n\n <div class=\"volume-slider-node\">\n <ux-slider [value]=\"volume\" (valueChange)=\"volume = $any($event)\" [options]=\"options\"></ux-slider>\n </div>\n </div>\n</div>\n\n<button #startButton\n uxFocusIndicator\n type=\"button\"\n class=\"control-button\"\n (click)=\"goToStart()\"\n (mouseup)=\"startButton.blur()\"\n [attr.aria-label]=\"mediaPlayerService.goToStartAriaLabel\">\n\n <svg viewBox=\"0 0 51.5 64\" width=\"14\" height=\"17\" focusable=\"false\">\n <rect x=\"0\" y=\"0\" width=\"7.5\" height=\"64\" />\n <polygon points=\"51.5,64 51.5,0 7.4,32 \" />\n </svg>\n</button>\n\n<button #playButton\n uxFocusIndicator\n type=\"button\"\n class=\"control-button\"\n [attr.aria-label]=\"mediaPlayerService.playAriaLabel(mediaPlayerService.playing | async)\"\n (click)=\"mediaPlayerService.togglePlay()\"\n (mouseup)=\"playButton.blur()\">\n\n @if ((mediaPlayerService.playing | async) === false) {\n <svg viewBox=\"0 0 45 64\" width=\"20\" height=\"29\" focusable=\"false\">\n <polygon points=\"0.4,0 0.4,64 44.6,32\" />\n </svg>\n }\n @if (mediaPlayerService.playing | async) {\n <svg viewBox=\"0 0 43 56.9\" width=\"20\" height=\"29\" focusable=\"false\">\n <rect y=\"0.1\" width=\"15.7\" height=\"56.9\" />\n <rect x=\"27.3\" y=\"0.1\" width=\"15.7\" height=\"56.9\" />\n </svg>\n }\n</button>\n\n<button #endButton\n uxFocusIndicator\n type=\"button\"\n class=\"control-button\"\n (click)=\"goToEnd()\"\n (mouseup)=\"endButton.blur()\"\n [attr.aria-label]=\"mediaPlayerService.goToEndAriaLabel\">\n\n <svg viewBox=\"0 0 51.5 64\" width=\"14\" height=\"17\" focusable=\"false\">\n <rect x=\"44.1\" y=\"0\" width=\"7.5\" height=\"64\" />\n <polygon points=\"0,64 0,0 44.1,32\" />\n </svg>\n</button>\n\n<div class=\"actions-list\">\n\n <ng-content></ng-content>\n\n @if (mediaPlayerService.textTracks.length > 0 && mediaPlayerService.type === 'video') {\n <div class=\"action-button-container\">\n <button #subtitlesButton\n uxFocusIndicator\n type=\"button\"\n class=\"action-button\"\n (keydown)=\"returnFocus = true\"\n (click)=\"subtitlesOpen = !subtitlesOpen\"\n (mouseup)=\"subtitlesButton.blur(); returnFocus = false\"\n [attr.aria-label]=\"mediaPlayerService.selectSubtitlesAriaLabel(getSubtitleTrack())\"\n [attr.aria-expanded]=\"subtitlesOpen\"\n [attr.aria-describedby]=\"subtitlesId\"\n aria-haspopup=\"true\">\n <ux-icon name=\"subtitles\"></ux-icon>\n </button>\n @if (subtitlesOpen) {\n <div #subtitles\n [style.top.px]=\"-subtitles.offsetHeight\"\n class=\"popover top media-player-subtitles-popover show\"\n [id]=\"subtitlesId\"\n (keydown.escape)=\"subtitlesOpen = false\"\n (uxClickOutside)=\"subtitlesOpen = false\"\n >\n <div class=\"arrow\"></div>\n <h3 class=\"popover-title\">{{ mediaPlayerService.subtitlesTitleAriaLabel }}</h3>\n <div class=\"popover-content\">\n <ul class=\"subtitles-list\" uxTabbableList [focusOnShow]=\"returnFocus\" [returnFocus]=\"returnFocus\">\n <li uxTabbableListItem\n tabindex=\"0\"\n class=\"subtitles-list-item\"\n [class.active]=\"!isSubtitleActive()\"\n [attr.aria-selected]=\"isSubtitleActive()\"\n (click)=\"mediaPlayerService.hideSubtitleTracks(); subtitlesOpen = false\"\n (keydown.enter)=\"mediaPlayerService.hideSubtitleTracks(); subtitlesOpen = false; returnFocus = true\">\n <ux-icon name=\"checkmark\" class=\"subtitles-list-item-checkmark\"></ux-icon>\n <span>{{ mediaPlayerService.subtitlesOffAriaLabel }}</span>\n </li>\n @for (track of mediaPlayerService.textTracks; track track) {\n <li uxTabbableListItem\n class=\"subtitles-list-item\"\n [class.active]=\"track.mode === 'showing'\"\n [attr.aria-selected]=\"isSubtitleActive()\"\n (click)=\"setSubtitleTrack(track); subtitlesOpen = false\"\n (keydown.enter)=\"setSubtitleTrack(track); subtitlesOpen = false; returnFocus = true\">\n <ux-icon name=\"checkmark\" class=\"subtitles-list-item-checkmark\"></ux-icon>\n <span>{{ track.label }}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n }\n\n <div class=\"action-button-container\">\n @if (mediaPlayerService.type !== 'audio') {\n <button #fullscreenButton\n uxFocusIndicator\n type=\"button\"\n class=\"action-button\"\n [attr.aria-label]=\"mediaPlayerService.fullscreenAriaLabel(mediaPlayerService.fullscreen)\"\n (click)=\"mediaPlayerService.toggleFullscreen()\"\n (mouseup)=\"fullscreenButton.blur()\">\n <ux-icon [name]=\"mediaPlayerService.fullscreen ? 'contract' : 'expand'\"></ux-icon>\n </button>\n }\n </div>\n</div>\n\n\n\n<ng-template #muteTooltip>\n <span aria-hidden=\"true\">{{ volume === 0 ? 'Unmute' : 'Mute' }}</span>\n</ng-template>\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.FocusWithinDirective, selector: "[uxFocusWithin],[uxBlurWithin]", outputs: ["uxFocusWithin", "uxBlurWithin"] }, { 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.ClickOutsideDirective, selector: "[uxClickOutside]", outputs: ["uxClickOutside"] }, { kind: "component", type: i6.IconComponent, selector: "ux-icon", inputs: ["name", "size", "rotate", "flipHorizontal", "flipVertical"] }, { kind: "component", type: i7.SliderComponent, selector: "ux-slider", inputs: ["value", "options", "disabled"], outputs: ["valueChange"] }, { kind: "directive", type: i8.TooltipDirective, selector: "[uxTooltip]", inputs: ["uxTooltip", "tooltipDisabled", "tooltipClass", "tooltipRole", "tooltipContext", "tooltipDelay", "isOpen", "placement", "fallbackPlacement", "alignment", "showTriggers", "hideTriggers"], outputs: ["shown", "hidden", "isOpenChange"], exportAs: ["ux-tooltip"] }, { kind: "pipe", type: i9.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.12", ngImport: i0, type: MediaPlayerControlsExtensionComponent, decorators: [{
type: Component,
args: [{ selector: 'ux-media-player-controls', changeDetection: ChangeDetectionStrategy.OnPush, host: {
'[class.quiet]': 'mediaPlayerService.quietMode || mediaPlayerService.fullscreen'
}, template: "<div class=\"volume-container\">\n\n <div class=\"volume-slider-container\"\n #volumeContainer\n [class.active]=\"volumeActive || volumeFocus\"\n (mouseenter)=\"mouseEnterVolume.next()\"\n (mouseleave)=\"mouseLeaveVolume.next()\"\n (uxFocusWithin)=\"volumeFocus = true\"\n (uxBlurWithin)=\"volumeFocus = false\">\n\n <button #volumeIcon\n uxFocusIndicator\n type=\"button\"\n class=\"volume-slider-icon\"\n [attr.aria-label]=\"mediaPlayerService.muteAriaLabel(volume)\"\n [uxTooltip]=\"muteTooltip\"\n [showTriggers]=\"['mouseenter']\"\n [hideTriggers]=\"['mouseleave']\"\n (click)=\"toggleMute()\"\n (mouseup)=\"volumeIcon.blur()\">\n\n @if (volume === 0) {\n <ux-icon name=\"volume-mute\"></ux-icon>\n }\n @if (volume > 0 && volume <= 70) {\n <ux-icon name=\"volume-low\"></ux-icon>\n }\n @if (volume > 70) {\n <ux-icon name=\"volume\"></ux-icon>\n }\n </button>\n\n <div class=\"volume-slider-node\">\n <ux-slider [value]=\"volume\" (valueChange)=\"volume = $any($event)\" [options]=\"options\"></ux-slider>\n </div>\n </div>\n</div>\n\n<button #startButton\n uxFocusIndicator\n type=\"button\"\n class=\"control-button\"\n (click)=\"goToStart()\"\n (mouseup)=\"startButton.blur()\"\n [attr.aria-label]=\"mediaPlayerService.goToStartAriaLabel\">\n\n <svg viewBox=\"0 0 51.5 64\" width=\"14\" height=\"17\" focusable=\"false\">\n <rect x=\"0\" y=\"0\" width=\"7.5\" height=\"64\" />\n <polygon points=\"51.5,64 51.5,0 7.4,32 \" />\n </svg>\n</button>\n\n<button #playButton\n uxFocusIndicator\n type=\"button\"\n class=\"control-button\"\n [attr.aria-label]=\"mediaPlayerService.playAriaLabel(mediaPlayerService.playing | async)\"\n (click)=\"mediaPlayerService.togglePlay()\"\n (mouseup)=\"playButton.blur()\">\n\n @if ((mediaPlayerService.playing | async) === false) {\n <svg viewBox=\"0 0 45 64\" width=\"20\" height=\"29\" focusable=\"false\">\n <polygon points=\"0.4,0 0.4,64 44.6,32\" />\n </svg>\n }\n @if (mediaPlayerService.playing | async) {\n <svg viewBox=\"0 0 43 56.9\" width=\"20\" height=\"29\" focusable=\"false\">\n <rect y=\"0.1\" width=\"15.7\" height=\"56.9\" />\n <rect x=\"27.3\" y=\"0.1\" width=\"15.7\" height=\"56.9\" />\n </svg>\n }\n</button>\n\n<button #endButton\n uxFocusIndicator\n type=\"button\"\n class=\"control-button\"\n (click)=\"goToEnd()\"\n (mouseup)=\"endButton.blur()\"\n [attr.aria-label]=\"mediaPlayerService.goToEndAriaLabel\">\n\n <svg viewBox=\"0 0 51.5 64\" width=\"14\" height=\"17\" focusable=\"false\">\n <rect x=\"44.1\" y=\"0\" width=\"7.5\" height=\"64\" />\n <polygon points=\"0,64 0,0 44.1,32\" />\n </svg>\n</button>\n\n<div class=\"actions-list\">\n\n <ng-content></ng-content>\n\n @if (mediaPlayerService.textTracks.length > 0 && mediaPlayerService.type === 'video') {\n <div class=\"action-button-container\">\n <button #subtitlesButton\n uxFocusIndicator\n type=\"button\"\n class=\"action-button\"\n (keydown)=\"returnFocus = true\"\n (click)=\"subtitlesOpen = !subtitlesOpen\"\n (mouseup)=\"subtitlesButton.blur(); returnFocus = false\"\n [attr.aria-label]=\"mediaPlayerService.selectSubtitlesAriaLabel(getSubtitleTrack())\"\n [attr.aria-expanded]=\"subtitlesOpen\"\n [attr.aria-describedby]=\"subtitlesId\"\n aria-haspopup=\"true\">\n <ux-icon name=\"subtitles\"></ux-icon>\n </button>\n @if (subtitlesOpen) {\n <div #subtitles\n [style.top.px]=\"-subtitles.offsetHeight\"\n class=\"popover top media-player-subtitles-popover show\"\n [id]=\"subtitlesId\"\n (keydown.escape)=\"subtitlesOpen = false\"\n (uxClickOutside)=\"subtitlesOpen = false\"\n >\n <div class=\"arrow\"></div>\n <h3 class=\"popover-title\">{{ mediaPlayerService.subtitlesTitleAriaLabel }}</h3>\n <div class=\"popover-content\">\n <ul class=\"subtitles-list\" uxTabbableList [focusOnShow]=\"returnFocus\" [returnFocus]=\"returnFocus\">\n <li uxTabbableListItem\n tabindex=\"0\"\n class=\"subtitles-list-item\"\n [class.active]=\"!isSubtitleActive()\"\n [attr.aria-selected]=\"isSubtitleActive()\"\n (click)=\"mediaPlayerService.hideSubtitleTracks(); subtitlesOpen = false\"\n (keydown.enter)=\"mediaPlayerService.hideSubtitleTracks(); subtitlesOpen = false; returnFocus = true\">\n <ux-icon name=\"checkmark\" class=\"subtitles-list-item-checkmark\"></ux-icon>\n <span>{{ mediaPlayerService.subtitlesOffAriaLabel }}</span>\n </li>\n @for (track of mediaPlayerService.textTracks; track track) {\n <li uxTabbableListItem\n class=\"subtitles-list-item\"\n [class.active]=\"track.mode === 'showing'\"\n [attr.aria-selected]=\"isSubtitleActive()\"\n (click)=\"setSubtitleTrack(track); subtitlesOpen = false\"\n (keydown.enter)=\"setSubtitleTrack(track); subtitlesOpen = false; returnFocus = true\">\n <ux-icon name=\"checkmark\" class=\"subtitles-list-item-checkmark\"></ux-icon>\n <span>{{ track.label }}</span>\n </li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n }\n\n <div class=\"action-button-container\">\n @if (mediaPlayerService.type !== 'audio') {\n <button #fullscreenButton\n uxFocusIndicator\n type=\"button\"\n class=\"action-button\"\n [attr.aria-label]=\"mediaPlayerService.fullscreenAriaLabel(mediaPlayerService.fullscreen)\"\n (click)=\"mediaPlayerService.toggleFullscreen()\"\n (mouseup)=\"fullscreenButton.blur()\">\n <ux-icon [name]=\"mediaPlayerService.fullscreen ? 'contract' : 'expand'\"></ux-icon>\n </button>\n }\n </div>\n</div>\n\n\n\n<ng-template #muteTooltip>\n <span aria-hidden=\"true\">{{ volume === 0 ? 'Unmute' : 'Mute' }}</span>\n</ng-template>\n" }]
}] });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udHJvbHMuY29tcG9uZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvbWVkaWEtcGxheWVyL2V4dGVuc2lvbnMvY29udHJvbHMvY29udHJvbHMuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vLi4vc3JjL2NvbXBvbmVudHMvbWVkaWEtcGxheWVyL2V4dGVuc2lvbnMvY29udHJvbHMvY29udHJvbHMuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLHVCQUF1QixFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQXFCLE1BQU0sZUFBZSxDQUFDO0FBQzlGLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3RDLE9BQU8sRUFBRSxTQUFTLEVBQUUsU0FBUyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDdEQsT0FBTyxFQUFpQixVQUFVLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNsRSxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQzs7Ozs7Ozs7Ozs7QUFFaEUsSUFBSSxRQUFRLEdBQVcsQ0FBQyxDQUFDO0FBVXpCLE1BQU0sT0FBTyxxQ0FBcUM7SUFSbEQ7UUFTYSx1QkFBa0IsR0FBRyxNQUFNLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUV6RCxpQkFBWSxHQUFZLEtBQUssQ0FBQztRQUM5QixnQkFBVyxHQUFZLEtBQUssQ0FBQztRQUM3QixnQkFBVyxHQUFZLElBQUksQ0FBQztRQUM1QixnQkFBVyxHQUFXLG9DQUFvQyxRQUFRLEVBQUUsRUFBRSxDQUFDO1FBQ3ZFLGtCQUFhLEdBQVksS0FBSyxDQUFDO1FBQy9CLHFCQUFnQixHQUFHLElBQUksT0FBTyxFQUFRLENBQUM7UUFDdkMscUJBQWdCLEdBQUcsSUFBSSxPQUFPLEVBQVEsQ0FBQztRQUV2QyxZQUFPLEdBQWtCO1lBQ3JCLE9BQU8sRUFBRTtnQkFDTCxJQUFJLEVBQUU7b0JBQ0YsS0FBSyxFQUFFLFFBQVE7aUJBQ2xCO2FBQ0o7WUFDRCxLQUFLLEVBQUU7Z0JBQ0gsTUFBTSxFQUFFO29CQUNKLEtBQUssRUFBRSxNQUFNO2lCQUNoQjtnQkFDRCxNQUFNLEVBQUUsVUFBVSxDQUFDLE1BQU07Z0JBQ3pCLEtBQUssRUFBRTtvQkFDSCxLQUFLLEVBQUU7d0JBQ0gsSUFBSSxFQUFFLEtBQUs7cUJBQ2Q7b0JBQ0QsS0FBSyxFQUFFO3dCQUNILElBQUksRUFBRSxLQUFLO3FCQUNkO2lCQUNKO2FBQ0o7U0FDSixDQUFDO1FBRU0sWUFBTyxHQUFXLEdBQUcsQ0FBQztRQUN0QixvQkFBZSxHQUFHLEdBQUcsQ0FBQztRQUNiLGVBQVUsR0FBRyxJQUFJLE9BQU8sRUFBUSxDQUFDO0tBZ0ZyRDtJQTdFRyxJQUFJLE1BQU07UUFDTixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUM7SUFDeEIsQ0FBQztJQUVELElBQUksTUFBTSxDQUFDLEtBQWE7UUFFcEIsSUFBSSxLQUFLLEtBQUssQ0FBQyxJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDcEMsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1FBQ3hDLENBQUM7UUFFRCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDakQsSUFBSSxDQUFDLGtCQUFrQixDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsT0FBTyxHQUFHLEdBQUcsQ0FBQztJQUN4RCxDQUFDO0lBRUQsUUFBUTtRQUNKLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDO1FBQzNILElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxDQUFDO1FBRXZJLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQyxDQUFDO1FBQ2pHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQ3RCLFNBQVMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FDbEcsQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLFlBQVksR0FBRyxLQUFLLENBQUMsQ0FBQztJQUNqRCxDQUFDO0lBRUQsV0FBVztRQUNQLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDdkIsSUFBSSxDQUFDLFVBQVUsQ0FBQyxRQUFRLEVBQUUsQ0FBQztJQUMvQixDQUFDO0lBRUQsVUFBVTtRQUNOLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMvRCxDQUFDO0lBRUQsU0FBUztRQUNMLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxXQUFXLEdBQUcsQ0FBQyxDQUFDO0lBQzVDLENBQUM7SUFFRCxPQUFPO1FBQ0gsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFdBQVcsR0FBRyxJQUFJLENBQUMsa0JBQWtCLENBQUMsUUFBUSxDQUFDO0lBQzNFLENBQUM7SUFFRCxnQkFBZ0I7UUFDWixLQUFLLElBQUksR0FBRyxHQUFHLENBQUMsRUFBRSxHQUFHLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLEVBQUUsQ0FBQztZQUN2RSxJQUFJLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUM3RCxPQUFPLElBQUksQ0FBQztZQUNoQixDQUFDO1FBQ0wsQ0FBQztRQUVELE9BQU8sS0FBSyxDQUFDO0lBQ2pCLENBQUM7SUFFRCxnQkFBZ0IsQ0FBQyxLQUFnQjtRQUM3QixrQkFBa0I7UUFDbEIsSUFBSSxDQUFDLGtCQUFrQixDQUFDLGtCQUFrQixFQUFFLENBQUM7UUFFN0MseUNBQXlDO1FBQ3pDLEtBQUssSUFBSSxHQUFHLEdBQUcsQ0FBQyxFQUFFLEdBQUcsR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUUsRUFBRSxDQUFDO1lBRS9DLE1BQU0sR0FBRyxHQUFRLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDakMsR0FBRyxDQUFDLElBQUksR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNsQixDQUFDO1FBRUQsNEJBQTRCO1FBQzVCLEtBQUssQ0FBQyxJQUFJLEdBQUcsU0FBUyxDQUFDO0lBRTNCLENBQUM7SUFFRCxnQkFBZ0I7UUFDWixLQUFLLElBQUksR0FBRyxHQUFHLENBQUMsRUFBRSxHQUFHLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFLEVBQUUsQ0FBQztZQUN2RSxJQUFJLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxLQUFLLFNBQVMsRUFBRSxDQUFDO2dCQUM3RCxPQUFPLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDO1lBQ3pELENBQUM7UUFDTCxDQUFDO1FBRUQsT0FBTyxJQUFJLENBQUMsa0JBQWtCLENBQUMsb0JBQW9CLENBQUM7SUFDeEQsQ0FBQzsrR0FqSFEscUNBQXFDO21HQUFyQyxxQ0FBcUMsMEtDaEJsRCwwbE1Bc0tBOzs0RkR0SmEscUNBQXFDO2tCQVJqRCxTQUFTOytCQUNJLDBCQUEwQixtQkFFbkIsdUJBQXVCLENBQUMsTUFBTSxRQUN6Qzt3QkFDRixlQUFlLEVBQUUsK0RBQStEO3FCQUNuRiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENoYW5nZURldGVjdGlvblN0cmF0ZWd5LCBDb21wb25lbnQsIGluamVjdCwgT25EZXN0cm95LCBPbkluaXQgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFN1YmplY3QsIHRpbWVyIH0gZnJvbSAncnhqcyc7XG5pbXBvcnQgeyBzd2l0Y2hNYXAsIHRha2VVbnRpbCB9IGZyb20gJ3J4anMvb3BlcmF0b3JzJztcbmltcG9ydCB7IFNsaWRlck9wdGlvbnMsIFNsaWRlclNpemUgfSBmcm9tICcuLi8uLi8uLi9zbGlkZXIvaW5kZXgnO1xuaW1wb3J0IHsgTWVkaWFQbGF5ZXJTZXJ2aWNlIH0gZnJvbSAnLi4vLi4vbWVkaWEtcGxheWVyLnNlcnZpY2UnO1xuXG5sZXQgdW5pcXVlSWQ6IG51bWJlciA9IDE7XG5cbkBDb21wb25lbnQoe1xuICAgIHNlbGVjdG9yOiAndXgtbWVkaWEtcGxheWVyLWNvbnRyb2xzJyxcbiAgICB0ZW1wbGF0ZVVybDogJy4vY29udHJvbHMuY29tcG9uZW50Lmh0bWwnLFxuICAgIGNoYW5nZURldGVjdGlvbjogQ2hhbmdlRGV0ZWN0aW9uU3RyYXRlZ3kuT25QdXNoLFxuICAgIGhvc3Q6IHtcbiAgICAgICAgJ1tjbGFzcy5xdWlldF0nOiAnbWVkaWFQbGF5ZXJTZXJ2aWNlLnF1aWV0TW9kZSB8fCBtZWRpYVBsYXllclNlcnZpY2UuZnVsbHNjcmVlbidcbiAgICB9XG59KVxuZXhwb3J0IGNsYXNzIE1lZGlhUGxheWVyQ29udHJvbHNFeHRlbnNpb25Db21wb25lbnQgaW1wbGVtZW50cyBPbkluaXQsIE9uRGVzdHJveSB7XG4gICAgcmVhZG9ubHkgbWVkaWFQbGF5ZXJTZXJ2aWNlID0gaW5qZWN0KE1lZGlhUGxheWVyU2VydmljZSk7XG5cbiAgICB2b2x1bWVBY3RpdmU6IGJvb2xlYW4gPSBmYWxzZTtcbiAgICB2b2x1bWVGb2N1czogYm9vbGVhbiA9IGZhbHNlO1xuICAgIHJldHVybkZvY3VzOiBib29sZWFuID0gdHJ1ZTtcbiAgICBzdWJ0aXRsZXNJZDogc3RyaW5nID0gYHV4LW1lZGlhLXBsYXllci1zdWJ0aXRsZS1wb3BvdmVyLSR7dW5pcXVlSWQrK31gO1xuICAgIHN1YnRpdGxlc09wZW46IGJvb2xlYW4gPSBmYWxzZTtcbiAgICBtb3VzZUVudGVyVm9sdW1lID0gbmV3IFN1YmplY3Q8dm9pZD4oKTtcbiAgICBtb3VzZUxlYXZlVm9sdW1lID0gbmV3IFN1YmplY3Q8dm9pZD4oKTtcblxuICAgIG9wdGlvbnM6IFNsaWRlck9wdGlvbnMgPSB7XG4gICAgICAgIGhhbmRsZXM6IHtcbiAgICAgICAgICAgIGFyaWE6IHtcbiAgICAgICAgICAgICAgICB0aHVtYjogJ1ZvbHVtZSdcbiAgICAgICAgICAgIH1cbiAgICAgICAgfSxcbiAgICAgICAgdHJhY2s6IHtcbiAgICAgICAgICAgIGNvbG9yczoge1xuICAgICAgICAgICAgICAgIGxvd2VyOiAnIzY2NidcbiAgICAgICAgICAgIH0sXG4gICAgICAgICAgICBoZWlnaHQ6IFNsaWRlclNpemUuTmFycm93LFxuICAgICAgICAgICAgdGlja3M6IHtcbiAgICAgICAgICAgICAgICBtYWpvcjoge1xuICAgICAgICAgICAgICAgICAgICBzaG93OiBmYWxzZVxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgbWlub3I6IHtcbiAgICAgICAgICAgICAgICAgICAgc2hvdzogZmFsc2VcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9O1xuXG4gICAgcHJpdmF0ZSBfdm9sdW1lOiBudW1iZXIgPSAxMDA7XG4gICAgcHJpdmF0ZSBfcHJldmlvdXNWb2x1bWUgPSAxMDA7XG4gICAgcHJpdmF0ZSByZWFkb25seSBfb25EZXN0cm95ID0gbmV3IFN1YmplY3Q8dm9pZD4oKTtcblxuXG4gICAgZ2V0IHZvbHVtZSgpOiBudW1iZXIge1xuICAgICAgICByZXR1cm4gdGhpcy5fdm9sdW1lO1xuICAgIH1cblxuICAgIHNldCB2b2x1bWUodmFsdWU6IG51bWJlcikge1xuXG4gICAgICAgIGlmICh2YWx1ZSA9PT0gMCAmJiB0aGlzLl92b2x1bWUgIT09IDApIHtcbiAgICAgICAgICAgIHRoaXMuX3ByZXZpb3VzVm9sdW1lID0gdGhpcy5fdm9sdW1lO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5fdm9sdW1lID0gTWF0aC5taW4oTWF0aC5tYXgodmFsdWUsIDApLCAxMDApO1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS52b2x1bWUgPSB0aGlzLl92b2x1bWUgLyAxMDA7XG4gICAgfVxuXG4gICAgbmdPbkluaXQoKTogdm9pZCB7XG4gICAgICAgIHRoaXMubWVkaWFQbGF5ZXJTZXJ2aWNlLnZvbHVtZUNoYW5nZUV2ZW50LnBpcGUodGFrZVVudGlsKHRoaXMuX29uRGVzdHJveSkpLnN1YnNjcmliZSh2b2x1bWUgPT4gdGhpcy52b2x1bWUgPSB2b2x1bWUgKiAxMDApO1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5pbml0RXZlbnQucGlwZSh0YWtlVW50aWwodGhpcy5fb25EZXN0cm95KSkuc3Vic2NyaWJlKCgpID0+IHRoaXMudm9sdW1lID0gdGhpcy5tZWRpYVBsYXllclNlcnZpY2Uudm9sdW1lICogMTAwKTtcblxuICAgICAgICB0aGlzLm1vdXNlRW50ZXJWb2x1bWUucGlwZSh0YWtlVW50aWwodGhpcy5fb25EZXN0cm95KSkuc3Vic2NyaWJlKCgpID0+IHRoaXMudm9sdW1lQWN0aXZlID0gdHJ1ZSk7XG4gICAgICAgIHRoaXMubW91c2VMZWF2ZVZvbHVtZS5waXBlKFxuICAgICAgICAgICAgc3dpdGNoTWFwKCgpID0+IHRpbWVyKDE1MDApLnBpcGUodGFrZVVudGlsKHRoaXMubW91c2VFbnRlclZvbHVtZSkpKSwgdGFrZVVudGlsKHRoaXMuX29uRGVzdHJveSlcbiAgICAgICAgKS5zdWJzY3JpYmUoKCkgPT4gdGhpcy52b2x1bWVBY3RpdmUgPSBmYWxzZSk7XG4gICAgfVxuXG4gICAgbmdPbkRlc3Ryb3koKTogdm9pZCB7XG4gICAgICAgIHRoaXMuX29uRGVzdHJveS5uZXh0KCk7XG4gICAgICAgIHRoaXMuX29uRGVzdHJveS5jb21wbGV0ZSgpO1xuICAgIH1cblxuICAgIHRvZ2dsZU11dGUoKTogdm9pZCB7XG4gICAgICAgIHRoaXMudm9sdW1lID0gdGhpcy52b2x1bWUgPT09IDAgPyB0aGlzLl9wcmV2aW91c1ZvbHVtZSA6IDA7XG4gICAgfVxuXG4gICAgZ29Ub1N0YXJ0KCk6IHZvaWQge1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5jdXJyZW50VGltZSA9IDA7XG4gICAgfVxuXG4gICAgZ29Ub0VuZCgpOiB2b2lkIHtcbiAgICAgICAgdGhpcy5tZWRpYVBsYXllclNlcnZpY2UuY3VycmVudFRpbWUgPSB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5kdXJhdGlvbjtcbiAgICB9XG5cbiAgICBpc1N1YnRpdGxlQWN0aXZlKCk6IGJvb2xlYW4ge1xuICAgICAgICBmb3IgKGxldCBpZHggPSAwOyBpZHggPCB0aGlzLm1lZGlhUGxheWVyU2VydmljZS50ZXh0VHJhY2tzLmxlbmd0aDsgaWR4KyspIHtcbiAgICAgICAgICAgIGlmICh0aGlzLm1lZGlhUGxheWVyU2VydmljZS50ZXh0VHJhY2tzW2lkeF0ubW9kZSA9PT0gJ3Nob3dpbmcnKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgc2V0U3VidGl0bGVUcmFjayh0cmFjazogVGV4dFRyYWNrKTogdm9pZCB7XG4gICAgICAgIC8vIGhpZGUgYWxsIHRyYWNrc1xuICAgICAgICB0aGlzLm1lZGlhUGxheWVyU2VydmljZS5oaWRlU3VidGl0bGVUcmFja3MoKTtcblxuICAgICAgICAvLyBzZXQgdGhlIHBvc2l0aW9uIG9mIHRoZSBzdWJ0aXRsZSB0cmFja1xuICAgICAgICBmb3IgKGxldCBpZHggPSAwOyBpZHggPCB0cmFjay5jdWVzLmxlbmd0aDsgaWR4KyspIHtcbiAgICAgICAgICAgICBcbiAgICAgICAgICAgIGNvbnN0IGN1ZTogYW55ID0gdHJhY2suY3Vlc1tpZHhdO1xuICAgICAgICAgICAgY3VlLmxpbmUgPSAtMztcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGFjdGl2YXRlIHRoZSBzZWxlY3RlZCBvbmVcbiAgICAgICAgdHJhY2subW9kZSA9ICdzaG93aW5nJztcblxuICAgIH1cblxuICAgIGdldFN1YnRpdGxlVHJhY2soKTogc3RyaW5nIHtcbiAgICAgICAgZm9yIChsZXQgaWR4ID0gMDsgaWR4IDwgdGhpcy5tZWRpYVBsYXllclNlcnZpY2UudGV4dFRyYWNrcy5sZW5ndGg7IGlkeCsrKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5tZWRpYVBsYXllclNlcnZpY2UudGV4dFRyYWNrc1tpZHhdLm1vZGUgPT09ICdzaG93aW5nJykge1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLm1lZGlhUGxheWVyU2VydmljZS50ZXh0VHJhY2tzW2lkeF0ubGFiZWw7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5tZWRpYVBsYXllclNlcnZpY2Uubm9TdWJ0aXRsZXNBcmlhTGFiZWw7XG4gICAgfVxuXG59XG4iLCI8ZGl2IGNsYXNzPVwidm9sdW1lLWNvbnRhaW5lclwiPlxuXG4gIDxkaXYgY2xhc3M9XCJ2b2x1bWUtc2xpZGVyLWNvbnRhaW5lclwiXG4gICAgI3ZvbHVtZUNvbnRhaW5lclxuICAgIFtjbGFzcy5hY3RpdmVdPVwidm9sdW1lQWN0aXZlIHx8IHZvbHVtZUZvY3VzXCJcbiAgICAobW91c2VlbnRlcik9XCJtb3VzZUVudGVyVm9sdW1lLm5leHQoKVwiXG4gICAgKG1vdXNlbGVhdmUpPVwibW91c2VMZWF2ZVZvbHVtZS5uZXh0KClcIlxuICAgICh1eEZvY3VzV2l0aGluKT1cInZvbHVtZUZvY3VzID0gdHJ1ZVwiXG4gICAgKHV4Qmx1cldpdGhpbik9XCJ2b2x1bWVGb2N1cyA9IGZhbHNlXCI+XG5cbiAgICA8YnV0dG9uICN2b2x1bWVJY29uXG4gICAgICB1eEZvY3VzSW5kaWNhdG9yXG4gICAgICB0eXBlPVwiYnV0dG9uXCJcbiAgICAgIGNsYXNzPVwidm9sdW1lLXNsaWRlci1pY29uXCJcbiAgICAgIFthdHRyLmFyaWEtbGFiZWxdPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLm11dGVBcmlhTGFiZWwodm9sdW1lKVwiXG4gICAgICBbdXhUb29sdGlwXT1cIm11dGVUb29sdGlwXCJcbiAgICAgIFtzaG93VHJpZ2dlcnNdPVwiWydtb3VzZWVudGVyJ11cIlxuICAgICAgW2hpZGVUcmlnZ2Vyc109XCJbJ21vdXNlbGVhdmUnXVwiXG4gICAgICAoY2xpY2spPVwidG9nZ2xlTXV0ZSgpXCJcbiAgICAgIChtb3VzZXVwKT1cInZvbHVtZUljb24uYmx1cigpXCI+XG5cbiAgICAgIEBpZiAodm9sdW1lID09PSAwKSB7XG4gICAgICAgIDx1eC1pY29uIG5hbWU9XCJ2b2x1bWUtbXV0ZVwiPjwvdXgtaWNvbj5cbiAgICAgIH1cbiAgICAgIEBpZiAodm9sdW1lID4gMCAmJiB2b2x1bWUgPD0gNzApIHtcbiAgICAgICAgPHV4LWljb24gbmFtZT1cInZvbHVtZS1sb3dcIj48L3V4LWljb24+XG4gICAgICB9XG4gICAgICBAaWYgKHZvbHVtZSA+IDcwKSB7XG4gICAgICAgIDx1eC1pY29uIG5hbWU9XCJ2b2x1bWVcIj48L3V4LWljb24+XG4gICAgICB9XG4gICAgPC9idXR0b24+XG5cbiAgICA8ZGl2IGNsYXNzPVwidm9sdW1lLXNsaWRlci1ub2RlXCI+XG4gICAgICA8dXgtc2xpZGVyIFt2YWx1ZV09XCJ2b2x1bWVcIiAodmFsdWVDaGFuZ2UpPVwidm9sdW1lID0gJGFueSgkZXZlbnQpXCIgW29wdGlvbnNdPVwib3B0aW9uc1wiPjwvdXgtc2xpZGVyPlxuICAgIDwvZGl2PlxuICA8L2Rpdj5cbjwvZGl2PlxuXG48YnV0dG9uICNzdGFydEJ1dHRvblxuICB1eEZvY3VzSW5kaWNhdG9yXG4gIHR5cGU9XCJidXR0b25cIlxuICBjbGFzcz1cImNvbnRyb2wtYnV0dG9uXCJcbiAgKGNsaWNrKT1cImdvVG9TdGFydCgpXCJcbiAgKG1vdXNldXApPVwic3RhcnRCdXR0b24uYmx1cigpXCJcbiAgW2F0dHIuYXJpYS1sYWJlbF09XCJtZWRpYVBsYXllclNlcnZpY2UuZ29Ub1N0YXJ0QXJpYUxhYmVsXCI+XG5cbiAgPHN2ZyB2aWV3Qm94PVwiMCAwIDUxLjUgNjRcIiB3aWR0aD1cIjE0XCIgaGVpZ2h0PVwiMTdcIiBmb2N1c2FibGU9XCJmYWxzZVwiPlxuICAgIDxyZWN0IHg9XCIwXCIgeT1cIjBcIiB3aWR0aD1cIjcuNVwiIGhlaWdodD1cIjY0XCIgLz5cbiAgICA8cG9seWdvbiBwb2ludHM9XCI1MS41LDY0IDUxLjUsMCA3LjQsMzIgXCIgLz5cbiAgPC9zdmc+XG48L2J1dHRvbj5cblxuPGJ1dHRvbiAjcGxheUJ1dHRvblxuICB1eEZvY3VzSW5kaWNhdG9yXG4gIHR5cGU9XCJidXR0b25cIlxuICBjbGFzcz1cImNvbnRyb2wtYnV0dG9uXCJcbiAgW2F0dHIuYXJpYS1sYWJlbF09XCJtZWRpYVBsYXllclNlcnZpY2UucGxheUFyaWFMYWJlbChtZWRpYVBsYXllclNlcnZpY2UucGxheWluZyB8IGFzeW5jKVwiXG4gIChjbGljayk9XCJtZWRpYVBsYXllclNlcnZpY2UudG9nZ2xlUGxheSgpXCJcbiAgKG1vdXNldXApPVwicGxheUJ1dHRvbi5ibHVyKClcIj5cblxuICBAaWYgKChtZWRpYVBsYXllclNlcnZpY2UucGxheWluZyB8IGFzeW5jKSA9PT0gZmFsc2UpIHtcbiAgICA8c3ZnIHZpZXdCb3g9XCIwIDAgNDUgNjRcIiB3aWR0aD1cIjIwXCIgaGVpZ2h0PVwiMjlcIiBmb2N1c2FibGU9XCJmYWxzZVwiPlxuICAgICAgPHBvbHlnb24gcG9pbnRzPVwiMC40LDAgMC40LDY0IDQ0LjYsMzJcIiAvPlxuICAgIDwvc3ZnPlxuICB9XG4gIEBpZiAobWVkaWFQbGF5ZXJTZXJ2aWNlLnBsYXlpbmcgfCBhc3luYykge1xuICAgIDxzdmcgdmlld0JveD1cIjAgMCA0MyA1Ni45XCIgd2lkdGg9XCIyMFwiIGhlaWdodD1cIjI5XCIgZm9jdXNhYmxlPVwiZmFsc2VcIj5cbiAgICAgIDxyZWN0IHk9XCIwLjFcIiB3aWR0aD1cIjE1LjdcIiBoZWlnaHQ9XCI1Ni45XCIgLz5cbiAgICAgIDxyZWN0IHg9XCIyNy4zXCIgeT1cIjAuMVwiIHdpZHRoPVwiMTUuN1wiIGhlaWdodD1cIjU2LjlcIiAvPlxuICAgIDwvc3ZnPlxuICB9XG48L2J1dHRvbj5cblxuPGJ1dHRvbiAjZW5kQnV0dG9uXG4gIHV4Rm9jdXNJbmRpY2F0b3JcbiAgdHlwZT1cImJ1dHRvblwiXG4gIGNsYXNzPVwiY29udHJvbC1idXR0b25cIlxuICAoY2xpY2spPVwiZ29Ub0VuZCgpXCJcbiAgKG1vdXNldXApPVwiZW5kQnV0dG9uLmJsdXIoKVwiXG4gIFthdHRyLmFyaWEtbGFiZWxdPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLmdvVG9FbmRBcmlhTGFiZWxcIj5cblxuICA8c3ZnIHZpZXdCb3g9XCIwIDAgNTEuNSA2NFwiIHdpZHRoPVwiMTRcIiBoZWlnaHQ9XCIxN1wiIGZvY3VzYWJsZT1cImZhbHNlXCI+XG4gICAgPHJlY3QgeD1cIjQ0LjFcIiB5PVwiMFwiIHdpZHRoPVwiNy41XCIgaGVpZ2h0PVwiNjRcIiAvPlxuICAgIDxwb2x5Z29uIHBvaW50cz1cIjAsNjQgMCwwIDQ0LjEsMzJcIiAvPlxuICA8L3N2Zz5cbjwvYnV0dG9uPlxuXG48ZGl2IGNsYXNzPVwiYWN0aW9ucy1saXN0XCI+XG5cbiAgPG5nLWNvbnRlbnQ+PC9uZy1jb250ZW50PlxuXG4gIEBpZiAobWVkaWFQbGF5ZXJTZXJ2aWNlLnRleHRUcmFja3MubGVuZ3RoID4gMCAmJiBtZWRpYVBsYXllclNlcnZpY2UudHlwZSA9PT0gJ3ZpZGVvJykge1xuICAgIDxkaXYgY2xhc3M9XCJhY3Rpb24tYnV0dG9uLWNvbnRhaW5lclwiPlxuICAgICAgPGJ1dHRvbiAjc3VidGl0bGVzQnV0dG9uXG4gICAgICAgIHV4Rm9jdXNJbmRpY2F0b3JcbiAgICAgICAgdHlwZT1cImJ1dHRvblwiXG4gICAgICAgIGNsYXNzPVwiYWN0aW9uLWJ1dHRvblwiXG4gICAgICAgIChrZXlkb3duKT1cInJldHVybkZvY3VzID0gdHJ1ZVwiXG4gICAgICAgIChjbGljayk9XCJzdWJ0aXRsZXNPcGVuID0gIXN1YnRpdGxlc09wZW5cIlxuICAgICAgICAobW91c2V1cCk9XCJzdWJ0aXRsZXNCdXR0b24uYmx1cigpOyByZXR1cm5Gb2N1cyA9IGZhbHNlXCJcbiAgICAgICAgW2F0dHIuYXJpYS1sYWJlbF09XCJtZWRpYVBsYXllclNlcnZpY2Uuc2VsZWN0U3VidGl0bGVzQXJpYUxhYmVsKGdldFN1YnRpdGxlVHJhY2soKSlcIlxuICAgICAgICBbYXR0ci5hcmlhLWV4cGFuZGVkXT1cInN1YnRpdGxlc09wZW5cIlxuICAgICAgICBbYXR0ci5hcmlhLWRlc2NyaWJlZGJ5XT1cInN1YnRpdGxlc0lkXCJcbiAgICAgICAgYXJpYS1oYXNwb3B1cD1cInRydWVcIj5cbiAgICAgICAgPHV4LWljb24gbmFtZT1cInN1YnRpdGxlc1wiPjwvdXgtaWNvbj5cbiAgICAgIDwvYnV0dG9uPlxuICAgICAgQGlmIChzdWJ0aXRsZXNPcGVuKSB7XG4gICAgICAgIDxkaXYgI3N1YnRpdGxlc1xuICAgICAgICAgIFtzdHlsZS50b3AucHhdPVwiLXN1YnRpdGxlcy5vZmZzZXRIZWlnaHRcIlxuICAgICAgICAgIGNsYXNzPVwicG9wb3ZlciB0b3AgbWVkaWEtcGxheWVyLXN1YnRpdGxlcy1wb3BvdmVyIHNob3dcIlxuICAgICAgICAgIFtpZF09XCJzdWJ0aXRsZXNJZFwiXG4gICAgICAgICAgKGtleWRvd24uZXNjYXBlKT1cInN1YnRpdGxlc09wZW4gPSBmYWxzZVwiXG4gICAgICAgICAgKHV4Q2xpY2tPdXRzaWRlKT1cInN1YnRpdGxlc09wZW4gPSBmYWxzZVwiXG4gICAgICAgICAgPlxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJhcnJvd1wiPjwvZGl2PlxuICAgICAgICAgIDxoMyBjbGFzcz1cInBvcG92ZXItdGl0bGVcIj57eyBtZWRpYVBsYXllclNlcnZpY2Uuc3VidGl0bGVzVGl0bGVBcmlhTGFiZWwgfX08L2gzPlxuICAgICAgICAgIDxkaXYgY2xhc3M9XCJwb3BvdmVyLWNvbnRlbnRcIj5cbiAgICAgICAgICAgIDx1bCBjbGFzcz1cInN1YnRpdGxlcy1saXN0XCIgdXhUYWJiYWJsZUxpc3QgW2ZvY3VzT25TaG93XT1cInJldHVybkZvY3VzXCIgW3JldHVybkZvY3VzXT1cInJldHVybkZvY3VzXCI+XG4gICAgICAgICAgICAgIDxsaSB1eFRhYmJhYmxlTGlzdEl0ZW1cbiAgICAgICAgICAgICAgICB0YWJpbmRleD1cIjBcIlxuICAgICAgICAgICAgICAgIGNsYXNzPVwic3VidGl0bGVzLWxpc3QtaXRlbVwiXG4gICAgICAgICAgICAgICAgW2NsYXNzLmFjdGl2ZV09XCIhaXNTdWJ0aXRsZUFjdGl2ZSgpXCJcbiAgICAgICAgICAgICAgICBbYXR0ci5hcmlhLXNlbGVjdGVkXT1cImlzU3VidGl0bGVBY3RpdmUoKVwiXG4gICAgICAgICAgICAgICAgKGNsaWNrKT1cIm1lZGlhUGxheWVyU2VydmljZS5oaWRlU3VidGl0bGVUcmFja3MoKTsgc3VidGl0bGVzT3BlbiA9IGZhbHNlXCJcbiAgICAgICAgICAgICAgICAoa2V5ZG93bi5lbnRlcik9XCJtZWRpYVBsYXllclNlcnZpY2UuaGlkZVN1YnRpdGxlVHJhY2tzKCk7IHN1YnRpdGxlc09wZW4gPSBmYWxzZTsgcmV0dXJuRm9jdXMgPSB0cnVlXCI+XG4gICAgICAgICAgICAgICAgPHV4LWljb24gbmFtZT1cImNoZWNrbWFya1wiIGNsYXNzPVwic3VidGl0bGVzLWxpc3QtaXRlbS1jaGVja21hcmtcIj48L3V4LWljb24+XG4gICAgICAgICAgICAgICAgPHNwYW4+e3sgbWVkaWFQbGF5ZXJTZXJ2aWNlLnN1YnRpdGxlc09mZkFyaWFMYWJlbCB9fTwvc3Bhbj5cbiAgICAgICAgICAgICAgPC9saT5cbiAgICAgICAgICAgICAgQGZvciAodHJhY2sgb2YgbWVkaWFQbGF5ZXJTZXJ2aWNlLnRleHRUcmFja3M7IHRyYWNrIHRyYWNrKSB7XG4gICAgICAgICAgICAgICAgPGxpIHV4VGFiYmFibGVMaXN0SXRlbVxuICAgICAgICAgICAgICAgICAgY2xhc3M9XCJzdWJ0aXRsZXMtbGlzdC1pdGVtXCJcbiAgICAgICAgICAgICAgICAgIFtjbGFzcy5hY3RpdmVdPVwidHJhY2subW9kZSA9PT0gJ3Nob3dpbmcnXCJcbiAgICAgICAgICAgICAgICAgIFthdHRyLmFyaWEtc2VsZWN0ZWRdPVwiaXNTdWJ0aXRsZUFjdGl2ZSgpXCJcbiAgICAgICAgICAgICAgICAgIChjbGljayk9XCJzZXRTdWJ0aXRsZVRyYWNrKHRyYWNrKTsgc3VidGl0bGVzT3BlbiA9IGZhbHNlXCJcbiAgICAgICAgICAgICAgICAgIChrZXlkb3duLmVudGVyKT1cInNldFN1YnRpdGxlVHJhY2sodHJhY2spOyBzdWJ0aXRsZXNPcGVuID0gZmFsc2U7IHJldHVybkZvY3VzID0gdHJ1ZVwiPlxuICAgICAgICAgICAgICAgICAgPHV4LWljb24gbmFtZT1cImNoZWNrbWFya1wiIGNsYXNzPVwic3VidGl0bGVzLWxpc3QtaXRlbS1jaGVja21hcmtcIj48L3V4LWljb24+XG4gICAgICAgICAgICAgICAgICA8c3Bhbj57eyB0cmFjay5sYWJlbCB9fTwvc3Bhbj5cbiAgICAgICAgICAgICAgICA8L2xpPlxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICA8L3VsPlxuICAgICAgICAgIDwvZGl2PlxuICAgICAgICA8L2Rpdj5cbiAgICAgIH1cbiAgICA8L2Rpdj5cbiAgfVxuXG4gIDxkaXYgY2xhc3M9XCJhY3Rpb24tYnV0dG9uLWNvbnRhaW5lclwiPlxuICAgIEBpZiAobWVkaWFQbGF5ZXJTZXJ2aWNlLnR5cGUgIT09ICdhdWRpbycpIHtcbiAgICAgIDxidXR0b24gI2Z1bGxzY3JlZW5CdXR0b25cbiAgICAgICAgdXhGb2N1c0luZGljYXRvclxuICAgICAgICB0eXBlPVwiYnV0dG9uXCJcbiAgICAgICAgY2xhc3M9XCJhY3Rpb24tYnV0dG9uXCJcbiAgICAgICAgW2F0dHIuYXJpYS1sYWJlbF09XCJtZWRpYVBsYXllclNlcnZpY2UuZnVsbHNjcmVlbkFyaWFMYWJlbChtZWRpYVBsYXllclNlcnZpY2UuZnVsbHNjcmVlbilcIlxuICAgICAgICAoY2xpY2spPVwibWVkaWFQbGF5ZXJTZXJ2aWNlLnRvZ2dsZUZ1bGxzY3JlZW4oKVwiXG4gICAgICAgIChtb3VzZXVwKT1cImZ1bGxzY3JlZW5CdXR0b24uYmx1cigpXCI+XG4gICAgICAgIDx1eC1pY29uIFtuYW1lXT1cIm1lZGlhUGxheWVyU2VydmljZS5mdWxsc2NyZWVuID8gJ2NvbnRyYWN0JyA6ICdleHBhbmQnXCI+PC91eC1pY29uPlxuICAgICAgPC9idXR0b24+XG4gICAgfVxuICA8L2Rpdj5cbjwvZGl2PlxuXG5cblxuPG5nLXRlbXBsYXRlICNtdXRlVG9vbHRpcD5cbiAgPHNwYW4gYXJpYS1oaWRkZW49XCJ0cnVlXCI+e3sgdm9sdW1lID09PSAwID8gJ1VubXV0ZScgOiAnTXV0ZScgfX08L3NwYW4+XG48L25nLXRlbXBsYXRlPlxuIl19