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
-
-
-
- Choose file
-
-
-
-
-
-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
-
-
-
- Choose files
-
-
-
-
-
-
-
-
-
-
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
-
-
-
-
- Clear
-
-
- Upload
-
-````
-### 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';
}