UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

271 lines 59.6 kB
import { Component, Input, ViewChild, ElementRef, HostListener, forwardRef, Inject, Injector, Output, EventEmitter } from '@angular/core'; import { fromEvent, merge, of } from 'rxjs'; import { distinctUntilChanged, filter, map, sample, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import * as mimeDB from 'mime-db'; import { FilesService } from '../common'; import { FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; import { FilePickerValidators } from './file-picker-validators'; import { validateArrayElements } from '../forms'; import { gettext } from '../i18n'; import * as i0 from "@angular/core"; import * as i1 from "../common"; import * as i2 from "../forms/form-group.component"; import * as i3 from "../forms/message.directive"; import * as i4 from "../forms/messages.component"; import * as i5 from "@angular/common"; import * as i6 from "../common/icon.directive"; import * as i7 from "../common/loading.component"; import * as i8 from "../forms/extract-array-validation-errors.pipe"; import * as i9 from "../forms/filter-non-array-validation-errors.pipe"; import * as i10 from "../forms/humanize-validation-message.pipe"; import * as i11 from "../i18n/c8y-translate.pipe"; import * as i12 from "../common/bytes.pipe"; import * as i13 from "../common/generic-file-icon.pipe"; /** File uploader */ export class FilePickerFormControlComponent { constructor(filesService, injector) { this.filesService = filesService; this.injector = injector; /** * Sets how many files can be submitted, accepts only numbers. */ this.maxAllowedFiles = Infinity; /** * Sets the minimum number of files to be uploaded, accepts only numbers. */ this.minRequiredFiles = 1; /** * Allows to disable default validators. Accepts object with following optional properties: - fileMaxSize - Providing `true` disables default validation for file size, accepts only boolean - fileNonEmpty - Providing `true` disables default validation for empty files, accepts only boolean - fileNameMaxLength - Providing `true` disables default validation for file name length, accepts only boolean */ this.disableValidators = {}; /** * Displays upload progress bar, accepts only boolean. */ this.uploadInProgress = false; /** Display warning if any file dragged over the drop area has unsupported extension. */ this.validateExtensionOnDrag = false; /** Emits an event with the array of File objects representing the dropped files. */ this.dropped = new EventEmitter(); this.droppedFiles = []; this.disabledValidators = { fileMaxSize: false, fileNonEmpty: false, fileNameMaxLength: false }; this.loadingMessage = gettext('Upload in progress'); this.onChange = () => undefined; this.onTouched = () => undefined; this.onValidatorChange = () => undefined; } ngOnInit() { const ngControl = this.injector.get(NgControl, false); if (ngControl && ngControl.control) { this.controlInstance = ngControl.control; } else { /** * In case if component is used without utilizing ControlValueAccessor, internal control * instance is created, so it can still benefit from built in validators. * (dropped) @Output event can be used to get list of files. */ this.controlInstance = new FormControl([], this.validate.bind(this)); this.onChange = () => { this.controlInstance.setValue(this.droppedFiles); }; this.onValidatorChange = () => { this.controlInstance.updateValueAndValidity(); }; this.onTouched = () => { this.controlInstance.markAsTouched(); }; } this.acceptedExts = this.filesService.extractFileExtensions(this.accept).map(t => `.${t}`); this.disabledValidators = { ...this.disabledValidators, ...this.disableValidators }; this.filesService.loadBytesSizeLimit().then(sizeLimit => { this.fileSizeLimit = sizeLimit; this.onValidatorChange(); }); } ngAfterViewInit() { /** * Emits DragEvent when user drag file inside or outside viewport. Only works when type of dragged elements is 'Files'. */ const draggingFilesGlobally$ = merge(fromEvent(document, 'dragenter'), fromEvent(document, 'dragleave').pipe( // TODO review why this specific filter is needed filter((e) => e.pageX === 0 && e.pageY === 0))).pipe(filter((ev) => ev.dataTransfer?.types.toString() === 'Files'), filter(() => !this.uploadInProgress)); /** * Emits boolean value when file is dragged inside or outside of viewport, or file is not dragged anymore. */ this.isDraggingFiles$ = merge(draggingFilesGlobally$.pipe(map(ev => ev.type === 'dragenter')), fromEvent(document, 'mouseenter').pipe(map(() => false)), fromEvent(document, 'drop').pipe(map(() => false))).pipe(distinctUntilChanged()); /** * Emits boolean value after checking if dragged files match configured extensions list, * When dragging of file starts, get mimetypes of each file, convert them to possible extensions list * that may be associated with given mimetype, and then check if any of these extensions match configured * extensions accept list. */ this.isExtensionAllowed$ = draggingFilesGlobally$.pipe(sample(this.isDraggingFiles$.pipe(filter(enlarged => enlarged))), switchMap(ev => { if (!this.accept) { return of(true); } return of(ev).pipe(map(ev => [...ev.dataTransfer.items]), map(files => files.map(file => mimeDB[file.type].extensions || ['unknown'])), map(extensionsForFiles => extensionsForFiles.every(extensionsForFile => extensionsForFile.some(ext => this.filesService.extractFileExtensions(this.accept).includes(ext))))); }), tap(allowed => { this.isExtensionAllowed = allowed; })); /** * If File is being dragged and extensions meets requirements, then drop-area can be enlarged. */ this.isDropAreaEnlarged$ = this.isDraggingFiles$; this.isDraggedFileUnsupported$ = this.isDraggingFiles$.pipe(withLatestFrom(this.isExtensionAllowed$), map(([fileDragged, extAllowed]) => fileDragged && !extAllowed)); /** * Emits true when file is hovering above drop-area. Only works when isDropAreaEnlarged$ also emits true. * This is workaround for CSS :hover pseudo-class not triggering when dragging file. */ this.isDraggingOverFilePlaceholder$ = merge(fromEvent(this.filePlaceholder.nativeElement, 'dragenter'), fromEvent(this.filePlaceholder.nativeElement, 'dragleave'), fromEvent(this.filePlaceholder.nativeElement, 'drop')).pipe(filter((ev) => ev.dataTransfer?.types.toString() === 'Files'), map(ev => ev.type === 'dragenter'), withLatestFrom(this.isDropAreaEnlarged$), map(([draggingOver, dropAreaEnlarged]) => draggingOver && dropAreaEnlarged)); } afterValueChanged() { this.onChange([...this.droppedFiles]); this.onTouched(); this.dropped.emit([...this.droppedFiles]); this.valid = this.controlInstance.valid; this.errors = this.controlInstance.errors; } onDrop(e) { if (this.uploadInProgress) { return; } if (e.dataTransfer.types.toString() !== 'Files') { return; } const files = e.dataTransfer.files; if (!files?.length) { return; } for (let i = 0; i < files.length; i++) { this.droppedFiles.push(files.item(i)); } this.afterValueChanged(); } filesSelected(ev) { const inputElement = ev.target; const files = Array.from(inputElement.files); this.droppedFiles.push(...files); inputElement.value = null; this.afterValueChanged(); } deleteAt(index) { this.droppedFiles.splice(index, 1); this.afterValueChanged(); } registerOnChange(fn) { this.onChange = fn; } registerOnTouched(fn) { this.onTouched = fn; } setDisabledState(isDisabled) { this.disabled = isDisabled; } registerOnValidatorChange(fn) { this.onValidatorChange = fn; } writeValue(files) { if (!files) { files = []; } this.droppedFiles = [...files]; this.onValidatorChange(); } onDragOver(e) { e.preventDefault(); } onWindowDrop(e) { e.preventDefault(); } validate(control) { let errors = {}; if (!this.disableValidators.fileMaxSize) { errors = appendErrors(errors, validateArrayElements(FilePickerValidators.fileMaxSize(this.fileSizeLimit, this.filesService))(control)); } if (!this.disableValidators.fileNonEmpty) { errors = appendErrors(errors, validateArrayElements(FilePickerValidators.fileNonEmpty(this.filesService))(control)); } if (!this.disableValidators.fileNameMaxLength) { errors = appendErrors(errors, validateArrayElements(FilePickerValidators.fileNameMaxLength(this.filesService))(control)); } if (this.accept) { errors = appendErrors(errors, validateArrayElements(FilePickerValidators.haveValidExtensions(this.filesService, this.accept))(control)); } if (this.maxAllowedFiles) { errors = appendErrors(errors, FilePickerValidators.fileCountMax(this.maxAllowedFiles)(control)); } if (this.minRequiredFiles) { errors = appendErrors(errors, FilePickerValidators.fileCountMin(this.minRequiredFiles)(control)); } return Object.keys(errors).length ? errors : null; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FilePickerFormControlComponent, deps: [{ token: i1.FilesService }, { token: Injector }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: FilePickerFormControlComponent, selector: "c8y-file-picker-form-control", inputs: { accept: "accept", maxAllowedFiles: "maxAllowedFiles", minRequiredFiles: "minRequiredFiles", disableValidators: "disableValidators", uploadInProgress: "uploadInProgress", uploadProgress: "uploadProgress", validateExtensionOnDrag: "validateExtensionOnDrag" }, outputs: { dropped: "dropped" }, host: { listeners: { "window:dragover": "onDragOver($event)", "window:drop": "onWindowDrop($event)" } }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FilePickerFormControlComponent), multi: true }, { provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => FilePickerFormControlComponent) } ], viewQueries: [{ propertyName: "filePlaceholder", first: true, predicate: ["filePlaceholder"], descendants: true, static: true }], ngImport: i0, template: "<c8y-form-group class=\"m-0\">\n <div\n class=\"file-picker-drop-zone\"\n [class.dragging]=\"isDropAreaEnlarged$ | async\"\n tabindex=\"0\"\n >\n <div\n class=\"file-placeholder pointer\"\n [class.drag-over]=\"isDraggingOverFilePlaceholder$ | async\"\n #filePlaceholder\n (drop)=\"onDrop($event)\"\n (click)=\"!uploadInProgress && picker.click()\"\n >\n <div *ngIf=\"validateExtensionOnDrag && (isDraggedFileUnsupported$ | async); else defaultHint\">\n <p class=\"form-control-static text-truncate\">\n <i\n class=\"text-warning m-r-4\"\n c8yIcon=\"exclamation-triangle\"\n ></i>\n <span>{{ 'At least one file is of an unsupported type.' | translate }}</span>\n </p>\n </div>\n <ng-template #defaultHint>\n <div\n class=\"hint-placeholder\"\n *ngIf=\"!uploadInProgress\"\n >\n <i c8yIcon=\"upload\"></i>\n <p>\n <b>{{ 'Drop files here or click to browse' | translate }}</b>\n </p>\n <p\n class=\"m-t-8 text-12 text-muted\"\n *ngIf=\"accept\"\n >\n {{ 'Supported file types:' | translate }} {{ accept }}\n </p>\n <c8y-form-group>\n <c8y-messages\n class=\"has-error\"\n *ngIf=\"\n controlInstance.touched && controlInstance.errors && controlInstance.errors\n | filterNonArrayValidationErrors\n \"\n >\n <c8y-message\n *ngFor=\"\n let error of controlInstance.errors | filterNonArrayValidationErrors | keyvalue\n \"\n >\n {{ error.key | humanizeValidationMessage | translate: error.value }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <div\n class=\"d-flex d-col p-4 flex-center\"\n *ngIf=\"uploadInProgress\"\n >\n <c8y-loading></c8y-loading>\n <p class=\"m-t-auto m-b-auto m-r-8 text-center\">\n {{ loadingMessage | translate }}\n </p>\n </div>\n </ng-template>\n </div>\n </div>\n <div class=\"file-container\">\n <ul class=\"list-group\">\n <ng-container *ngFor=\"let file of droppedFiles; let i = index\">\n <li class=\"list-group-item p-4 a-i-center\">\n <div class=\"d-flex a-i-center\">\n <i\n class=\"icon-20 m-r-8\"\n [c8yIcon]=\"file | fileIcon\"\n ></i>\n <div\n class=\"m-r-16 text-truncate\"\n [title]=\"file.name\"\n >\n {{ file.name }}\n </div>\n <div class=\"m-r-16 text-nowrap\">({{ file.size | bytes }})</div>\n <div class=\"m-l-auto\">\n <button\n class=\"btn btn-dot btn-dot--danger\"\n title=\"{{ 'Remove' | translate }}\"\n [attr.aria-label]=\"'Remove' | translate\"\n type=\"button\"\n *ngIf=\"!uploadInProgress\"\n (click)=\"deleteAt(i)\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </div>\n <c8y-form-group>\n <c8y-messages class=\"has-error\">\n <c8y-message\n *ngFor=\"\n let error of controlInstance.errors | extractArrayValidationErrors: i | keyvalue\n \"\n >\n {{ error.key | humanizeValidationMessage | translate: error.value }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <div\n class=\"d-block\"\n data-cy=\"c8y-file-picker-form-control--upload-progress-bar\"\n *ngIf=\"uploadInProgress\"\n >\n <div\n class=\"progress progress-striped active\"\n *ngIf=\"uploadProgress\"\n >\n <div\n class=\"progress-bar progress-bar-info\"\n [style.width]=\"uploadProgress[i].percentage + '%'\"\n aria-valuenow=\"0\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n role=\"progressbar\"\n >\n {{ uploadProgress[i].percentage }}%\n </div>\n </div>\n </div>\n </li>\n </ng-container>\n </ul>\n </div>\n <input\n class=\"hidden\"\n id=\"file\"\n type=\"file\"\n #picker\n (change)=\"filesSelected($event)\"\n [accept]=\"acceptedExts\"\n [multiple]=\"maxAllowedFiles > 1\"\n />\n</c8y-form-group>\n", dependencies: [{ kind: "component", type: i2.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: i3.MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "component", type: i4.MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: i5.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i6.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: i7.LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "pipe", type: i8.ExtractArrayValidationErrorsPipe, name: "extractArrayValidationErrors" }, { kind: "pipe", type: i9.FilterNonArrayValidationErrorsPipe, name: "filterNonArrayValidationErrors" }, { kind: "pipe", type: i10.HumanizeValidationMessagePipe, name: "humanizeValidationMessage" }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i5.KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: i11.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i12.BytesPipe, name: "bytes" }, { kind: "pipe", type: i13.GenericFileIconPipe, name: "fileIcon" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: FilePickerFormControlComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-file-picker-form-control', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FilePickerFormControlComponent), multi: true }, { provide: NG_VALIDATORS, multi: true, useExisting: forwardRef(() => FilePickerFormControlComponent) } ], template: "<c8y-form-group class=\"m-0\">\n <div\n class=\"file-picker-drop-zone\"\n [class.dragging]=\"isDropAreaEnlarged$ | async\"\n tabindex=\"0\"\n >\n <div\n class=\"file-placeholder pointer\"\n [class.drag-over]=\"isDraggingOverFilePlaceholder$ | async\"\n #filePlaceholder\n (drop)=\"onDrop($event)\"\n (click)=\"!uploadInProgress && picker.click()\"\n >\n <div *ngIf=\"validateExtensionOnDrag && (isDraggedFileUnsupported$ | async); else defaultHint\">\n <p class=\"form-control-static text-truncate\">\n <i\n class=\"text-warning m-r-4\"\n c8yIcon=\"exclamation-triangle\"\n ></i>\n <span>{{ 'At least one file is of an unsupported type.' | translate }}</span>\n </p>\n </div>\n <ng-template #defaultHint>\n <div\n class=\"hint-placeholder\"\n *ngIf=\"!uploadInProgress\"\n >\n <i c8yIcon=\"upload\"></i>\n <p>\n <b>{{ 'Drop files here or click to browse' | translate }}</b>\n </p>\n <p\n class=\"m-t-8 text-12 text-muted\"\n *ngIf=\"accept\"\n >\n {{ 'Supported file types:' | translate }} {{ accept }}\n </p>\n <c8y-form-group>\n <c8y-messages\n class=\"has-error\"\n *ngIf=\"\n controlInstance.touched && controlInstance.errors && controlInstance.errors\n | filterNonArrayValidationErrors\n \"\n >\n <c8y-message\n *ngFor=\"\n let error of controlInstance.errors | filterNonArrayValidationErrors | keyvalue\n \"\n >\n {{ error.key | humanizeValidationMessage | translate: error.value }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n <div\n class=\"d-flex d-col p-4 flex-center\"\n *ngIf=\"uploadInProgress\"\n >\n <c8y-loading></c8y-loading>\n <p class=\"m-t-auto m-b-auto m-r-8 text-center\">\n {{ loadingMessage | translate }}\n </p>\n </div>\n </ng-template>\n </div>\n </div>\n <div class=\"file-container\">\n <ul class=\"list-group\">\n <ng-container *ngFor=\"let file of droppedFiles; let i = index\">\n <li class=\"list-group-item p-4 a-i-center\">\n <div class=\"d-flex a-i-center\">\n <i\n class=\"icon-20 m-r-8\"\n [c8yIcon]=\"file | fileIcon\"\n ></i>\n <div\n class=\"m-r-16 text-truncate\"\n [title]=\"file.name\"\n >\n {{ file.name }}\n </div>\n <div class=\"m-r-16 text-nowrap\">({{ file.size | bytes }})</div>\n <div class=\"m-l-auto\">\n <button\n class=\"btn btn-dot btn-dot--danger\"\n title=\"{{ 'Remove' | translate }}\"\n [attr.aria-label]=\"'Remove' | translate\"\n type=\"button\"\n *ngIf=\"!uploadInProgress\"\n (click)=\"deleteAt(i)\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </div>\n <c8y-form-group>\n <c8y-messages class=\"has-error\">\n <c8y-message\n *ngFor=\"\n let error of controlInstance.errors | extractArrayValidationErrors: i | keyvalue\n \"\n >\n {{ error.key | humanizeValidationMessage | translate: error.value }}\n </c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <div\n class=\"d-block\"\n data-cy=\"c8y-file-picker-form-control--upload-progress-bar\"\n *ngIf=\"uploadInProgress\"\n >\n <div\n class=\"progress progress-striped active\"\n *ngIf=\"uploadProgress\"\n >\n <div\n class=\"progress-bar progress-bar-info\"\n [style.width]=\"uploadProgress[i].percentage + '%'\"\n aria-valuenow=\"0\"\n aria-valuemin=\"0\"\n aria-valuemax=\"100\"\n role=\"progressbar\"\n >\n {{ uploadProgress[i].percentage }}%\n </div>\n </div>\n </div>\n </li>\n </ng-container>\n </ul>\n </div>\n <input\n class=\"hidden\"\n id=\"file\"\n type=\"file\"\n #picker\n (change)=\"filesSelected($event)\"\n [accept]=\"acceptedExts\"\n [multiple]=\"maxAllowedFiles > 1\"\n />\n</c8y-form-group>\n" }] }], ctorParameters: () => [{ type: i1.FilesService }, { type: i0.Injector, decorators: [{ type: Inject, args: [Injector] }] }], propDecorators: { filePlaceholder: [{ type: ViewChild, args: ['filePlaceholder', { static: true }] }], accept: [{ type: Input }], maxAllowedFiles: [{ type: Input }], minRequiredFiles: [{ type: Input }], disableValidators: [{ type: Input }], uploadInProgress: [{ type: Input }], uploadProgress: [{ type: Input }], validateExtensionOnDrag: [{ type: Input }], dropped: [{ type: Output }], onDragOver: [{ type: HostListener, args: ['window:dragover', ['$event']] }], onWindowDrop: [{ type: HostListener, args: ['window:drop', ['$event']] }] } }); function appendErrors(errors, newErrors) { return newErrors ? { ...errors, ...newErrors } : errors; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsZS1waWNrZXItZm9ybS1jb250cm9sLmNvbXBvbmVudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2NvcmUvZmlsZS1waWNrZXItZm9ybS1jb250cm9sL2ZpbGUtcGlja2VyLWZvcm0tY29udHJvbC5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9jb3JlL2ZpbGUtcGlja2VyLWZvcm0tY29udHJvbC9maWxlLXBpY2tlci1mb3JtLWNvbnRyb2wuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUNMLFNBQVMsRUFDVCxLQUFLLEVBQ0wsU0FBUyxFQUNULFVBQVUsRUFFVixZQUFZLEVBRVosVUFBVSxFQUNWLE1BQU0sRUFDTixRQUFRLEVBQ1IsTUFBTSxFQUNOLFlBQVksRUFDYixNQUFNLGVBQWUsQ0FBQztBQUN2QixPQUFPLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBYyxFQUFFLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFDeEQsT0FBTyxFQUNMLG9CQUFvQixFQUNwQixNQUFNLEVBQ04sR0FBRyxFQUNILE1BQU0sRUFDTixTQUFTLEVBQ1QsR0FBRyxFQUNILGNBQWMsRUFDZixNQUFNLGdCQUFnQixDQUFDO0FBQ3hCLE9BQU8sS0FBSyxNQUFNLE1BQU0sU0FBUyxDQUFDO0FBQ2xDLE9BQU8sRUFBRSxZQUFZLEVBQXNCLE1BQU0sV0FBVyxDQUFDO0FBQzdELE9BQU8sRUFHTCxXQUFXLEVBQ1gsYUFBYSxFQUNiLGlCQUFpQixFQUNqQixTQUFTLEVBR1YsTUFBTSxnQkFBZ0IsQ0FBQztBQUN4QixPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNoRSxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFDakQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLFNBQVMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7O0FBUWxDOztHQUVHO0FBa0JILE1BQU0sT0FBTyw4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QWtDO0lBRWxDLE9BQU8sU0FBUyxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsTUFBTSxFQUFFLEdBQUcsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQztBQUMxRCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgQ29tcG9uZW50LFxuICBJbnB1dCxcbiAgVmlld0NoaWxkLFxuICBFbGVtZW50UmVmLFxuICBBZnRlclZpZXdJbml0LFxuICBIb3N0TGlzdGVuZXIsXG4gIE9uSW5pdCxcbiAgZm9yd2FyZFJlZixcbiAgSW5qZWN0LFxuICBJbmplY3RvcixcbiAgT3V0cHV0LFxuICBFdmVudEVtaXR0ZXJcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBmcm9tRXZlbnQsIG1lcmdlLCBPYnNlcnZhYmxlLCBvZiB9IGZyb20gJ3J4anMnO1xuaW1wb3J0IHtcbiAgZGlzdGluY3RVbnRpbENoYW5nZWQsXG4gIGZpbHRlcixcbiAgbWFwLFxuICBzYW1wbGUsXG4gIHN3aXRjaE1hcCxcbiAgdGFwLFxuICB3aXRoTGF0ZXN0RnJvbVxufSBmcm9tICdyeGpzL29wZXJhdG9ycyc7XG5pbXBvcnQgKiBhcyBtaW1lREIgZnJvbSAnbWltZS1kYic7XG5pbXBvcnQgeyBGaWxlc1NlcnZpY2UsIElGZXRjaFdpdGhQcm9ncmVzcyB9IGZyb20gJy4uL2NvbW1vbic7XG5pbXBvcnQge1xuICBBYnN0cmFjdENvbnRyb2wsXG4gIENvbnRyb2xWYWx1ZUFjY2Vzc29yLFxuICBGb3JtQ29udHJvbCxcbiAgTkdfVkFMSURBVE9SUyxcbiAgTkdfVkFMVUVfQUNDRVNTT1IsXG4gIE5nQ29udHJvbCxcbiAgVmFsaWRhdGlvbkVycm9ycyxcbiAgVmFsaWRhdG9yXG59IGZyb20gJ0Bhbmd1bGFyL2Zvcm1zJztcbmltcG9ydCB7IEZpbGVQaWNrZXJWYWxpZGF0b3JzIH0gZnJvbSAnLi9maWxlLXBpY2tlci12YWxpZGF0b3JzJztcbmltcG9ydCB7IHZhbGlkYXRlQXJyYXlFbGVtZW50cyB9IGZyb20gJy4uL2Zvcm1zJztcbmltcG9ydCB7IGdldHRleHQgfSBmcm9tICcuLi9pMThuJztcblxuaW50ZXJmYWNlIElBdmFpbGFibGVWYWxpZGF0b3JzIHtcbiAgZmlsZU1heFNpemU/OiBib29sZWFuO1xuICBmaWxlTm9uRW1wdHk/OiBib29sZWFuO1xuICBmaWxlTmFtZU1heExlbmd0aD86IGJvb2xlYW47XG59XG5cbi8qKlxuIEZpbGUgdXBsb2FkZXJcbiAqL1xuXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICdjOHktZmlsZS1waWNrZXItZm9ybS1jb250cm9sJyxcbiAgdGVtcGxhdGVVcmw6ICcuL2ZpbGUtcGlja2VyLWZvcm0tY29udHJvbC5jb21wb25lbnQuaHRtbCcsXG4gIHByb3ZpZGVyczogW1xuICAgIHtcbiAgICAgIHByb3ZpZGU6IE5HX1ZBTFVFX0FDQ0VTU09SLFxuICAgICAgdXNlRXhpc3Rpbmc6IGZvcndhcmRSZWYoKCkgPT4gRmlsZVBpY2tlckZvcm1Db250cm9sQ29tcG9uZW50KSxcbiAgICAgIG11bHRpOiB0cnVlXG4gICAgfSxcbiAgICB7XG4gICAgICBwcm92aWRlOiBOR19WQUxJREFUT1JTLFxuICAgICAgbXVsdGk6IHRydWUsXG4gICAgICB1c2VFeGlzdGluZzogZm9yd2FyZFJlZigoKSA9PiBGaWxlUGlja2VyRm9ybUNvbnRyb2xDb21wb25lbnQpXG4gICAgfVxuICBdXG59KVxuZXhwb3J0IGNsYXNzIEZpbGVQaWNrZXJGb3JtQ29udHJvbENvbXBvbmVudFxuICBpbXBsZW1lbnRzIE9uSW5pdCwgQWZ0ZXJWaWV3SW5pdCwgQ29udHJvbFZhbHVlQWNjZXNzb3IsIFZhbGlkYXRvclxue1xuICBAVmlld0NoaWxkKCdmaWxlUGxhY2Vob2xkZXInLCB7IHN0YXRpYzogdHJ1ZSB9KVxuICBmaWxlUGxhY2Vob2xkZXI6IEVsZW1lbnRSZWY8SFRNTEVsZW1lbnQ+O1xuXG4gIC8qKiBTcGVjaWZpZXMgYSBmaWx0ZXIgZm9yIHdoYXQgZmlsZSB0eXBlcyB0aGUgdXNlciBjYW4gcGljayBmcm9tIHRoZSBmaWxlIGlucHV0IGRpYWxvZyBib3guXG4gICAqIEJ5IGRlZmF1bHQgYWxsIHR5cGVzIGFyZSBhY2NlcHRlZC5cbiAgICpcbiAgICogU3BlY2lmeSBmaWxlIHR5cGVzIGJ5IGV4dGVuc2lvbnM6XG4gICAqIGBgYGh0bWxcbiAgICogIC4uLlxuICAgKiAgW2FjY2VwdF09XCInLnppcCwuN3onXCJcbiAgICogIC4uLlxuICAgKiBgYGBcbiAgICpcbiAgICogU3BlY2lmeSBmaWxlIHR5cGVzIGJ5IGV4dGVuc2lvbnMgYW5kIGdlbmVyaWMgdHlwZXMgW0dFTkVSSUNfRklMRV9UWVBFXXtAbGluayBHRU5FUklDX0ZJTEVfVFlQRX06XG4gICAqIGBgYGh0bWxcbiAgICogIC4uLlxuICAgKiAgW2FjY2VwdF09XCInLnBkZixhcmNoaXZlJ1wiXG4gICAqICAuLi5cbiAgICogYGBgXG4gICAqXG4gICAqIFNwZWNpZnkgZmlsZSB0eXBlcyBieSBnZW5lcmljIHR5cGVzIFtHRU5FUklDX0ZJTEVfVFlQRV17QGxpbmsgR0VORVJJQ19GSUxFX1RZUEV9OlxuICAgKiAgYGBgaHRtbFxuICAgKiAgLi4uXG4gICAqICBbYWNjZXB0XT1cIidhcmNoaXZlLHZpZGVvJ1wiXG4gICAqICAuLi5cbiAgICpcbiAgICogYGBgXG4gICAqL1xuICBASW5wdXQoKVxuICBhY2NlcHQ/OiBzdHJpbmc7XG4gIC8qKlxuICAgKiBTZXRzIGhvdyBtYW55IGZpbGVzIGNhbiBiZSBzdWJtaXR0ZWQsIGFjY2VwdHMgb25seSBudW1iZXJzLlxuICAgKi9cbiAgQElucHV0KClcbiAgbWF4QWxsb3dlZEZpbGVzPzogbnVtYmVyID0gSW5maW5pdHk7XG4gIC8qKlxuICAgKiBTZXRzIHRoZSBtaW5pbXVtIG51bWJlciBvZiBmaWxlcyB0byBiZSB1cGxvYWRlZCwgYWNjZXB0cyBvbmx5IG51bWJlcnMuXG4gICAqL1xuICBASW5wdXQoKVxuICBtaW5SZXF1aXJlZEZpbGVzPzogbnVtYmVyID0gMTtcbiAgLyoqXG4gICAqIEFsbG93cyB0byBkaXNhYmxlIGRlZmF1bHQgdmFsaWRhdG9ycy4gQWNjZXB0cyBvYmplY3Qgd2l0aCBmb2xsb3dpbmcgb3B0aW9uYWwgcHJvcGVydGllczogXG4gICAgLSBmaWxlTWF4U2l6ZSAtIFByb3ZpZGluZyBgdHJ1ZWAgZGlzYWJsZXMgZGVmYXVsdCB2YWxpZGF0aW9uIGZvciBmaWxlIHNpemUsIGFjY2VwdHMgb25seSBib29sZWFuXG4gICAgLSBmaWxlTm9uRW1wdHkgLSBQcm92aWRpbmcgYHRydWVgIGRpc2FibGVzIGRlZmF1bHQgdmFsaWRhdGlvbiBmb3IgZW1wdHkgZmlsZXMsIGFjY2VwdHMgb25seSBib29sZWFuXG4gICAgLSBmaWxlTmFtZU1heExlbmd0aCAtIFByb3ZpZGluZyBgdHJ1ZWAgZGlzYWJsZXMgZGVmYXVsdCB2YWxpZGF0aW9uIGZvciBmaWxlIG5hbWUgbGVuZ3RoLCBhY2NlcHRzIG9ubHkgYm9vbGVhblxuICAgKi9cbiAgQElucHV0KClcbiAgZGlzYWJsZVZhbGlkYXRvcnM6IElBdmFpbGFibGVWYWxpZGF0b3JzID0ge307XG4gIC8qKlxuICAgKiBEaXNwbGF5cyB1cGxvYWQgcHJvZ3Jlc3MgYmFyLCBhY2NlcHRzIG9ubHkgYm9vbGVhbi5cbiAgICovXG4gIEBJbnB1dCgpXG4gIHVwbG9hZEluUHJvZ3Jlc3MgPSBmYWxzZTtcbiAgLyoqIFRoZSBhcnJheSBvZiBwcm9ncmVzcyBzdGF0ZSBvYmplY3RzIGZvciBlYWNoIHNlbGVjdGVkIGZpbGUgKG1hdGNoZWQgYnkgaW5kZXgpLiAqL1xuICBASW5wdXQoKVxuICB1cGxvYWRQcm9ncmVzczogSUZldGNoV2l0aFByb2dyZXNzW107XG4gIC8qKiBEaXNwbGF5IHdhcm5pbmcgaWYgYW55IGZpbGUgZHJhZ2dlZCBvdmVyIHRoZSBkcm9wIGFyZWEgaGFzIHVuc3VwcG9ydGVkIGV4dGVuc2lvbi4gKi9cbiAgQElucHV0KClcbiAgdmFsaWRhdGVFeHRlbnNpb25PbkRyYWcgPSBmYWxzZTtcbiAgLyoqIEVtaXRzIGFuIGV2ZW50IHdpdGggdGhlIGFycmF5IG9mIEZpbGUgb2JqZWN0cyByZXByZXNlbnRpbmcgdGhlIGRyb3BwZWQgZmlsZXMuICovXG4gIEBPdXRwdXQoKVxuICBkcm9wcGVkOiBFdmVudEVtaXR0ZXI8RmlsZVtdPiA9IG5ldyBFdmVudEVtaXR0ZXIoKTtcblxuICBkcm9wcGVkRmlsZXM6IEZpbGVbXSA9IFtdO1xuXG4gIGlzRHJhZ2dpbmdGaWxlcyQ6IE9ic2VydmFibGU8Ym9vbGVhbj47XG4gIGlzRHJhZ2dpbmdPdmVyRmlsZVBsYWNlaG9sZGVyJDogT2JzZXJ2YWJsZTxib29sZWFuPjtcbiAgaXNFeHRlbnNpb25BbGxvd2VkJDogT2JzZXJ2YWJsZTxib29sZWFuPjtcbiAgaXNEcm9wQXJlYUVubGFyZ2VkJDogT2JzZXJ2YWJsZTxib29sZWFuPjtcbiAgaXNEcmFnZ2VkRmlsZVVuc3VwcG9ydGVkJDogT2JzZXJ2YWJsZTxib29sZWFuPjtcbiAgYWNjZXB0ZWRFeHRzOiBzdHJpbmdbXTtcbiAgdmFsaWQ6IGJvb2xlYW47XG4gIGVycm9yczogVmFsaWRhdGlvbkVycm9ycztcbiAgY29udHJvbEluc3RhbmNlOiBBYnN0cmFjdENvbnRyb2w7XG4gIC8vIFRPRE8gaGFuZGxlIGNvbnRyb2wgZGlzYWJsZWQgc3RhdGVcbiAgZGlzYWJsZWQ6IGJvb2xlYW47XG4gIGRpc2FibGVkVmFsaWRhdG9yczogSUF2YWlsYWJsZVZhbGlkYXRvcnMgPSB7XG4gICAgZmlsZU1heFNpemU6IGZhbHNlLFxuICAgIGZpbGVOb25FbXB0eTogZmFsc2UsXG4gICAgZmlsZU5hbWVNYXhMZW5ndGg6IGZhbHNlXG4gIH07XG4gIGxvYWRpbmdNZXNzYWdlID0gZ2V0dGV4dCgnVXBsb2FkIGluIHByb2dyZXNzJyk7XG4gIHByaXZhdGUgaXNFeHRlbnNpb25BbGxvd2VkOiBib29sZWFuO1xuICBwcml2YXRlIGZpbGVTaXplTGltaXQ6IG51bWJlcjtcblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIGZpbGVzU2VydmljZTogRmlsZXNTZXJ2aWNlLFxuICAgIEBJbmplY3QoSW5qZWN0b3IpIHByaXZhdGUgaW5qZWN0b3I6IEluamVjdG9yXG4gICkge31cblxuICBuZ09uSW5pdCgpOiB2b2lkIHtcbiAgICBjb25zdCBuZ0NvbnRyb2wgPSB0aGlzLmluamVjdG9yLmdldDxOZ0NvbnRyb2wgfCBmYWxzZT4oTmdDb250cm9sLCBmYWxzZSk7XG5cbiAgICBpZiAobmdDb250cm9sICYmIG5nQ29udHJvbC5jb250cm9sKSB7XG4gICAgICB0aGlzLmNvbnRyb2xJbnN0YW5jZSA9IG5nQ29udHJvbC5jb250cm9sO1xuICAgIH0gZWxzZSB7XG4gICAgICAvKipcbiAgICAgICAqIEluIGNhc2UgaWYgY29tcG9uZW50IGlzIHVzZWQgd2l0aG91dCB1dGlsaXppbmcgQ29udHJvbFZhbHVlQWNjZXNzb3IsIGludGVybmFsIGNvbnRyb2xcbiAgICAgICAqIGluc3RhbmNlIGlzIGNyZWF0ZWQsIHNvIGl0IGNhbiBzdGlsbCBiZW5lZml0IGZyb20gYnVpbHQgaW4gdmFsaWRhdG9ycy5cbiAgICAgICAqIChkcm9wcGVkKSBAT3V0cHV0IGV2ZW50IGNhbiBiZSB1c2VkIHRvIGdldCBsaXN0IG9mIGZpbGVzLlxuICAgICAgICovXG4gICAgICB0aGlzLmNvbnRyb2xJbnN0YW5jZSA9IG5ldyBGb3JtQ29udHJvbChbXSwgdGhpcy52YWxpZGF0ZS5iaW5kKHRoaXMpKTtcbiAgICAgIHRoaXMub25DaGFuZ2UgPSAoKSA9PiB7XG4gICAgICAgIHRoaXMuY29udHJvbEluc3RhbmNlLnNldFZhbHVlKHRoaXMuZHJvcHBlZEZpbGVzKTtcbiAgICAgIH07XG4gICAgICB0aGlzLm9uVmFsaWRhdG9yQ2hhbmdlID0gKCkgPT4ge1xuICAgICAgICB0aGlzLmNvbnRyb2xJbnN0YW5jZS51cGRhdGVWYWx1ZUFuZFZhbGlkaXR5KCk7XG4gICAgICB9O1xuICAgICAgdGhpcy5vblRvdWNoZWQgPSAoKSA9PiB7XG4gICAgICAgIHRoaXMuY29udHJvbEluc3RhbmNlLm1hcmtBc1RvdWNoZWQoKTtcbiAgICAgIH07XG4gICAgfVxuXG4gICAgdGhpcy5hY2NlcHRlZEV4dHMgPSB0aGlzLmZpbGVzU2VydmljZS5leHRyYWN0RmlsZUV4dGVuc2lvbnModGhpcy5hY2NlcHQpLm1hcCh0ID0+IGAuJHt0fWApO1xuXG4gICAgdGhpcy5kaXNhYmxlZFZhbGlkYXRvcnMgPSB7IC4uLnRoaXMuZGlzYWJsZWRWYWxpZGF0b3JzLCAuLi50aGlzLmRpc2FibGVWYWxpZGF0b3JzIH07XG5cbiAgICB0aGlzLmZpbGVzU2VydmljZS5sb2FkQnl0ZXNTaXplTGltaXQoKS50aGVuKHNpemVMaW1pdCA9PiB7XG4gICAgICB0aGlzLmZpbGVTaXplTGltaXQgPSBzaXplTGltaXQ7XG4gICAgICB0aGlzLm9uVmFsaWRhdG9yQ2hhbmdlKCk7XG4gICAgfSk7XG4gIH1cblxuICBuZ0FmdGVyVmlld0luaXQoKSB7XG4gICAgLyoqXG4gICAgICogRW1pdHMgRHJhZ0V2ZW50IHdoZW4gdXNlciBkcmFnIGZpbGUgaW5zaWRlIG9yIG91dHNpZGUgdmlld3BvcnQuIE9ubHkgd29ya3Mgd2hlbiB0eXBlIG9mIGRyYWdnZWQgZWxlbWVudHMgaXMgJ0ZpbGVzJy5cbiAgICAgKi9cbiAgICBjb25zdCBkcmFnZ2luZ0ZpbGVzR2xvYmFsbHkkID0gbWVyZ2UoXG4gICAgICBmcm9tRXZlbnQoZG9jdW1lbnQsICdkcmFnZW50ZXInKSxcbiAgICAgIGZyb21FdmVudChkb2N1bWVudCwgJ2RyYWdsZWF2ZScpLnBpcGUoXG4gICAgICAgIC8vIFRPRE8gcmV2aWV3IHdoeSB0aGlzIHNwZWNpZmljIGZpbHRlciBpcyBuZWVkZWRcbiAgICAgICAgZmlsdGVyKChlOiBEcmFnRXZlbnQpID0+IGUucGFnZVggPT09IDAgJiYgZS5wYWdlWSA9PT0gMClcbiAgICAgIClcbiAgICApLnBpcGUoXG4gICAgICBmaWx0ZXIoKGV2OiBEcmFnRXZlbnQpID0+IGV2LmRhdGFUcmFuc2Zlcj8udHlwZXMudG9TdHJpbmcoKSA9PT0gJ0ZpbGVzJyksXG4gICAgICBmaWx0ZXIoKCkgPT4gIXRoaXMudXBsb2FkSW5Qcm9ncmVzcylcbiAgICApO1xuXG4gICAgLyoqXG4gICAgICogRW1pdHMgYm9vbGVhbiB2YWx1ZSB3aGVuIGZpbGUgaXMgZHJhZ2dlZCBpbnNpZGUgb3Igb3V0c2lkZSBvZiB2aWV3cG9ydCwgb3IgZmlsZSBpcyBub3QgZHJhZ2dlZCBhbnltb3JlLlxuICAgICAqL1xuICAgIHRoaXMuaXNEcmFnZ2luZ0ZpbGVzJCA9IG1lcmdlKFxuICAgICAgZHJhZ2dpbmdGaWxlc0dsb2JhbGx5JC5waXBlKG1hcChldiA9PiBldi50eXBlID09PSAnZHJhZ2VudGVyJykpLFxuICAgICAgZnJvbUV2ZW50KGRvY3VtZW50LCAnbW91c2VlbnRlcicpLnBpcGUobWFwKCgpID0+IGZhbHNlKSksXG4gICAgICBmcm9tRXZlbnQoZG9jdW1lbnQsICdkcm9wJykucGlwZShtYXAoKCkgPT4gZmFsc2UpKVxuICAgICkucGlwZShkaXN0aW5jdFVudGlsQ2hhbmdlZCgpKTtcblxuICAgIC8qKlxuICAgICAqIEVtaXRzIGJvb2xlYW4gdmFsdWUgYWZ0ZXIgY2hlY2tpbmcgaWYgZHJhZ2dlZCBmaWxlcyBtYXRjaCBjb25maWd1cmVkIGV4dGVuc2lvbnMgbGlzdCxcbiAgICAgKiBXaGVuIGRyYWdnaW5nIG9mIGZpbGUgc3RhcnRzLCBnZXQgbWltZXR5cGVzIG9mIGVhY2ggZmlsZSwgY29udmVydCB0aGVtIHRvIHBvc3NpYmxlIGV4dGVuc2lvbnMgbGlzdFxuICAgICAqIHRoYXQgbWF5IGJlIGFzc29jaWF0ZWQgd2l0aCBnaXZlbiBtaW1ldHlwZSwgYW5kIHRoZW4gY2hlY2sgaWYgYW55IG9mIHRoZXNlIGV4dGVuc2lvbnMgbWF0Y2ggY29uZmlndXJlZFxuICAgICAqIGV4dGVuc2lvbnMgYWNjZXB0IGxpc3QuXG4gICAgICovXG4gICAgdGhpcy5pc0V4dGVuc2lvbkFsbG93ZWQkID0gZHJhZ2dpbmdGaWxlc0dsb2JhbGx5JC5waXBlKFxuICAgICAgc2FtcGxlKHRoaXMuaXNEcmFnZ2luZ0ZpbGVzJC5waXBlKGZpbHRlcihlbmxhcmdlZCA9PiBlbmxhcmdlZCkpKSxcbiAgICAgIHN3aXRjaE1hcChldiA9PiB7XG4gICAgICAgIGlmICghdGhpcy5hY2NlcHQpIHtcbiAgICAgICAgICByZXR1cm4gb2YodHJ1ZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gb2YoZXYpLnBpcGUoXG4gICAgICAgICAgbWFwKGV2ID0+IFsuLi4oZXYuZGF0YVRyYW5zZmVyLml0ZW1zIGFzIHVua25vd24gYXMgRGF0YVRyYW5zZmVySXRlbVtdKV0pLFxuICAgICAgICAgIG1hcChmaWxlcyA9PlxuICAgICAgICAgICAgZmlsZXMubWFwKGZpbGUgPT4gKG1pbWVEQltmaWxlLnR5cGVdLmV4dGVuc2lvbnMgYXMgc3RyaW5nW10gfCB1bmRlZmluZWQpIHx8IFsndW5rbm93biddKVxuICAgICAgICAgICksXG4gICAgICAgICAgbWFwKGV4dGVuc2lvbnNGb3JGaWxlcyA9PlxuICAgICAgICAgICAgZXh0ZW5zaW9uc0ZvckZpbGVzLmV2ZXJ5KGV4dGVuc2lvbnNGb3JGaWxlID0+XG4gICAgICAgICAgICAgIGV4dGVuc2lvbnNGb3JGaWxlLnNvbWUoZXh0ID0+XG4gICAgICAgICAgICAgICAgdGhpcy5maWxlc1NlcnZpY2UuZXh0cmFjdEZpbGVFeHRlbnNpb25zKHRoaXMuYWNjZXB0KS5pbmNsdWRlcyhleHQpXG4gICAgICAgICAgICAgIClcbiAgICAgICAgICAgIClcbiAgICAgICAgICApXG4gICAgICAgICk7XG4gICAgICB9KSxcbiAgICAgIHRhcChhbGxvd2VkID0+IHtcbiAgICAgICAgdGhpcy5pc0V4dGVuc2lvbkFsbG93ZWQgPSBhbGxvd2VkO1xuICAgICAgfSlcbiAgICApO1xuXG4gICAgLyoqXG4gICAgICogSWYgRmlsZSBpcyBiZWluZyBkcmFnZ2VkIGFuZCBleHRlbnNpb25zIG1lZXRzIHJlcXVpcmVtZW50cywgdGhlbiBkcm9wLWFyZWEgY2FuIGJlIGVubGFyZ2VkLlxuICAgICAqL1xuICAgIHRoaXMuaXNEcm9wQXJlYUVubGFyZ2VkJCA9IHRoaXMuaXNEcmFnZ2luZ0ZpbGVzJDtcbiAgICB0aGlzLmlzRHJhZ2dlZEZpbGVVbnN1cHBvcnRlZCQgPSB0aGlzLmlzRHJhZ2dpbmdGaWxlcyQucGlwZShcbiAgICAgIHdpdGhMYXRlc3RGcm9tKHRoaXMuaXNFeHRlbnNpb25BbGxvd2VkJCksXG4gICAgICBtYXAoKFtmaWxlRHJhZ2dlZCwgZXh0QWxsb3dlZF0pID0+IGZpbGVEcmFnZ2VkICYmICFleHRBbGxvd2VkKVxuICAgICk7XG5cbiAgICAvKipcbiAgICAgKiBFbWl0cyB0cnVlIHdoZW4gZmlsZSBpcyBob3ZlcmluZyBhYm92ZSBkcm9wLWFyZWEuIE9ubHkgd29ya3Mgd2hlbiBpc0Ryb3BBcmVhRW5sYXJnZWQkIGFsc28gZW1pdHMgdHJ1ZS5cbiAgICAgKiBUaGlzIGlzIHdvcmthcm91bmQgZm9yIENTUyA6aG92ZXIgcHNldWRvLWNsYXNzIG5vdCB0cmlnZ2VyaW5nIHdoZW4gZHJhZ2dpbmcgZmlsZS5cbiAgICAgKi9cbiAgICB0aGlzLmlzRHJhZ2dpbmdPdmVyRmlsZVBsYWNlaG9sZGVyJCA9IG1lcmdlKFxuICAgICAgZnJvbUV2ZW50KHRoaXMuZmlsZVBsYWNlaG9sZGVyLm5hdGl2ZUVsZW1lbnQsICdkcmFnZW50ZXInKSxcbiAgICAgIGZyb21FdmVudCh0aGlzLmZpbGVQbGFjZWhvbGRlci5uYXRpdmVFbGVtZW50LCAnZHJhZ2xlYXZlJyksXG4gICAgICBmcm9tRXZlbnQodGhpcy5maWxlUGxhY2Vob2xkZXIubmF0aXZlRWxlbWVudCwgJ2Ryb3AnKVxuICAgICkucGlwZShcbiAgICAgIGZpbHRlcigoZXY6IERyYWdFdmVudCkgPT4gZXYuZGF0YVRyYW5zZmVyPy50eXBlcy50b1N0cmluZygpID09PSAnRmlsZXMnKSxcbiAgICAgIG1hcChldiA9PiBldi50eXBlID09PSAnZHJhZ2VudGVyJyksXG4gICAgICB3aXRoTGF0ZXN0RnJvbSh0aGlzLmlzRHJvcEFyZWFFbmxhcmdlZCQpLFxuICAgICAgbWFwKChbZHJhZ2dpbmdPdmVyLCBkcm9wQXJlYUVubGFyZ2VkXSkgPT4gZHJhZ2dpbmdPdmVyICYmIGRyb3BBcmVhRW5sYXJnZWQpXG4gICAgKTtcbiAgfVxuXG4gIGFmdGVyVmFsdWVDaGFuZ2VkKCkge1xuICAgIHRoaXMub25DaGFuZ2UoWy4uLnRoaXMuZHJvcHBlZEZpbGVzXSk7XG4gICAgdGhpcy5vblRvdWNoZWQoKTtcbiAgICB0aGlzLmRyb3BwZWQuZW1pdChbLi4udGhpcy5kcm9wcGVkRmlsZXNdKTtcbiAgICB0aGlzLnZhbGlkID0gdGhpcy5jb250cm9sSW5zdGFuY2UudmFsaWQ7XG4gICAgdGhpcy5lcnJvcnMgPSB0aGlzLmNvbnRyb2xJbnN0YW5jZS5lcnJvcnM7XG4gIH1cblxuICBvbkRyb3AoZTogRHJhZ0V2ZW50KSB7XG4gICAgaWYgKHRoaXMudXBsb2FkSW5Qcm9ncmVzcykge1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGlmIChlLmRhdGFUcmFuc2Zlci50eXBlcy50b1N0cmluZygpICE9PSAnRmlsZXMnKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGNvbnN0IGZpbGVzID0gZS5kYXRhVHJhbnNmZXIuZmlsZXM7XG4gICAgaWYgKCFmaWxlcz8ubGVuZ3RoKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBmaWxlcy5sZW5ndGg7IGkrKykge1xuICAgICAgdGhpcy5kcm9wcGVkRmlsZXMucHVzaChmaWxlcy5pdGVtKGkpKTtcbiAgICB9XG4gICAgdGhpcy5hZnRlclZhbHVlQ2hhbmdlZCgpO1xuICB9XG5cbiAgZmlsZXNTZWxlY3RlZChldjogRXZlbnQpOiB2b2lkIHtcbiAgICBjb25zdCBpbnB1dEVsZW1lbnQgPSBldi50YXJnZXQgYXMgSFRNTElucHV0RWxlbWVudDtcbiAgICBjb25zdCBmaWxlczogRmlsZVtdID0gQXJyYXkuZnJvbShpbnB1dEVsZW1lbnQuZmlsZXMpO1xuICAgIHRoaXMuZHJvcHBlZEZpbGVzLnB1c2goLi4uZmlsZXMpO1xuICAgIGlucHV0RWxlbWVudC52YWx1ZSA9IG51bGw7XG4gICAgdGhpcy5hZnRlclZhbHVlQ2hhbmdlZCgpO1xuICB9XG5cbiAgZGVsZXRlQXQoaW5kZXg6IG51bWJlcikge1xuICAgIHRoaXMuZHJvcHBlZEZpbGVzLnNwbGljZShpbmRleCwgMSk7XG4gICAgdGhpcy5hZnRlclZhbHVlQ2hhbmdlZCgpO1xuICB9XG5cbiAgcmVnaXN0ZXJPbkNoYW5nZShmbjogbmV2ZXIpOiB