From e502075fbd24a1e272e6bb349981f9dd933900a6 Mon Sep 17 00:00:00 2001 From: Jan Kuri Date: Mon, 5 Sep 2016 05:57:41 +0200 Subject: [PATCH] 1.0.0 --- README.md | 376 ++++++------------------------- index.ts | 1 - ng2-uploader.ts | 15 +- package.json | 2 +- src/directives/index.ts | 2 - src/directives/ng-file-drop.ts | 64 ++++-- src/directives/ng-file-select.ts | 60 +++-- src/services/index.ts | 1 - src/services/ng2-uploader.ts | 121 ++++++---- 9 files changed, 249 insertions(+), 393 deletions(-) delete mode 100644 index.ts delete mode 100644 src/directives/index.ts delete mode 100644 src/services/index.ts diff --git a/README.md b/README.md index 59071aad..09d7b548 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,29 @@ For demos please see [demos page](http://ng2-uploader.com). ### Installation ``` -npm install ng2-uploader +npm install ng2-uploader --save ``` +### Available parameters + +|Parameter | Example Value +|--- |--- | +| url | http://api.ng2-uploader.com:10050 | +| filterExtensions | true/false | +| allowedExtensions | ['image/png', 'image/jpg'] or ['jpg', 'png'] | +| calculateSpeed | true/false | +| data | { userId: 12, isAdmin: true } | +| customHeaders | { 'custom-header': 'value' } | +| authToken | 012313asdadklj123123 | +| authTokenPrefix | 'Bearer' (default) | + +**All parameters except `url` are optional.** + + #### Examples 1. [Basic Example](https://github.com/jkuri/ng2-uploader#basic-example) -2. [Multiple Files Example](https://github.com/jkuri/ng2-uploader#multiple-files-example) -3. [Basic Progressbar Example](https://github.com/jkuri/ng2-uploader#progressbar-example) -4. [Multiple Files Progressbars Example](https://github.com/jkuri/ng2-uploader#multiple-files-progressbars-example) +2. [Advanced Example](https://github.com/jkuri/ng2-uploader#advanced-example) #### Backend Examples @@ -25,15 +39,23 @@ npm install ng2-uploader ### Basic Example -`component.ts` -````typescript -import {Component} from '@angular/core'; -import {UPLOAD_DIRECTIVES} from 'ng2-uploader/ng2-uploader'; +````ts +// app.module.ts +import { UPLOAD_DIRECTIVES } from 'ng2-uploader'; +... +@NgModule({ + ... + declarations: [ + UPLOAD_DIRECTIVES + ], + ... +}) +// app.component.ts +import { Component } from '@angular/core'; @Component({ selector: 'demo-app', - templateUrl: 'app/demo.html', - directives: [UPLOAD_DIRECTIVES], + templateUrl: 'app/demo.html' }) export class DemoApp { uploadFile: any; @@ -50,10 +72,11 @@ export class DemoApp { } ```` -`component.html` ````html +
@@ -61,317 +84,50 @@ Response: {{ uploadFile | json }}
```` -### Multiple files example - -`component.ts` -````typescript -import {Component} from '@angular/core'; -import {UPLOAD_DIRECTIVES} from 'ng2-uploader/ng2-uploader'; - -@Component({ - selector: 'basic-multiple', - templateUrl: 'basic-multiple.html', - directives: [UPLOAD_DIRECTIVES], -}) -export class BasicMultiple { - uploadedFiles: any[] = []; - options: Object = { - url: 'http://localhost:10050/upload' - }; - - handleUpload(data): void { - if (data && data.response) { - data = JSON.parse(data.response); - this.uploadedFiles.push(data); - } - } -} -```` - -`component.html` -````html - - - -
-Response:
{{ uploadedFiles | json }} -
-```` +### Advanced Example -### Progressbar example +This example show how to use available options and progress. -`component.ts` -````typescript -import {Component, NgZone} from '@angular/core'; -import {UPLOAD_DIRECTIVES} from 'ng2-uploader/ng2-uploader'; +```ts +import { Component, OnInit, NgZone } from '@angular/core'; @Component({ - selector: 'basic-progressbar', - templateUrl: 'app/components/basic-progressbar/basic-progressbar.html', - directives: [UPLOAD_DIRECTIVES], + selector: 'app-component', + templateUrl: 'app.component.html' }) -export class BasicProgressbar { - uploadFile: any; - uploadProgress: number; - uploadResponse: Object; - zone: NgZone; - options: Object = { - url: 'http://localhost:10050/upload' - }; +export class AppDemoComponent implements OnInit { + private zone: NgZone; + private options: Object; + private progress: number = 0; + private response: any = {}; - constructor() { - this.uploadProgress = 0; - this.uploadResponse = {}; + ngOnInit() { this.zone = new NgZone({ enableLongStackTrace: false }); + this.options = { + url: 'http://api.ng2-uploader.com:10050/upload', + filterExtensions: true, + allowedExtensions: ['image/png', 'image/jpg'], + calculateSpeed: true, + data: { + userId: 12, + isAdmin: true + }, + customHeaders: { + 'custom-header': 'value' + }, + authToken: 'asd123b123zxc08234cxcv', + authTokenPrefix: 'Bearer' + }; } - handleUpload(data): void { - this.uploadFile = data; + handleUpload(data: any): void { this.zone.run(() => { - this.uploadProgress = data.progress.percent; + this.response = data; + this.progress = Math.floor(data.progress.percent / 100); }); - let resp = data.response; - if (resp) { - resp = JSON.parse(resp); - this.uploadResponse = resp; - } - } -} -```` - -`component.html` -````html -
- - -
- -
-Progress: {{ uploadProgress }}% -
-
-
-
-
Uploading file ({{ uploadProgress }}%)
-
-
- -
-Response:
{{ uploadFile | json }} -
-```` - -### Multiple files progressbars example - -`component.ts` -````typescript -import {Component, NgZone} from '@angular/core'; -import {UPLOAD_DIRECTIVES} from 'ng2-uploader/ng2-uploader'; - -@Component({ - selector: 'multiple-progressbar', - templateUrl: 'app/components/multiple-progressbar/multiple-progressbar.html', - directives: [UPLOAD_DIRECTIVES] -}) -export class MultipleProgressbar { - uploadFiles: any[]; - uploadProgresses: any[] = []; - zone: NgZone; - options: Object = { - url: 'http://localhost:10050/upload' - }; - - constructor() { - this.zone = new NgZone({ enableLongStackTrace: false }); } - - handleUpload(data): void { - let id = data.id; - let index = this.findIndex(id); - if (index === -1) { - this.uploadProgresses.push({id: id, percent: 0}); - } - if (this.uploadProgresses[index]) { - this.zone.run(() => { - this.uploadProgresses[index].percent = data.progress.percent; - }); - } - } - - findIndex(id: string): number { - return this.uploadProgresses.findIndex(x => x.id === id); - } - } -```` - -`component.html` -````html -
- - -
- -
- -
-
-
-
Uploading file ({{ progressObj.percent }}%)
-
-
-```` - -### Image preview example and manual upload - -`component.ts` -````typescript -import {Component, NgZone} from '@angular/core'; -import {UPLOAD_DIRECTIVES} from 'ng2-uploader/ng2-uploader'; - -@Component({ - selector: 'image-preview', - templateUrl: 'app/components/image-preview/image-preview.html', - directives: [UPLOAD_DIRECTIVES] -}) -export class ImagePreview { - @ViewChild(NgFileSelect) private fileSelect: NgFileSelect; - previewData = '' - options: Object = { - url: 'http://localhost:10050/upload', - previewUrl: true, - autoUpload: false - }; - - constructor() { - - } - getData(data) { - this.previewData = data; - } - upload() { - this.fileSelect.uploader.uploadFilesInQueue(); - } - - -} -```` - -`component.html` -````html -
-
- - - -```` -### Token-authorized call example - -`component.ts` -````typescript -import {Component} from '@angular/core'; -import {UPLOAD_DIRECTIVES} from 'ng2-uploader/ng2-uploader'; - -@Component({ - selector: 'demo-app', - templateUrl: 'app/demo.html', - directives: [UPLOAD_DIRECTIVES], -}) -export class DemoApp { - uploadFile: any; - options: Object = { - url: 'http://localhost:10050/upload', - withCredentials: true, - authToken: localStorage.getItem('token'), - authTokenPrefix: "Bearer" // required only if different than "Bearer" - - }; - - handleUpload(data): void { - if (data && data.response) { - data = JSON.parse(data.response); - this.uploadFile = data; - } - } -} -```` - -`component.html` -````html - - -
-Response: {{ uploadFile | json }} -
-```` - -### Custom field name example - -You may want to sent file with specific form field name. For that you can use options.fieldName. If not provided then the field will be named "file". - -`component.ts` -````typescript -import {Component} from '@angular/core'; -import {UPLOAD_DIRECTIVES} from 'ng2-uploader/ng2-uploader'; - -@Component({ - selector: 'demo-app', - templateUrl: 'app/demo.html', - directives: [UPLOAD_DIRECTIVES], -}) -export class DemoApp { - uploadFile: any; - options: Object = { - url: 'http://localhost:10050/upload', - fieldName: 'logo' - }; - - handleUpload(data): void { - if (data && data.response) { - data = JSON.parse(data.response); - this.uploadFile = data; - } - } -} -```` - -`component.html` -````html - - -
-Response: {{ uploadFile | json }} -
-```` +``` ### Backend Example Using HapiJS diff --git a/index.ts b/index.ts deleted file mode 100644 index 8cc2af38..00000000 --- a/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ng2-uploader'; \ No newline at end of file diff --git a/ng2-uploader.ts b/ng2-uploader.ts index 9f5ff86b..f2c51aae 100644 --- a/ng2-uploader.ts +++ b/ng2-uploader.ts @@ -1,14 +1,11 @@ -import {Ng2Uploader} from './src/services/ng2-uploader'; -import {NgFileSelect} from './src/directives/ng-file-select'; -import {NgFileDrop} from './src/directives/ng-file-drop'; +import { NgFileSelectDirective } from './src/directives/ng-file-select'; +import { NgFileDropDirective } from './src/directives/ng-file-drop'; export * from './src/services/ng2-uploader'; export * from './src/directives/ng-file-select'; export * from './src/directives/ng-file-drop'; -export default { - directives: [NgFileSelect, NgFileDrop], - providers: [Ng2Uploader] -}; - -export const UPLOAD_DIRECTIVES: [any] = [NgFileSelect, NgFileDrop]; +export const UPLOAD_DIRECTIVES: any[] = [ + NgFileDropDirective, + NgFileSelectDirective +]; diff --git a/package.json b/package.json index c317a103..1131e281 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ng2-uploader", "description": "Angular2 File Uploader", - "version": "0.5.15", + "version": "1.0.0", "license": "MIT", "main": "ng2-uploader.ts", "author": "Jan Kuri ", diff --git a/src/directives/index.ts b/src/directives/index.ts deleted file mode 100644 index 489b5154..00000000 --- a/src/directives/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ng-file-drop'; -export * from './ng-file-select'; diff --git a/src/directives/ng-file-drop.ts b/src/directives/ng-file-drop.ts index 409c4c1a..9413818e 100644 --- a/src/directives/ng-file-drop.ts +++ b/src/directives/ng-file-drop.ts @@ -1,16 +1,24 @@ -import {Directive, ElementRef, EventEmitter} from '@angular/core'; +import { + Directive, + ElementRef, + EventEmitter, + Input, + Output, + HostListener +} from '@angular/core'; import {Ng2Uploader} from '../services/ng2-uploader'; @Directive({ - selector: '[ng-file-drop]', - inputs: ['options: ng-file-drop'], - outputs: ['onUpload', 'onPreviewData'] + selector: '[ngFileDrop]' }) -export class NgFileDrop { +export class NgFileDropDirective { + @Input() options: any; + @Output() onUpload: EventEmitter = new EventEmitter(); + @Output() onPreviewData: EventEmitter = new EventEmitter(); + + files: any[] = []; uploader: Ng2Uploader; - options: any; - onUpload: EventEmitter = new EventEmitter(); - onPreviewData: EventEmitter = new EventEmitter(); + constructor(public el: ElementRef) { this.uploader = new Ng2Uploader(); setTimeout(() => { @@ -19,10 +27,15 @@ export class NgFileDrop { this.uploader._emitter.subscribe((data: any) => { this.onUpload.emit(data); + if (data.done) { + this.files = this.files.filter(f => f.name !== data.originalName); + } }); + this.uploader._previewEmitter.subscribe((data: any) => { this.onPreviewData.emit(data); }); + this.initEvents(); } @@ -31,11 +44,9 @@ export class NgFileDrop { e.stopPropagation(); e.preventDefault(); - let dt = e.dataTransfer; - let files = dt.files; - - if (files.length) { - this.uploader.addFilesToQueue(files); + this.files = Array.from(e.dataTransfer.files); + if (this.files.length) { + this.uploader.addFilesToQueue(this.files); } }, false); @@ -49,4 +60,31 @@ export class NgFileDrop { e.preventDefault(); }, false); } + + filterFilesByExtension(): void { + this.files = this.files.filter(f => { + if (this.options.allowedExtensions.indexOf(f.type) !== -1) { + return true; + } + + let ext: string = f.name.split('.').pop(); + if (this.options.allowedExtensions.indexOf(ext) !== -1 ) { + return true; + } + + return false; + }); + } + + @HostListener('change') onChange(): void { + this.files = Array.from(this.el.nativeElement.files); + + if (this.options.filterExtensions && this.options.allowedExtensions) { + this.filterFilesByExtension(); + } + + if (this.files.length) { + this.uploader.addFilesToQueue(this.files); + } + } } diff --git a/src/directives/ng-file-select.ts b/src/directives/ng-file-select.ts index e9b07b3f..a794070f 100644 --- a/src/directives/ng-file-select.ts +++ b/src/directives/ng-file-select.ts @@ -1,17 +1,24 @@ -import {Directive, ElementRef, EventEmitter} from '@angular/core'; -import {Ng2Uploader} from '../services/ng2-uploader'; +import { + Directive, + ElementRef, + EventEmitter, + Input, + Output, + HostListener +} from '@angular/core'; +import { Ng2Uploader } from '../services/ng2-uploader'; @Directive({ - selector: '[ng-file-select]', - inputs: ['options: ng-file-select'], - outputs: ['onUpload', 'onPreviewData'], - host: { '(change)': 'onFiles()' } + selector: '[ngFileSelect]' }) -export class NgFileSelect { +export class NgFileSelectDirective { + @Input() options: any; + @Output() onUpload: EventEmitter = new EventEmitter(); + @Output() onPreviewData: EventEmitter = new EventEmitter(); + + files: any[] = []; uploader: Ng2Uploader; - options: any; - onUpload: EventEmitter = new EventEmitter(); - onPreviewData: EventEmitter = new EventEmitter(); + constructor(public el: ElementRef) { this.uploader = new Ng2Uploader(); setTimeout(() => { @@ -20,16 +27,39 @@ export class NgFileSelect { this.uploader._emitter.subscribe((data: any) => { this.onUpload.emit(data); + if (data.done) { + this.files = this.files.filter(f => f.name !== data.originalName); + } }); + this.uploader._previewEmitter.subscribe((data: any) => { this.onPreviewData.emit(data); - }) + }); } - onFiles(): void { - let files = this.el.nativeElement.files; - if (files.length) { - this.uploader.addFilesToQueue(files); + filterFilesByExtension(): void { + this.files = this.files.filter(f => { + if (this.options.allowedExtensions.indexOf(f.type) !== -1) { + return true; + } + + let ext: string = f.name.split('.').pop(); + if (this.options.allowedExtensions.indexOf(ext) !== -1 ) { + return true; + } + + return false; + }); + } + + @HostListener('change') onChange(): void { + this.files = Array.from(this.el.nativeElement.files); + if (this.options.filterExtensions && this.options.allowedExtensions) { + this.filterFilesByExtension(); + } + + if (this.files.length) { + this.uploader.addFilesToQueue(this.files); } } } diff --git a/src/services/index.ts b/src/services/index.ts deleted file mode 100644 index 8cc2af38..00000000 --- a/src/services/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ng2-uploader'; \ No newline at end of file diff --git a/src/services/ng2-uploader.ts b/src/services/ng2-uploader.ts index 4766c438..41a02c80 100644 --- a/src/services/ng2-uploader.ts +++ b/src/services/ng2-uploader.ts @@ -1,4 +1,4 @@ -import {Injectable, EventEmitter} from '@angular/core'; +import { EventEmitter } from '@angular/core'; export class UploadedFile { id: string; @@ -11,6 +11,10 @@ export class UploadedFile { done: boolean; error: boolean; abort: boolean; + startTime: number; + endTime: number; + speedAverage: number; + speedAverageHumanized: string; constructor(id: string, originalName: string, size: number) { this.id = id; @@ -19,11 +23,17 @@ export class UploadedFile { this.progress = { loaded: 0, total: 0, - percent: 0 + percent: 0, + speed: 0, + speedHumanized: null }; this.done = false; this.error = false; this.abort = false; + this.startTime = new Date().getTime(); + this.endTime = 0; + this.speedAverage = 0; + this.speedAverageHumanized = null; } setProgres(progress: Object): void { @@ -41,6 +51,10 @@ export class UploadedFile { } onFinished(status: number, statusText: string, response: string): void { + this.endTime = new Date().getTime(); + this.speedAverage = this.size / (this.endTime - this.startTime) * 1000; + this.speedAverage = parseInt(this.speedAverage, 10); + this.speedAverageHumanized = humanizeBytes(this.speedAverage); this.status = status; this.statusText = statusText; this.response = response; @@ -48,51 +62,45 @@ export class UploadedFile { } } -@Injectable() export class Ng2Uploader { url: string; cors: boolean = false; withCredentials: boolean = false; multiple: boolean = false; maxUploads: number = 3; - allowedExtensions: string[] = []; - maxSize: boolean = false; data: Object = {}; - noParams: boolean = true; autoUpload: boolean = true; multipart: boolean = true; method: string = 'POST'; debug: boolean = false; customHeaders: any = {}; encodeHeaders: boolean = true; - authTokenPrefix: string = "Bearer"; + authTokenPrefix: string = 'Bearer'; authToken: string = undefined; - fieldName: string = "file"; + fieldName: string = 'file'; previewUrl: boolean = false; + calculateSpeed: boolean = false; _queue: any[] = []; - _emitter: EventEmitter = new EventEmitter(true); - _previewEmitter: EventEmitter = new EventEmitter(true); - setOptions(options: any): void { + _emitter: EventEmitter = new EventEmitter(); + _previewEmitter: EventEmitter = new EventEmitter(); + setOptions(options: any): void { this.url = options.url != null ? options.url : this.url; this.cors = options.cors != null ? options.cors : this.cors; this.withCredentials = options.withCredentials != null ? options.withCredentials : this.withCredentials; this.multiple = options.multiple != null ? options.multiple : this.multiple; this.maxUploads = options.maxUploads != null ? options.maxUploads : this.maxUploads; - this.allowedExtensions = options.allowedExtensions != null ? options.allowedExtensions : this.allowedExtensions; - this.maxSize = options.maxSize != null ? options.maxSize : this.maxSize; this.data = options.data != null ? options.data : this.data; - this.noParams = options.noParams != null ? options.noParams : this.noParams; this.autoUpload = options.autoUpload != null ? options.autoUpload : this.autoUpload; this.multipart = options.multipart != null ? options.multipart : this.multipart; this.method = options.method != null ? options.method : this.method; - this.debug = options.debug != null ? options.debug : this.debug; this.customHeaders = options.customHeaders != null ? options.customHeaders : this.customHeaders; this.encodeHeaders = options.encodeHeaders != null ? options.encodeHeaders : this.encodeHeaders; this.authTokenPrefix = options.authTokenPrefix != null ? options.authTokenPrefix : this.authTokenPrefix; this.authToken = options.authToken != null ? options.authToken : this.authToken; this.fieldName = options.fieldName != null ? options.fieldName : this.fieldName; this.previewUrl = options.previewUrl != null ? options.previewUrl : this.previewUrl; + this.calculateSpeed = options.calculateSpeed != null ? options.calculateSpeed : this.calculateSpeed; if (!this.multiple) { this.maxUploads = 1; } @@ -110,22 +118,49 @@ export class Ng2Uploader { let form = new FormData(); form.append(this.fieldName, file, file.name); + Object.keys(this.data).forEach(k => { + form.append(k, this.data[k]); + }); + let uploadingFile = new UploadedFile( - this.generateRandomIndex(), - file.name, - file.size + this.generateRandomIndex(), + file.name, + file.size ); let queueIndex = this._queue.indexOf(file); + let time: number = new Date().getTime(); + let load = 0; + let speed = 0; + let speedHumanized: string = null; + xhr.upload.onprogress = (e: ProgressEvent) => { if (e.lengthComputable) { + if (this.calculateSpeed) { + time = new Date().getTime() - time; + load = e.loaded - load; + speed = load / time * 1000; + speed = parseInt(speed, 10); + speedHumanized = humanizeBytes(speed); + } + let percent = Math.round(e.loaded / e.total * 100); - uploadingFile.setProgres({ - total: e.total, - loaded: e.loaded, - percent: percent - }); + if (speed === 0) { + uploadingFile.setProgres({ + total: e.total, + loaded: e.loaded, + percent: percent + }); + } else { + uploadingFile.setProgres({ + total: e.total, + loaded: e.loaded, + percent: percent, + speed: speed, + speedHumanized: speedHumanized + }); + } this._emitter.emit(uploadingFile); } @@ -163,35 +198,36 @@ export class Ng2Uploader { } if (this.authToken) { - xhr.setRequestHeader("Authorization", `${this.authTokenPrefix} ${this.authToken}`); + xhr.setRequestHeader('Authorization', `${this.authTokenPrefix} ${this.authToken}`); } xhr.send(form); } - addFilesToQueue(files: FileList): void { - let index: number, file: File; - - for (index = 0; index < files.length; index++) { - file = files.item(index); + addFilesToQueue(files: File[]): void { + files.forEach((file: File, i: number) => { if (this.isFile(file) && !this.inQueue(file)) { this._queue.push(file); } + }); + + if (this.previewUrl) { + files.forEach(file => this.createFileUrl(file)); } - if(this.previewUrl){ - this.createFileUrl(file) - } + if (this.autoUpload) { this.uploadFilesInQueue(); } } + createFileUrl(file){ - var reader = new FileReader(); + let reader: FileReader = new FileReader(); reader.addEventListener('load', () => { - this._previewEmitter.emit(reader.result) + this._previewEmitter.emit(reader.result); }); reader.readAsDataURL(file); } + removeFileFromQueue(i: number): void { this._queue.splice(i, 1); } @@ -213,15 +249,18 @@ export class Ng2Uploader { return file !== null && (file instanceof Blob || (file.name && file.size)); } - log(msg: any): void { - if (!this.debug) { - return; - } - console.log('[Ng2Uploader]:', msg); - } - generateRandomIndex(): string { return Math.random().toString(36).substring(7); } +} + +function humanizeBytes(bytes): string { + if (bytes === 0) { + return '0 Byte'; + } + let k = 1024; + const sizes: string[] = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; + let i: number = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] + '/s'; }