UNPKG

@myog-io/ngx-chunk-file-upload-base

Version:

Angular file upload directives with Chunk Upload

579 lines (517 loc) 15.3 kB
import { Headers } from './file-uploader.class'; import { EventEmitter } from '@angular/core'; import { FileLikeObject } from './file-like-object.class'; import { FileItem } from './file-item.class'; import { FileType } from './file-type.class'; import { HttpResponse, HttpHeaders, HttpProgressEvent, HttpSentEvent, HttpErrorResponse, } from '@angular/common/http'; function isFile(value: any): boolean { return File && value instanceof File; } export interface Headers { name: string; value: string; } export interface FilterFunction { name: string; fn: (item?: FileLikeObject, options?: FileUploaderOptions) => boolean; } export interface FileUploaderOptions { uploaderService: any; allowedMimeType?: string[]; allowedFileType?: string[]; autoUpload?: boolean; isHTML5?: boolean; filters?: FilterFunction[]; headers?: Headers[]; method?: string; authToken?: string; maxFileSize?: number; queueLimit?: number; removeAfterUpload?: boolean; url?: string; disableMultipart?: boolean; itemAlias?: string; authTokenHeader?: string; additionalParameter?: { [key: string]: any }; parametersBeforeFiles?: boolean; formatDataFunction?: Function; formatDataFunctionIsAsync?: boolean; } export class FileUploader { public uploaderService: any; public authToken: string; public isUploading: boolean = false; public queue: FileItem[] = []; public progress: number = 0; public _nextIndex: number = 0; public autoUpload: any; public authTokenHeader: string; public response: EventEmitter<any>; public chunkSize: number = null; public options: FileUploaderOptions = { uploaderService: null, autoUpload: false, isHTML5: true, filters: [], removeAfterUpload: false, disableMultipart: false, formatDataFunction: (item: FileItem) => item._file, formatDataFunctionIsAsync: false, }; protected _failFilterIndex: number; public constructor(options: FileUploaderOptions) { this.setOptions(options); this.response = new EventEmitter<any>(); } public setOptions(options: FileUploaderOptions): void { this.options = Object.assign(this.options, options); this.uploaderService = this.options.uploaderService; this.uploaderService.uploader = this; this.authToken = this.options.authToken; this.authTokenHeader = this.options.authTokenHeader || 'Authorization'; this.autoUpload = this.options.autoUpload; this.options.filters.unshift({ name: 'queueLimit', fn: this._queueLimitFilter, }); if (this.options.maxFileSize) { this.options.filters.unshift({ name: 'fileSize', fn: this._fileSizeFilter, }); } if (this.options.allowedFileType) { this.options.filters.unshift({ name: 'fileType', fn: this._fileTypeFilter, }); } if (this.options.allowedMimeType) { this.options.filters.unshift({ name: 'mimeType', fn: this._mimeTypeFilter, }); } for (let i = 0; i < this.queue.length; i++) { this.queue[i].url = this.options.url; } } public addToQueue( files: File[], options?: FileUploaderOptions, filters?: FilterFunction[] | string, ): void { const list: File[] = []; for (const file of files) { list.push(file); } const arrayOfFilters = this._getFilters(filters); const count = this.queue.length; const addedFileItems: FileItem[] = []; list.map((some: File) => { if (!options) { options = this.options; } const temp = new FileLikeObject(some); if (this._isValidFile(temp, arrayOfFilters, options)) { const fileItem = new FileItem(this, some, options); addedFileItems.push(fileItem); this.queue.push(fileItem); this._onAfterAddingFile(fileItem); } else { const filter = arrayOfFilters[this._failFilterIndex]; this._onWhenAddingFileFailed(temp, filter, options); } }); if (this.queue.length !== count) { this._onAfterAddingAll(addedFileItems); this.progress = this._getTotalProgress(); } this._render(); if (this.options.autoUpload) { this.uploadAll(); } } public removeFromQueue(value: FileItem): void { const index = this.getIndexOfItem(value); const item = this.queue[index]; if (item.isUploading) { item.cancel(); } this.queue.splice(index, 1); this.progress = this._getTotalProgress(); this.onRemoveItem(item); } public clearQueue(): void { while (this.queue.length) { this.queue[0].remove(); } this.progress = 0; } public uploadItem(value: FileItem): void { const index = this.getIndexOfItem(value); const item = this.queue[index]; item._prepareToUploading(); if (this.isUploading) { return; } this.isUploading = true; this._uploadFile(item); } public cancelItem(value: FileItem): void { const index = this.getIndexOfItem(value); const item = this.queue[index]; if (item && item.isUploading) { this.uploaderService.stopUpload(); } } public uploadAll(): void { const items = this.getNotUploadedItems().filter( (item: FileItem) => !item.isUploading, ); if (!items.length) { return; } items.map((item: FileItem) => item._prepareToUploading()); items[0].upload(); } public cancelAll(): void { const items = this.getNotUploadedItems(); items.map((item: FileItem) => item.cancel()); } public isFile(value: any): boolean { return isFile(value); } public isFileLikeObject(value: any): boolean { return value instanceof FileLikeObject; } public getIndexOfItem(value: any): number { return typeof value === 'number' ? value : this.queue.indexOf(value); } public getIsErrorItems(): any[] { return this.queue.filter((item: FileItem) => item.isError); } public getIsCancelItems(): any[] { return this.queue.filter((item: FileItem) => item.isCancel); } public getIsSuccessItems(): any[] { return this.queue.filter((item: FileItem) => item.isSuccess); } public getAllItems(): any[] { return this.queue; } public getNotUploadedItems(): any[] { return this.queue.filter((item: FileItem) => !item.isUploaded); } public getReadyItems(): any[] { return this.queue .filter((item: FileItem) => item.isReady && !item.isUploading) .sort((item1: any, item2: any) => item1.index - item2.index); } public destroy(): void { return void 0; } public onAfterAddingAll(fileItems: any): any { return { fileItems }; } public onBuildItemForm(fileItem: FileItem, form: any): any { return { fileItem, form }; } public onAfterAddingFile(fileItem: FileItem): any { return { fileItem }; } public onWhenAddingFileFailed( item: FileLikeObject, filter: any, options: any, ): any { return { item, filter, options }; } public onBeforeUploadItem(fileItem: FileItem): any { return { fileItem }; } public onProgressItem(fileItem: FileItem, progress: any): any { return { fileItem, progress }; } public onProgressAll(progress: any): any { return { progress }; } public onSuccessItem( item: FileItem, response: string, status: number, headers: HttpHeaders, ): any { return { item, response, status, headers }; } public onErrorItem( item: FileItem, response: string, status: number, headers: HttpHeaders, ): any { return { item, response, status, headers }; } public onCancelItem(item: FileItem): any { return { item }; } public onRemoveItem(item: FileItem): any { return { item }; } public onCompleteChunk( item: FileItem, response: string, status: number, headers: HttpHeaders, ): any { return { item, response, status, headers }; } public onErrorChunk( item: FileItem, response: string, status: number, headers: HttpHeaders, ): any { return { item, response, status, headers }; } public onCompleteItem( item: FileItem, response: string, status: number, headers: HttpHeaders, ): any { return { item, response, status, headers }; } public onCompleteAll(): any { return void 0; } public _mimeTypeFilter(item: FileLikeObject): boolean { return !( this.options.allowedMimeType && this.options.allowedMimeType.indexOf(item.type) === -1 ); } public _fileSizeFilter(item: FileLikeObject): boolean { return !(this.options.maxFileSize && item.size > this.options.maxFileSize); } public _fileTypeFilter(item: FileLikeObject): boolean { return !( this.options.allowedFileType && this.options.allowedFileType.indexOf(FileType.getMimeClass(item)) === -1 ); } public _onErrorItem( item: FileItem, response: string, status: number, headers: HttpHeaders, ): void { item._onError(response, status, headers); this.onErrorItem(item, response, status, headers); } public _onCompleteChunk( item: FileItem, response: string, status: number, headers: HttpHeaders, ): void { this.onCompleteChunk(item, response, status, headers); item._onCompleteChunk(response, status, headers); this.progress = this._getTotalProgress(); this._render(); } public _onCompleteItem( item: FileItem, response?: string, status?: number, headers?: HttpHeaders, ): void { item._onComplete(response, status, headers); this.onCompleteItem(item, response, status, headers); const nextItem = this.getReadyItems()[0]; this.isUploading = false; if (nextItem) { nextItem.upload(); return; } this.onCompleteAll(); this.progress = this._getTotalProgress(); this._render(); } protected _onProgress(event: HttpProgressEvent, item: FileItem) { event = event; let progress = (100 * event.loaded) / event.total; if (this.uploaderService.options.chunkSize > 0) { progress = (100 * item.getCurrentChunk()) / item.getTotalChunks() + progress / item.getTotalChunks(); } // TODO: Check why it's not showing upload progress per item only on complete. this._onProgressItem(item, Math.round(progress)); } public onProgress(event: HttpProgressEvent, item: FileItem) { this._onProgress(event, item); } protected _onStart(event: HttpSentEvent, item: FileItem) { } public onStart(event: HttpSentEvent, item: FileItem) { this._onStart(event, item); } protected _onLoad(event: HttpResponse<any>, item: FileItem) { const headers = event.headers; const response = this._transformResponse(event.body, headers); const gist = this._isSuccessCode(event.status) ? 'Success' : 'Error'; const method = '_on' + gist + 'Item'; if (this.uploaderService.options.chunkSize > 0) { if (item.getCurrentChunk() + 1 >= item.getTotalChunks()) { (this as any)[method](item, response, event.status, headers); this._onCompleteItem(item, response, event.status, headers); } else { this._onCompleteChunk(item, response, event.status, headers); } } else { (this as any)[method](item, response, event.status, headers); this._onCompleteItem(item, response, event.status, headers); } } public onLoad(event: HttpResponse<any>, item: FileItem) { this._onLoad(event, item); } protected _onError(error: any, item: FileItem) { const headers: HttpHeaders = error.Headers; const response = this._transformResponse(error.error, headers); if (this.uploaderService.options.chunkSize > 0) { this._onErrorChunk(item, response, error.status, headers); } else { this._onErrorItem(item, response, error.status, headers); this._onCompleteItem(item, response, error.status, headers); } } public onError(error: HttpErrorResponse, item: FileItem) { this._onError(error, item); } protected _onAbort(error: HttpErrorResponse, item: FileItem) { this._onCancelItem(item); } public onAbort(error: HttpErrorResponse, item: FileItem) { this._onAbort(error, item); } protected _uploadFile(item: FileItem): any { this._onBeforeUploadItem(item); if (typeof item._file.size !== 'number') { throw new TypeError('The file specified is no longer valid'); } this.uploaderService.uploadFile(item, this.options); } protected _getTotalProgress(value: number = 0): number { if (this.options.removeAfterUpload) { return value; } const notUploaded = this.getNotUploadedItems().length; const uploaded = notUploaded ? this.queue.length - notUploaded : this.queue.length; const ratio = 100 / this.queue.length; const current = (value * ratio) / 100; return Math.round(uploaded * ratio + current); } protected _getFilters(filters: FilterFunction[] | string): FilterFunction[] { if (!filters) { return this.options.filters; } if (Array.isArray(filters)) { return filters; } if (typeof filters === 'string') { const names = filters.match(/[^\s,]+/g); return this.options.filters.filter( (filter: any) => names.indexOf(filter.name) !== -1, ); } return this.options.filters; } protected _render(): any { return void 0; } protected _queueLimitFilter(): boolean { return ( this.options.queueLimit === undefined || this.queue.length < this.options.queueLimit ); } protected _isValidFile( file: FileLikeObject, filters: FilterFunction[], options: FileUploaderOptions, ): boolean { this._failFilterIndex = -1; return !filters.length ? true : filters.every((filter: FilterFunction) => { this._failFilterIndex++; return filter.fn.call(this, file, options); }); } protected _isSuccessCode(status: number): boolean { return (status >= 200 && status < 300) || status === 304; } protected _transformResponse(response: string, headers: HttpHeaders): string { return response; } protected _onWhenAddingFileFailed( item: FileLikeObject, filter: any, options: any, ): void { this.onWhenAddingFileFailed(item, filter, options); } protected _onAfterAddingFile(item: FileItem): void { this.onAfterAddingFile(item); } protected _onAfterAddingAll(items: any): void { this.onAfterAddingAll(items); } protected _onBeforeUploadItem(item: FileItem): void { item._onBeforeUpload(); this.onBeforeUploadItem(item); } public _onBuildItemForm(item: FileItem, form: any): void { item._onBuildForm(form); this.onBuildItemForm(item, form); } protected _onProgressItem(item: FileItem, progress: any): void { const total = this._getTotalProgress(progress); this.progress = total; item._onProgress(progress); this.onProgressItem(item, progress); this.onProgressAll(total); this._render(); } protected _onErrorChunk( item: FileItem, response: string, status: number, headers: HttpHeaders, ): void { item._onErrorChunk(response, status, headers); this.onErrorChunk(item, response, status, headers); } protected _onSuccessItem( item: FileItem, response: string, status: number, headers: HttpHeaders, ): void { item._onSuccess(response, status, headers); this.onSuccessItem(item, response, status, headers); } protected _onCancelItem(item: FileItem): void { item._onCancel(); this.onCancelItem(item); } }