ngx-image-uploader
Version:
Angular2 asynchronous image uploader with preview
818 lines (798 loc) • 88 kB
JavaScript
import { Observable } from 'rxjs';
import { Injectable, NgModule, Component, ViewChild, Renderer, Input, Output, EventEmitter, ChangeDetectorRef, forwardRef, HostListener, defineInjectable, inject } from '@angular/core';
import { HttpClient, HttpRequest, HttpEventType, HttpResponse, HttpHeaders, HttpClientModule } from '@angular/common/http';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import Cropper from 'cropperjs';
import { CommonModule } from '@angular/common';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/** @enum {number} */
const FileQueueStatus = {
Pending: 0,
Success: 1,
Error: 2,
Progress: 3,
};
FileQueueStatus[FileQueueStatus.Pending] = "Pending";
FileQueueStatus[FileQueueStatus.Success] = "Success";
FileQueueStatus[FileQueueStatus.Error] = "Error";
FileQueueStatus[FileQueueStatus.Progress] = "Progress";
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class FileQueueObject {
/**
* @param {?} file
*/
constructor(file) {
this.status = FileQueueStatus.Pending;
this.progress = 0;
this.request = null;
this.response = null;
this.isPending = () => this.status === FileQueueStatus.Pending;
this.isSuccess = () => this.status === FileQueueStatus.Success;
this.isError = () => this.status === FileQueueStatus.Error;
this.inProgress = () => this.status === FileQueueStatus.Progress;
this.isUploadable = () => this.status === FileQueueStatus.Pending || this.status === FileQueueStatus.Error;
this.file = file;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class ImageUploaderService {
/**
* @param {?} http
*/
constructor(http) {
this.http = http;
}
/**
* @param {?} file
* @param {?} options
* @param {?=} cropOptions
* @return {?}
*/
uploadFile(file, options, cropOptions) {
this.setDefaults(options);
const /** @type {?} */ form = new FormData();
form.append(options.fieldName, file, file.name);
if (cropOptions) {
form.append('X', cropOptions.x.toString());
form.append('Y', cropOptions.y.toString());
form.append('Width', cropOptions.width.toString());
form.append('Height', cropOptions.height.toString());
}
// upload file and report progress
const /** @type {?} */ req = new HttpRequest('POST', options.uploadUrl, form, {
reportProgress: true,
withCredentials: options.withCredentials,
headers: this._buildHeaders(options)
});
return Observable.create(obs => {
const /** @type {?} */ queueObj = new FileQueueObject(file);
queueObj.request = this.http.request(req).subscribe((event) => {
if (event.type === HttpEventType.UploadProgress) {
this._uploadProgress(queueObj, event);
obs.next(queueObj);
}
else if (event instanceof HttpResponse) {
this._uploadComplete(queueObj, event);
obs.next(queueObj);
obs.complete();
}
}, (err) => {
if (err.error instanceof Error) {
// A client-side or network error occurred. Handle it accordingly.
this._uploadFailed(queueObj, err);
obs.next(queueObj);
obs.complete();
}
else {
// The backend returned an unsuccessful response code.
this._uploadFailed(queueObj, err);
obs.next(queueObj);
obs.complete();
}
});
});
}
/**
* @param {?} url
* @param {?} options
* @return {?}
*/
getFile(url, options) {
return Observable.create((observer) => {
let /** @type {?} */ headers = new HttpHeaders();
if (options.authToken) {
headers = headers.append('Authorization', `${options.authTokenPrefix} ${options.authToken}`);
}
this.http.get(url, { responseType: 'blob', headers: headers }).subscribe(res => {
const /** @type {?} */ file = new File([res], 'filename', { type: res.type });
observer.next(file);
observer.complete();
}, err => {
observer.error(err.status);
observer.complete();
});
});
}
/**
* @param {?} options
* @return {?}
*/
_buildHeaders(options) {
let /** @type {?} */ headers = new HttpHeaders();
if (options.authToken) {
headers = headers.append('Authorization', `${options.authTokenPrefix} ${options.authToken}`);
}
if (options.customHeaders) {
Object.keys(options.customHeaders).forEach((key) => {
headers = headers.append(key, options.customHeaders[key]);
});
}
return headers;
}
/**
* @param {?} queueObj
* @param {?} event
* @return {?}
*/
_uploadProgress(queueObj, event) {
// update the FileQueueObject with the current progress
const /** @type {?} */ progress = Math.round(100 * event.loaded / event.total);
queueObj.progress = progress;
queueObj.status = FileQueueStatus.Progress;
// this._queue.next(this._files);
}
/**
* @param {?} queueObj
* @param {?} response
* @return {?}
*/
_uploadComplete(queueObj, response) {
// update the FileQueueObject as completed
queueObj.progress = 100;
queueObj.status = FileQueueStatus.Success;
queueObj.response = response;
// this._queue.next(this._files);
// this.onCompleteItem(queueObj, response.body);
}
/**
* @param {?} queueObj
* @param {?} response
* @return {?}
*/
_uploadFailed(queueObj, response) {
// update the FileQueueObject as errored
queueObj.progress = 0;
queueObj.status = FileQueueStatus.Error;
queueObj.response = response;
// this._queue.next(this._files);
}
/**
* @param {?} options
* @return {?}
*/
setDefaults(options) {
options.withCredentials = options.withCredentials || false;
options.httpMethod = options.httpMethod || 'POST';
options.authTokenPrefix = options.authTokenPrefix || 'Bearer';
options.fieldName = options.fieldName || 'file';
}
}
ImageUploaderService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] },
];
/** @nocollapse */
ImageUploaderService.ctorParameters = () => [
{ type: HttpClient, },
];
/** @nocollapse */ ImageUploaderService.ngInjectableDef = defineInjectable({ factory: function ImageUploaderService_Factory() { return new ImageUploaderService(inject(HttpClient)); }, token: ImageUploaderService, providedIn: "root" });
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @param {?} url
* @param {?} cb
* @return {?}
*/
function createImage(url, cb) {
const /** @type {?} */ image = new Image();
image.onload = function () {
cb(image);
};
image.src = url;
}
const /** @type {?} */ resizeAreaId = 'imageupload-resize-area';
/**
* @return {?}
*/
function getResizeArea() {
let /** @type {?} */ resizeArea = document.getElementById(resizeAreaId);
if (!resizeArea) {
resizeArea = document.createElement('canvas');
resizeArea.id = resizeAreaId;
resizeArea.style.display = 'none';
document.body.appendChild(resizeArea);
}
return /** @type {?} */ (resizeArea);
}
/**
* @param {?} origImage
* @param {?=} __1
* @return {?}
*/
function resizeImage(origImage, { resizeHeight, resizeWidth, resizeQuality = 0.7, resizeType = 'image/jpeg', resizeMode = 'fill' } = {}) {
const /** @type {?} */ canvas = getResizeArea();
let /** @type {?} */ height = origImage.height;
let /** @type {?} */ width = origImage.width;
let /** @type {?} */ offsetX = 0;
let /** @type {?} */ offsetY = 0;
if (resizeMode === 'fill') {
// calculate the width and height, constraining the proportions
if (width / height > resizeWidth / resizeHeight) {
width = Math.round(height * resizeWidth / resizeHeight);
}
else {
height = Math.round(width * resizeHeight / resizeWidth);
}
canvas.width = resizeWidth <= width ? resizeWidth : width;
canvas.height = resizeHeight <= height ? resizeHeight : height;
offsetX = origImage.width / 2 - width / 2;
offsetY = origImage.height / 2 - height / 2;
// draw image on canvas
const /** @type {?} */ ctx = canvas.getContext('2d');
ctx.drawImage(origImage, offsetX, offsetY, width, height, 0, 0, canvas.width, canvas.height);
}
else if (resizeMode === 'fit') {
// calculate the width and height, constraining the proportions
if (width > height) {
if (width > resizeWidth) {
height = Math.round(height *= resizeWidth / width);
width = resizeWidth;
}
}
else {
if (height > resizeHeight) {
width = Math.round(width *= resizeHeight / height);
height = resizeHeight;
}
}
canvas.width = width;
canvas.height = height;
// draw image on canvas
const /** @type {?} */ ctx = canvas.getContext('2d');
ctx.drawImage(origImage, 0, 0, width, height);
}
else {
throw new Error('Unknown resizeMode: ' + resizeMode);
}
// get the data from canvas as 70% jpg (or specified type).
return canvas.toDataURL(resizeType, resizeQuality);
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/** @enum {number} */
const Status = {
NotSelected: 0,
Selected: 1,
Uploading: 2,
Loading: 3,
Loaded: 4,
Error: 5,
};
Status[Status.NotSelected] = "NotSelected";
Status[Status.Selected] = "Selected";
Status[Status.Uploading] = "Uploading";
Status[Status.Loading] = "Loading";
Status[Status.Loaded] = "Loaded";
Status[Status.Error] = "Error";
class ImageUploaderComponent {
/**
* @param {?} renderer
* @param {?} uploader
* @param {?} changeDetector
*/
constructor(renderer, uploader, changeDetector) {
this.renderer = renderer;
this.uploader = uploader;
this.changeDetector = changeDetector;
this.statusEnum = Status;
this._status = Status.NotSelected;
this.thumbnailWidth = 150;
this.thumbnailHeight = 150;
this.cropper = undefined;
this.upload = new EventEmitter();
this.statusChange = new EventEmitter();
this.propagateChange = (_) => { };
}
/**
* @return {?}
*/
get imageThumbnail() {
return this._imageThumbnail;
}
/**
* @param {?} value
* @return {?}
*/
set imageThumbnail(value) {
this._imageThumbnail = value;
this.propagateChange(this._imageThumbnail);
if (value !== undefined) {
this.status = Status.Selected;
}
else {
this.status = Status.NotSelected;
}
}
/**
* @return {?}
*/
get errorMessage() {
return this._errorMessage;
}
/**
* @param {?} value
* @return {?}
*/
set errorMessage(value) {
this._errorMessage = value;
if (value) {
this.status = Status.Error;
}
else {
this.status = Status.NotSelected;
}
}
/**
* @return {?}
*/
get status() {
return this._status;
}
/**
* @param {?} value
* @return {?}
*/
set status(value) {
this._status = value;
this.statusChange.emit(value);
}
/**
* @param {?} value
* @return {?}
*/
writeValue(value) {
if (value) {
this.loadAndResize(value);
}
else {
this._imageThumbnail = undefined;
this.status = Status.NotSelected;
}
}
/**
* @param {?} fn
* @return {?}
*/
registerOnChange(fn) {
this.propagateChange = fn;
}
/**
* @return {?}
*/
registerOnTouched() { }
/**
* @return {?}
*/
ngOnInit() {
if (this.options) {
if (this.options.thumbnailWidth) {
this.thumbnailWidth = this.options.thumbnailWidth;
}
if (this.options.thumbnailHeight) {
this.thumbnailHeight = this.options.thumbnailHeight;
}
if (this.options.resizeOnLoad === undefined) {
this.options.resizeOnLoad = true;
}
if (this.options.autoUpload === undefined) {
this.options.autoUpload = true;
}
if (this.options.cropEnabled === undefined) {
this.options.cropEnabled = false;
}
if (this.options.autoUpload && this.options.cropEnabled) {
throw new Error('autoUpload and cropEnabled cannot be enabled simultaneously');
}
}
}
/**
* @return {?}
*/
ngAfterViewChecked() {
if (this.options && this.options.cropEnabled && this.imageElement && this.fileToUpload && !this.cropper) {
this.cropper = new Cropper(this.imageElement.nativeElement, {
viewMode: 1,
aspectRatio: this.options.cropAspectRatio ? this.options.cropAspectRatio : null
});
}
}
/**
* @return {?}
*/
ngOnDestroy() {
if (this.cropper) {
this.cropper.destroy();
this.cropper = null;
}
}
/**
* @param {?} url
* @return {?}
*/
loadAndResize(url) {
this.status = Status.Loading;
this.uploader.getFile(url, this.options).subscribe(file => {
if (this.options.resizeOnLoad) {
// thumbnail
const /** @type {?} */ result = {
file: file,
url: URL.createObjectURL(file)
};
this.resize(result).then(r => {
this._imageThumbnail = r.resized.dataURL;
this.status = Status.Loaded;
});
}
else {
const /** @type {?} */ result = {
file: null,
url: null
};
this.fileToDataURL(file, result).then(r => {
this._imageThumbnail = r.dataURL;
this.status = Status.Loaded;
});
}
}, error => {
this.errorMessage = error || 'Error while getting an image';
});
}
/**
* @return {?}
*/
onImageClicked() {
this.renderer.invokeElementMethod(this.fileInputElement.nativeElement, 'click');
}
/**
* @return {?}
*/
onFileChanged() {
const /** @type {?} */ file = this.fileInputElement.nativeElement.files[0];
if (!file) {
return;
}
this.validateAndUpload(file);
}
/**
* @param {?} file
* @return {?}
*/
validateAndUpload(file) {
this.propagateChange(null);
if (this.options && this.options.allowedImageTypes) {
if (!this.options.allowedImageTypes.some(allowedType => file.type === allowedType)) {
this.errorMessage = 'Only these image types are allowed: ' + this.options.allowedImageTypes.join(', ');
return;
}
}
if (this.options && this.options.maxImageSize) {
if (file.size > this.options.maxImageSize * 1024 * 1024) {
this.errorMessage = `Image must not be larger than ${this.options.maxImageSize} MB`;
return;
}
}
this.fileToUpload = file;
if (this.options && this.options.autoUpload) {
this.uploadImage();
}
// thumbnail
const /** @type {?} */ result = {
file: file,
url: URL.createObjectURL(file)
};
this.resize(result).then(r => {
this._imageThumbnail = r.resized.dataURL;
this.origImageWidth = r.width;
this.orgiImageHeight = r.height;
if (this.options && !this.options.autoUpload) {
this.status = Status.Selected;
}
});
}
/**
* @return {?}
*/
uploadImage() {
this.progress = 0;
this.status = Status.Uploading;
let /** @type {?} */ cropOptions;
if (this.cropper) {
const /** @type {?} */ scale = this.origImageWidth / this.cropper.getImageData().naturalWidth;
const /** @type {?} */ cropData = this.cropper.getData();
cropOptions = {
x: Math.round(cropData.x * scale),
y: Math.round(cropData.y * scale),
width: Math.round(cropData.width * scale),
height: Math.round(cropData.height * scale)
};
}
// const queueObj = this.uploader.uploadFile(this.fileToUpload, this.options, cropOptions);
// file progress
this.uploader.uploadFile(this.fileToUpload, this.options, cropOptions).subscribe(file => {
this.progress = file.progress;
if (file.isError()) {
if (file.response.status || file.response.statusText) {
this.errorMessage = `${file.response.status}: ${file.response.statusText}`;
}
else {
this.errorMessage = 'Error while uploading';
}
// on some upload errors change detection does not work, so we are forcing manually
this.changeDetector.detectChanges();
}
if (!file.inProgress()) {
// notify that value was changed only when image was uploaded and no error
if (file.isSuccess()) {
this.propagateChange(this._imageThumbnail);
this.status = Status.Selected;
this.fileToUpload = undefined;
}
this.upload.emit(file);
}
});
}
/**
* @return {?}
*/
removeImage() {
this.fileInputElement.nativeElement.value = null;
this.imageThumbnail = undefined;
if (this.cropper) {
this.cropper.destroy();
this.cropper = null;
}
}
/**
* @return {?}
*/
dismissError() {
this.errorMessage = undefined;
this.removeImage();
}
/**
* @param {?} e
* @return {?}
*/
drop(e) {
e.preventDefault();
e.stopPropagation();
if (!e.dataTransfer || !e.dataTransfer.files.length) {
return;
}
this.validateAndUpload(e.dataTransfer.files[0]);
this.updateDragOverlayStyles(false);
}
/**
* @param {?} e
* @return {?}
*/
dragenter(e) {
e.preventDefault();
e.stopPropagation();
}
/**
* @param {?} e
* @return {?}
*/
dragover(e) {
e.preventDefault();
e.stopPropagation();
this.updateDragOverlayStyles(true);
}
/**
* @param {?} e
* @return {?}
*/
dragleave(e) {
e.preventDefault();
e.stopPropagation();
this.updateDragOverlayStyles(false);
}
/**
* @param {?} isDragOver
* @return {?}
*/
updateDragOverlayStyles(isDragOver) {
// TODO: find a way that does not trigger dragleave when displaying overlay
// if (isDragOver) {
// this.renderer.setElementStyle(this.dragOverlayElement.nativeElement, 'display', 'block');
// } else {
// this.renderer.setElementStyle(this.dragOverlayElement.nativeElement, 'display', 'none');
// }
}
/**
* @param {?} result
* @return {?}
*/
resize(result) {
const /** @type {?} */ resizeOptions = {
resizeHeight: this.thumbnailHeight,
resizeWidth: this.thumbnailWidth,
resizeType: result.file.type,
resizeMode: this.options.thumbnailResizeMode
};
return new Promise((resolve) => {
createImage(result.url, image => {
const /** @type {?} */ dataUrl = resizeImage(image, resizeOptions);
result.width = image.width;
result.height = image.height;
result.resized = {
dataURL: dataUrl,
type: this.getType(dataUrl)
};
resolve(result);
});
});
}
/**
* @param {?} dataUrl
* @return {?}
*/
getType(dataUrl) {
return dataUrl.match(/:(.+\/.+;)/)[1];
}
/**
* @param {?} file
* @param {?} result
* @return {?}
*/
fileToDataURL(file, result) {
return new Promise((resolve) => {
const /** @type {?} */ reader = new FileReader();
reader.onload = function (e) {
result.dataURL = reader.result;
resolve(result);
};
reader.readAsDataURL(file);
});
}
}
ImageUploaderComponent.decorators = [
{ type: Component, args: [{
selector: 'ngx-image-uploader',
template: `<div class="image-container">
<div class="match-parent" [ngSwitch]="status">
<div class="match-parent" *ngSwitchCase="statusEnum.NotSelected">
<button type="button" class="add-image-btn" (click)="onImageClicked()">
<div>
<p class="plus">+</p>
<p>Click here to add image</p>
<p>Or drop image here</p>
</div>
</button>
</div>
<div class="selected-status-wrapper match-parent" *ngSwitchCase="statusEnum.Loaded">
<img [src]="imageThumbnail" #imageElement>
<button type="button" class="remove" (click)="removeImage()">×</button>
</div>
<div class="selected-status-wrapper match-parent" *ngSwitchCase="statusEnum.Selected">
<img [src]="imageThumbnail" #imageElement>
<button type="button" class="remove" (click)="removeImage()">×</button>
</div>
<div *ngSwitchCase="statusEnum.Uploading">
<img [attr.src]="imageThumbnail ? imageThumbnail : null" (click)="onImageClicked()">
<div class="progress-bar">
<div class="bar" [style.width]="progress+'%'"></div>
</div>
</div>
<div class="match-parent" *ngSwitchCase="statusEnum.Loading">
<div class="sk-fading-circle">
<div class="sk-circle1 sk-circle"></div>
<div class="sk-circle2 sk-circle"></div>
<div class="sk-circle3 sk-circle"></div>
<div class="sk-circle4 sk-circle"></div>
<div class="sk-circle5 sk-circle"></div>
<div class="sk-circle6 sk-circle"></div>
<div class="sk-circle7 sk-circle"></div>
<div class="sk-circle8 sk-circle"></div>
<div class="sk-circle9 sk-circle"></div>
<div class="sk-circle10 sk-circle"></div>
<div class="sk-circle11 sk-circle"></div>
<div class="sk-circle12 sk-circle"></div>
</div>
</div>
<div class="match-parent" *ngSwitchCase="statusEnum.Error">
<div class="error">
<div class="error-message">
<p>{{errorMessage}}</p>
</div>
<button type="button" class="remove" (click)="dismissError()">×</button>
</div>
</div>
</div>
<input type="file" #fileInput (change)="onFileChanged()">
<div class="drag-overlay" [hidden]="true" #dragOverlay></div>
</div>
`,
styles: [`:host{display:block}.match-parent{width:100%;height:100%}.add-image-btn{width:100%;height:100%;font-weight:700;opacity:.5;border:0}.add-image-btn:hover{opacity:.7;cursor:pointer;background-color:#ddd;box-shadow:inset 0 0 5px rgba(0,0,0,.3)}.add-image-btn .plus{font-size:30px;font-weight:400;margin-bottom:5px;margin-top:5px}img{cursor:pointer;position:absolute;top:50%;left:50%;margin-right:-50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);max-width:100%}.image-container{width:100%;height:100%;position:relative;display:inline-block;background-color:#f1f1f1;box-shadow:inset 0 0 5px rgba(0,0,0,.2)}.remove{position:absolute;top:0;right:0;width:40px;height:40px;font-size:25px;text-align:center;opacity:.8;border:0;cursor:pointer}.selected-status-wrapper>.remove:hover{opacity:.7;background-color:#fff}.error .remove{opacity:.5}.error .remove:hover{opacity:.7}input{display:none}.error{width:100%;height:100%;border:1px solid #e3a5a2;color:#d2706b;background-color:#fbf1f0;position:relative;text-align:center;display:flex;align-items:center}.error-message{width:100%;line-height:18px}.progress-bar{position:absolute;bottom:10%;left:10%;width:80%;height:5px;background-color:grey;opacity:.9;overflow:hidden}.bar{position:absolute;height:100%;background-color:#a4c639}.drag-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background-color:#ff0;opacity:.3}.sk-fading-circle{width:40px;height:40px;position:relative;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.sk-fading-circle .sk-circle{width:100%;height:100%;position:absolute;left:0;top:0}.sk-fading-circle .sk-circle:before{content:'';display:block;margin:0 auto;width:15%;height:15%;background-color:#333;border-radius:100%;-webkit-animation:1.2s ease-in-out infinite both sk-circleFadeDelay;animation:1.2s ease-in-out infinite both sk-circleFadeDelay}.sk-fading-circle .sk-circle2{-webkit-transform:rotate(30deg);transform:rotate(30deg)}.sk-fading-circle .sk-circle3{-webkit-transform:rotate(60deg);transform:rotate(60deg)}.sk-fading-circle .sk-circle4{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.sk-fading-circle .sk-circle5{-webkit-transform:rotate(120deg);transform:rotate(120deg)}.sk-fading-circle .sk-circle6{-webkit-transform:rotate(150deg);transform:rotate(150deg)}.sk-fading-circle .sk-circle7{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.sk-fading-circle .sk-circle8{-webkit-transform:rotate(210deg);transform:rotate(210deg)}.sk-fading-circle .sk-circle9{-webkit-transform:rotate(240deg);transform:rotate(240deg)}.sk-fading-circle .sk-circle10{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.sk-fading-circle .sk-circle11{-webkit-transform:rotate(300deg);transform:rotate(300deg)}.sk-fading-circle .sk-circle12{-webkit-transform:rotate(330deg);transform:rotate(330deg)}.sk-fading-circle .sk-circle2:before{-webkit-animation-delay:-1.1s;animation-delay:-1.1s}.sk-fading-circle .sk-circle3:before{-webkit-animation-delay:-1s;animation-delay:-1s}.sk-fading-circle .sk-circle4:before{-webkit-animation-delay:-.9s;animation-delay:-.9s}.sk-fading-circle .sk-circle5:before{-webkit-animation-delay:-.8s;animation-delay:-.8s}.sk-fading-circle .sk-circle6:before{-webkit-animation-delay:-.7s;animation-delay:-.7s}.sk-fading-circle .sk-circle7:before{-webkit-animation-delay:-.6s;animation-delay:-.6s}.sk-fading-circle .sk-circle8:before{-webkit-animation-delay:-.5s;animation-delay:-.5s}.sk-fading-circle .sk-circle9:before{-webkit-animation-delay:-.4s;animation-delay:-.4s}.sk-fading-circle .sk-circle10:before{-webkit-animation-delay:-.3s;animation-delay:-.3s}.sk-fading-circle .sk-circle11:before{-webkit-animation-delay:-.2s;animation-delay:-.2s}.sk-fading-circle .sk-circle12:before{-webkit-animation-delay:-.1s;animation-delay:-.1s}@-webkit-keyframes sk-circleFadeDelay{0%,100%,39%{opacity:0}40%{opacity:1}}@keyframes sk-circleFadeDelay{0%,100%,39%{opacity:0}40%{opacity:1}}`],
host: {
'[style.width]': 'thumbnailWidth + "px"',
'[style.height]': 'thumbnailHeight + "px"'
},
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ImageUploaderComponent),
multi: true
}
]
},] },
];
/** @nocollapse */
ImageUploaderComponent.ctorParameters = () => [
{ type: Renderer, },
{ type: ImageUploaderService, },
{ type: ChangeDetectorRef, },
];
ImageUploaderComponent.propDecorators = {
"imageElement": [{ type: ViewChild, args: ['imageElement',] },],
"fileInputElement": [{ type: ViewChild, args: ['fileInput',] },],
"dragOverlayElement": [{ type: ViewChild, args: ['dragOverlay',] },],
"options": [{ type: Input },],
"upload": [{ type: Output },],
"statusChange": [{ type: Output },],
"drop": [{ type: HostListener, args: ['drop', ['$event'],] },],
"dragenter": [{ type: HostListener, args: ['dragenter', ['$event'],] },],
"dragover": [{ type: HostListener, args: ['dragover', ['$event'],] },],
"dragleave": [{ type: HostListener, args: ['dragleave', ['$event'],] },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
class ImageUploaderModule {
}
ImageUploaderModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule,
HttpClientModule
],
declarations: [ImageUploaderComponent],
exports: [ImageUploaderComponent]
},] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
export { ImageUploaderService, Status, ImageUploaderComponent, ImageUploaderModule, FileQueueObject };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWltYWdlLXVwbG9hZGVyLmpzLm1hcCIsInNvdXJjZXMiOlsibmc6Ly9uZ3gtaW1hZ2UtdXBsb2FkZXIvbGliL2ZpbGUtcXVldWUtb2JqZWN0LnRzIiwibmc6Ly9uZ3gtaW1hZ2UtdXBsb2FkZXIvbGliL2ltYWdlLXVwbG9hZGVyLnNlcnZpY2UudHMiLCJuZzovL25neC1pbWFnZS11cGxvYWRlci9saWIvdXRpbHMudHMiLCJuZzovL25neC1pbWFnZS11cGxvYWRlci9saWIvaW1hZ2UtdXBsb2FkZXIuY29tcG9uZW50LnRzIiwibmc6Ly9uZ3gtaW1hZ2UtdXBsb2FkZXIvbGliL2ltYWdlLXVwbG9hZGVyLm1vZHVsZS50cyJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBIdHRwUmVzcG9uc2UsIEh0dHBFcnJvclJlc3BvbnNlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uL2h0dHAnO1xyXG5pbXBvcnQgeyBTdWJzY3JpcHRpb24gfSBmcm9tICdyeGpzJztcclxuXHJcbmltcG9ydCB7IEZpbGVRdWV1ZVN0YXR1cyB9IGZyb20gJy4vZmlsZS1xdWV1ZS1zdGF0dXMnO1xyXG5cclxuZXhwb3J0IGNsYXNzIEZpbGVRdWV1ZU9iamVjdCB7XHJcbiAgcHVibGljIGZpbGU6IGFueTtcclxuICBwdWJsaWMgc3RhdHVzOiBGaWxlUXVldWVTdGF0dXMgPSBGaWxlUXVldWVTdGF0dXMuUGVuZGluZztcclxuICBwdWJsaWMgcHJvZ3Jlc3M6IG51bWJlciA9IDA7XHJcbiAgcHVibGljIHJlcXVlc3Q6IFN1YnNjcmlwdGlvbiA9IG51bGw7XHJcbiAgcHVibGljIHJlc3BvbnNlOiBIdHRwUmVzcG9uc2U8YW55PiB8IEh0dHBFcnJvclJlc3BvbnNlID0gbnVsbDtcclxuXHJcbiAgY29uc3RydWN0b3IoZmlsZTogYW55KSB7XHJcbiAgICB0aGlzLmZpbGUgPSBmaWxlO1xyXG4gIH1cclxuXHJcbiAgLy8gYWN0aW9uc1xyXG4gIC8vIHB1YmxpYyB1cGxvYWQgPSAoKSA9PiB7IC8qIHNldCBpbiBzZXJ2aWNlICovIH07XHJcbiAgLy8gcHVibGljIGNhbmNlbCA9ICgpID0+IHsgLyogc2V0IGluIHNlcnZpY2UgKi8gfTtcclxuICAvLyBwdWJsaWMgcmVtb3ZlID0gKCkgPT4geyAvKiBzZXQgaW4gc2VydmljZSAqLyB9O1xyXG5cclxuICAvLyBzdGF0dXNlc1xyXG4gIHB1YmxpYyBpc1BlbmRpbmcgPSAoKSA9PiB0aGlzLnN0YXR1cyA9PT0gRmlsZVF1ZXVlU3RhdHVzLlBlbmRpbmc7XHJcbiAgcHVibGljIGlzU3VjY2VzcyA9ICgpID0+IHRoaXMuc3RhdHVzID09PSBGaWxlUXVldWVTdGF0dXMuU3VjY2VzcztcclxuICBwdWJsaWMgaXNFcnJvciA9ICgpID0+IHRoaXMuc3RhdHVzID09PSBGaWxlUXVldWVTdGF0dXMuRXJyb3I7XHJcbiAgcHVibGljIGluUHJvZ3Jlc3MgPSAoKSA9PiB0aGlzLnN0YXR1cyA9PT0gRmlsZVF1ZXVlU3RhdHVzLlByb2dyZXNzO1xyXG4gIHB1YmxpYyBpc1VwbG9hZGFibGUgPSAoKSA9PiB0aGlzLnN0YXR1cyA9PT0gRmlsZVF1ZXVlU3RhdHVzLlBlbmRpbmcgfHwgdGhpcy5zdGF0dXMgPT09IEZpbGVRdWV1ZVN0YXR1cy5FcnJvcjtcclxufVxyXG4iLCJpbXBvcnQgeyBPYnNlcnZlciwgT2JzZXJ2YWJsZSB9IGZyb20gJ3J4anMnO1xyXG5pbXBvcnQgeyBJbmplY3RhYmxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IEh0dHBDbGllbnQsIEh0dHBSZXF1ZXN0LCBIdHRwRXZlbnRUeXBlLCBIdHRwUmVzcG9uc2UsIEh0dHBFcnJvclJlc3BvbnNlLCBIdHRwSGVhZGVycyB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbi9odHRwJztcclxuXHJcbmltcG9ydCB7IEZpbGVRdWV1ZU9iamVjdCB9IGZyb20gJy4vZmlsZS1xdWV1ZS1vYmplY3QnO1xyXG5pbXBvcnQgeyBGaWxlUXVldWVTdGF0dXMgfSBmcm9tICcuL2ZpbGUtcXVldWUtc3RhdHVzJztcclxuaW1wb3J0IHsgRmlsZVVwbG9hZGVyT3B0aW9ucywgQ3JvcE9wdGlvbnMgfSBmcm9tICcuL2ludGVyZmFjZXMnO1xyXG5cclxuQEluamVjdGFibGUoe1xyXG4gIHByb3ZpZGVkSW46ICdyb290J1xyXG59KVxyXG5leHBvcnQgY2xhc3MgSW1hZ2VVcGxvYWRlclNlcnZpY2Uge1xyXG5cclxuICBjb25zdHJ1Y3Rvcihwcml2YXRlIGh0dHA6IEh0dHBDbGllbnQpIHt9XHJcblxyXG4gIHVwbG9hZEZpbGUoZmlsZTogRmlsZSwgb3B0aW9uczogRmlsZVVwbG9hZGVyT3B0aW9ucywgY3JvcE9wdGlvbnM/OiBDcm9wT3B0aW9ucyk6IE9ic2VydmFibGU8RmlsZVF1ZXVlT2JqZWN0PiB7XHJcbiAgICB0aGlzLnNldERlZmF1bHRzKG9wdGlvbnMpO1xyXG5cclxuICAgIGNvbnN0IGZvcm0gPSBuZXcgRm9ybURhdGEoKTtcclxuICAgIGZvcm0uYXBwZW5kKG9wdGlvbnMuZmllbGROYW1lLCBmaWxlLCBmaWxlLm5hbWUpO1xyXG5cclxuICAgIGlmIChjcm9wT3B0aW9ucykge1xyXG4gICAgICBmb3JtLmFwcGVuZCgnWCcsIGNyb3BPcHRpb25zLngudG9TdHJpbmcoKSk7XHJcbiAgICAgIGZvcm0uYXBwZW5kKCdZJywgY3JvcE9wdGlvbnMueS50b1N0cmluZygpKTtcclxuICAgICAgZm9ybS5hcHBlbmQoJ1dpZHRoJywgY3JvcE9wdGlvbnMud2lkdGgudG9TdHJpbmcoKSk7XHJcbiAgICAgIGZvcm0uYXBwZW5kKCdIZWlnaHQnLCBjcm9wT3B0aW9ucy5oZWlnaHQudG9TdHJpbmcoKSk7XHJcbiAgICB9XHJcblxyXG4gICAgLy8gdXBsb2FkIGZpbGUgYW5kIHJlcG9ydCBwcm9ncmVzc1xyXG4gICAgY29uc3QgcmVxID0gbmV3IEh0dHBSZXF1ZXN0KCdQT1NUJywgb3B0aW9ucy51cGxvYWRVcmwsIGZvcm0sIHtcclxuICAgICAgcmVwb3J0UHJvZ3Jlc3M6IHRydWUsXHJcbiAgICAgIHdpdGhDcmVkZW50aWFsczogb3B0aW9ucy53aXRoQ3JlZGVudGlhbHMsXHJcbiAgICAgIGhlYWRlcnM6IHRoaXMuX2J1aWxkSGVhZGVycyhvcHRpb25zKVxyXG4gICAgfSk7XHJcblxyXG4gICAgcmV0dXJuIE9ic2VydmFibGUuY3JlYXRlKG9icyA9PiB7XHJcbiAgICAgIGNvbnN0IHF1ZXVlT2JqID0gbmV3IEZpbGVRdWV1ZU9iamVjdChmaWxlKTtcclxuXHJcbiAgICAgIHF1ZXVlT2JqLnJlcXVlc3QgPSB0aGlzLmh0dHAucmVxdWVzdChyZXEpLnN1YnNjcmliZShcclxuICAgICAgICAoZXZlbnQ6IGFueSkgPT4ge1xyXG4gICAgICAgICAgaWYgKGV2ZW50LnR5cGUgPT09IEh0dHBFdmVudFR5cGUuVXBsb2FkUHJvZ3Jlc3MpIHtcclxuICAgICAgICAgICAgdGhpcy5fdXBsb2FkUHJvZ3Jlc3MocXVldWVPYmosIGV2ZW50KTtcclxuICAgICAgICAgICAgb2JzLm5leHQocXVldWVPYmopO1xyXG4gICAgICAgICAgfSBlbHNlIGlmIChldmVudCBpbnN0YW5jZW9mIEh0dHBSZXNwb25zZSkge1xyXG4gICAgICAgICAgICB0aGlzLl91cGxvYWRDb21wbGV0ZShxdWV1ZU9iaiwgZXZlbnQpO1xyXG4gICAgICAgICAgICBvYnMubmV4dChxdWV1ZU9iaik7XHJcbiAgICAgICAgICAgIG9icy5jb21wbGV0ZSgpO1xyXG4gICAgICAgICAgfVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgKGVycjogSHR0cEVycm9yUmVzcG9uc2UpID0+IHtcclxuICAgICAgICAgIGlmIChlcnIuZXJyb3IgaW5zdGFuY2VvZiBFcnJvcikge1xyXG4gICAgICAgICAgICAvLyBBIGNsaWVudC1zaWRlIG9yIG5ldHdvcmsgZXJyb3Igb2NjdXJyZWQuIEhhbmRsZSBpdCBhY2NvcmRpbmdseS5cclxuICAgICAgICAgICAgdGhpcy5fdXBsb2FkRmFpbGVkKHF1ZXVlT2JqLCBlcnIpO1xyXG4gICAgICAgICAgICBvYnMubmV4dChxdWV1ZU9iaik7XHJcbiAgICAgICAgICAgIG9icy5jb21wbGV0ZSgpO1xyXG4gICAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgLy8gVGhlIGJhY2tlbmQgcmV0dXJuZWQgYW4gdW5zdWNjZXNzZnVsIHJlc3BvbnNlIGNvZGUuXHJcbiAgICAgICAgICAgIHRoaXMuX3VwbG9hZEZhaWxlZChxdWV1ZU9iaiwgZXJyKTtcclxuICAgICAgICAgICAgb2JzLm5leHQocXVldWVPYmopO1xyXG4gICAgICAgICAgICBvYnMuY29tcGxldGUoKTtcclxuICAgICAgICAgIH1cclxuICAgICAgICB9XHJcbiAgICAgICk7XHJcbiAgICB9KTtcclxuICB9XHJcblxyXG4gIGdldEZpbGUodXJsOiBzdHJpbmcsIG9wdGlvbnM6IHsgYXV0aFRva2VuPzogc3RyaW5nLCBhdXRoVG9rZW5QcmVmaXg/OiBzdHJpbmcgfSk6IE9ic2VydmFibGU8RmlsZT4ge1xyXG4gICAgcmV0dXJuIE9ic2VydmFibGUuY3JlYXRlKChvYnNlcnZlcjogT2JzZXJ2ZXI8RmlsZT4pID0+IHtcclxuICAgICAgbGV0IGhlYWRlcnMgPSBuZXcgSHR0cEhlYWRlcnMoKTtcclxuXHJcbiAgICAgIGlmIChvcHRpb25zLmF1dGhUb2tlbikge1xyXG4gICAgICAgIGhlYWRlcnMgPSBoZWFkZXJzLmFwcGVuZCgnQXV0aG9yaXphdGlvbicsIGAke29wdGlvbnMuYXV0aFRva2VuUHJlZml4fSAke29wdGlvbnMuYXV0aFRva2VufWApO1xyXG4gICAgICB9XHJcblxyXG4gICAgICB0aGlzLmh0dHAuZ2V0KHVybCwgeyByZXNwb25zZVR5cGU6ICdibG9iJywgaGVhZGVyczogaGVhZGVyc30pLnN1YnNjcmliZShyZXMgPT4ge1xyXG4gICAgICAgIGNvbnN0IGZpbGUgPSBuZXcgRmlsZShbcmVzXSwgJ2ZpbGVuYW1lJywgeyB0eXBlOiByZXMudHlwZSB9KTtcclxuICAgICAgICBvYnNlcnZlci5uZXh0KGZpbGUpO1xyXG4gICAgICAgIG9ic2VydmVyLmNvbXBsZXRlKCk7XHJcbiAgICAgIH0sIGVyciA9PiB7XHJcbiAgICAgICAgb2JzZXJ2ZXIuZXJyb3IoZXJyLnN0YXR1cyk7XHJcbiAgICAgICAgb2JzZXJ2ZXIuY29tcGxldGUoKTtcclxuICAgICAgfSk7XHJcbiAgICB9KTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgX2J1aWxkSGVhZGVycyhvcHRpb25zOiBGaWxlVXBsb2FkZXJPcHRpb25zKTogSHR0cEhlYWRlcnMge1xyXG4gICAgbGV0IGhlYWRlcnMgPSBuZXcgSHR0cEhlYWRlcnMoKTtcclxuXHJcbiAgICBpZiAob3B0aW9ucy5hdXRoVG9rZW4pIHtcclxuICAgICAgaGVhZGVycyA9IGhlYWRlcnMuYXBwZW5kKCdBdXRob3JpemF0aW9uJywgYCR7b3B0aW9ucy5hdXRoVG9rZW5QcmVmaXh9ICR7b3B0aW9ucy5hdXRoVG9rZW59YCk7XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKG9wdGlvbnMuY3VzdG9tSGVhZGVycykge1xyXG4gICAgICBPYmplY3Qua2V5cyhvcHRpb25zLmN1c3RvbUhlYWRlcnMpLmZvckVhY2goKGtleSkgPT4ge1xyXG4gICAgICAgIGhlYWRlcnMgPSBoZWFkZXJzLmFwcGVuZChrZXksIG9wdGlvbnMuY3VzdG9tSGVhZGVyc1trZXldKTtcclxuICAgICAgfSk7XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIGhlYWRlcnM7XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIF91cGxvYWRQcm9ncmVzcyhxdWV1ZU9iajogRmlsZVF1ZXVlT2JqZWN0LCBldmVudDogYW55KSB7XHJcbiAgICAvLyB1cGRhdGUgdGhlIEZpbGVRdWV1ZU9iamVjdCB3aXRoIHRoZSBjdXJyZW50IHByb2dyZXNzXHJcbiAgICBjb25zdCBwcm9ncmVzcyA9IE1hdGgucm91bmQoMTAwICogZXZlbnQubG9hZGVkIC8gZXZlbnQudG90YWwpO1xyXG4gICAgcXVldWVPYmoucHJvZ3Jlc3MgPSBwcm9ncmVzcztcclxuICAgIHF1ZXVlT2JqLnN0YXR1cyA9IEZpbGVRdWV1ZVN0YXR1cy5Qcm9ncmVzcztcclxuICAgIC8vIHRoaXMuX3F1ZXVlLm5leHQodGhpcy5fZmlsZXMpO1xyXG4gIH1cclxuXHJcbiAgcHJpdmF0ZSBfdXBsb2FkQ29tcGxldGUocXVldWVPYmo6IEZpbGVRdWV1ZU9iamVjdCwgcmVzcG9uc2U6IEh0dHBSZXNwb25zZTxhbnk+KSB7XHJcbiAgICAvLyB1cGRhdGUgdGhlIEZpbGVRdWV1ZU9iamVjdCBhcyBjb21wbGV0ZWRcclxuICAgIHF1ZXVlT2JqLnByb2dyZXNzID0gMTAwO1xyXG4gICAgcXVldWVPYmouc3RhdHVzID0gRmlsZVF1ZXVlU3RhdHVzLlN1Y2Nlc3M7XHJcbiAgICBxdWV1ZU9iai5yZXNwb25zZSA9IHJlc3BvbnNlO1xyXG4gICAgLy8gdGhpcy5fcXVldWUubmV4dCh0aGlzLl9maWxlcyk7XHJcbiAgICAvLyB0aGlzLm9uQ29tcGxldGVJdGVtKHF1ZXVlT2JqLCByZXNwb25zZS5ib2R5KTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgX3VwbG9hZEZhaWxlZChxdWV1ZU9iajogRmlsZVF1ZXVlT2JqZWN0LCByZXNwb25zZTogSHR0cEVycm9yUmVzcG9uc2UpIHtcclxuICAgIC8vIHVwZGF0ZSB0aGUgRmlsZVF1ZXVlT2JqZWN0IGFzIGVycm9yZWRcclxuICAgIHF1ZXVlT2JqLnByb2dyZXNzID0gMDtcclxuICAgIHF1ZXVlT2JqLnN0YXR1cyA9IEZpbGVRdWV1ZVN0YXR1cy5FcnJvcjtcclxuICAgIHF1ZXVlT2JqLnJlc3BvbnNlID0gcmVzcG9uc2U7XHJcbiAgICAvLyB0aGlzLl9xdWV1ZS5uZXh0KHRoaXMuX2ZpbGVzKTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgc2V0RGVmYXVsdHMob3B0aW9uczogRmlsZVVwbG9hZGVyT3B0aW9ucykge1xyXG4gICAgb3B0aW9ucy53aXRoQ3JlZGVudGlhbHMgPSBvcHRpb25zLndpdGhDcmVkZW50aWFscyB8fCBmYWxzZTtcclxuICAgIG9wdGlvbnMuaHR0cE1ldGhvZCA9IG9wdGlvbnMuaHR0cE1ldGhvZCB8fCAnUE9TVCc7XHJcbiAgICBvcHRpb25zLmF1dGhUb2tlblByZWZpeCA9IG9wdGlvbnMuYXV0aFRva2VuUHJlZml4IHx8ICdCZWFyZXInO1xyXG4gICAgb3B0aW9ucy5maWVsZE5hbWUgPSBvcHRpb25zLmZpZWxkTmFtZSB8fCAnZmlsZSc7XHJcbiAgfVxyXG59XHJcbiIsImltcG9ydCB7UmVzaXplT3B0aW9uc30gZnJvbSAnLi9pbnRlcmZhY2VzJztcclxuXHJcbmV4cG9ydCBmdW5jdGlvbiBjcmVhdGVJbWFnZSh1cmw6IHN0cmluZywgY2I6IChpOiBIVE1MSW1hZ2VFbGVtZW50KSA9PiB2b2lkKSB7XHJcbiAgY29uc3QgaW1hZ2UgPSBuZXcgSW1hZ2UoKTtcclxuICBpbWFnZS5vbmxvYWQgPSBmdW5jdGlvbiAoKSB7XHJcbiAgICBjYihpbWFnZSk7XHJcbiAgfTtcclxuICBpbWFnZS5zcmMgPSB1cmw7XHJcbn1cclxuXHJcbmNvbnN0IHJlc2l6ZUFyZWFJZCA9ICdpbWFnZXVwbG9hZC1yZXNpemUtYXJlYSc7XHJcblxyXG5mdW5jdGlvbiBnZXRSZXNpemVBcmVhKCkge1xyXG4gIGxldCByZXNpemVBcmVhID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQocmVzaXplQXJlYUlkKTtcclxuICBpZiAoIXJlc2l6ZUFyZWEpIHtcclxuICAgIHJlc2l6ZUFyZWEgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdjYW52YXMnKTtcclxuICAgIHJlc2l6ZUFyZWEuaWQgPSByZXNpemVBcmVhSWQ7XHJcbiAgICByZXNpemVBcmVhLnN0eWxlLmRpc3BsYXkgPSAnbm9uZSc7XHJcbiAgICBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHJlc2l6ZUFyZWEpO1xyXG4gIH1cclxuXHJcbiAgcmV0dXJuIDxIVE1MQ2FudmFzRWxlbWVudD5yZXNpemVBcmVhO1xyXG59XHJcblxyXG5leHBvcnQgZnVuY3Rpb24gcmVzaXplSW1hZ2Uob3JpZ0ltYWdlOiBIVE1MSW1hZ2VFbGVtZW50LCB7XHJcbiAgcmVzaXplSGVpZ2h0LFxyXG4gIHJlc2l6ZVdpZHRoLFxyXG4gIHJlc2l6ZVF1YWxpdHkgPSAwLjcsXHJcbiAgcmVzaXplVHlwZSA9ICdpbWFnZS9qcGVnJyxcclxuICByZXNpemVNb2RlID0gJ2ZpbGwnXHJcbn06IFJlc2l6ZU9wdGlvbnMgPSB7fSkge1xyXG5cclxuICBjb25zdCBjYW52YXMgPSBnZXRSZXNpemVBcmVhKCk7XHJcblxyXG4gIGxldCBoZWlnaHQgPSBvcmlnSW1hZ2UuaGVpZ2h0O1xyXG4gIGxldCB3aWR0aCA9IG9yaWdJbWFnZS53aWR0aDtcclxuICBsZXQgb2Zmc2V0WCA9IDA7XHJcbiAgbGV0IG9mZnNldFkgPSAwO1xyXG5cclxuICBpZiAocmVzaXplTW9kZSA9PT0gJ2ZpbGwnKSB7XHJcbiAgICAvLyBjYWxjdWxhdGUgdGhlIHdpZHRoIGFuZCBoZWlnaHQsIGNvbnN0cmFpbmluZyB0aGUgcHJvcG9ydGlvbnNcclxuICAgIGlmICh3aWR0aCAvIGhlaWdodCA+IHJlc2l6ZVdpZHRoIC8gcmVzaXplSGVpZ2h0KSB7XHJcbiAgICAgIHdpZHRoID0gTWF0aC5yb3VuZChoZWlnaHQgKiByZXNpemVXaWR0aCAvIHJlc2l6ZUhlaWdodCk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICBoZWlnaHQgPSBNYXRoLnJvdW5kKHdpZHRoICogcmVzaXplSGVpZ2h0IC8gcmVzaXplV2lkdGgpO1xyXG4gICAgfVxyXG5cclxuICAgIGNhbnZhcy53aWR0aCA9IHJlc2l6ZVdpZHRoIDw9IHdpZHRoID8gcmVzaXplV2lkdGggOiB3aWR0aDtcclxuICAgIGNhbnZhcy5oZWlnaHQgPSByZXNpemVIZWlnaHQgPD0gaGVpZ2h0ID8gcmVzaXplSGVpZ2h0IDogaGVpZ2h0O1xyXG5cclxuICAgIG9mZnNldFggPSBvcmlnSW1hZ2Uud2lkdGggLyAyIC0gd2lkdGggLyAyO1xyXG4gICAgb2Zmc2V0WSA9IG9yaWdJbWFnZS5oZWlnaHQgLyAyIC0gaGVpZ2h0IC8gMjtcclxuXHJcbiAgICAvLyBkcmF3IGltYWdlIG9uIGNhbnZhc1xyXG4gICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoJzJkJyk7XHJcbiAgICBjdHguZHJhd0ltYWdlKG9yaWdJbWFnZSwgb2Zmc2V0WCwgb2Zmc2V0WSwgd2lkdGgsIGhlaWdodCwgMCwgMCwgY2FudmFzLndpZHRoLCBjYW52YXMuaGVpZ2h0KTtcclxuICB9IGVsc2UgaWYgKHJlc2l6ZU1vZGUgPT09ICdmaXQnKSB7XHJcbiAgICAgIC8vIGNhbGN1bGF0ZSB0aGUgd2lkdGggYW5kIGhlaWdodCwgY29uc3RyYWluaW5nIHRoZSBwcm9wb3J0aW9uc1xyXG4gICAgICBpZiAod2lkdGggPiBoZWlnaHQpIHtcclxuICAgICAgICAgIGlmICh3aWR0aCA+IHJlc2l6ZVdpZHRoKSB7XHJcbiAgICAgICAgICAgICAgaGVpZ2h0ID0gTWF0aC5yb3VuZChoZWlnaHQgKj0gcmVzaXplV2lkdGggLyB3aWR0aCk7XHJcbiAgICAgICAgICAgICAgd2lkdGggPSByZXNpemVXaWR0aDtcclxuICAgICAgICAgIH1cclxuICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgIGlmIChoZWlnaHQgPiByZXNpemVIZWlnaHQpIHtcclxuICAgICAgICAgICAgICB3aWR0aCA9IE1hdGgucm91bmQod2lkdGggKj0gcmVzaXplSGVpZ2h0IC8gaGVpZ2h0KTtcclxuICAgICAgICAgICAgICBoZWlnaHQgPSByZXNpemVIZWlnaHQ7XHJcbiAgICAgICAgICB9XHJcbiAgICAgIH1cclxuXHJcbiAgICAgIGNhbnZhcy53aWR0aCA9IHdpZHRoO1xyXG4gICAgICBjYW52YXMuaGVpZ2h0ID0gaGVpZ2h0O1xyXG5cclxuICAgICAgLy8gZHJhdyBpbWFnZSBvbiBjYW52YXNcclxuICAgICAgY29uc3QgY3R4ID0gY2FudmFzLmdldENvbnRleHQoJzJkJyk7XHJcbiAgICAgIGN0eC5kcmF3SW1hZ2Uob3JpZ0ltYWdlLCAwLCAwLCB3aWR0aCwgaGVpZ2h0KTtcclxuICB9IGVsc2Uge1xyXG4gICAgdGhyb3cgbmV3IEVycm9yKCdVbmtub3duIHJlc2l6ZU1vZGU6ICcgKyByZXNpemVNb2RlKTtcclxuICB9XHJcblxyXG4gIC8vIGdldCB0aGUgZGF0YSBmcm9tIGNhbnZhcyBhcyA3MCUganBnIChvciBzcGVjaWZpZWQgdHlwZSkuXHJcbiAgcmV0dXJuIGNhbnZhcy50b0RhdGFVUkwocmVzaXplVHlwZSwgcmVzaXplUXVhbGl0eSk7XHJcbn1cclxuXHJcblxyXG4iLCJpbXBvcnQge1xyXG4gIENvbXBvbmVudCwgT25Jbml0LCBPbkRlc3Ryb3ksIEFmdGVyVmlld0NoZWNrZWQsIFZpZXdDaGlsZCwgRWxlbWVudFJlZixcclxuICBSZW5kZXJlciwgSW5wdXQsIE91dHB1dCwgRXZlbnRFbWl0dGVyLCBDaGFuZ2VEZXRlY3RvclJlZiwgZm9yd2FyZFJlZiwgSG9zdExpc3RlbmVyXHJcbn0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IENvbnRyb2xWYWx1ZUFjY2Vzc29yLCBOR19WQUxVRV9BQ0NFU1NPUiB9IGZyb20gJ0Bhbmd1bGFyL2Zvcm1zJztcclxuaW1wb3J0IENyb3BwZXIgZnJvbSAnY3JvcHBlcmpzJztcclxuXHJcbmltcG9ydCB7IEltYWdlVXBsb2FkZXJTZXJ2aWNlIH0gZnJvbSAnLi9pbWFnZS11cGxvYWRlci5zZXJ2aWNlJztcclxuaW1wb3J0IHsgSW1hZ2VVcGxvYWRlck9wdGlvbnMsIEltYWdlUmVzdWx0LCBSZXNpemVPcHRpb25zLCBDcm9wT3B0aW9ucyB9IGZyb20gJy4vaW50ZXJmYWNlcyc7XHJcbmltcG9ydCB7IGNyZWF0ZUltYWdlLCByZXNpemVJbWFnZSB9IGZyb20gJy4vdXRpbHMnO1xyXG5pbXBvcnQgeyBGaWxlUXVldWVPYmplY3QgfSBmcm9tICcuL2ZpbGUtcXVldWUtb2JqZWN0JztcclxuXHJcbmV4cG9ydCBlbnVtIFN0YXR1cyB7XHJcbiAgTm90U2VsZWN0ZWQsXHJcbiAgU2VsZWN0ZWQsXHJcbiAgVXBsb2FkaW5nLFxyXG4gIExvYWRpbmcsXHJcbiAgTG9hZGVkLFxyXG4gIEVycm9yXHJcbn1cclxuXHJcbkBDb21wb25lbnQoe1xyXG4gIHNlbGVjdG9yOiAnbmd4LWltYWdlLXVwbG9hZGVyJyxcclxuICB0ZW1wbGF0ZTogYDxkaXYgY2xhc3M9XCJpbWFnZS1jb250YWluZXJcIj5cclxuICA8ZGl2IGNsYXNzPVwibWF0Y2gtcGFyZW50XCIgW25nU3dpdGNoXT1cInN0YXR1c1wiPlxyXG5cclxuICAgIDxkaXYgY2xhc3M9XCJtYXRjaC1wYXJlbnRcIiAqbmdTd2l0Y2hDYXNlPVwic3RhdHVzRW51bS5Ob3RTZWxlY3RlZFwiPlxyXG4gICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiBjbGFzcz1cImFkZC1pbWFnZS1idG5cIiAoY2xpY2spPVwib25JbWFnZUNsaWNrZWQoKVwiPlxyXG4gICAgICAgICAgPGRpdj5cclxuICAgICAgICAgICAgPHAgY2xhc3M9XCJwbHVzXCI+KzwvcD5cclxuICAgICAgICAgICAgPHA+Q2xpY2sgaGVyZSB0byBhZGQgaW1hZ2U8L3A+XHJcbiAgICAgICAgICAgIDxwPk9yIGRyb3AgaW1hZ2UgaGVyZTwvcD5cclxuICAgICAgICAgIDwvZGl2PlxyXG4gICAgICA8L2J1dHRvbj5cclxuICAgIDwvZGl2PlxyXG5cclxuICAgIDxkaXYgY2xhc3M9XCJzZWxlY3RlZC1zdGF0dXMtd3JhcHBlciBtYXRjaC1wYXJlbnRcIiAqbmdTd2l0Y2hDYXNlPVwic3RhdHVzRW51bS5Mb2FkZWRcIj5cclxuICAgICAgPGltZyBbc3JjXT1cImltYWdlVGh1bWJuYWlsXCIgI2ltYWdlRWxlbWVudD5cclxuXHJcbiAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGNsYXNzPVwicmVtb3ZlXCIgKGNsaWNrKT1cInJlbW92ZUltYWdlKClcIj7Dg8KXPC9idXR0b24+XHJcbiAgICA8L2Rpdj5cclxuXHJcbiAgICA8ZGl2IGNsYXNzPVwic2VsZWN0ZWQtc3RhdHVzLXdyYXBwZXIgbWF0Y2gtcGFyZW50XCIgKm5nU3dpdGNoQ2FzZT1cInN0YXR1c0VudW0uU2VsZWN0ZWRcIj5cclxuICAgICAgPGltZyBbc3JjXT1cImltYWdlVGh1bWJuYWlsXCIgI2ltYWdlRWxlbWVudD5cclxuXHJcbiAgICAgIDxidXR0b24gdHlwZT1cImJ1dHRvblwiIGNsYXNzPVwicmVtb3ZlXCIgKGNsaWNrKT1cInJlbW92ZUltYWdlKClcIj7Dg8KXPC9idXR0b24+XHJcbiAgICA8L2Rpdj5cclxuXHJcbiAgICA8ZGl2ICpuZ1N3aXRjaENhc2U9XCJzdGF0dXNFbnVtLlVwbG9hZGluZ1wiPlxyXG4gICAgICA8aW1nIFthdHRyLnNyY109XCJpbWFnZVRodW1ibmFpbCA/IGltYWdlVGh1bWJuYWlsIDogbnVsbFwiIChjbGljayk9XCJvbkltYWdlQ2xpY2tlZCgpXCI+XHJcblxyXG4gICAgICA8ZGl2IGNsYXNzPVwicHJvZ3Jlc3MtYmFyXCI+XHJcbiAgICAgICAgPGRpdiBjbGFzcz1cImJhclwiIFtzdHlsZS53aWR0aF09XCJwcm9ncmVzcysnJSdcIj48L2Rpdj5cclxuICAgICAgPC9kaXY+XHJcbiAgICA8L2Rpdj5cclxuXHJcbiAgICA8ZGl2IGNsYXNzPVwibWF0Y2gtcGFyZW50XCIgKm5nU3dpdGNoQ2FzZT1cInN0YXR1c0VudW0uTG9hZGluZ1wiPlxyXG4gICAgICA8ZGl2IGNsYXNzPVwic2stZmFkaW5nLWNpcmNsZVwiPlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGUxIHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGUyIHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGUzIHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGU0IHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGU1IHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGU2IHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGU3IHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGU4IHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGU5IHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICAgIDxkaXYgY2xhc3M9XCJzay1jaXJjbGUxMCBzay1jaXJjbGVcIj48L2Rpdj5cclxuICAgICAgICA8ZGl2IGNsYXNzPVwic2stY2lyY2xlMTEgc2stY2lyY2xlXCI+PC9kaXY+XHJcbiAgICAgICAgPGRpdiBjbGFzcz1cInNrLWNpcmNsZTEyIHNrLWNpcmNsZVwiPjwvZGl2PlxyXG4gICAgICA8L2Rpdj5cclxuICAgIDwvZGl2PlxyXG5cclxuICAgIDxkaXYgY2xhc3M9XCJtYXRjaC1wYXJlbnRcIiAqbmdTd2l0Y2hDYXNlPVwic3RhdHVzRW51bS5FcnJvclwiPlxyXG4gICAgICA8ZGl2IGNsYXNzPVwiZXJyb3JcIj5cclxuICAgICAgICA8ZGl2IGNsYXNzPVwiZXJyb3ItbWVzc2FnZVwiPlxyXG4gICAgICAgICAgPHA+e3tlcnJvck1lc3NhZ2V9fTwvcD5cclxuICAgICAgICA8L2Rpdj5cclxuICAgICAgICA8YnV0dG9uIHR5cGU9XCJidXR0b25cIiBjbGFzcz1cInJlbW92ZVwiIChjbGljayk9XCJkaXNtaXNzRXJyb3IoKVwiPsODwpc8L2J1dHRvbj5cclxuICAgICAgPC9kaXY+XHJcbiAgICA8L2Rpdj5cclxuICA8L2Rpdj5cclxuXHJcbiAgPGlucHV0IHR5cGU9XCJmaWxlXCIgI2ZpbGVJbnB1dCAoY2hhbmdlKT1cIm9uRmlsZUNoYW5nZWQoKVwiPlxyXG4gIDxkaXYgY2xhc3M9XCJkcmFnLW92ZXJsYXlcIiBbaGlkZGVuXT1cInRydWVcIiAjZHJhZ092ZXJsYXk+PC9kaXY+XHJcbjwvZGl2PlxyXG5gLFxyXG4gIHN0eWxlczogW2A6aG9zdHtkaXNwbGF5OmJsb2NrfS5tYXRjaC1wYXJlbnR7d2lkdGg6MTAwJTtoZWlnaHQ6MTAwJX0uYWRkLWltYWdlLWJ0bnt3aWR0aDoxMDAlO2hlaWdodDoxMDAlO2ZvbnQtd2VpZ2h0OjcwMDtvcGFjaXR5Oi41O2JvcmRlcjowfS5hZGQtaW1hZ2UtYnRuOmhvdmVye29wYWNpdHk6Ljc7Y3Vyc29yOnBvaW50ZXI7YmFja2dyb3VuZC1jb2xvcjojZGRkO2JveC1zaGFkb3c6aW5zZXQgMCAwIDVweCByZ2JhKDAsMCwwLC4zKX0uYWRkLWltYWdlLWJ0biAucGx1c3tmb250LXNpemU6MzBweDtmb250LXdlaWdodDo0MDA7bWFyZ2luLWJvdHRvbTo1cHg7bWFyZ2luLXRvcDo1cHh9aW1ne2N1cnNvcjpwb2ludGVyO3Bvc2l0aW9uOmFic29sdXRlO3RvcDo1MCU7bGVmdDo1MCU7bWFyZ2luLXJpZ2h0Oi01MCU7LXdlYmtpdC10cmFuc2Zvcm06dHJhbnNsYXRlKC01MCUsLTUwJSk7dHJhbnNmb3JtOnRyYW5zbGF0ZSgtNTAlLC01MCUpO21heC13aWR0aDoxMDAlfS5pbWFnZS1jb250YWluZXJ7d2lkdGg6MTAwJTtoZWlnaHQ6MTAwJTtwb3NpdGlvbjpyZWxhdGl2ZTtkaXNwbGF5OmlubGluZS1ibG9jaztiYWNrZ3JvdW5kLWNvbG9yOiNmMWYxZjE7Ym94LXNoYWRvdzppbnNldCAwIDAgNXB4IHJnYmEoMCwwLDAsLjIpfS5yZW1vdmV7cG9zaXRpb246YWJzb2x1dGU7dG9wOjA7cmlnaHQ6MDt3aWR0aDo0MHB4O2hlaWdodDo0MHB4O2ZvbnQtc2l6ZToyNXB4O3RleHQtYWxpZ246Y2VudGVyO29wYWNpdHk6Ljg7Ym9yZGVyOjA7Y3Vyc29yOnBvaW50ZXJ9LnNlbGVjdGVkLXN0YXR1cy13cmFwcGVyPi5yZW1vdmU6aG92ZXJ7b3BhY2l0eTouNztiYWNrZ3JvdW5kLWNvbG9yOiNmZmZ9LmVycm9yIC5yZW1vdmV7b3BhY2l0eTouNX0uZXJyb3IgLnJlbW92ZTpob3ZlcntvcGFjaXR5Oi43fWlucHV0e2Rpc3BsYXk6bm9uZX0uZXJyb3J7d2lkdGg6MTAwJTtoZWlnaHQ6MTAwJTtib3JkZXI6MXB4IHNvbGlkICNlM2E1YTI7Y29sb3I6I2QyNzA2YjtiYWNrZ3JvdW5kLWNvbG9yOiNmYmYxZjA7cG9zaXRpb246cmVsYXRpdmU7dGV4dC1hbGlnbjpjZW50ZXI7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcn0uZXJyb3ItbWVzc2FnZXt3aWR0aDoxMDAlO2xpbmUtaGVpZ2h0OjE4cHh9LnByb2dyZXNzLWJhcntwb3NpdGlvbjphYnNvbHV0ZTtib3R0b206MTAlO2xlZnQ6MTAlO3dpZHRoOjgwJTtoZWlnaHQ6NXB4O2JhY2tncm91bmQtY29sb3I6Z3JleTtvcGFjaXR5Oi45O292ZXJmbG93OmhpZGRlbn0uYmFye3Bvc2l0aW9uOmFic29sdXRlO2hlaWdodDoxMDAlO2JhY2tncm91bmQtY29sb3I6I2E0YzYzOX0uZHJhZy1vdmVybGF5e3Bvc2l0aW9uOmFic29sdXRlO3RvcDowO2xlZnQ6MDt3aWR0aDoxMDAlO2hlaWdodDoxMDAlO2JhY2tncm91bmQtY29sb3I6I2ZmMDtvcGFjaXR5Oi4zfS5zay1mYWRpbmctY2lyY2xle3dpZHRoOjQwcHg7aGVpZ2h0OjQwcHg7cG9zaXRpb246cmVsYXRpdmU7dG9wOjUwJTtsZWZ0OjUwJTstd2Via2l0LXRyYW5zZm9ybTp0cmFuc2xhdGUoLTUwJSwtNTAlKTt0cmFuc2Zvcm06dHJhbnNsYXRlKC01MCUsLTUwJSl9LnNrLWZhZGluZy1jaXJjbGUgLnNrLWNpcmNsZXt3aWR0aDoxMDAlO2hlaWdodDoxMDAlO3Bvc2l0aW9uOmFic29sdXRlO2xlZnQ6MDt0b3A6MH0uc2stZmFkaW5nLWNpcmNsZSAuc2stY2lyY2xlOmJlZm9yZXtjb250ZW50OicnO2Rpc3BsYXk6YmxvY2s7bWFyZ2luOjAgYXV0bzt