From d938aa1c396a5a9145edf6fcdf30931cf4188246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vojt=C4=9Bch=20Kal=C3=A1b?= Date: Tue, 23 Feb 2016 09:02:45 +0100 Subject: [PATCH] # This is a combination of 3 commits. # The first commit's message is: feat(uploader): huge uploader update - creted uploader options - added `file type` support - added headers to xhr - rename FileUploaderSettings => FileUploaderOptionsInterface - added public methods set Options - created filter from set options - added demo component # The 2nd commit message will be skipped: # rename FileUploaderSettings => FileUploaderOptionsInterface # The 3rd commit message will be skipped: # added public method setOptions --- components/file-upload/file-item.class.ts | 4 +- components/file-upload/file-type.ts | 158 ++++++++++++++++++ components/file-upload/file-uploader.class.ts | 123 ++++++++++---- .../file-upload/zs-file-demo/demo.html | 3 + .../file-upload/zs-file-demo/demo.ts | 124 ++++++++++++++ 5 files changed, 377 insertions(+), 35 deletions(-) create mode 100644 components/file-upload/file-type.ts create mode 100644 demo/components/file-upload/zs-file-demo/demo.html create mode 100644 demo/components/file-upload/zs-file-demo/demo.ts diff --git a/components/file-upload/file-item.class.ts b/components/file-upload/file-item.class.ts index aa06890..1151510 100644 --- a/components/file-upload/file-item.class.ts +++ b/components/file-upload/file-item.class.ts @@ -32,7 +32,7 @@ export class FileItem { this.options = options; this.file = new FileLikeObject(some); this._file = some; - this.url = uploader.url; + this.url = uploader.options.url; this._zone = new NgZone({ enableLongStackTrace: false }); } @@ -134,7 +134,7 @@ export class FileItem { public _onComplete(response:any, status:any, headers:any):void { this.onComplete(response, status, headers); - if (this.uploader.removeAfterUpload) { + if (this.uploader.options.removeAfterUpload) { this.remove(); } } diff --git a/components/file-upload/file-type.ts b/components/file-upload/file-type.ts new file mode 100644 index 0000000..e256953 --- /dev/null +++ b/components/file-upload/file-type.ts @@ -0,0 +1,158 @@ +export class FileType { + /* MS office */ + static mime_doc = [ + 'application/msword', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'application/vnd.ms-word.document.macroEnabled.12', + 'application/vnd.ms-word.template.macroEnabled.12' + ]; + static mime_xsl = [ + 'application/vnd.ms-excel', + 'application/vnd.ms-excel', + 'application/vnd.ms-excel', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'application/vnd.ms-excel.template.macroEnabled.12', + 'application/vnd.ms-excel.addin.macroEnabled.12', + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' + ]; + static mime_ppt = [ + 'application/vnd.ms-powerpoint', + 'application/vnd.ms-powerpoint', + 'application/vnd.ms-powerpoint', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' + ]; + + /* PSD */ + static mime_psd = [ + 'image/photoshop', + 'image/x-photoshop', + 'image/psd', + 'application/photoshop', + 'application/psd', + 'zz-application/zz-winassoc-psd' + ]; + + /* Compressed files */ + static mime_compress = [ + 'application/x-gtar', + 'application/x-gcompress', + 'application/compress', + 'application/x-tar', + 'application/x-rar-compressed', + 'application/octet-stream' + ]; + + static getMimeClass(file) { + let mimeClass = 'application'; + if (this.mime_psd.indexOf(file.type) !== -1) { + mimeClass = 'image'; + } else if (file.type.match('image.*')) { + mimeClass = 'image'; + } else if (file.type.match('video.*')) { + mimeClass = 'video'; + } else if (file.type.match('audio.*')) { + mimeClass = 'audio'; + } else if (file.type === 'application/pdf') { + mimeClass = 'pdf'; + } else if (this.mime_compress.indexOf(file.type) !== -1) { + mimeClass = 'compress'; + } else if (this.mime_doc.indexOf(file.type) !== -1) { + mimeClass = 'doc'; + } else if (this.mime_xsl.indexOf(file.type) !== -1) { + mimeClass = 'xls'; + } else if (this.mime_ppt.indexOf(file.type) !== -1) { + mimeClass = 'ppt'; + } + if (mimeClass === 'application') { + mimeClass = this.fileTypeDetection(file.name); + } + + + return mimeClass; + } + + + static fileTypeDetection(inputFilename) { + let types = { + 'jpg': 'image', + 'jpeg': 'image', + 'tif': 'image', + 'psd': 'image', + 'bmp': 'image', + 'png': 'image', + 'nef': 'image', + 'tiff': 'image', + 'cr2': 'image', + 'dwg': 'image', + 'cdr': 'image', + 'ai': 'image', + 'indd': 'image', + 'pin': 'image', + 'cdp': 'image', + 'skp': 'image', + 'stp': 'image', + '3dm': 'image', + 'mp3': 'audio', + 'wav': 'audio', + 'wma': 'audio', + 'mod': 'audio', + 'm4a': 'audio', + 'compress': 'compress', + 'rar': 'compress', + '7z': 'compress', + 'lz': 'compress', + 'z01': 'compress', + 'pdf': 'pdf', + 'xls': 'xls', + 'xlsx': 'xls', + 'ods': 'xls', + 'mp4': 'video', + 'avi': 'video', + 'wmv': 'video', + 'mpg': 'video', + 'mts': 'video', + 'flv': 'video', + '3gp': 'video', + 'vob': 'video', + 'm4v': 'video', + 'mpeg': 'video', + 'm2ts': 'video', + 'mov': 'video', + 'doc': 'doc', + 'docx': 'doc', + 'eps': 'doc', + 'rtf': 'doc', + 'txt': 'doc', + 'odt': 'doc', + 'rtf': 'doc', + 'ppt': 'ppt', + 'pptx': 'ppt', + 'pps': 'ppt', + 'ppsx': 'ppt', + 'odp': 'ppt' + }; + + let chunks = inputFilename.split('.'); + if (chunks.length < 2) { + return 'application'; + } + let extension = chunks[chunks.length - 1].toLowerCase(); + if (types[extension] === undefined) { + return 'application'; + } else { + return types[extension]; + } + } + +} diff --git a/components/file-upload/file-uploader.class.ts b/components/file-upload/file-uploader.class.ts index 54365e4..8349148 100644 --- a/components/file-upload/file-uploader.class.ts +++ b/components/file-upload/file-uploader.class.ts @@ -1,44 +1,85 @@ import {FileLikeObject} from './file-like-object.class'; import {FileItem} from './file-item.class'; -function isFile(value:any):boolean { +import {FileType} from './file-type'; +function isFile(value: any) { return (File && value instanceof File); } // function isFileLikeObject(value:any) { -// return value instanceof FileLikeObject; -// } -export class FileUploader { - public url:string; - public authToken:string; - public isUploading:boolean = false; - public queue:Array = []; - public progress:number = 0; - public autoUpload:boolean = false; - public isHTML5:boolean = true; - public removeAfterUpload:boolean = false; - public queueLimit:number; - public _nextIndex:number = 0; - public filters:Array = []; - public options:any; - private _failFilterIndex:number; +export interface Headers { + name: string; + value: string; +} - public constructor(options:any) { - // Object.assign(this, options); - this.url = options.url; - this.authToken = options.authToken; - this.autoUpload = options.autoUpload; - this.filters.unshift({name: 'queueLimit', fn: this._queueLimitFilter}); - this.filters.unshift({name: 'folder', fn: this._folderFilter}); +export interface FileUploaderOptionsInterface { + allowedMimeType?: Array; + allowedFileType?: Array; + autoUpload?:boolean; + isHTML5?:boolean; + filters?:Array; + headers?: Array; + maxFileSize?: number; + queueLimit?:number; + removeAfterUpload?:boolean; + url?:string; +} + +export class FileUploader { + + public authToken: string; + public isUploading: boolean = false; + public queue: Array = []; + public progress: number = 0; + public _nextIndex:number = 0; + public options:any; + private _failFilterIndex: number; + + public options: FileUploaderOptionsInterface = { + autoUpload: false, + isHTML5: true, + filters: [], + removeAfterUpload: false, + }; + + + constructor() { } - public addToQueue(files:any[], options:any, filters:any):void { - let list:any[] = []; + public setOptions(options: any) { + this.options = Object.assign(this.options, options); + + this.authToken = options.authToken; + this.autoUpload = 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}); + } + + // this.options.filters.unshift({name: 'folder', fn: this._folderFilter}); + } + + + public addToQueue(files: any[], options?: any, filters?: any) { + let list: any[] = []; for (let file of files) { list.push(file); } let arrayOfFilters = this._getFilters(filters); let count = this.queue.length; - let addedFileItems:any[] = []; + let addedFileItems: any[] = []; list.map((some:any) => { + if (!options) { + options = this.options; + } + let temp = new FileLikeObject(some); if (this._isValidFile(temp, arrayOfFilters, options)) { let fileItem = new FileItem(this, some, options); @@ -55,7 +96,7 @@ export class FileUploader { this.progress = this._getTotalProgress(); } this._render(); - if (this.autoUpload) { + if (this.options.autoUpload) { this.uploadAll(); } } @@ -80,7 +121,7 @@ export class FileUploader { public uploadItem(value:FileItem):void { let index = this.getIndexOfItem(value); let item = this.queue[index]; - let transport = this.isHTML5 ? '_xhrTransport' : '_iframeTransport'; + let transport = this.options.isHTML5 ? '_xhrTransport' : '_iframeTransport'; item._prepareToUploading(); if (this.isUploading) { return; @@ -92,7 +133,7 @@ export class FileUploader { public cancelItem(value:any):void { let index = this.getIndexOfItem(value); let item = this.queue[index]; - let prop = this.isHTML5 ? '_xhr' : '_form'; + let prop = this.options.isHTML5 ? '_xhr' : '_form'; if (item && item.isUploading) { item[prop].abort(); } @@ -175,8 +216,19 @@ export class FileUploader { return {item, response, status, headers}; } - public onCancelItem(item:any, response:any, status:any, headers:any):any { - return {item, response, status, headers}; + private _mimeTypeFilter(item: any) { + return !(this.options.allowedMimeType && this.options.allowedMimeType.indexOf(item.type) === -1); + } + + private _fileSizeFilter(item: any) { + return !(this.options.maxFileSize && item.size > this.options.maxFileSize); + } + + private _fileTypeFilter(item: any) { + return !(this.options.allowedFileType && + this.options.allowedFileType.indexOf(FileType.getMimeClass(item)) === -1); + } + } public onCompleteItem(item:any, response:any, status:any, headers:any):any { @@ -207,7 +259,7 @@ export class FileUploader { } protected _headersGetter(parsedHeaders:any):any { - return (name:any) => { + return (name: any) => { if (name) { return parsedHeaders[name.toLowerCase()] || void 0; } @@ -259,6 +311,11 @@ export class FileUploader { /*item.headers.map((value, name) => { xhr.setRequestHeader(name, value); });*/ + if (this.options.headers) { + for (let header of this.options.headers) { + xhr.setRequestHeader(header.name, header.value); + } + } if (this.authToken) { xhr.setRequestHeader('Authorization', this.authToken); } diff --git a/demo/components/file-upload/zs-file-demo/demo.html b/demo/components/file-upload/zs-file-demo/demo.html new file mode 100644 index 0000000..6984b24 --- /dev/null +++ b/demo/components/file-upload/zs-file-demo/demo.html @@ -0,0 +1,3 @@ + diff --git a/demo/components/file-upload/zs-file-demo/demo.ts b/demo/components/file-upload/zs-file-demo/demo.ts new file mode 100644 index 0000000..e4140c0 --- /dev/null +++ b/demo/components/file-upload/zs-file-demo/demo.ts @@ -0,0 +1,124 @@ +import {Component, EventEmitter, ElementRef, Renderer, Input} from 'angular2/core'; +import {FileUploader, FileUploaderOptionsInterface} from '../../../../ng2-file-upload'; + +@Component({ + selector: 'demo-file-upload', + providers: [FileUploader], + directives: [], + pipes: [], + host: { + '(drop)': 'onDrop($event)', + '(dragover)': 'onDragOver($event)', + '(dragleave)': 'onDragLeave($event)', + '[class.hover]': 'isHover' + }, + template: require('./demo.html'), + styles: [':host {border:1px solid black; padding:59px;display: block;}' + + '.hover {border: 3px solid green; backgroud: black;}'] +}) +export class DemoFileUpload { + + @Input() url: string; + @Input() queueLimit: number; + @Input() maxFileSize: number; + @Input() autoUpload: boolean; + @Input() allowedMimeType: Array; + @Input() allowedFileType: Array; + @Input() headers: Array; + + private inputs = ['allowedMimeType', + 'allowedFileType', + 'autoUpload', + 'isHTML5', + 'headers', + 'maxFileSize', + 'queueLimit', + 'removeAfterUpload', + 'url', + ]; + + private uploaderOptions: FileUploaderOptionsInterface = {}; + + private isHover: boolean = false; + private multiple: boolean = true; + + constructor(private element: ElementRef, + private fileUploadService: FileUploader, + private renderer: Renderer) { + } + + ngOnInit() { + for (let input of this.inputs) { + if (this[input]) { + this.uploaderOptions[input] = this[input]; + } + } + this.fileUploadService.setOptions(this.uploaderOptions); + + this.multiple = (!this.queueLimit || this.queueLimit > 1); + } + + + onDrop(event: any) { + this._preventAndStop(event); + this.isHover = false; + + let transfer = this._getTransfer(event); + if (!transfer) { + return; + } + this.fileUploadService.addToQueue(transfer.files); + } + + onDragOver(event: any) { + this._preventAndStop(event); + + if (this.isHover) { + return; + } + + let transfer = this._getTransfer(event); + if (!this._haveFiles(transfer.types)) { + return; + } + this.isHover = true; + } + + onDragLeave(event: any): any { + this._preventAndStop(event); + if (event.currentTarget === (this).element[0]) { + return; + } + this.isHover = false; + } + + onChange($event) { + this.fileUploadService.addToQueue($event.srcElement.files); + } + + + private _getTransfer(event: any): any { + return event.dataTransfer ? event.dataTransfer : event.originalEvent.dataTransfer; + } + + private _preventAndStop(event: any): any { + event.preventDefault(); + event.stopPropagation(); + } + + private _haveFiles(types: any): any { + if (!types) { + return false; + } + + if (types.indexOf) { + return types.indexOf('Files') !== -1; + } else if (types.contains) { + return types.contains('Files'); + } else { + return false; + } + } + + +}