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
JavaScript
/**
* @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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