UNPKG

ngx-uploader-directive

Version:

Angular File Uploader Directive which provides two directives, which are select and file drag and drop to upload files on server.

756 lines 91.3 kB
/** * @fileoverview added by tsickle * Generated from: lib/services/ngx-uploader-directive.service.ts * @suppress {checkTypes,constantProperty,extraRequire,missingOverride,missingRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ /** * @license * The MIT License (MIT) * Copyright (c) 2015-2018 Jan Kuri jan@bleenco.com * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // tslint:disable: max-line-length // tslint:disable: no-console import { EventEmitter } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { finalize, mergeMap } from 'rxjs/operators'; import { HttpRequest, HttpEventType, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import { environment } from '../configs/config'; // @Injectable({ // providedIn: 'root' // }) export class NgxUploaderDirectiveService { /** * @param {?=} requestConcurrency * @param {?=} maxFilesToAddInSingleRequest * @param {?=} fileTypes * @param {?=} maxFileUploads * @param {?=} maxFileSize * @param {?=} httpClient * @param {?=} logs */ constructor(requestConcurrency = Number.POSITIVE_INFINITY, maxFilesToAddInSingleRequest = Number.POSITIVE_INFINITY, fileTypes = ['*'], maxFileUploads = Number.POSITIVE_INFINITY, maxFileSize = Number.POSITIVE_INFINITY, httpClient, logs) { this.httpClient = httpClient; this.logs = logs; this.devEnv = !environment.production; this.queue = new Array(); this.MaxNumberOfRequest = 10; this.fileServiceEvents = new EventEmitter(); this.uploadScheduler = new Subject(); this.maxFilesToAddInSingleRequest = 0; this.fileTypes = fileTypes; this.maxFileUploads = maxFileUploads; this.maxFilesToAddInSingleRequest = maxFilesToAddInSingleRequest; this.maxFileSize = maxFileSize; this.subscriptions = new Array(); this.uploadScheduler .pipe(mergeMap((/** * @param {?} upload * @return {?} */ upload => this.startUpload(upload)), requestConcurrency === 0 ? this.MaxNumberOfRequest : requestConcurrency)) .subscribe((/** * @param {?} uploadOutput * @return {?} */ uploadOutput => this.fileServiceEvents.emit(uploadOutput))); } /** * Handles uploaded files * @param {?} selectedFiles Selected Files * @param {?} selectedEventType * @return {?} */ handleSelectedFiles(selectedFiles, selectedEventType) { this.queue = new Array(); this.fileServiceEvents.emit({ type: 'init', fileSelectedEventType: selectedEventType }); if (selectedFiles.length > this.maxFileUploads) { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Maxium ' + this.maxFileUploads + ' files can be upload' }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: selectedEventType }); return; } // verify files with allowed files and max uploads /** @type {?} */ const allowedFiles = new Array(); /** @type {?} */ const rejectedFiles = new Array(); // tslint:disable-next-line: prefer-for-of for (let checkingFileIndex = 0; checkingFileIndex < selectedFiles.length; checkingFileIndex++) { /** @type {?} */ const checkingFile = selectedFiles[checkingFileIndex]; /** @type {?} */ const queueLength = allowedFiles.length + this.queue.length + 1; if (this.isFileTypeAllowed(checkingFile.type) && this.isFileSizeAllowed(checkingFile.size)) { allowedFiles.push(checkingFile); } else { /** @type {?} */ const rejectedFile = this.convertToSelectedFile(checkingFile, checkingFileIndex, this.generateRandomeId(), selectedEventType); rejectedFiles.push(rejectedFile); } } if (rejectedFiles.length > 0) { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Invalid file type or file size exceeded the limit ' + this.humanizeBytes(this.maxFileSize), statusText: 'Invalid Input' }); this.fileServiceEvents.emit({ type: 'rejected', files: rejectedFiles, fileSelectedEventType: selectedEventType, response: this.httpErrorResponse }); } if (this.logs) { console.info('Allowed Files', allowedFiles); } // Adding files to queue /** @type {?} */ let filesAddedToQueue; /** @type {?} */ const totalFilesAdded = new Array(); if (this.maxFilesToAddInSingleRequest === 0 || this.maxFilesToAddInSingleRequest === 1) { /** @type {?} */ const eventId = this.generateRandomeId(); // tslint:disable-next-line: prefer-for-of for (let fileIndex = 0; fileIndex < allowedFiles.length; fileIndex++) { /** @type {?} */ const file = allowedFiles[fileIndex]; /** @type {?} */ let selectedFile; if (this.maxFilesToAddInSingleRequest === 0) { selectedFile = this.convertToSelectedFile(file, fileIndex, eventId, selectedEventType); } else if (this.maxFilesToAddInSingleRequest === 1) { selectedFile = this.convertToSelectedFile(file, fileIndex, this.generateRandomeId(), selectedEventType); } this.queue.push(selectedFile); filesAddedToQueue = new Array(); filesAddedToQueue.push(selectedFile); totalFilesAdded.push(selectedFile); this.fileServiceEvents.emit({ type: 'addedToQueue', files: filesAddedToQueue, requestId: selectedFile.requestId, fileSelectedEventType: selectedEventType }); } } else { // generate id for max files to add in single request. /** @type {?} */ const chunkedArray = this.chunkArray(allowedFiles, this.maxFilesToAddInSingleRequest); /** @type {?} */ let fileIndex = 0; // tslint:disable-next-line: prefer-for-of for (let chukedQueueArrayIndex = 0; chukedQueueArrayIndex < chunkedArray.length; chukedQueueArrayIndex++) { /** @type {?} */ const chunkedElement = chunkedArray[chukedQueueArrayIndex]; /** @type {?} */ const eventId = this.generateRandomeId(); filesAddedToQueue = new Array(); // tslint:disable-next-line: prefer-for-of for (let chunkElementIndex = 0; chunkElementIndex < chunkedElement.length; chunkElementIndex++) { /** @type {?} */ const selectedFileElement = chunkedElement[chunkElementIndex]; /** @type {?} */ const convertdFile = this.convertToSelectedFile(selectedFileElement, fileIndex, eventId, selectedEventType); this.queue.push(convertdFile); filesAddedToQueue.push(convertdFile); totalFilesAdded.push(convertdFile); fileIndex += 1; } this.fileServiceEvents.emit({ type: 'addedToQueue', files: filesAddedToQueue, requestId: eventId, fileSelectedEventType: selectedEventType }); } } if (this.queue.length > 0) { this.fileServiceEvents.emit({ type: 'allAddedToQueue', files: totalFilesAdded, fileSelectedEventType: selectedEventType }); } if (this.logs) { console.info('Queue', this.queue); } } /** * Handles input events upload | remove | cancel * @param {?} inputEvnets Input events of file upload process * @return {?} */ handleInputEvents(inputEvnets) { return inputEvnets.subscribe((/** * @param {?} event * @return {?} */ (event) => { if (this.logs && this.devEnv) { console.info('Input event', event); } if (this.queue.length === 0) { return; } /** @type {?} */ const requestId = event.requestId; switch (event.type) { case 'uploadFile': if (!requestId) { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Invalid request id.', statusText: 'Invalid Input' }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: 'ALL' }); return; } this.uploadScheduler.next({ files: this.queue.filter((/** * @param {?} file * @return {?} */ (file) => { return file.requestId === event.requestId; })), event }); break; case 'uploadAll': /** @type {?} */ const groupOfRequests = this.groupByArray(this.queue.filter((/** * @param {?} file * @return {?} */ (file) => file.progress.status === 'Queue')), 'requestId'); if (this.logs) { console.info('Group of request', groupOfRequests); } for (const request in groupOfRequests) { if (groupOfRequests.hasOwnProperty(request)) { /** @type {?} */ const requestFiles = groupOfRequests[request]; if (this.logs && this.devEnv) { console.info('Requesting for id ' + request, requestFiles); } this.uploadScheduler.next({ files: requestFiles, event }); } } break; case 'cancel': if (!requestId) { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Invalid request id.', statusText: 'Invalid Input' }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: 'ALL' }); return; } /** @type {?} */ const subs = this.subscriptions.filter((/** * @param {?} sub * @return {?} */ sub => sub.id === requestId)); if (this.logs && this.devEnv) { console.info('subscriptions ', subs); } subs.forEach((/** * @param {?} sub * @return {?} */ sub => { if (sub.sub) { sub.sub.unsubscribe(); // tslint:disable-next-line: no-shadowed-variable /** @type {?} */ const cancelledFilesArray = this.queue.filter((/** * @param {?} file * @return {?} */ (file) => file.requestId === requestId)); if (cancelledFilesArray.length > 0) { this.queue.forEach((/** * @param {?} file * @param {?} fileIndex * @param {?} queue * @return {?} */ (file, fileIndex, queue) => { queue[fileIndex].progress.status = 'Cancelled'; })); this.fileServiceEvents.emit({ type: 'cancelled', requestId, files: cancelledFilesArray, fileSelectedEventType: cancelledFilesArray[0].selectedEventType }); } else { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Files not found with request id ' + requestId }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: 'ALL' }); } } })); break; case 'cancelAll': this.subscriptions.forEach((/** * @param {?} sub * @return {?} */ sub => { if (sub.sub) { sub.sub.unsubscribe(); } if (this.logs && this.devEnv) { console.info('subscriptions ', subs); } /** @type {?} */ const canceldFileArray = this.queue.filter((/** * @param {?} uploadFile * @return {?} */ (uploadFile) => uploadFile.requestId === sub.id)); if (canceldFileArray.length > 0) { this.queue.forEach((/** * @param {?} file * @param {?} fileIndex * @param {?} queue * @return {?} */ (file, fileIndex, queue) => { queue[fileIndex].progress.status = 'Cancelled'; })); this.fileServiceEvents.emit({ type: 'cancelled', files: canceldFileArray, fileSelectedEventType: canceldFileArray[0].selectedEventType }); } else { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Files not found with request id ' + requestId, statusText: 'Invalid Input' }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: 'ALL' }); } })); break; case 'remove': if (!requestId) { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Invalid request id.', statusText: 'Invalid Input' }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: 'ALL' }); return; } /** @type {?} */ const filesToRemove = this.queue.filter((/** * @param {?} file * @return {?} */ (file) => file.requestId === event.requestId)); if (filesToRemove.length > 0) { /** @type {?} */ const remainingFilesArray = this.queue.filter((/** * @param {?} file * @return {?} */ (file) => file.requestId !== event.requestId)); this.queue = remainingFilesArray; this.fileServiceEvents.emit({ type: 'removed', requestId: event.requestId, files: filesToRemove, fileSelectedEventType: 'ALL' }); } else { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Files not found with request id ' + requestId, statusText: 'Invalid Input' }); this.fileServiceEvents.emit({ type: 'error', response: this.httpErrorResponse, fileSelectedEventType: 'ALL' }); } break; case 'removeAll': if (this.queue.length) { this.queue = new Array(); this.fileServiceEvents.emit({ type: 'removedAll', files: this.queue, fileSelectedEventType: 'ALL' }); } break; } // Temporary taken reference number not in use any where if (NgxUploaderDirectiveService.inputEventReferenceNumber !== event.inputReferenceNumber) { NgxUploaderDirectiveService.inputEventReferenceNumber = event.inputReferenceNumber; } })); } /** * Check for file type is valid or not * @param {?} mimeType file mime type * @return {?} */ isFileTypeAllowed(mimeType) { /** @type {?} */ const allAllowed = this.fileTypes.find((/** * @param {?} type * @return {?} */ (type) => type === '*')) !== undefined; if (allAllowed) { return true; } return this.fileTypes.find((/** * @param {?} type * @return {?} */ (type) => type === mimeType)) !== undefined; } /** * Start file upload * @param {?} upload object with files and upload input event * @return {?} */ startUpload(upload) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ const sub = this.uploadFilesHttpRequest(upload.files, upload.event) .pipe(finalize((/** * @return {?} */ () => { if (!observer.closed) { observer.complete(); } }))) .subscribe((/** * @param {?} output * @return {?} */ output => { observer.next(output); }), (/** * @param {?} err * @return {?} */ err => { observer.error(err); observer.complete(); }), (/** * @return {?} */ () => { observer.complete(); })); this.subscriptions.push({ id: upload.files[0].requestId, sub }); if (this.logs && this.devEnv) { console.info('subscriptions ', this.subscriptions); } })); } /** * Upload files to server * @param {?} files Array of files input * @param {?} event Upload inout event * @return {?} */ uploadFilesHttpRequest(files, event) { return new Observable((/** * @param {?} observer * @return {?} */ observer => { /** @type {?} */ const time = new Date().getTime(); /** @type {?} */ let speed = 0; /** @type {?} */ let eta = null; /** @type {?} */ const fileList = files; /** @type {?} */ const headers = event.headers || {}; if (this.logs && this.devEnv) { console.info('Files to Upload', fileList); } if (fileList.length > 0) { /** @type {?} */ let totalSize = 0; files.forEach((/** * @param {?} file * @param {?} index * @return {?} */ (file, index) => { totalSize += file.nativeFile.size; })); /** @type {?} */ const formData = new FormData(); if (event.data !== undefined) { Object.keys(event.data).forEach((/** * @param {?} key * @return {?} */ key => formData.append(key, event.data[key]))); } if (fileList.length > 1) { // tslint:disable-next-line: prefer-for-of for (let fileIndex = 0; fileIndex < files.length; fileIndex++) { /** @type {?} */ const element = files[fileIndex]; formData.append('file_' + (fileIndex + 1), fileList[fileIndex].nativeFile, fileList[fileIndex].name); } } else { formData.append('file', fileList[0].nativeFile, fileList[0].name); } /** @type {?} */ const cancelledFiles = this.queue.filter((/** * @param {?} file * @return {?} */ file => file.requestId === fileList[0].requestId)); if (cancelledFiles[0].progress.status === 'Cancelled') { observer.complete(); } observer.next({ type: 'start', requestId: files[0].requestId, files, fileSelectedEventType: files[0].selectedEventType }); /** @type {?} */ const req = new HttpRequest(event.method, event.url, formData, { headers: new HttpHeaders(headers), reportProgress: true }); /** @type {?} */ const httpRequestSubscription = this.httpClient.request(req).subscribe(( // tslint:disable-next-line: no-shadowed-variable /** * @param {?} data * @return {?} */ (data) => { switch (data.type) { case HttpEventType.UploadProgress: /** @type {?} */ const percentage = Math.round((data.loaded * 100) / data.total); /** @type {?} */ const diff = new Date().getTime() - time; speed = Math.round(data.loaded / diff * 1000); eta = Math.ceil((data.total - data.loaded) / speed); // console.log('Progress: ' + this.fileUploadProgress); /** @type {?} */ const fileProgress = { status: 'Uploading', data: { percentage, speed, speedHuman: `${this.humanizeBytes(speed)}/s`, startTime: null, endTime: null, eta, etaHuman: this.secondsToHuman(eta) } }; files.forEach((/** * @param {?} file * @param {?} index * @param {?} filesArray * @return {?} */ (file, index, filesArray) => { filesArray[index].progress = fileProgress; })); observer.next({ type: 'uploading', requestId: files[0].requestId, files, progress: fileProgress, fileSelectedEventType: files[0].selectedEventType }); break; case HttpEventType.Response: files[0].response = data.body; /** @type {?} */ const progress = { status: 'Done', data: { percentage: 100, speed, speedHuman: `${this.humanizeBytes(speed)}/s`, startTime: null, endTime: new Date().getTime(), eta, etaHuman: this.secondsToHuman(eta || 0) } }; files.forEach((/** * @param {?} file * @param {?} index * @param {?} filesArray * @return {?} */ (file, index, filesArray) => { filesArray[index].response = data.body; })); observer.next({ type: 'done', requestId: files[0].requestId, response: data.body, progress, fileSelectedEventType: files[0].selectedEventType, files }); observer.complete(); break; } }), (/** * @param {?} error * @return {?} */ (error) => { // console.log(error); observer.next({ type: 'error', requestId: files[0].requestId, response: error, fileSelectedEventType: files[0].selectedEventType }); observer.complete(); })); this.subscriptions.push({ id: files[0].requestId, sub: httpRequestSubscription }); } else { this.httpErrorResponse = new HttpErrorResponse({ status: 0, error: 'Files not available for upload', statusText: 'Invalid Input' }); observer.next({ type: 'error', requestId: files[0].requestId, response: this.httpErrorResponse }); observer.complete(); } })); } /** * Http Request to upload file(s). * @param {?} requestMethod Request method POST | GET * @param {?} apiUrl Url to send request * @param {?} body FormData to passwith * @param {?=} headers * @return {?} */ httpRequest(requestMethod, apiUrl, body, headers) { /** @type {?} */ const req = new HttpRequest(requestMethod, apiUrl, body, { headers, reportProgress: true }); return this.httpClient.request(req); } /** * Converting seconds to human readable * @param {?} sec Seconds * @return {?} */ secondsToHuman(sec) { return new Date(sec * 1000).toISOString().substr(11, 8); } /** * Check for max file size is allowed or not * @param {?} fileSize file size * @return {?} */ isFileSizeAllowed(fileSize) { if (!this.maxFileSize) { return true; } return fileSize <= this.maxFileSize; } /** * Generate Randome file id * @return {?} */ generateRandomeId() { return '_' + Math.random().toString(36).substr(2, 9); } /** * Humanize file Bytes * @param {?} bytes file bytes * @return {?} */ humanizeBytes(bytes) { if (bytes === 0) { return '0 Byte'; } /** @type {?} */ const k = 1024; /** @type {?} */ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; /** @type {?} */ const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } /** * Convert selected file to Selected file Interface * @param {?} file Selected File * @param {?} fileIndex File index in array * @param {?} id * @param {?} selectedEventType * @return {?} */ convertToSelectedFile(file, fileIndex, id, selectedEventType) { // if (this.logs && this.devEnv) { // console.info('Converting file to Input Selected File index: ' + fileIndex, file); // } return { fileIndex, requestId: id, name: file.name, nativeFile: file, type: file.type, selectedEventType, progress: { status: 'Queue', data: { percentage: 0, eta: 0, speed: 0, speedHuman: this.humanizeBytes(0), startTime: null, endTime: null, etaHuman: null, } } }; } /** * Make chunks of array. * @param {?} array Array to make chunks. * @param {?} chunkSize Chunk size. * @return {?} */ chunkArray(array, chunkSize) { /** @type {?} */ const chunkedArray = new Array(); /** @type {?} */ let index = 0; /** @type {?} */ const arrayLength = array.length; for (index = 0; index < arrayLength; index += chunkSize) { /** @type {?} */ const myChunk = array.slice(index, index + chunkSize); // Do something if you want with the group chunkedArray.push(myChunk); } return chunkedArray; } /** * Group by an Array. * @param {?} array Array of objects * @param {?} key key * @return {?} */ groupByArray(array, key) { return array.reduce((/** * @param {?} previousValue * @param {?} currentValue * @return {?} */ (previousValue, currentValue) => { (previousValue[currentValue[key]] = previousValue[currentValue[key]] || []).push(currentValue); return previousValue; }), {}); } } NgxUploaderDirectiveService.inputEventReferenceNumber = 0; if (false) { /** @type {?} */ NgxUploaderDirectiveService.inputEventReferenceNumber; /** * @type {?} * @private */ NgxUploaderDirectiveService.prototype.devEnv; /** @type {?} */ NgxUploaderDirectiveService.prototype.queue; /** @type {?} */ NgxUploaderDirectiveService.prototype.MaxNumberOfRequest; /** @type {?} */ NgxUploaderDirectiveService.prototype.subscriptions; /** @type {?} */ NgxUploaderDirectiveService.prototype.fileServiceEvents; /** @type {?} */ NgxUploaderDirectiveService.prototype.uploadScheduler; /** @type {?} */ NgxUploaderDirectiveService.prototype.fileTypes; /** @type {?} */ NgxUploaderDirectiveService.prototype.maxFileUploads; /** @type {?} */ NgxUploaderDirectiveService.prototype.maxFileSize; /** @type {?} */ NgxUploaderDirectiveService.prototype.requestConcurrency; /** @type {?} */ NgxUploaderDirectiveService.prototype.maxFilesToAddInSingleRequest; /** @type {?} */ NgxUploaderDirectiveService.prototype.httpErrorResponse; /** * @type {?} * @private */ NgxUploaderDirectiveService.prototype.httpClient; /** * @type {?} * @private */ NgxUploaderDirectiveService.prototype.logs; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LXVwbG9hZGVyLWRpcmVjdGl2ZS5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6Ii4uLy4uLy4uL3Byb2plY3RzL25neC11cGxvYWRlci1kaXJlY3RpdmUvc3JjLyIsInNvdXJjZXMiOlsibGliL3NlcnZpY2VzL25neC11cGxvYWRlci1kaXJlY3RpdmUuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQXlCQSxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRTdDLE9BQU8sRUFBRSxVQUFVLEVBQWdCLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUN6RCxPQUFPLEVBQUUsUUFBUSxFQUFFLFFBQVEsRUFBYSxNQUFNLGdCQUFnQixDQUFDO0FBQy9ELE9BQU8sRUFBRSxXQUFXLEVBQWMsYUFBYSxFQUFlLFdBQVcsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzNILE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQzs7OztBQU1oRCxNQUFNLE9BQU8sMkJBQTJCOzs7Ozs7Ozs7O0lBa0J0QyxZQUNFLHFCQUE2QixNQUFNLENBQUMsaUJBQWlCLEVBQ3JELCtCQUF1QyxNQUFNLENBQUMsaUJBQWlCLEVBQy9ELFlBQTJCLENBQUMsR0FBRyxDQUFDLEVBQ2hDLGlCQUF5QixNQUFNLENBQUMsaUJBQWlCLEVBQ2pELGNBQXNCLE1BQU0sQ0FBQyxpQkFBaUIsRUFDdEMsVUFBc0IsRUFDdEIsSUFBYztRQURkLGVBQVUsR0FBVixVQUFVLENBQVk7UUFDdEIsU0FBSSxHQUFKLElBQUksQ0FBVTtRQXJCaEIsV0FBTSxHQUFHLENBQUMsV0FBVyxDQUFDLFVBQVUsQ0FBQztRQUNsQyxVQUFLLEdBQXlCLElBQUksS0FBSyxFQUFpQixDQUFDO1FBQ3pELHVCQUFrQixHQUFHLEVBQUUsQ0FBQztRQXFCN0IsSUFBSSxDQUFDLGlCQUFpQixHQUFHLElBQUksWUFB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