@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
150 lines • 44.5 kB
JavaScript
import { Component, Input, Optional } from '@angular/core';
import { WidgetConfigComponent } from '@c8y/ngx-components/context-dashboard';
import { C8yValidators, FilesService, AlertService, gettext, FormGroupComponent, MessagesComponent, MessageDirective, C8yTranslatePipe, DropAreaComponent } from '@c8y/ngx-components';
import { get } from 'lodash-es';
import { defaultObjectFitValue, defaultObjectPositionValue } from '../image-widget.model';
import { ControlContainer, FormBuilder, NgForm, ReactiveFormsModule, Validators } from '@angular/forms';
import { BehaviorSubject, merge, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators';
import { ImageWidgetService } from '../image-widget.service';
import { AsyncPipe, NgClass, NgForOf, NgIf, NgStyle } from '@angular/common';
import * as i0 from "@angular/core";
import * as i1 from "@angular/forms";
import * as i2 from "@c8y/ngx-components";
import * as i3 from "../image-widget.service";
import * as i4 from "@c8y/ngx-components/context-dashboard";
export class ImageWidgetConfigComponent {
constructor(formBuilder, form, fileService, alert, imageWidget, widgetConfig) {
this.formBuilder = formBuilder;
this.form = form;
this.fileService = fileService;
this.alert = alert;
this.imageWidget = imageWidget;
this.widgetConfig = widgetConfig;
this.imageBinaryId$ = new BehaviorSubject(null);
this.loading = false;
this.destroyed$ = new Subject();
this.objectFitOptions = [
{
label: gettext('Contain`verb, image fitting option`'),
value: 'contain',
description: gettext('The image is entirely displayed within the widget while preserving the aspect ratio.')
},
{
label: gettext('Cover`verb, image fitting option`'),
value: 'cover',
description: gettext('The image is resized to fill the widget while preserving the aspect ratio. Overflowing areas are clipped.')
},
{
label: gettext('Fill`verb, image fitting option`'),
value: 'fill',
description: gettext('The image is stretched to fill the widget, overriding the aspect-ratio.')
},
{
label: gettext('Full width`image fitting option`'),
value: 'full-width',
description: gettext(`The image is resized to fit the widget's width while preserving the aspect ratio. Overflowing area is scrollable.`)
}
];
}
async onBeforeSave(config) {
if (this.formGroup.invalid) {
return false;
}
const fileFromForm = this.getFileFromFormValue(this.formGroup.value);
if (fileFromForm && fileFromForm !== this.fileFromConfig) {
try {
const imageBinaryId = await this.imageWidget.uploadFile(fileFromForm, {
dashboardMoId: this.getDashboardMoId(),
isDeviceTypeDashboard: this.widgetConfig?.isDeviceTypeDashboard
});
Object.assign(config, { imageBinaryId });
}
catch (e) {
this.alert.danger(gettext('Unable to upload image.'), e?.data);
return false;
}
}
const styling = this.formGroup.value.styling;
Object.assign(config, { styling });
return true;
}
ngOnInit() {
this.initForm();
const imageFromConfig = this.imageBinaryId$.pipe(filter(id => !!id), distinctUntilChanged(), tap(() => (this.loading = true)), switchMap(imageBinaryId => this.imageWidget.getImageDetails(imageBinaryId)), tap(details => {
this.loading = false;
this.fileFromConfig = details.file;
if (details) {
this.formGroup.patchValue({ images: [{ file: details.file, name: details.file.name }] });
}
}), map(details => details?.base64), shareReplay(1));
const selectedFile = this.formGroup.valueChanges.pipe(map(value => this.getFileFromFormValue(value)), distinctUntilChanged(), switchMap(file => (file ? this.fileService.toBase64(file) : Promise.resolve(''))));
this.imageSrc$ = merge(imageFromConfig, selectedFile).pipe(map(base64 => (base64 ? base64 : '')));
this.imageBinaryId$.next(this.config?.imageBinaryId);
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
getDashboardMoId() {
return get(this.widgetConfig, 'mo.id', null);
}
async initForm() {
const stylingFormGroup = this.formBuilder.group({
objectFit: [
// fallback to 'full-width' for old legacy widgets, newly created widgets should use defaultObjectFitValue
this.config.imageBinaryId && !this.config.styling ? 'full-width' : defaultObjectFitValue,
[Validators.required]
],
objectPositionX: [defaultObjectPositionValue, [Validators.required]],
objectPositionY: [defaultObjectPositionValue, [Validators.required]]
});
this.formGroup = this.formBuilder.group({
images: [
null,
[
Validators.required,
Validators.minLength(1),
Validators.maxLength(1),
C8yValidators.filesValidator({ maximumFileSizeInKb: 1000, typePrefix: 'image/' })
]
],
styling: stylingFormGroup
});
this.form.form.addControl('config', this.formGroup);
this.formGroup.patchValue(this.config);
this.formGroup.valueChanges
.pipe(takeUntil(this.destroyed$))
.subscribe(changes => this.setStyling(changes));
}
getFileFromFormValue(formValue) {
const images = formValue?.images || [];
return images[0]?.file || null;
}
setStyling(config) {
this.styling = this.imageWidget.getStyling(config);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ImageWidgetConfigComponent, deps: [{ token: i1.FormBuilder }, { token: i1.NgForm }, { token: i2.FilesService }, { token: i2.AlertService }, { token: i3.ImageWidgetService }, { token: i4.WidgetConfigComponent, optional: true }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: ImageWidgetConfigComponent, isStandalone: true, selector: "c8y-image-widget-config", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"p-l-24 p-r-24\">\n <div [formGroup]=\"formGroup\" class=\"row p-t-8\">\n <div class=\"col-md-12\">\n <c8y-form-group>\n <label>{{ 'Image' | translate }}</label>\n <c8y-drop-area\n formControlName=\"images\"\n class=\"drop-area-sm\"\n [icon]=\"'upload'\"\n [accept]=\"'image'\"\n [maxAllowedFiles]=\"1\"\n ></c8y-drop-area>\n <c8y-messages>\n <c8y-message\n name=\"maxFileSizeReached\"\n [text]=\"\n 'The maximum image size is {{ maxFileSize }} kB. The selected image has a size of {{ actualFileSize }} kB.'\n | translate\n \"\n ></c8y-message>\n <c8y-message\n name=\"wrongFileType\"\n [text]=\"'Wrong file type, select an image.' | translate\"\n ></c8y-message>\n <c8y-message name=\"maxlength\" [text]=\"'Only select one image.' | translate\"></c8y-message>\n <c8y-message name=\"required\" [text]=\"'An image is required.' | translate\"></c8y-message>\n <c8y-message name=\"minlength\" [text]=\"'An image is required.' | translate\"></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <ng-container *ngIf=\"imageSrc$ | async as src\">\n <div class=\"col-md-6\">\n <div class=\"legend form-block\">\n {{ 'Size and alignment' | translate }}\n </div>\n <div [formGroupName]=\"'styling'\">\n <c8y-form-group>\n <label>\n {{ 'Image display' | translate }}\n </label>\n <ul class=\"list-group separator-top-bottom\">\n <li\n class=\"list-group-item d-flex a-i-center p-l-0\"\n *ngFor=\"let objectFitOption of objectFitOptions; let i = index\"\n >\n <label class=\"c8y-radio\">\n <input\n type=\"radio\"\n [id]=\"'groupradiocontentclass' + i\"\n formControlName=\"objectFit\"\n [value]=\"objectFitOption.value\"\n />\n <span></span>\n <span class=\"l-h-1\">\n {{ objectFitOption.label | translate }}\n <br />\n <small class=\"text-muted\">\n {{ objectFitOption.description | translate }}\n </small>\n </span>\n </label>\n </li>\n </ul>\n </c8y-form-group>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group>\n <label>{{ 'Horizontal alignment' | translate }}</label>\n <div class=\"c8y-select-wrapper\">\n <select class=\"form-control\" formControlName=\"objectPositionX\">\n <option [ngValue]=\"'left'\">\n {{ 'left`horizontal alignment`' | translate }}\n </option>\n <option [ngValue]=\"'center'\">\n {{ 'center`horizontal alignment`' | translate }}\n </option>\n <option [ngValue]=\"'right'\">\n {{ 'right`horizontal alignment`' | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-md-6\">\n <c8y-form-group>\n <label>{{ 'Vertical alignment' | translate }}</label>\n <div class=\"c8y-select-wrapper\">\n <select class=\"form-control\" formControlName=\"objectPositionY\">\n <option [ngValue]=\"'top'\">\n {{ 'top`vertical alignment`' | translate }}\n </option>\n <option [ngValue]=\"'center'\">\n {{ 'center`vertical alignment`' | translate }}\n </option>\n <option [ngValue]=\"'bottom'\">\n {{ 'bottom`vertical alignment`' | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"legend form-block\">{{ 'Preview' | translate }}</div>\n <div class=\"form-group\">\n <div\n class=\"border-top border-right border-bottom border-left inner-scroll\"\n style=\"height: 300px\"\n >\n <img\n [src]=\"src\"\n [ngClass]=\"styling ? 'fit-h fit-w' : 'img-responsive'\"\n [ngStyle]=\"styling\"\n />\n </div>\n </div>\n </div>\n </ng-container>\n <div class=\"col-md-12 d-flex j-c-center\" *ngIf=\"loading\">\n <c8y-loading></c8y-loading>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.RadioControlValueAccessor, selector: "input[type=radio][formControlName],input[type=radio][formControl],input[type=radio][ngModel]", inputs: ["name", "formControlName", "value"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: DropAreaComponent, selector: "c8y-drop-area", inputs: ["formControl", "title", "message", "icon", "loadingMessage", "forceHideList", "alwaysShow", "clickToOpen", "loading", "progress", "maxAllowedFiles", "files", "maxFileSizeInMegaBytes", "accept"], outputs: ["dropped"] }], viewProviders: [{ provide: ControlContainer, useExisting: NgForm }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ImageWidgetConfigComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-image-widget-config', viewProviders: [{ provide: ControlContainer, useExisting: NgForm }], standalone: true, imports: [
ReactiveFormsModule,
FormGroupComponent,
MessagesComponent,
MessageDirective,
C8yTranslatePipe,
AsyncPipe,
NgIf,
NgStyle,
NgClass,
NgForOf,
DropAreaComponent
], template: "<div class=\"p-l-24 p-r-24\">\n <div [formGroup]=\"formGroup\" class=\"row p-t-8\">\n <div class=\"col-md-12\">\n <c8y-form-group>\n <label>{{ 'Image' | translate }}</label>\n <c8y-drop-area\n formControlName=\"images\"\n class=\"drop-area-sm\"\n [icon]=\"'upload'\"\n [accept]=\"'image'\"\n [maxAllowedFiles]=\"1\"\n ></c8y-drop-area>\n <c8y-messages>\n <c8y-message\n name=\"maxFileSizeReached\"\n [text]=\"\n 'The maximum image size is {{ maxFileSize }} kB. The selected image has a size of {{ actualFileSize }} kB.'\n | translate\n \"\n ></c8y-message>\n <c8y-message\n name=\"wrongFileType\"\n [text]=\"'Wrong file type, select an image.' | translate\"\n ></c8y-message>\n <c8y-message name=\"maxlength\" [text]=\"'Only select one image.' | translate\"></c8y-message>\n <c8y-message name=\"required\" [text]=\"'An image is required.' | translate\"></c8y-message>\n <c8y-message name=\"minlength\" [text]=\"'An image is required.' | translate\"></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <ng-container *ngIf=\"imageSrc$ | async as src\">\n <div class=\"col-md-6\">\n <div class=\"legend form-block\">\n {{ 'Size and alignment' | translate }}\n </div>\n <div [formGroupName]=\"'styling'\">\n <c8y-form-group>\n <label>\n {{ 'Image display' | translate }}\n </label>\n <ul class=\"list-group separator-top-bottom\">\n <li\n class=\"list-group-item d-flex a-i-center p-l-0\"\n *ngFor=\"let objectFitOption of objectFitOptions; let i = index\"\n >\n <label class=\"c8y-radio\">\n <input\n type=\"radio\"\n [id]=\"'groupradiocontentclass' + i\"\n formControlName=\"objectFit\"\n [value]=\"objectFitOption.value\"\n />\n <span></span>\n <span class=\"l-h-1\">\n {{ objectFitOption.label | translate }}\n <br />\n <small class=\"text-muted\">\n {{ objectFitOption.description | translate }}\n </small>\n </span>\n </label>\n </li>\n </ul>\n </c8y-form-group>\n <div class=\"row\">\n <div class=\"col-md-6\">\n <c8y-form-group>\n <label>{{ 'Horizontal alignment' | translate }}</label>\n <div class=\"c8y-select-wrapper\">\n <select class=\"form-control\" formControlName=\"objectPositionX\">\n <option [ngValue]=\"'left'\">\n {{ 'left`horizontal alignment`' | translate }}\n </option>\n <option [ngValue]=\"'center'\">\n {{ 'center`horizontal alignment`' | translate }}\n </option>\n <option [ngValue]=\"'right'\">\n {{ 'right`horizontal alignment`' | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n <div class=\"col-md-6\">\n <c8y-form-group>\n <label>{{ 'Vertical alignment' | translate }}</label>\n <div class=\"c8y-select-wrapper\">\n <select class=\"form-control\" formControlName=\"objectPositionY\">\n <option [ngValue]=\"'top'\">\n {{ 'top`vertical alignment`' | translate }}\n </option>\n <option [ngValue]=\"'center'\">\n {{ 'center`vertical alignment`' | translate }}\n </option>\n <option [ngValue]=\"'bottom'\">\n {{ 'bottom`vertical alignment`' | translate }}\n </option>\n </select>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"legend form-block\">{{ 'Preview' | translate }}</div>\n <div class=\"form-group\">\n <div\n class=\"border-top border-right border-bottom border-left inner-scroll\"\n style=\"height: 300px\"\n >\n <img\n [src]=\"src\"\n [ngClass]=\"styling ? 'fit-h fit-w' : 'img-responsive'\"\n [ngStyle]=\"styling\"\n />\n </div>\n </div>\n </div>\n </ng-container>\n <div class=\"col-md-12 d-flex j-c-center\" *ngIf=\"loading\">\n <c8y-loading></c8y-loading>\n </div>\n </div>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i1.NgForm }, { type: i2.FilesService }, { type: i2.AlertService }, { type: i3.ImageWidgetService }, { type: i4.WidgetConfigComponent, decorators: [{
type: Optional
}] }], propDecorators: { config: [{
type: Input
}] } });
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2Utd2lkZ2V0LWNvbmZpZy5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi93aWRnZXRzL2ltcGxlbWVudGF0aW9ucy9pbWFnZS9pbWFnZS13aWRnZXQtY29uZmlnL2ltYWdlLXdpZGdldC1jb25maWcuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vLi4vLi4vd2lkZ2V0cy9pbXBsZW1lbnRhdGlvbnMvaW1hZ2UvaW1hZ2Utd2lkZ2V0LWNvbmZpZy9pbWFnZS13aWRnZXQtY29uZmlnLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFxQixRQUFRLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDOUUsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sdUNBQXVDLENBQUM7QUFDOUUsT0FBTyxFQUNMLGFBQWEsRUFFYixZQUFZLEVBQ1osWUFBWSxFQUNaLE9BQU8sRUFDUCxrQkFBa0IsRUFDbEIsaUJBQWlCLEVBQ2pCLGdCQUFnQixFQUNoQixnQkFBZ0IsRUFDaEIsaUJBQWlCLEVBQ2xCLE1BQU0scUJBQXFCLENBQUM7QUFDN0IsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNoQyxPQUFPLEVBQ0wscUJBQXFCLEVBQ3JCLDBCQUEwQixFQUkzQixNQUFNLHVCQUF1QixDQUFDO0FBQy9CLE9BQU8sRUFDTCxnQkFBZ0IsRUFDaEIsV0FBVyxFQUVYLE1BQU0sRUFDTixtQkFBbUIsRUFDbkIsVUFBVSxFQUNYLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEIsT0FBTyxFQUFFLGVBQWUsRUFBYyxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ25FLE9BQU8sRUFDTCxvQkFBb0IsRUFDcEIsTUFBTSxFQUNOLEdBQUcsRUFDSCxXQUFXLEVBQ1gsU0FBUyxFQUNULFNBQVMsRUFDVCxHQUFHLEVBQ0osTUFB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QUFVLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDckQsQ0FBQzsrR0FsSlUsMEJBQTBCO21HQUExQiwwQkFBMEIsaUhDOUR2Qyx5NkpBNkhBLDJDRDVFSSxtQkFBbUIseWpEQUNuQixrQkFBa0IsdUlBQ2xCLGlCQUFpQixzR0FDakIsZ0JBQWdCLDZFQUNoQixnQkFBZ0IsNkNBQ2hCLFNBQVMsOENBQ1QsSUFBSSw2RkFDSixPQUFPLDJFQUNQLE9BQU8sb0ZBQ1AsT0FBTyxtSEFDUCxpQkFBaUIsaVJBYkosQ0FBQyxFQUFFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxXQUFXLEVBQUUsTUFBTSxFQUFFLENBQUM7OzRGQWdCeEQsMEJBQTBCO2tCQW5CdEMsU0FBUzsrQkFDRSx5QkFBeUIsaUJBRXBCLENBQUMsRUFBRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLE1BQU0sRUFBRSxDQUFDLGNBQ3ZELElBQUksV0FDUDt3QkFDUCxtQkFBbUI7d0JBQ25CLGtCQUFrQjt3QkFDbEIsaUJBQWlCO3dCQUNqQixnQkFBZ0I7d0JBQ2hCLGdCQUFnQjt3QkFDaEIsU0FBUzt3QkFDVCxJQUFJO3dCQUNKLE9BQU87d0JBQ1AsT0FBTzt3QkFDUCxPQUFPO3dCQUNQLGlCQUFpQjtxQkFDbEI7OzBCQWdERSxRQUFRO3lDQTdDRixNQUFNO3NCQUFkLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDb21wb25lbnQsIElucHV0LCBPbkRlc3Ryb3ksIE9uSW5pdCwgT3B0aW9uYWwgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFdpZGdldENvbmZpZ0NvbXBvbmVudCB9IGZyb20gJ0BjOHkvbmd4LWNvbXBvbmVudHMvY29udGV4dC1kYXNoYm9hcmQnO1xuaW1wb3J0IHtcbiAgQzh5VmFsaWRhdG9ycyxcbiAgT25CZWZvcmVTYXZlLFxuICBGaWxlc1NlcnZpY2UsXG4gIEFsZXJ0U2VydmljZSxcbiAgZ2V0dGV4dCxcbiAgRm9ybUdyb3VwQ29tcG9uZW50LFxuICBNZXNzYWdlc0NvbXBvbmVudCxcbiAgTWVzc2FnZURpcmVjdGl2ZSxcbiAgQzh5VHJhbnNsYXRlUGlwZSxcbiAgRHJvcEFyZWFDb21wb25lbnRcbn0gZnJvbSAnQGM4eS9uZ3gtY29tcG9uZW50cyc7XG5pbXBvcnQgeyBnZXQgfSBmcm9tICdsb2Rhc2gtZXMnO1xuaW1wb3J0IHtcbiAgZGVmYXVsdE9iamVjdEZpdFZhbHVlLFxuICBkZWZhdWx0T2JqZWN0UG9zaXRpb25WYWx1ZSxcbiAgSW1hZ2VXaWRnZXRDb25maWcsXG4gIEltYWdlV2lkZ2V0Q29uZmlnU3R5bGluZyxcbiAgSW1hZ2VXaWRnZXRTdHlsZVxufSBmcm9tICcuLi9pbWFnZS13aWRnZXQubW9kZWwnO1xuaW1wb3J0IHtcbiAgQ29udHJvbENvbnRhaW5lcixcbiAgRm9ybUJ1aWxkZXIsXG4gIEZvcm1Hcm91cCxcbiAgTmdGb3JtLFxuICBSZWFjdGl2ZUZvcm1zTW9kdWxlLFxuICBWYWxpZGF0b3JzXG59IGZyb20gJ0Bhbmd1bGFyL2Zvcm1zJztcbmltcG9ydCB7IEJlaGF2aW9yU3ViamVjdCwgT2JzZXJ2YWJsZSwgbWVyZ2UsIFN1YmplY3QgfSBmcm9tICdyeGpzJztcbmltcG9ydCB7XG4gIGRpc3RpbmN0VW50aWxDaGFuZ2VkLFxuICBmaWx0ZXIsXG4gIG1hcCxcbiAgc2hhcmVSZXBsYXksXG4gIHN3aXRjaE1hcCxcbiAgdGFrZVVudGlsLFxuICB0YXBcbn0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xuaW1wb3J0IHsgSW1hZ2VXaWRnZXRTZXJ2aWNlIH0gZnJvbSAnLi4vaW1hZ2Utd2lkZ2V0LnNlcnZpY2UnO1xuaW1wb3J0IHsgQXN5bmNQaXBlLCBOZ0NsYXNzLCBOZ0Zvck9mLCBOZ0lmLCBOZ1N0eWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uJztcblxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnYzh5LWltYWdlLXdpZGdldC1jb25maWcnLFxuICB0ZW1wbGF0ZVVybDogJy4vaW1hZ2Utd2lkZ2V0LWNvbmZpZy5jb21wb25lbnQuaHRtbCcsXG4gIHZpZXdQcm92aWRlcnM6IFt7IHByb3ZpZGU6IENvbnRyb2xDb250YWluZXIsIHVzZUV4aXN0aW5nOiBOZ0Zvcm0gfV0sXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtcbiAgICBSZWFjdGl2ZUZvcm1zTW9kdWxlLFxuICAgIEZvcm1Hcm91cENvbXBvbmVudCxcbiAgICBNZXNzYWdlc0NvbXBvbmVudCxcbiAgICBNZXNzYWdlRGlyZWN0aXZlLFxuICAgIEM4eVRyYW5zbGF0ZVBpcGUsXG4gICAgQXN5bmNQaXBlLFxuICAgIE5nSWYsXG4gICAgTmdTdHlsZSxcbiAgICBOZ0NsYXNzLFxuICAgIE5nRm9yT2YsXG4gICAgRHJvcEFyZWFDb21wb25lbnRcbiAgXVxufSlcbmV4cG9ydCBjbGFzcyBJbWFnZVdpZGdldENvbmZpZ0NvbXBvbmVudCBpbXBsZW1lbnRzIE9uSW5pdCwgT25EZXN0cm95LCBPbkJlZm9yZVNhdmUge1xuICBASW5wdXQoKSBjb25maWc6IEltYWdlV2lkZ2V0Q29uZmlnO1xuICBpbWFnZUJpbmFyeUlkJCA9IG5ldyBCZWhhdmlvclN1YmplY3Q8c3RyaW5nPihudWxsKTtcbiAgaW1hZ2VTcmMkOiBPYnNlcnZhYmxlPHN0cmluZz47XG4gIGxvYWRpbmcgPSBmYWxzZTtcbiAgZm9ybUdyb3VwOiBGb3JtR3JvdXA7XG4gIGZpbGVGcm9tQ29uZmlnOiBGaWxlO1xuICBzdHlsaW5nOiBJbWFnZVdpZGdldFN0eWxlIHwgbnVsbDtcbiAgZGVzdHJveWVkJCA9IG5ldyBTdWJqZWN0PHZvaWQ+KCk7XG4gIG9iamVjdEZpdE9wdGlvbnMgPSBbXG4gICAge1xuICAgICAgbGFiZWw6IGdldHRleHQoJ0NvbnRhaW5gdmVyYiwgaW1hZ2UgZml0dGluZyBvcHRpb25gJyksXG4gICAgICB2YWx1ZTogJ2NvbnRhaW4nLFxuICAgICAgZGVzY3JpcHRpb246IGdldHRleHQoXG4gICAgICAgICdUaGUgaW1hZ2UgaXMgZW50aXJlbHkgZGlzcGxheWVkIHdpdGhpbiB0aGUgd2lkZ2V0IHdoaWxlIHByZXNlcnZpbmcgdGhlIGFzcGVjdCByYXRpby4nXG4gICAgICApXG4gICAgfSxcbiAgICB7XG4gICAgICBsYWJlbDogZ2V0dGV4dCgnQ292ZXJgdmVyYiwgaW1hZ2UgZml0dGluZyBvcHRpb25gJyksXG4gICAgICB2YWx1ZTogJ2NvdmVyJyxcbiAgICAgIGRlc2NyaXB0aW9uOiBnZXR0ZXh0KFxuICAgICAgICAnVGhlIGltYWdlIGlzIHJlc2l6ZWQgdG8gZmlsbCB0aGUgd2lkZ2V0IHdoaWxlIHByZXNlcnZpbmcgdGhlIGFzcGVjdCByYXRpby4gT3ZlcmZsb3dpbmcgYXJlYXMgYXJlIGNsaXBwZWQuJ1xuICAgICAgKVxuICAgIH0sXG4gICAge1xuICAgICAgbGFiZWw6IGdldHRleHQoJ0ZpbGxgdmVyYiwgaW1hZ2UgZml0dGluZyBvcHRpb25gJyksXG4gICAgICB2YWx1ZTogJ2ZpbGwnLFxuICAgICAgZGVzY3JpcHRpb246IGdldHRleHQoXG4gICAgICAgICdUaGUgaW1hZ2UgaXMgc3RyZXRjaGVkIHRvIGZpbGwgdGhlIHdpZGdldCwgb3ZlcnJpZGluZyB0aGUgYXNwZWN0LXJhdGlvLidcbiAgICAgIClcbiAgICB9LFxuICAgIHtcbiAgICAgIGxhYmVsOiBnZXR0ZXh0KCdGdWxsIHdpZHRoYGltYWdlIGZpdHRpbmcgb3B0aW9uYCcpLFxuICAgICAgdmFsdWU6ICdmdWxsLXdpZHRoJyxcbiAgICAgIGRlc2NyaXB0aW9uOiBnZXR0ZXh0KFxuICAgICAgICBgVGhlIGltYWdlIGlzIHJlc2l6ZWQgdG8gZml0IHRoZSB3aWRnZXQncyB3aWR0aCB3aGlsZSBwcmVzZXJ2aW5nIHRoZSBhc3BlY3QgcmF0aW8uIE92ZXJmbG93aW5nIGFyZWEgaXMgc2Nyb2xsYWJsZS5gXG4gICAgICApXG4gICAgfVxuICBdO1xuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHByaXZhdGUgZm9ybUJ1aWxkZXI6IEZvcm1CdWlsZGVyLFxuICAgIHByaXZhdGUgZm9ybTogTmdGb3JtLFxuICAgIHByaXZhdGUgZmlsZVNlcnZpY2U6IEZpbGVzU2VydmljZSxcbiAgICBwcml2YXRlIGFsZXJ0OiBBbGVydFNlcnZpY2UsXG4gICAgcHJpdmF0ZSBpbWFnZVdpZGdldDogSW1hZ2VXaWRnZXRTZXJ2aWNlLFxuICAgIEBPcHRpb25hbCgpIHByaXZhdGUgd2lkZ2V0Q29uZmlnOiBXaWRnZXRDb25maWdDb21wb25lbnRcbiAgKSB7fVxuXG4gIGFzeW5jIG9uQmVmb3JlU2F2ZShjb25maWc/OiBJbWFnZVdpZGdldENvbmZpZyk6IFByb21pc2U8Ym9vbGVhbj4ge1xuICAgIGlmICh0aGlzLmZvcm1Hcm91cC5pbnZhbGlkKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgY29uc3QgZmlsZUZyb21Gb3JtID0gdGhpcy5nZXRGaWxlRnJvbUZvcm1WYWx1ZSh0aGlzLmZvcm1Hcm91cC52YWx1ZSk7XG4gICAgaWYgKGZpbGVGcm9tRm9ybSAmJiBmaWxlRnJvbUZvcm0gIT09IHRoaXMuZmlsZUZyb21Db25maWcpIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGltYWdlQmluYXJ5SWQgPSBhd2FpdCB0aGlzLmltYWdlV2lkZ2V0LnVwbG9hZEZpbGUoZmlsZUZyb21Gb3JtLCB7XG4gICAgICAgICAgZGFzaGJvYXJkTW9JZDogdGhpcy5nZXREYXNoYm9hcmRNb0lkKCksXG4gICAgICAgICAgaXNEZXZpY2VUeXBlRGFzaGJvYXJkOiB0aGlzLndpZGdldENvbmZpZz8uaXNEZXZpY2VUeXBlRGFzaGJvYXJkXG4gICAgICAgIH0pO1xuICAgICAgICBPYmplY3QuYXNzaWduKGNvbmZpZywgeyBpbWFnZUJpbmFyeUlkIH0pO1xuICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICB0aGlzLmFsZXJ0LmRhbmdlcihnZXR0ZXh0KCdVbmFibGUgdG8gdXBsb2FkIGltYWdlLicpLCBlPy5kYXRhKTtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgIH1cbiAgICBjb25zdCBzdHlsaW5nOiBJbWFnZVdpZGdldENvbmZpZ1N0eWxpbmcgPSB0aGlzLmZvcm1Hcm91cC52YWx1ZS5zdHlsaW5nO1xuICAgIE9iamVjdC5hc3NpZ24oY29uZmlnLCB7IHN0eWxpbmcgfSk7XG5cbiAgICByZXR1cm4gdHJ1ZTtcbiAgfVxuXG4gIG5nT25Jbml0KCkge1xuICAgIHRoaXMuaW5pdEZvcm0oKTtcbiAgICBjb25zdCBpbWFnZUZyb21Db25maWcgPSB0aGlzLmltYWdlQmluYXJ5SWQkLnBpcGUoXG4gICAgICBmaWx0ZXIoaWQgPT4gISFpZCksXG4gICAgICBkaXN0aW5jdFVudGlsQ2hhbmdlZCgpLFxuICAgICAgdGFwKCgpID0+ICh0aGlzLmxvYWRpbmcgPSB0cnVlKSksXG4gICAgICBzd2l0Y2hNYXAoaW1hZ2VCaW5hcnlJZCA9PiB0aGlzLmltYWdlV2lkZ2V0LmdldEltYWdlRGV0YWlscyhpbWFnZUJpbmFyeUlkKSksXG4gICAgICB0YXAoZGV0YWlscyA9PiB7XG4gICAgICAgIHRoaXMubG9hZGluZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLmZpbGVGcm9tQ29uZmlnID0gZGV0YWlscy5maWxlO1xuICAgICAgICBpZiAoZGV0YWlscykge1xuICAgICAgICAgIHRoaXMuZm9ybUdyb3VwLnBhdGNoVmFsdWUoeyBpbWFnZXM6IFt7IGZpbGU6IGRldGFpbHMuZmlsZSwgbmFtZTogZGV0YWlscy5maWxlLm5hbWUgfV0gfSk7XG4gICAgICAgIH1cbiAgICAgIH0pLFxuICAgICAgbWFwKGRldGFpbHMgPT4gZGV0YWlscz8uYmFzZTY0KSxcbiAgICAgIHNoYXJlUmVwbGF5KDEpXG4gICAgKTtcbiAgICBjb25zdCBzZWxlY3RlZEZpbGUgPSB0aGlzLmZvcm1Hcm91cC52YWx1ZUNoYW5nZXMucGlwZShcbiAgICAgIG1hcCh2YWx1ZSA9PiB0aGlzLmdldEZpbGVGcm9tRm9ybVZhbHVlKHZhbHVlKSksXG4gICAgICBkaXN0aW5jdFVudGlsQ2hhbmdlZCgpLFxuICAgICAgc3dpdGNoTWFwKGZpbGUgPT4gKGZpbGUgPyB0aGlzLmZpbGVTZXJ2aWNlLnRvQmFzZTY0KGZpbGUpIDogUHJvbWlzZS5yZXNvbHZlKCcnKSkpXG4gICAgKTtcbiAgICB0aGlzLmltYWdlU3JjJCA9IG1lcmdlKGltYWdlRnJvbUNvbmZpZywgc2VsZWN0ZWRGaWxlKS5waXBlKFxuICAgICAgbWFwKGJhc2U2NCA9PiAoYmFzZTY0ID8gKGJhc2U2NCBhcyBzdHJpbmcpIDogJycpKVxuICAgICk7XG4gICAgdGhpcy5pbWFnZUJpbmFyeUlkJC5uZXh0KHRoaXMuY29uZmlnPy5pbWFnZUJpbmFyeUlkKTtcbiAgfVxuXG4gIG5nT25EZXN0cm95KCk6IHZvaWQge1xuICAgIHRoaXMuZGVzdHJveWVkJC5uZXh0KCk7XG4gICAgdGhpcy5kZXN0cm95ZWQkLmNvbXBsZXRlKCk7XG4gIH1cblxuICBwcml2YXRlIGdldERhc2hib2FyZE1vSWQoKTogc3RyaW5nIHwgbnVsbCB7XG4gICAgcmV0dXJuIGdldCh0aGlzLndpZGdldENvbmZpZywgJ21vLmlkJywgbnVsbCk7XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGluaXRGb3JtKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IHN0eWxpbmdGb3JtR3JvdXAgPSB0aGlzLmZvcm1CdWlsZGVyLmdyb3VwKHtcbiAgICAgIG9iamVjdEZpdDogW1xuICAgICAgICAvLyBmYWxsYmFjayB0byAnZnVsbC13aWR0aCcgZm9yIG9sZCBsZWdhY3kgd2lkZ2V0cywgbmV3bHkgY3JlYXRlZCB3aWRnZXRzIHNob3VsZCB1c2UgZGVmYXVsdE9iamVjdEZpdFZhbHVlXG4gICAgICAgIHRoaXMuY29uZmlnLmltYWdlQmluYXJ5SWQgJiYgIXRoaXMuY29uZmlnLnN0eWxpbmcgPyAnZnVsbC13aWR0aCcgOiBkZWZhdWx0T2JqZWN0Rml0VmFsdWUsXG4gICAgICAgIFtWYWxpZGF0b3JzLnJlcXVpcmVkXVxuICAgICAgXSxcbiAgICAgIG9iamVjdFBvc2l0aW9uWDogW2RlZmF1bHRPYmplY3RQb3NpdGlvblZhbHVlLCBbVmFsaWRhdG9ycy5yZXF1aXJlZF1dLFxuICAgICAgb2JqZWN0UG9zaXRpb25ZOiBbZGVmYXVsdE9iamVjdFBvc2l0aW9uVmFsdWUsIFtWYWxpZGF0b3JzLnJlcXVpcmVkXV1cbiAgICB9KTtcbiAgICB0aGlzLmZvcm1Hcm91cCA9IHRoaXMuZm9ybUJ1aWxkZXIuZ3JvdXAoe1xuICAgICAgaW1hZ2VzOiBbXG4gICAgICAgIG51bGwsXG4gICAgICAgIFtcbiAgICAgICAgICBWYWxpZGF0b3JzLnJlcXVpcmVkLFxuICAgICAgICAgIFZhbGlkYXRvcnMubWluTGVuZ3RoKDEpLFxuICAgICAgICAgIFZhbGlkYXRvcnMubWF4TGVuZ3RoKDEpLFxuICAgICAgICAgIEM4eVZhbGlkYXRvcnMuZmlsZXNWYWxpZGF0b3IoeyBtYXhpbXVtRmlsZVNpemVJbktiOiAxMDAwLCB0eXBlUHJlZml4OiAnaW1hZ2UvJyB9KVxuICAgICAgICBdXG4gICAgICBdLFxuICAgICAgc3R5bGluZzogc3R5bGluZ0Zvcm1Hcm91cFxuICAgIH0pO1xuICAgIHRoaXMuZm9ybS5mb3JtLmFkZENvbnRyb2woJ2NvbmZpZycsIHRoaXMuZm9ybUdyb3VwKTtcbiAgICB0aGlzLmZvcm1Hcm91cC5wYXRjaFZhbHVlKHRoaXMuY29uZmlnKTtcbiAgICB0aGlzLmZvcm1Hcm91cC52YWx1ZUNoYW5nZXNcbiAgICAgIC5waXBlKHRha2VVbnRpbCh0aGlzLmRlc3Ryb3llZCQpKVxuICAgICAgLnN1YnNjcmliZShjaGFuZ2VzID0+IHRoaXMuc2V0U3R5bGluZyhjaGFuZ2VzKSk7XG4gIH1cblxuICBwcml2YXRlIGdldEZpbGVGcm9tRm9ybVZhbHVlKGZvcm1WYWx1ZTogYW55KTogRmlsZSB8IG51bGwge1xuICAgIGNvbnN0IGltYWdlczogYW55W10gPSBmb3JtVmFsdWU/LmltYWdlcyB8fCBbXTtcbiAgICByZXR1cm4gaW1hZ2VzWzBdPy5maWxlIHx8IG51bGw7XG4gIH1cblxuICBwcml2YXRlIHNldFN0eWxpbmcoY29uZmlnOiBJbWFnZVdpZGdldENvbmZpZyk6IHZvaWQge1xuICAgIHRoaXMuc3R5bGluZyA9IHRoaXMuaW1hZ2VXaWRnZXQuZ2V0U3R5bGluZyhjb25maWcpO1xuICB9XG59XG4iLCI8ZGl2IGNsYXNzPVwicC1sLTI0IHAtci0yNFwiPlxuICA8ZGl2IFtmb3JtR3JvdXBdPVwiZm9ybUdyb3VwXCIgY2xhc3M9XCJyb3cgcC10LThcIj5cbiAgICA8ZGl2IGNsYXNzPVwiY29sLW1kLTEyXCI+XG4gICAgICA8Yzh5LWZvcm0tZ3JvdXA+XG4gICAgICAgIDxsYWJlbD57eyAnSW1hZ2UnIHwgdHJhbnNsYXRlIH19PC9sYWJlbD5cbiAgICAgICAgPGM4eS1kcm9wLWFyZWFcbiAgICAgICAgICBmb3JtQ29udHJvbE5hbWU9XCJpbWFnZXNcIlxuICAgICAgICAgIGNsYXNzPVwiZHJvcC1hcmVhLXNtXCJcbiAgICAgICAgICBbaWNvbl09XCIndXBsb2FkJ1wiXG4gICAgICAgICAgW2FjY2VwdF09XCInaW1hZ2UnXCJcbiAgICAgICAgICBbbWF4QWxsb3dlZEZpbGVzXT1cIjFcIlxuICAgICAgICA+PC9jOHktZHJvcC1hcmVhPlxuICAgICAgICA8Yzh5LW1lc3NhZ2VzPlxuICAgICAgICAgIDxjOHktbWVzc2FnZVxuICAgICAgICAgICAgbmFtZT1cIm1heEZpbGVTaXplUmVhY2hlZFwiXG4gICAgICAgICAgICBbdGV4dF09XCJcbiAgICAgICAgICAgICAgJ1RoZSBtYXhpbXVtIGltYWdlIHNpemUgaXMge3sgbWF4RmlsZVNpemUgfX0ga0IuIFRoZSBzZWxlY3RlZCBpbWFnZSBoYXMgYSBzaXplIG9mIHt7IGFjdHVhbEZpbGVTaXplIH19IGtCLidcbiAgICAgICAgICAgICAgICB8IHRyYW5zbGF0ZVxuICAgICAgICAgICAgXCJcbiAgICAgICAgICA+PC9jOHktbWVzc2FnZT5cbiAgICAgICAgICA8Yzh5LW1lc3NhZ2VcbiAgICAgICAgICAgIG5hbWU9XCJ3cm9uZ0ZpbGVUeXBlXCJcbiAgICAgICAgICAgIFt0ZXh0XT1cIidXcm9uZyBmaWxlIHR5cGUsIHNlbGVjdCBhbiBpbWFnZS4nIHwgdHJhbnNsYXRlXCJcbiAgICAgICAgICA+PC9jOHktbWVzc2FnZT5cbiAgICAgICAgICA8Yzh5LW1lc3NhZ2UgbmFtZT1cIm1heGxlbmd0aFwiIFt0ZXh0XT1cIidPbmx5IHNlbGVjdCBvbmUgaW1hZ2UuJyB8IHRyYW5zbGF0ZVwiPjwvYzh5LW1lc3NhZ2U+XG4gICAgICAgICAgPGM4eS1tZXNzYWdlIG5hbWU9XCJyZXF1aXJlZFwiIFt0ZXh0XT1cIidBbiBpbWFnZSBpcyByZXF1aXJlZC4nIHwgdHJhbnNsYXRlXCI+PC9jOHktbWVzc2FnZT5cbiAgICAgICAgICA8Yzh5LW1lc3NhZ2UgbmFtZT1cIm1pbmxlbmd0aFwiIFt0ZXh0XT1cIidBbiBpbWFnZSBpcyByZXF1aXJlZC4nIHwgdHJhbnNsYXRlXCI+PC9jOHktbWVzc2FnZT5cbiAgICAgICAgPC9jOHktbWVzc2FnZXM+XG4gICAgICA8L2M4eS1mb3JtLWdyb3VwPlxuICAgIDwvZGl2PlxuICAgIDxuZy1jb250YWluZXIgKm5nSWY9XCJpbWFnZVNyYyQgfCBhc3luYyBhcyBzcmNcIj5cbiAgICAgIDxkaXYgY2xhc3M9XCJjb2wtbWQtNlwiPlxuICAgICAgICA8ZGl2IGNsYXNzPVwibGVnZW5kIGZvcm0tYmxvY2tcIj5cbiAgICAgICAgICB7eyAnU2l6ZSBhbmQgYWxpZ25tZW50JyB8IHRyYW5zbGF0ZSB9fVxuICAgICAgICA8L2Rpdj5cbiAgICAgICAgPGRpdiBbZm9ybUdyb3VwTmFtZV09XCInc3R5bGluZydcIj5cbiAgICAgICAgICA8Yzh5LWZvcm0tZ3JvdXA+XG4gICAgICAgICAgICA8bGFiZWw+XG4gICAgICAgICAgICAgIHt7ICdJbWFnZSBkaXNwbGF5JyB8IHRyYW5zbGF0ZSB9fVxuICAgICAgICAgICAgPC9sYWJlbD5cbiAgICAgICAgICAgIDx1bCBjbGFzcz1cImxpc3QtZ3JvdXAgc2VwYXJhdG9yLXRvcC1ib3R0b21cIj5cbiAgICAgICAgICAgICAgPGxpXG4gICAgICAgICAgICAgICAgY2xhc3M9XCJsaXN0LWdyb3VwLWl0ZW0gZC1mbGV4IGEtaS1jZW50ZXIgcC1sLTBcIlxuICAgICAgICAgICAgICAgICpuZ0Zvcj1cImxldCBvYmplY3RGaXRPcHRpb24gb2Ygb2JqZWN0Rml0T3B0aW9uczsgbGV0IGkgPSBpbmRleFwiXG4gICAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICA8bGFiZWwgY2xhc3M9XCJjOHktcmFkaW9cIj5cbiAgICAgICAgICAgICAgICAgIDxpbnB1dFxuICAgICAgICAgICAgICAgICAgICB0eXBlPVwicmFkaW9cIlxuICAgICAgICAgICAgICAgICAgICBbaWRdPVwiJ2dyb3VwcmFkaW9jb250ZW50Y2xhc3MnICsgaVwiXG4gICAgICAgICAgICAgICAgICAgIGZvcm1Db250cm9sTmFtZT1cIm9iamVjdEZpdFwiXG4gICAgICAgICAgICAgICAgICAgIFt2YWx1ZV09XCJvYmplY3RGaXRPcHRpb24udmFsdWVcIlxuICAgICAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgICAgIDxzcGFuPjwvc3Bhbj5cbiAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwibC1oLTFcIj5cbiAgICAgICAgICAgICAgICAgICAge3sgb2JqZWN0Rml0T3B0aW9uLmxhYmVsIHwgdHJhbnNsYXRlIH19XG4gICAgICAgICAgICAgICAgICAgIDxiciAvPlxuICAgICAgICAgICAgICAgICAgICA8c21hbGwgY2xhc3M9XCJ0ZXh0LW11dGVkXCI+XG4gICAgICAgICAgICAgICAgICAgICAge3sgb2JqZWN0Rml0T3B0aW9uLmRlc2NyaXB0aW9uIHwgdHJhbnNsYXRlIH19XG4gICAgICAgICAgICAgICAgICAgIDwvc21hbGw+XG4gICAgICAgICAgICAgICAgICA8L3NwYW4+XG4gICAgICAgICAgICAgICAgPC9sYWJlbD5cbiAgICAgICAgICAgICAgPC9saT5cbiAgICAgICAgICAgIDwvdWw+XG4gICAgICAgICAgPC9jOHktZm9ybS1ncm91cD5cbiAgICAgICAgICA8ZGl2IGNsYXNzPVwicm93XCI+XG4gICAgICAgICAgICA8ZGl2IGNsYXNzPVwiY29sLW1kLTZcIj5cbiAgICAgICAgICAgICAgPGM4eS1mb3JtLWdyb3VwPlxuICAgICAgICAgICAgICAgIDxsYWJlbD57eyAnSG9yaXpvbnRhbCBhbGlnbm1lbnQnIHwgdHJhbnNsYXRlIH19PC9sYWJlbD5cbiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPVwiYzh5LXNlbGVjdC13cmFwcGVyXCI+XG4gICAgICAgICAgICAgICAgICA8c2VsZWN0IGNsYXNzPVwiZm9ybS1jb250cm9sXCIgZm9ybUNvbnRyb2xOYW1lPVwib2JqZWN0UG9zaXRpb25YXCI+XG4gICAgICAgICAgICAgICAgICAgIDxvcHRpb24gW25nVmFsdWVdPVwiJ2xlZnQnXCI+XG4gICAgICAgICAgICAgICAgICAgICAge3sgJ2xlZnRgaG9yaXpvbnRhbCBhbGlnbm1lbnRgJyB8IHRyYW5zbGF0ZSB9fVxuICAgICAgICAgICAgICAgICAgICA8L29wdGlvbj5cbiAgICAgICAgICAgICAgICAgICAgPG9wdGlvbiBbbmdWYWx1ZV09XCInY2VudGVyJ1wiPlxuICAgICAgICAgICAgICAgICAgICAgIHt7ICdjZW50ZXJgaG9yaXpvbnRhbCBhbGlnbm1lbnRgJyB8IHRyYW5zbGF0ZSB9fVxuICAgICAgICAgICAgICAgICAgICA8L29wdGlvbj5cbiAgICAgICAgICAgICAgICAgICAgPG9wdGlvbiBbbmdWYWx1ZV09XCIncmlnaHQnXCI+XG4gICAgICAgICAgICAgICAgICAgICAge3sgJ3JpZ2h0YGhvcml6b250YWwgYWxpZ25tZW50YCcgfCB0cmFuc2xhdGUgfX1cbiAgICAgICAgICAgICAgICAgICAgPC9vcHRpb24+XG4gICAgICAgICAgICAgICAgICA8L3NlbGVjdD5cbiAgICAgICAgICAgICAgICA8L2Rpdj5cbiAgICAgICAgICAgICAgPC9jOHktZm9ybS1ncm91cD5cbiAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgPGRpdiBjbGFzcz1cImNvbC1tZC02XCI+XG4gICAgICAgICAgICAgIDxjOHktZm9ybS1ncm91cD5cbiAgICAgICAgICAgICAgICA8bGFiZWw+e3sgJ1ZlcnRpY2FsIGFsaWdubWVudCcgfCB0cmFuc2xhdGUgfX08L2xhYmVsPlxuICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9XCJjOHktc2VsZWN0LXdyYXBwZXJcIj5cbiAgICAgICAgICAgICAgICAgIDxzZWxlY3QgY2xhc3M9XCJmb3JtLWNvbnRyb2xcIiBmb3JtQ29udHJvbE5hbWU9XCJvYmplY3RQb3NpdGlvbllcIj5cbiAgICAgICAgICAgICAgICAgICAgPG9wdGlvbiBbbmdWYWx1ZV09XCIndG9wJ1wiPlxuICAgICAgICAgICAgICAgICAgICAgIHt7ICd0b3BgdmVydGljYWwgYWxpZ25tZW50YCcgfCB0cmFuc2xhdGUgfX1cbiAgICAgICAgICAgICAgICAgICAgPC9vcHRpb24+XG4gICAgICAgICAgICAgICAgICAgIDxvcHRpb24gW25nVmFsdWVdPVwiJ2NlbnRlcidcIj5cbiAgICAgICAgICAgICAgICAgICAgICB7eyAnY2VudGVyYHZlcnRpY2FsIGFsaWdubWVudGAnIHwgdHJhbnNsYXRlIH19XG4gICAgICAgICAgICAgICAgICAgIDwvb3B0aW9uPlxuICAgICAgICAgICAgICAgICAgICA8b3B0aW9uIFtuZ1ZhbHVlXT1cIidib3R0b20nXCI+XG4gICAgICAgICAgICAgICAgICAgICAge3sgJ2JvdHRvbWB2ZXJ0aWNhbCBhbGlnbm1lbnRgJyB8IHRyYW5zbGF0ZSB9fVxuICAgICAgICAgICAgICAgICAgICA8L29wdGlvbj5cbiAgICAgICAgICAgICAgICAgIDwvc2VsZWN0PlxuICAgICAgICAgICAgICAgIDwvZGl2PlxuICAgICAgICAgICAgICA8L2M4eS1mb3JtLWdyb3VwPlxuICAgICAgICAgICAgPC9kaXY+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgIDwvZGl2PlxuICAgICAgPC9kaXY+XG4gICAgICA8ZGl2IGNsYXNzPVwiY29sLW1kLTZcIj5cbiAgICAgICAgPGRpdiBjbGFzcz1cImxlZ2VuZCBmb3JtLWJsb2NrXCI+e3sgJ1ByZXZpZXcnIHwgdHJhbnNsYXRlIH19PC9kaXY+XG4gICAgICAgIDxkaXYgY2xhc3M9XCJmb3JtLWdyb3VwXCI+XG4gICAgICAgICAgPGRpdlxuICAgICAgICAgICAgY2xhc3M9XCJib3JkZXItdG9wIGJvcmRlci1yaWdodCBib3JkZXItYm90dG9tIGJvcmRlci1sZWZ0IGlubmVyLXNjcm9sbFwiXG4gICAgICAgICAgICBzdHlsZT1cImhlaWdodDogMzAwcHhcIlxuICAgICAgICAgID5cbiAgICAgICAgICAgIDxpbWdcbiAgICAgICAgICAgICAgW3NyY109XCJzcmNcIlxuICAgICAgICAgICAgICBbbmdDbGFzc109XCJzdHlsaW5nID8gJ2ZpdC1oIGZpdC13JyA6ICdpbWctcmVzcG9uc2l2ZSdcIlxuICAgICAgICAgICAgICBbbmdTdHlsZV09XCJzdHlsaW5nXCJcbiAgICAgICAgICAgIC8+XG4gICAgICAgICAgPC9kaXY+XG4gICAgICAgIDwvZGl2PlxuICAgICAgPC9kaXY+XG4gICAgPC9uZy1jb250YWluZXI+XG4gICAgPGRpdiBjbGFzcz1cImNvbC1tZC0xMiBkLWZsZXggai1jLWNlbnRlclwiICpuZ0lmPVwibG9hZGluZ1wiPlxuICAgICAgPGM4eS1sb2FkaW5nPjwvYzh5LWxvYWRpbmc+XG4gICAgPC9kaXY+XG4gIDwvZGl2PlxuPC9kaXY+XG4iXX0=