├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── demo ├── .vscode │ └── settings.json ├── package.json ├── src │ ├── app │ │ ├── app.html │ │ ├── app.module.ts │ │ ├── app.routes.ts │ │ ├── app.ts │ │ └── home │ │ │ ├── home.css │ │ │ ├── home.html │ │ │ └── home.ts │ ├── custom-typings.d.ts │ ├── favicon.ico │ ├── index.html │ ├── main.browser.ts │ ├── polyfills.browser.ts │ ├── test.module.ts │ └── vendor.browser.ts ├── tsconfig.json └── webpack.config.js ├── index.ts ├── package-lock.json ├── package.json ├── src ├── fancy-image-uploader.component.ts ├── fancy-image-uploader.module.ts ├── file-uploader.ts ├── interfaces.ts ├── template.ts ├── uploaded-file.ts └── utils.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | 4 | # dependencies 5 | /node_modules 6 | /demo/node_modules 7 | 8 | # IDEs and editors 9 | /.idea 10 | 11 | # misc 12 | /connect.lock 13 | /coverage/* 14 | /libpeerconnection.log 15 | npm-debug.log 16 | testem.log 17 | /dist 18 | 19 | # e2e 20 | /e2e/*.js 21 | /e2e/*.map 22 | 23 | #System Files 24 | .DS_Store 25 | Thumbs.db 26 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // The number of spaces a tab is equal to. 4 | "editor.tabSize": 2, 5 | // When opening a file, `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents. 6 | "editor.detectIndentation": false, 7 | "typescript.tsdk": "node_modules\\typescript\\lib" 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 ogix 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fancy Image Uploader [![npm version](https://badge.fury.io/js/ng2-fancy-image-uploader.svg)](https://badge.fury.io/js/ng2-fancy-image-uploader) ![Dependencies](https://david-dm.org/ogix/ng2-fancy-image-uploader.svg) 2 | 3 | Angular2 component that uploads selected or dropped image asynchronously with preview. 4 | 5 | ### Demo 6 | See demo here: [demo](https://ogix.github.io/fancy-image-uploader-demo) 7 | 8 | ### Install 9 | ``` 10 | npm install ng2-fancy-image-uploader --save 11 | ``` 12 | ### Usage 13 | 14 | Add image uploader module to your module's ```imports``` 15 | 16 | ```js 17 | import { NgModule } from '@angular/core'; 18 | import { BrowserModule } from '@angular/platform-browser'; 19 | import { AppComponent } from './app'; 20 | 21 | import { FancyImageUploaderModule } from 'ng2-fancy-image-uploader'; 22 | 23 | @NgModule({ 24 | imports: [BrowserModule, FancyImageUploaderModule], 25 | declarations: [AppComponent], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule {} 29 | ``` 30 | 31 | Use it in your component 32 | 33 | ```js 34 | import { Component } from '@angular/core'; 35 | import { FancyImageUploaderOptions, UploadedFile } from 'ng2-fancy-image-uploader'; 36 | 37 | @Component({ 38 | selector: 'example-app', 39 | template: '' 40 | }) 41 | export class AppComponent { 42 | options: FancyImageUploaderOptions = { 43 | thumbnailHeight: 150, 44 | thumbnailWidth: 150, 45 | uploadUrl: 'http://some-server.com/upload', 46 | allowedImageTypes: ['image/png', 'image/jpeg'], 47 | maxImageSize: 3 48 | }; 49 | 50 | onUpload(file: UploadedFile) { 51 | console.log(file.response); 52 | } 53 | } 54 | 55 | ``` 56 | 57 | ### License 58 | 59 | [MIT](https://tldrlegal.com/license/mit-license) © [Olegas Gončarovas](https://github.com/ogix) 60 | -------------------------------------------------------------------------------- /demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // The number of spaces a tab is equal to. 4 | "editor.tabSize": 2, 5 | // When opening a file, `editor.tabSize` and `editor.insertSpaces` will be detected based on the file contents. 6 | "editor.detectIndentation": false, 7 | "typescript.tsdk": "node_modules\\typescript\\lib" 8 | } -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-fancy-image-uploader-demo", 3 | "version": "1.0.0", 4 | "description": "Demo project for ng2-fancy-image-uploader", 5 | "scripts": { 6 | "tsc": "tsc", 7 | "build": "webpack --colors --progress --display-error-details --display-cached", 8 | "watch": "npm run build -- --watch", 9 | "server": "webpack-dev-server --inline --colors --progress --display-error-details --display-cached --port 3000 --content-base src", 10 | "start": "npm run server" 11 | }, 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@types/core-js": "^0.9.32", 15 | "@types/node": "^6.0.38", 16 | "angular2-template-loader": "^0.5.0", 17 | "awesome-typescript-loader": "^2.2.3", 18 | "css-loader": "^0.23.1", 19 | "raw-loader": "^0.5.1", 20 | "to-string-loader": "^1.1.4", 21 | "typescript": "^2.0.2", 22 | "webpack": "2.1.0-beta.22", 23 | "webpack-dev-server": "^1.14.0", 24 | "webpack-merge": "^0.8.4" 25 | }, 26 | "dependencies": { 27 | "@angular/common": "2.0.0-rc.6", 28 | "@angular/compiler": "2.0.0-rc.6", 29 | "@angular/core": "2.0.0-rc.6", 30 | "@angular/forms": "2.0.0-rc.6", 31 | "@angular/http": "2.0.0-rc.6", 32 | "@angular/platform-browser": "2.0.0-rc.6", 33 | "@angular/platform-browser-dynamic": "2.0.0-rc.6", 34 | "@angular/platform-server": "2.0.0-rc.6", 35 | "@angular/router": "3.0.0-rc.2", 36 | "core-js": "^2.4.0", 37 | "ie-shim": "^0.1.0", 38 | "ng2-fancy-image-uploader": "^1.0.2", 39 | "rxjs": "5.0.0-beta.11", 40 | "zone.js": "~0.6.17" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demo/src/app/app.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core' 2 | import {RouterModule} from "@angular/router"; 3 | import {rootRouterConfig} from "./app.routes"; 4 | import {AppComponent} from "./app"; 5 | import {FormsModule} from "@angular/forms"; 6 | import {BrowserModule} from "@angular/platform-browser"; 7 | import {HttpModule} from "@angular/http"; 8 | import {Home} from './home/home'; 9 | import {LocationStrategy, HashLocationStrategy} from '@angular/common'; 10 | import {FancyImageUploaderModule} from 'ng2-fancy-image-uploader'; 11 | 12 | @NgModule({ 13 | declarations: [AppComponent, Home], 14 | imports : [BrowserModule, FormsModule, HttpModule, FancyImageUploaderModule, RouterModule.forRoot(rootRouterConfig)], 15 | providers : [{provide: LocationStrategy, useClass: HashLocationStrategy}], 16 | bootstrap : [AppComponent] 17 | }) 18 | export class AppModule { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /demo/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import {Routes} from '@angular/router'; 2 | import {Home} from './home/home'; 3 | 4 | export const rootRouterConfig: Routes = [ 5 | {path: '', redirectTo: 'home', pathMatch: 'full'}, 6 | {path: 'home', component: Home} 7 | ]; 8 | 9 | -------------------------------------------------------------------------------- /demo/src/app/app.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | 3 | @Component({ 4 | selector : 'app', 5 | templateUrl: './app.html', 6 | }) 7 | export class AppComponent { 8 | } 9 | -------------------------------------------------------------------------------- /demo/src/app/home/home.css: -------------------------------------------------------------------------------- 1 | .header-wrapper { 2 | padding-top: 30px; 3 | } -------------------------------------------------------------------------------- /demo/src/app/home/home.html: -------------------------------------------------------------------------------- 1 |
2 |

Fancy Image Uploader Demo

3 |
4 |
5 |
6 |
7 |

Basic upload example

8 | 9 |

Response: {{response}}

10 |
11 |
12 |
-------------------------------------------------------------------------------- /demo/src/app/home/home.ts: -------------------------------------------------------------------------------- 1 | import {Component} from '@angular/core'; 2 | import {FancyImageUploaderOptions, UploadedFile} from 'ng2-fancy-image-uploader'; 3 | 4 | @Component({ 5 | selector: 'home', 6 | styleUrls: ['./home.css'], 7 | templateUrl: './home.html' 8 | }) 9 | export class Home { 10 | options: FancyImageUploaderOptions = { 11 | thumbnailHeight: 200, 12 | thumbnailWidth: 200, 13 | uploadUrl: 'https://fancy-image-uploader-demo.azurewebsites.net/api/demo/upload', 14 | allowedImageTypes: ['image/png', 'image/jpeg'], 15 | maxImageSize: 3 16 | }; 17 | 18 | response: string; 19 | 20 | onUpload(file: UploadedFile) { 21 | this.response = file.response; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/custom-typings.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Custom Type Definitions 3 | * When including 3rd party modules you also need to include the type definition for the module 4 | * if they don't provide one within the module. You can try to install it with typings 5 | 6 | typings install node --save 7 | 8 | * If you can't find the type definition in the registry we can make an ambient definition in 9 | * this file for now. For example 10 | 11 | declare module "my-module" { 12 | export function doesSomething(value: string): string; 13 | } 14 | 15 | * 16 | * If you're prototying and you will fix the types later you can also declare it as type any 17 | * 18 | 19 | declare var assert: any; 20 | 21 | * 22 | * If you're importing a module that uses Node.js modules which are CommonJS you need to import as 23 | * 24 | 25 | import * as _ from 'lodash' 26 | 27 | * You can include your type definitions in this file until you create one for the typings registry 28 | * see https://github.com/typings/registry 29 | * 30 | */ 31 | -------------------------------------------------------------------------------- /demo/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ogix/ng2-fancy-image-uploader/bc2fd589be3c903a84250c32f9907cc126b85658/demo/src/favicon.ico -------------------------------------------------------------------------------- /demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular2 Fancy Image Uploader Demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | Loading... 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /demo/src/main.browser.ts: -------------------------------------------------------------------------------- 1 | export const __esModule = true 2 | import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; 3 | import {AppModule} from './app/app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule) 6 | .catch(err => console.error(err)); 7 | -------------------------------------------------------------------------------- /demo/src/polyfills.browser.ts: -------------------------------------------------------------------------------- 1 | // Polyfills 2 | 3 | import 'ie-shim'; // Internet Explorer 9 support 4 | 5 | // import 'core-js/es6'; 6 | // Added parts of es6 which are necessary for your project or your browser support requirements. 7 | import 'core-js/es6/symbol'; 8 | import 'core-js/es6/object'; 9 | import 'core-js/es6/function'; 10 | import 'core-js/es6/parse-int'; 11 | import 'core-js/es6/parse-float'; 12 | import 'core-js/es6/number'; 13 | import 'core-js/es6/math'; 14 | import 'core-js/es6/string'; 15 | import 'core-js/es6/date'; 16 | import 'core-js/es6/array'; 17 | import 'core-js/es6/regexp'; 18 | import 'core-js/es6/map'; 19 | import 'core-js/es6/set'; 20 | import 'core-js/es6/weak-map'; 21 | import 'core-js/es6/weak-set'; 22 | import 'core-js/es6/typed'; 23 | import 'core-js/es6/reflect'; 24 | // see issue https://github.com/AngularClass/angular2-webpack-starter/issues/709 25 | // import 'core-js/es6/promise'; 26 | 27 | import 'core-js/es7/reflect'; 28 | import 'zone.js/dist/zone'; 29 | import 'zone.js/dist/long-stack-trace-zone'; 30 | -------------------------------------------------------------------------------- /demo/src/test.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core'; 2 | import {BrowserModule} from '@angular/platform-browser'; 3 | 4 | @NgModule({ 5 | imports: [BrowserModule], 6 | providers: [], 7 | declarations: [], 8 | exports : [BrowserModule] 9 | }) 10 | export class FancyImageUploaderModule {} -------------------------------------------------------------------------------- /demo/src/vendor.browser.ts: -------------------------------------------------------------------------------- 1 | // Vendors 2 | 3 | // Angular 2 4 | import '@angular/platform-browser-dynamic'; 5 | import '@angular/platform-browser'; 6 | import '@angular/core'; 7 | import '@angular/http'; 8 | import '@angular/router'; 9 | 10 | 11 | // RxJS 5 12 | // import 'rxjs/Rx'; 13 | 14 | 15 | // For vendors for example jQuery, Lodash, angular2-jwt import them here 16 | // Also see src/typings.d.ts as you also need to run `typings install x` where `x` is your module -------------------------------------------------------------------------------- /demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "outDir": "dist", 6 | "baseUrl": "./src", 7 | "sourceMap": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "moduleResolution": "node", 11 | "types": [ 12 | "core-js", 13 | "node" 14 | ] 15 | }, 16 | "exclude": [ 17 | "node_modules" 18 | ], 19 | "awesomeTypescriptLoaderOptions": { 20 | "useWebpackText": true 21 | }, 22 | "compileOnSave": false, 23 | "buildOnSave": false, 24 | "atom": { "rewriteTsconfig": false } 25 | } 26 | -------------------------------------------------------------------------------- /demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | 4 | 5 | // Webpack Config 6 | var webpackConfig = { 7 | entry: { 8 | 'polyfills': './src/polyfills.browser.ts', 9 | 'vendor': './src/vendor.browser.ts', 10 | 'main': './src/main.browser.ts', 11 | }, 12 | 13 | output: { 14 | path: './dist', 15 | }, 16 | 17 | plugins: [ 18 | new webpack.optimize.CommonsChunkPlugin({ name: ['main', 'vendor', 'polyfills'], minChunks: Infinity }), 19 | ], 20 | 21 | module: { 22 | loaders: [ 23 | // .ts files for TypeScript 24 | { test: /\.ts$/, loaders: ['awesome-typescript-loader', 'angular2-template-loader'] }, 25 | { test: /\.css$/, loaders: ['to-string-loader', 'css-loader'] }, 26 | { test: /\.html$/, loader: 'raw-loader' } 27 | ] 28 | } 29 | 30 | }; 31 | 32 | 33 | // Our Webpack Defaults 34 | var defaultConfig = { 35 | devtool: 'cheap-module-source-map', 36 | cache: true, 37 | debug: true, 38 | output: { 39 | filename: '[name].bundle.js', 40 | sourceMapFilename: '[name].map', 41 | chunkFilename: '[id].chunk.js' 42 | }, 43 | 44 | resolve: { 45 | root: [ path.join(__dirname, 'src') ], 46 | extensions: ['', '.ts', '.js'] 47 | }, 48 | 49 | // resolveLoader: { 50 | // root: path.join(__dirname, 'node_modules') 51 | // }, 52 | 53 | devServer: { 54 | historyApiFallback: true, 55 | watchOptions: { aggregateTimeout: 300, poll: 1000 } 56 | }, 57 | 58 | node: { 59 | global: 1, 60 | crypto: 'empty', 61 | module: 0, 62 | Buffer: 0, 63 | clearImmediate: 0, 64 | setImmediate: 0 65 | } 66 | }; 67 | 68 | var webpackMerge = require('webpack-merge'); 69 | module.exports = webpackMerge(defaultConfig, webpackConfig); 70 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/fancy-image-uploader.component' 2 | export * from './src/interfaces' 3 | export * from './src/uploaded-file'; 4 | export * from './src/fancy-image-uploader.module'; 5 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-fancy-image-uploader", 3 | "version": "2.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@angular/common": { 8 | "version": "5.0.1", 9 | "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.0.1.tgz", 10 | "integrity": "sha1-QwBas8i4/68Xaq+zuGupMcPkvfk=", 11 | "requires": { 12 | "tslib": "1.8.0" 13 | } 14 | }, 15 | "@angular/compiler": { 16 | "version": "5.0.1", 17 | "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.0.1.tgz", 18 | "integrity": "sha1-f9TH+ku770wUaWL6lGuCczCmyO0=", 19 | "requires": { 20 | "tslib": "1.8.0" 21 | } 22 | }, 23 | "@angular/compiler-cli": { 24 | "version": "5.0.1", 25 | "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-5.0.1.tgz", 26 | "integrity": "sha1-Um3BuzlPsWrZFmAe6pqgDrRLT/8=", 27 | "dev": true, 28 | "requires": { 29 | "chokidar": "1.7.0", 30 | "minimist": "1.2.0", 31 | "reflect-metadata": "0.1.10", 32 | "tsickle": "0.24.1" 33 | } 34 | }, 35 | "@angular/core": { 36 | "version": "5.0.1", 37 | "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.0.1.tgz", 38 | "integrity": "sha1-pKdK/H4gWNMLgmPrbWbarOn0J7o=", 39 | "requires": { 40 | "tslib": "1.8.0" 41 | } 42 | }, 43 | "@angular/forms": { 44 | "version": "5.0.1", 45 | "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.0.1.tgz", 46 | "integrity": "sha1-afMDxME9o8qg3mNDdYg4i2rWKyE=", 47 | "requires": { 48 | "tslib": "1.8.0" 49 | } 50 | }, 51 | "@angular/platform-browser": { 52 | "version": "5.0.1", 53 | "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.0.1.tgz", 54 | "integrity": "sha1-FIld0w7Sow7nuZx2t2R0j0bBqGI=", 55 | "requires": { 56 | "tslib": "1.8.0" 57 | } 58 | }, 59 | "@types/cropperjs": { 60 | "version": "1.1.0", 61 | "resolved": "https://registry.npmjs.org/@types/cropperjs/-/cropperjs-1.1.0.tgz", 62 | "integrity": "sha512-yLsDjda4EfrWBrsNHRiEAUif7xwxc6/QXUGOVqzHCWiJvYvh/U8CttJnDVcA0bj115O42lltrTR2MwVPs6vkfg==" 63 | }, 64 | "@types/node": { 65 | "version": "https://registry.npmjs.org/@types/node/-/node-6.0.85.tgz", 66 | "integrity": "sha1-7AK/5UphBE8r5E8Ts4nGoOjuBa4=", 67 | "dev": true 68 | }, 69 | "anymatch": { 70 | "version": "1.3.2", 71 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", 72 | "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", 73 | "dev": true, 74 | "requires": { 75 | "micromatch": "2.3.11", 76 | "normalize-path": "2.1.1" 77 | } 78 | }, 79 | "arr-diff": { 80 | "version": "2.0.0", 81 | "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", 82 | "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", 83 | "dev": true, 84 | "requires": { 85 | "arr-flatten": "1.1.0" 86 | } 87 | }, 88 | "arr-flatten": { 89 | "version": "1.1.0", 90 | "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", 91 | "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", 92 | "dev": true 93 | }, 94 | "array-unique": { 95 | "version": "0.2.1", 96 | "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", 97 | "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", 98 | "dev": true 99 | }, 100 | "async-each": { 101 | "version": "1.0.1", 102 | "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", 103 | "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", 104 | "dev": true 105 | }, 106 | "balanced-match": { 107 | "version": "1.0.0", 108 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 109 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 110 | "dev": true 111 | }, 112 | "binary-extensions": { 113 | "version": "1.10.0", 114 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.10.0.tgz", 115 | "integrity": "sha1-muuabF6IY4qtFx4Wf1kAq+JINdA=", 116 | "dev": true 117 | }, 118 | "brace-expansion": { 119 | "version": "1.1.8", 120 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 121 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 122 | "dev": true, 123 | "requires": { 124 | "balanced-match": "1.0.0", 125 | "concat-map": "0.0.1" 126 | } 127 | }, 128 | "braces": { 129 | "version": "1.8.5", 130 | "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", 131 | "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", 132 | "dev": true, 133 | "requires": { 134 | "expand-range": "1.8.2", 135 | "preserve": "0.2.0", 136 | "repeat-element": "1.1.2" 137 | } 138 | }, 139 | "chokidar": { 140 | "version": "1.7.0", 141 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", 142 | "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", 143 | "dev": true, 144 | "requires": { 145 | "anymatch": "1.3.2", 146 | "async-each": "1.0.1", 147 | "glob-parent": "2.0.0", 148 | "inherits": "2.0.3", 149 | "is-binary-path": "1.0.1", 150 | "is-glob": "2.0.1", 151 | "path-is-absolute": "1.0.1", 152 | "readdirp": "2.1.0" 153 | } 154 | }, 155 | "concat-map": { 156 | "version": "0.0.1", 157 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 158 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 159 | "dev": true 160 | }, 161 | "core-js": { 162 | "version": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz", 163 | "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=" 164 | }, 165 | "core-util-is": { 166 | "version": "1.0.2", 167 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 168 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 169 | "dev": true 170 | }, 171 | "cropperjs": { 172 | "version": "1.1.3", 173 | "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.1.3.tgz", 174 | "integrity": "sha512-bRdddd35KoPQiTJEX/Pv4mYe6YnNvg4fNsBOZSeBxXt4L3RFhUSfhinc95P6AsbzrHf+ucNftqRF4449Rra6Vw==" 175 | }, 176 | "expand-brackets": { 177 | "version": "0.1.5", 178 | "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", 179 | "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", 180 | "dev": true, 181 | "requires": { 182 | "is-posix-bracket": "0.1.1" 183 | } 184 | }, 185 | "expand-range": { 186 | "version": "1.8.2", 187 | "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", 188 | "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", 189 | "dev": true, 190 | "requires": { 191 | "fill-range": "2.2.3" 192 | } 193 | }, 194 | "extglob": { 195 | "version": "0.3.2", 196 | "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", 197 | "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", 198 | "dev": true, 199 | "requires": { 200 | "is-extglob": "1.0.0" 201 | } 202 | }, 203 | "filename-regex": { 204 | "version": "2.0.1", 205 | "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", 206 | "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", 207 | "dev": true 208 | }, 209 | "fill-range": { 210 | "version": "2.2.3", 211 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", 212 | "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", 213 | "dev": true, 214 | "requires": { 215 | "is-number": "2.1.0", 216 | "isobject": "2.1.0", 217 | "randomatic": "1.1.7", 218 | "repeat-element": "1.1.2", 219 | "repeat-string": "1.6.1" 220 | } 221 | }, 222 | "for-in": { 223 | "version": "1.0.2", 224 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 225 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", 226 | "dev": true 227 | }, 228 | "for-own": { 229 | "version": "0.1.5", 230 | "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", 231 | "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", 232 | "dev": true, 233 | "requires": { 234 | "for-in": "1.0.2" 235 | } 236 | }, 237 | "glob-base": { 238 | "version": "0.3.0", 239 | "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", 240 | "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", 241 | "dev": true, 242 | "requires": { 243 | "glob-parent": "2.0.0", 244 | "is-glob": "2.0.1" 245 | } 246 | }, 247 | "glob-parent": { 248 | "version": "2.0.0", 249 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", 250 | "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", 251 | "dev": true, 252 | "requires": { 253 | "is-glob": "2.0.1" 254 | } 255 | }, 256 | "graceful-fs": { 257 | "version": "4.1.11", 258 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 259 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", 260 | "dev": true 261 | }, 262 | "inherits": { 263 | "version": "2.0.3", 264 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 265 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 266 | "dev": true 267 | }, 268 | "is-binary-path": { 269 | "version": "1.0.1", 270 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", 271 | "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", 272 | "dev": true, 273 | "requires": { 274 | "binary-extensions": "1.10.0" 275 | } 276 | }, 277 | "is-buffer": { 278 | "version": "1.1.6", 279 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 280 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", 281 | "dev": true 282 | }, 283 | "is-dotfile": { 284 | "version": "1.0.3", 285 | "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", 286 | "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", 287 | "dev": true 288 | }, 289 | "is-equal-shallow": { 290 | "version": "0.1.3", 291 | "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", 292 | "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", 293 | "dev": true, 294 | "requires": { 295 | "is-primitive": "2.0.0" 296 | } 297 | }, 298 | "is-extendable": { 299 | "version": "0.1.1", 300 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 301 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", 302 | "dev": true 303 | }, 304 | "is-extglob": { 305 | "version": "1.0.0", 306 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", 307 | "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", 308 | "dev": true 309 | }, 310 | "is-glob": { 311 | "version": "2.0.1", 312 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", 313 | "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", 314 | "dev": true, 315 | "requires": { 316 | "is-extglob": "1.0.0" 317 | } 318 | }, 319 | "is-number": { 320 | "version": "2.1.0", 321 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", 322 | "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", 323 | "dev": true, 324 | "requires": { 325 | "kind-of": "3.2.2" 326 | } 327 | }, 328 | "is-posix-bracket": { 329 | "version": "0.1.1", 330 | "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", 331 | "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", 332 | "dev": true 333 | }, 334 | "is-primitive": { 335 | "version": "2.0.0", 336 | "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", 337 | "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", 338 | "dev": true 339 | }, 340 | "isarray": { 341 | "version": "1.0.0", 342 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 343 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 344 | "dev": true 345 | }, 346 | "isobject": { 347 | "version": "2.1.0", 348 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", 349 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", 350 | "dev": true, 351 | "requires": { 352 | "isarray": "1.0.0" 353 | } 354 | }, 355 | "kind-of": { 356 | "version": "3.2.2", 357 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 358 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 359 | "dev": true, 360 | "requires": { 361 | "is-buffer": "1.1.6" 362 | } 363 | }, 364 | "micromatch": { 365 | "version": "2.3.11", 366 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", 367 | "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", 368 | "dev": true, 369 | "requires": { 370 | "arr-diff": "2.0.0", 371 | "array-unique": "0.2.1", 372 | "braces": "1.8.5", 373 | "expand-brackets": "0.1.5", 374 | "extglob": "0.3.2", 375 | "filename-regex": "2.0.1", 376 | "is-extglob": "1.0.0", 377 | "is-glob": "2.0.1", 378 | "kind-of": "3.2.2", 379 | "normalize-path": "2.1.1", 380 | "object.omit": "2.0.1", 381 | "parse-glob": "3.0.4", 382 | "regex-cache": "0.4.4" 383 | } 384 | }, 385 | "minimatch": { 386 | "version": "3.0.4", 387 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 388 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 389 | "dev": true, 390 | "requires": { 391 | "brace-expansion": "1.1.8" 392 | } 393 | }, 394 | "minimist": { 395 | "version": "1.2.0", 396 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 397 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 398 | "dev": true 399 | }, 400 | "mkdirp": { 401 | "version": "0.5.1", 402 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 403 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 404 | "dev": true, 405 | "requires": { 406 | "minimist": "0.0.8" 407 | }, 408 | "dependencies": { 409 | "minimist": { 410 | "version": "0.0.8", 411 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 412 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 413 | "dev": true 414 | } 415 | } 416 | }, 417 | "normalize-path": { 418 | "version": "2.1.1", 419 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", 420 | "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", 421 | "dev": true, 422 | "requires": { 423 | "remove-trailing-separator": "1.1.0" 424 | } 425 | }, 426 | "object.omit": { 427 | "version": "2.0.1", 428 | "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", 429 | "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", 430 | "dev": true, 431 | "requires": { 432 | "for-own": "0.1.5", 433 | "is-extendable": "0.1.1" 434 | } 435 | }, 436 | "parse-glob": { 437 | "version": "3.0.4", 438 | "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", 439 | "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", 440 | "dev": true, 441 | "requires": { 442 | "glob-base": "0.3.0", 443 | "is-dotfile": "1.0.3", 444 | "is-extglob": "1.0.0", 445 | "is-glob": "2.0.1" 446 | } 447 | }, 448 | "path-is-absolute": { 449 | "version": "1.0.1", 450 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 451 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 452 | "dev": true 453 | }, 454 | "preserve": { 455 | "version": "0.2.0", 456 | "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", 457 | "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", 458 | "dev": true 459 | }, 460 | "process-nextick-args": { 461 | "version": "1.0.7", 462 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 463 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", 464 | "dev": true 465 | }, 466 | "randomatic": { 467 | "version": "1.1.7", 468 | "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", 469 | "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", 470 | "dev": true, 471 | "requires": { 472 | "is-number": "3.0.0", 473 | "kind-of": "4.0.0" 474 | }, 475 | "dependencies": { 476 | "is-number": { 477 | "version": "3.0.0", 478 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", 479 | "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", 480 | "dev": true, 481 | "requires": { 482 | "kind-of": "3.2.2" 483 | }, 484 | "dependencies": { 485 | "kind-of": { 486 | "version": "3.2.2", 487 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 488 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 489 | "dev": true, 490 | "requires": { 491 | "is-buffer": "1.1.6" 492 | } 493 | } 494 | } 495 | }, 496 | "kind-of": { 497 | "version": "4.0.0", 498 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", 499 | "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", 500 | "dev": true, 501 | "requires": { 502 | "is-buffer": "1.1.6" 503 | } 504 | } 505 | } 506 | }, 507 | "readable-stream": { 508 | "version": "2.3.3", 509 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 510 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 511 | "dev": true, 512 | "requires": { 513 | "core-util-is": "1.0.2", 514 | "inherits": "2.0.3", 515 | "isarray": "1.0.0", 516 | "process-nextick-args": "1.0.7", 517 | "safe-buffer": "5.1.1", 518 | "string_decoder": "1.0.3", 519 | "util-deprecate": "1.0.2" 520 | } 521 | }, 522 | "readdirp": { 523 | "version": "2.1.0", 524 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", 525 | "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", 526 | "dev": true, 527 | "requires": { 528 | "graceful-fs": "4.1.11", 529 | "minimatch": "3.0.4", 530 | "readable-stream": "2.3.3", 531 | "set-immediate-shim": "1.0.1" 532 | } 533 | }, 534 | "reflect-metadata": { 535 | "version": "0.1.10", 536 | "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.10.tgz", 537 | "integrity": "sha1-tPg3BEFqytiZiMmxVjXUfgO5NEo=", 538 | "dev": true 539 | }, 540 | "regex-cache": { 541 | "version": "0.4.4", 542 | "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", 543 | "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", 544 | "dev": true, 545 | "requires": { 546 | "is-equal-shallow": "0.1.3" 547 | } 548 | }, 549 | "remove-trailing-separator": { 550 | "version": "1.1.0", 551 | "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", 552 | "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", 553 | "dev": true 554 | }, 555 | "repeat-element": { 556 | "version": "1.1.2", 557 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", 558 | "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", 559 | "dev": true 560 | }, 561 | "repeat-string": { 562 | "version": "1.6.1", 563 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 564 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", 565 | "dev": true 566 | }, 567 | "rxjs": { 568 | "version": "5.5.2", 569 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz", 570 | "integrity": "sha512-oRYoIKWBU3Ic37fLA5VJu31VqQO4bWubRntcHSJ+cwaDQBwdnZ9x4zmhJfm/nFQ2E82/I4loSioHnACamrKGgA==", 571 | "requires": { 572 | "symbol-observable": "1.0.4" 573 | } 574 | }, 575 | "safe-buffer": { 576 | "version": "5.1.1", 577 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 578 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", 579 | "dev": true 580 | }, 581 | "set-immediate-shim": { 582 | "version": "1.0.1", 583 | "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", 584 | "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", 585 | "dev": true 586 | }, 587 | "source-map": { 588 | "version": "0.5.7", 589 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 590 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 591 | "dev": true 592 | }, 593 | "source-map-support": { 594 | "version": "0.4.18", 595 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", 596 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", 597 | "dev": true, 598 | "requires": { 599 | "source-map": "0.5.7" 600 | } 601 | }, 602 | "string_decoder": { 603 | "version": "1.0.3", 604 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 605 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 606 | "dev": true, 607 | "requires": { 608 | "safe-buffer": "5.1.1" 609 | } 610 | }, 611 | "symbol-observable": { 612 | "version": "1.0.4", 613 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", 614 | "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" 615 | }, 616 | "tsickle": { 617 | "version": "0.24.1", 618 | "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.24.1.tgz", 619 | "integrity": "sha512-XloFQZhVhgjpQsi3u2ORNRJvuID5sflOg6HfP093IqAbhE1+fIUXznULpdDwHgG4p+v8w78KdHruQtkWUKx5AQ==", 620 | "dev": true, 621 | "requires": { 622 | "minimist": "1.2.0", 623 | "mkdirp": "0.5.1", 624 | "source-map": "0.5.7", 625 | "source-map-support": "0.4.18" 626 | } 627 | }, 628 | "tslib": { 629 | "version": "1.8.0", 630 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.8.0.tgz", 631 | "integrity": "sha512-ymKWWZJST0/CkgduC2qkzjMOWr4bouhuURNXCn/inEX0L57BnRG6FhX76o7FOnsjHazCjfU2LKeSrlS2sIKQJg==" 632 | }, 633 | "typescript": { 634 | "version": "2.4.2", 635 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz", 636 | "integrity": "sha1-+DlfhdRZJ2BnyYiqQYN6j4KHCEQ=", 637 | "dev": true 638 | }, 639 | "util-deprecate": { 640 | "version": "1.0.2", 641 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 642 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", 643 | "dev": true 644 | }, 645 | "zone.js": { 646 | "version": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.16.tgz", 647 | "integrity": "sha1-rDG2xBj4jA+Ritas2KQCrKkxOrs=" 648 | } 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng2-fancy-image-uploader", 3 | "version": "2.0.1", 4 | "description": "Angular2 asynchronous image uploader with preview", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "directories": { 8 | "example": "example" 9 | }, 10 | "scripts": { 11 | "ngc": "ngc", 12 | "test": "echo \"Error: no test specified\" && exit 1", 13 | "build": "npm run ngc", 14 | "prepublish": "npm run build" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/ogix/ng2-fancy-image-uploader.git" 19 | }, 20 | "keywords": [ 21 | "angular2", 22 | "ng2", 23 | "img", 24 | "image", 25 | "fancy", 26 | "uploader" 27 | ], 28 | "author": "Olegas Gončarovas ", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/ogix/ng2-fancy-image-uploader/issues" 32 | }, 33 | "homepage": "https://github.com/ogix/ng2-fancy-image-uploader#readme", 34 | "peerDependencies": { 35 | "@angular/common": "^5.0.0", 36 | "@angular/core": "^5.0.0", 37 | "@angular/forms": "^5.0.0", 38 | "@angular/platform-browser": "^5.0.0" 39 | }, 40 | "dependencies": { 41 | "@angular/common": "^5.0.0", 42 | "@angular/compiler": "^5.0.0", 43 | "@angular/core": "^5.0.0", 44 | "@angular/forms": "^5.0.0", 45 | "@angular/platform-browser": "^5.0.0", 46 | "@types/cropperjs": "1.1.0", 47 | "core-js": "^2.4.1", 48 | "cropperjs": "^1.1.3", 49 | "rxjs": "^5.5.2", 50 | "zone.js": "^0.8.4" 51 | }, 52 | "devDependencies": { 53 | "@angular/compiler-cli": "^5.0.0", 54 | "@types/node": "~6.0.60", 55 | "typescript": "~2.4.0" 56 | }, 57 | "files": [ 58 | "dist" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /src/fancy-image-uploader.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, OnDestroy, AfterViewChecked, ViewChild, ElementRef, Renderer, Input, Output, EventEmitter, ChangeDetectorRef, forwardRef} from '@angular/core'; 2 | import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; 3 | import {FancyImageUploaderOptions, ImageResult, ResizeOptions} from './interfaces'; 4 | import {createImage, resizeImage} from './utils'; 5 | import {FileUploader} from './file-uploader'; 6 | import {UploadedFile} from './uploaded-file'; 7 | import 'rxjs/add/operator/filter'; 8 | import * as Cropper from 'cropperjs'; 9 | import {CropOptions} from './interfaces'; 10 | import {cssTemplate, htmlTemplate} from './template'; 11 | 12 | export enum Status { 13 | NotSelected, 14 | Selected, 15 | Uploading, 16 | Loading, 17 | Loaded, 18 | Error 19 | } 20 | 21 | @Component({ 22 | selector: 'fancy-image-uploader', 23 | template: htmlTemplate, 24 | styles: [cssTemplate], 25 | host: { 26 | '[style.width]': 'thumbnailWidth + "px"', 27 | '[style.height]': 'thumbnailHeight + "px"', 28 | '(drop)': 'drop($event)', 29 | '(dragenter)': 'dragenter($event)', 30 | '(dragover)': 'dragover($event)', 31 | '(dragleave)': 'dragleave($event)', 32 | }, 33 | providers: [ 34 | { 35 | provide: NG_VALUE_ACCESSOR, 36 | useExisting: forwardRef(() => FancyImageUploaderComponent), 37 | multi: true 38 | } 39 | ] 40 | }) 41 | export class FancyImageUploaderComponent implements OnInit, OnDestroy, AfterViewChecked, ControlValueAccessor { 42 | statusEnum = Status; 43 | _status: Status = Status.NotSelected; 44 | 45 | thumbnailWidth: number = 150; 46 | thumbnailHeight: number = 150; 47 | _imageThumbnail: any; 48 | _errorMessage: string; 49 | progress: number; 50 | propagateChange = (_: any) => {}; 51 | origImageWidth: number; 52 | orgiImageHeight: number; 53 | 54 | cropper: Cropper = undefined; 55 | fileToUpload: File; 56 | 57 | @ViewChild('imageElement') imageElement: ElementRef; 58 | @ViewChild('fileInput') fileInputElement: ElementRef; 59 | @ViewChild('dragOverlay') dragOverlayElement: ElementRef; 60 | @Input() options: FancyImageUploaderOptions; 61 | @Output() onUpload: EventEmitter = new EventEmitter(); 62 | @Output() onStatusChange: EventEmitter = new EventEmitter(); 63 | 64 | constructor( 65 | private renderer: Renderer, 66 | private uploader: FileUploader, 67 | private changeDetector: ChangeDetectorRef) { } 68 | 69 | get imageThumbnail() { 70 | return this._imageThumbnail; 71 | } 72 | 73 | set imageThumbnail(value) { 74 | this._imageThumbnail = value; 75 | this.propagateChange(this._imageThumbnail); 76 | 77 | if (value !== undefined) { 78 | this.status = Status.Selected 79 | } else { 80 | this.status = Status.NotSelected; 81 | } 82 | } 83 | 84 | get errorMessage() { 85 | return this._errorMessage; 86 | } 87 | 88 | set errorMessage(value) { 89 | this._errorMessage = value; 90 | 91 | if (value) { 92 | this.status = Status.Error; 93 | } else { 94 | this.status = Status.NotSelected; 95 | } 96 | } 97 | 98 | get status() { 99 | return this._status; 100 | } 101 | 102 | set status(value) { 103 | this._status = value; 104 | this.onStatusChange.emit(value); 105 | } 106 | 107 | writeValue(value: any) { 108 | if (value) { 109 | this.loadAndResize(value); 110 | } else { 111 | this._imageThumbnail = undefined; 112 | this.status = Status.NotSelected; 113 | } 114 | } 115 | 116 | registerOnChange(fn: (_: any) => void) { 117 | this.propagateChange = fn; 118 | } 119 | 120 | registerOnTouched() {} 121 | 122 | ngOnInit() { 123 | if (this.options) { 124 | if (this.options.thumbnailWidth) { 125 | this.thumbnailWidth = this.options.thumbnailWidth; 126 | } 127 | if (this.options.thumbnailHeight) { 128 | this.thumbnailHeight = this.options.thumbnailHeight; 129 | } 130 | if (this.options.resizeOnLoad === undefined) { 131 | this.options.resizeOnLoad = true; 132 | } 133 | if (this.options.autoUpload === undefined) { 134 | this.options.autoUpload = true; 135 | } 136 | if (this.options.cropEnabled === undefined) { 137 | this.options.cropEnabled = false; 138 | } 139 | 140 | if (this.options.autoUpload && this.options.cropEnabled) { 141 | throw new Error('autoUpload and cropEnabled cannot be enabled simultaneously'); 142 | } 143 | } 144 | } 145 | 146 | ngAfterViewChecked() { 147 | if (this.options && this.options.cropEnabled && this.imageElement && this.fileToUpload && !this.cropper) { 148 | this.cropper = new Cropper(this.imageElement.nativeElement, { 149 | viewMode: 1, 150 | aspectRatio: this.options.cropAspectRatio ? this.options.cropAspectRatio : null 151 | }); 152 | } 153 | } 154 | 155 | ngOnDestroy() { 156 | if (this.cropper) { 157 | this.cropper.destroy(); 158 | this.cropper = null; 159 | } 160 | } 161 | 162 | loadAndResize(url: string) { 163 | this.status = Status.Loading; 164 | 165 | this.uploader.getFile(url, this.options).subscribe(file => { 166 | if (this.options.resizeOnLoad) { 167 | // thumbnail 168 | let result: ImageResult = { 169 | file: file, 170 | url: URL.createObjectURL(file) 171 | }; 172 | 173 | this.resize(result).then(r => { 174 | this._imageThumbnail = r.resized.dataURL; 175 | this.status = Status.Loaded; 176 | }); 177 | } else { 178 | let result: ImageResult = { 179 | file: null, 180 | url: null 181 | }; 182 | 183 | this.fileToDataURL(file, result).then(r => { 184 | this._imageThumbnail = r.dataURL; 185 | this.status = Status.Loaded; 186 | }); 187 | } 188 | }, error => { 189 | this.errorMessage = error || 'Error while getting an image'; 190 | }); 191 | } 192 | 193 | onImageClicked() { 194 | this.renderer.invokeElementMethod(this.fileInputElement.nativeElement, 'click'); 195 | } 196 | 197 | onFileChanged() { 198 | let file = this.fileInputElement.nativeElement.files[0]; 199 | if (!file) return; 200 | 201 | this.validateAndUpload(file); 202 | } 203 | 204 | validateAndUpload(file: File) { 205 | this.propagateChange(null); 206 | 207 | if (this.options && this.options.allowedImageTypes) { 208 | if (!this.options.allowedImageTypes.some(allowedType => file.type === allowedType)) { 209 | this.errorMessage = 'Only these image types are allowed: ' + this.options.allowedImageTypes.join(', '); 210 | return; 211 | } 212 | } 213 | 214 | if (this.options && this.options.maxImageSize) { 215 | if (file.size > this.options.maxImageSize * 1024 * 1024) { 216 | this.errorMessage = `Image must not be larger than ${this.options.maxImageSize} MB`; 217 | return; 218 | } 219 | } 220 | 221 | this.fileToUpload = file; 222 | 223 | if (this.options && this.options.autoUpload) { 224 | this.upload(); 225 | } 226 | 227 | // thumbnail 228 | let result: ImageResult = { 229 | file: file, 230 | url: URL.createObjectURL(file) 231 | }; 232 | 233 | this.resize(result).then(r => { 234 | this._imageThumbnail = r.resized.dataURL; 235 | this.origImageWidth = r.width; 236 | this.orgiImageHeight = r.height; 237 | 238 | if (this.options && !this.options.autoUpload) { 239 | this.status = Status.Selected; 240 | } 241 | }); 242 | } 243 | 244 | upload() { 245 | this.progress = 0; 246 | this.status = Status.Uploading; 247 | 248 | let cropOptions: CropOptions = undefined; 249 | 250 | if (this.cropper) { 251 | let scale = this.origImageWidth / this.cropper.getImageData().naturalWidth; 252 | let cropData = this.cropper.getData(); 253 | 254 | cropOptions = { 255 | x: Math.round(cropData.x * scale), 256 | y: Math.round(cropData.y * scale), 257 | width: Math.round(cropData.width * scale), 258 | height: Math.round(cropData.height * scale) 259 | }; 260 | } 261 | 262 | let id = this.uploader.uploadFile(this.fileToUpload, this.options, cropOptions); 263 | 264 | // file progress 265 | let sub = this.uploader.fileProgress$.filter(file => file.id === id).subscribe(file => { 266 | this.progress = file.progress; 267 | 268 | if (file.error) { 269 | if (file.status || file.statusText) { 270 | this.errorMessage = `${file.status}: ${file.statusText}`; 271 | } else { 272 | this.errorMessage = 'Error while uploading' 273 | } 274 | // on some upload errors change detection does not work, so we are forcing manually 275 | this.changeDetector.detectChanges(); 276 | } 277 | 278 | if (file.done) { 279 | // notify that value was changed only when image was uploaded and no error 280 | if (!file.error) { 281 | this.propagateChange(this._imageThumbnail); 282 | this.status = Status.Selected; 283 | this.fileToUpload = undefined; 284 | } 285 | this.onUpload.emit(file); 286 | sub.unsubscribe(); 287 | } 288 | }); 289 | } 290 | 291 | removeImage() { 292 | this.fileInputElement.nativeElement.value = null; 293 | this.imageThumbnail = undefined; 294 | 295 | if (this.cropper) { 296 | this.cropper.destroy(); 297 | this.cropper = null; 298 | } 299 | } 300 | 301 | dismissError() { 302 | this.errorMessage = undefined; 303 | this.removeImage(); 304 | } 305 | 306 | drop(e: DragEvent) { 307 | e.preventDefault(); 308 | e.stopPropagation(); 309 | 310 | if (!e.dataTransfer || !e.dataTransfer.files.length) { 311 | return; 312 | } 313 | 314 | this.validateAndUpload(e.dataTransfer.files[0]); 315 | this.updateDragOverlayStyles(false); 316 | } 317 | 318 | dragenter(e: DragEvent) { 319 | e.preventDefault(); 320 | e.stopPropagation(); 321 | } 322 | 323 | dragover(e: DragEvent) { 324 | e.preventDefault(); 325 | e.stopPropagation(); 326 | this.updateDragOverlayStyles(true); 327 | } 328 | 329 | dragleave(e: DragEvent) { 330 | e.preventDefault(); 331 | e.stopPropagation(); 332 | this.updateDragOverlayStyles(false); 333 | } 334 | 335 | private updateDragOverlayStyles(isDragOver: boolean) { 336 | // TODO: find a way that does not trigger dragleave when displaying overlay 337 | // if (isDragOver) { 338 | // this.renderer.setElementStyle(this.dragOverlayElement.nativeElement, 'display', 'block'); 339 | // } else { 340 | // this.renderer.setElementStyle(this.dragOverlayElement.nativeElement, 'display', 'none'); 341 | // } 342 | } 343 | 344 | private resize(result: ImageResult): Promise { 345 | let resizeOptions: ResizeOptions = { 346 | resizeHeight: this.thumbnailHeight, 347 | resizeWidth: this.thumbnailWidth, 348 | resizeType: result.file.type, 349 | resizeMode: this.options.thumbnailResizeMode 350 | }; 351 | 352 | return new Promise((resolve) => { 353 | createImage(result.url, image => { 354 | let dataUrl = resizeImage(image, resizeOptions); 355 | 356 | result.width = image.width; 357 | result.height = image.height; 358 | result.resized = { 359 | dataURL: dataUrl, 360 | type: this.getType(dataUrl) 361 | }; 362 | 363 | resolve(result); 364 | }); 365 | }); 366 | } 367 | 368 | private getType(dataUrl: string) { 369 | return dataUrl.match(/:(.+\/.+;)/)[1]; 370 | } 371 | 372 | private fileToDataURL(file: File, result: ImageResult): Promise { 373 | return new Promise((resolve) => { 374 | let reader = new FileReader(); 375 | reader.onload = function (e) { 376 | result.dataURL = reader.result; 377 | resolve(result); 378 | }; 379 | reader.readAsDataURL(file); 380 | }); 381 | } 382 | } -------------------------------------------------------------------------------- /src/fancy-image-uploader.module.ts: -------------------------------------------------------------------------------- 1 | import {NgModule} from '@angular/core' 2 | import {CommonModule} from '@angular/common'; 3 | import {FancyImageUploaderComponent} from './fancy-image-uploader.component'; 4 | import {FileUploader} from './file-uploader'; 5 | 6 | @NgModule({ 7 | imports: [CommonModule], 8 | providers: [FileUploader], 9 | declarations: [FancyImageUploaderComponent], 10 | exports : [FancyImageUploaderComponent] 11 | }) 12 | export class FancyImageUploaderModule {} 13 | -------------------------------------------------------------------------------- /src/file-uploader.ts: -------------------------------------------------------------------------------- 1 | import { Observer } from 'rxjs/Rx'; 2 | import { Injectable } from '@angular/core'; 3 | import { Subject } from 'rxjs/Subject'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import { UploadedFile } from './uploaded-file'; 6 | import { FileUploaderOptions, CropOptions } from './interfaces'; 7 | 8 | @Injectable() 9 | export class FileUploader { 10 | private _fileProgress$: Subject; 11 | 12 | constructor() { 13 | this._fileProgress$ = >new Subject(); 14 | } 15 | 16 | get fileProgress$() { 17 | return this._fileProgress$.asObservable(); 18 | } 19 | 20 | uploadFile(file: File, options: FileUploaderOptions, cropOptions?: CropOptions): string { 21 | this.setDefaults(options); 22 | let xhr = new XMLHttpRequest(); 23 | let form = new FormData(); 24 | 25 | form.append(options.fieldName, file, file.name); 26 | 27 | if (cropOptions) { 28 | form.append('X', cropOptions.x.toString()); 29 | form.append('Y', cropOptions.y.toString()); 30 | form.append('Width', cropOptions.width.toString()); 31 | form.append('Height', cropOptions.height.toString()); 32 | } 33 | 34 | let uploadingFile = new UploadedFile( 35 | this.generateRandomIndex(), 36 | file.name, 37 | file.size 38 | ); 39 | 40 | xhr.upload.onprogress = (e: ProgressEvent) => { 41 | if (e.lengthComputable) { 42 | let percent = Math.round(e.loaded / e.total * 100); 43 | uploadingFile.progress = percent; 44 | this._fileProgress$.next(uploadingFile); 45 | } 46 | }; 47 | 48 | xhr.upload.onabort = (e: Event) => { 49 | uploadingFile.setAbort(); 50 | this._fileProgress$.next(uploadingFile); 51 | }; 52 | 53 | xhr.upload.onerror = (e: Event) => { 54 | uploadingFile.setError(); 55 | this._fileProgress$.next(uploadingFile); 56 | }; 57 | 58 | xhr.onload = () => { 59 | let success = this.isSuccessCode(xhr.status); 60 | 61 | if (!success) { 62 | uploadingFile.setError(); 63 | } 64 | 65 | uploadingFile.onFinished( 66 | xhr.status, 67 | xhr.statusText, 68 | xhr.response 69 | ); 70 | 71 | this._fileProgress$.next(uploadingFile); 72 | } 73 | 74 | xhr.open(options.httpMethod, options.uploadUrl, true); 75 | xhr.withCredentials = options.withCredentials; 76 | 77 | if (options.customHeaders) { 78 | Object.keys(options.customHeaders).forEach((key) => { 79 | xhr.setRequestHeader(key, options.customHeaders[key]); 80 | }); 81 | } 82 | 83 | if (options.authToken) { 84 | xhr.setRequestHeader("Authorization", `${options.authTokenPrefix} ${options.authToken}`); 85 | } 86 | 87 | xhr.send(form); 88 | return uploadingFile.id; 89 | } 90 | 91 | getFile(url: string, options: { authToken?: string, authTokenPrefix?: string }): Observable { 92 | return Observable.create((observer: Observer) => { 93 | let xhr = new XMLHttpRequest(); 94 | xhr.open('GET', url, true); 95 | xhr.responseType = 'blob'; 96 | 97 | xhr.onload = () => { 98 | let success = this.isSuccessCode(xhr.status); 99 | 100 | if (!success) { 101 | observer.error(xhr.status); 102 | observer.complete(); 103 | } else { 104 | let contentType = xhr.getResponseHeader('Content-Type'); 105 | let blob = new File([xhr.response], 'filename', { type: contentType }); 106 | 107 | if (blob.size > 0) { 108 | observer.next(blob); 109 | observer.complete(); 110 | } else { 111 | observer.error('No image'); 112 | observer.complete(); 113 | } 114 | } 115 | }; 116 | 117 | xhr.onerror = (e) => { 118 | observer.error(xhr.status); 119 | observer.complete(); 120 | }; 121 | 122 | if (options.authToken) { 123 | xhr.setRequestHeader("Authorization", `${options.authTokenPrefix} ${options.authToken}`); 124 | } 125 | 126 | xhr.send(); 127 | }); 128 | } 129 | 130 | private setDefaults(options: FileUploaderOptions) { 131 | options.withCredentials = options.withCredentials || false; 132 | options.httpMethod = options.httpMethod || 'POST'; 133 | options.authTokenPrefix = options.authTokenPrefix || 'Bearer'; 134 | options.fieldName = options.fieldName || 'file'; 135 | } 136 | 137 | private isSuccessCode(status: number): boolean { 138 | return (status >= 200 && status < 300) || status === 304; 139 | } 140 | 141 | private generateRandomIndex(): string { 142 | return Math.random().toString(36).substring(7); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface FileUploaderOptions { 2 | uploadUrl: string; 3 | httpMethod?: string; 4 | withCredentials?: boolean; 5 | customHeaders?: any; 6 | fieldName?: string; 7 | authToken?: string; 8 | authTokenPrefix?: string; 9 | } 10 | 11 | export interface FancyImageUploaderOptions extends FileUploaderOptions { 12 | thumbnailHeight?: number; 13 | thumbnailWidth?: number; 14 | thumbnailResizeMode?: string; 15 | allowedImageTypes?: string[]; 16 | maxImageSize?: number; 17 | resizeOnLoad?: boolean; 18 | autoUpload?: boolean; 19 | cropEnabled?: boolean; 20 | cropAspectRatio?: number; 21 | } 22 | 23 | export interface ImageResult { 24 | file: File; 25 | url: string; 26 | dataURL?: string; 27 | width?: number; 28 | height?: number; 29 | resized?: { 30 | dataURL: string; 31 | type: string; 32 | } 33 | } 34 | 35 | export interface ResizeOptions { 36 | resizeHeight?: number; 37 | resizeWidth?: number; 38 | resizeQuality?: number; 39 | resizeType?: string; 40 | resizeMode?: string; 41 | } 42 | 43 | export interface CropOptions { 44 | x: number; 45 | y: number; 46 | width: number; 47 | height: number; 48 | } 49 | -------------------------------------------------------------------------------- /src/template.ts: -------------------------------------------------------------------------------- 1 | export const htmlTemplate = 2 | `
3 |
4 | 5 |
6 | 13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |

{{errorMessage}}

56 |
57 | 58 |
59 |
60 |
61 | 62 | 63 |
64 |
`; 65 | 66 | export const cssTemplate = 67 | `:host { 68 | display: block; 69 | } 70 | 71 | .match-parent { 72 | width: 100%; 73 | height: 100%; 74 | } 75 | 76 | .add-image-btn { 77 | width: 100%; 78 | height: 100%; 79 | font-weight: bold; 80 | opacity: 0.5; 81 | border: 0; 82 | } 83 | 84 | .add-image-btn:hover { 85 | opacity: .7; 86 | cursor: pointer; 87 | background-color: #ddd; 88 | box-shadow: inset 0 0 5px rgba(0,0,0,0.3); 89 | } 90 | 91 | .add-image-btn .plus { 92 | font-size: 30px; 93 | font-weight: normal; 94 | margin-bottom: 5px; 95 | margin-top: 5px; 96 | } 97 | 98 | img { 99 | cursor: pointer; 100 | position: absolute; 101 | top: 50%; 102 | left: 50%; 103 | margin-right: -50%; 104 | transform: translate(-50%, -50%) 105 | } 106 | 107 | .image-container { 108 | width: 100%; 109 | height: 100%; 110 | position: relative; 111 | display: inline-block; 112 | background-color: #f1f1f1; 113 | box-shadow: inset 0 0 5px rgba(0,0,0,0.2); 114 | } 115 | 116 | .remove { 117 | position: absolute; 118 | top: 0; 119 | right: 0; 120 | width: 40px; 121 | height: 40px; 122 | font-size: 25px; 123 | text-align: center; 124 | opacity: 0.8; 125 | border: 0; 126 | cursor: pointer; 127 | } 128 | 129 | .selected-status-wrapper > .remove:hover { 130 | opacity: 0.7; 131 | background-color: #fff; 132 | } 133 | 134 | .error .remove { 135 | opacity: 0.5; 136 | } 137 | 138 | .error .remove:hover { 139 | opacity: 0.7; 140 | } 141 | 142 | input { 143 | display: none; 144 | } 145 | 146 | .error { 147 | width: 100%; 148 | height: 100%; 149 | border: 1px solid #e3a5a2; 150 | color: #d2706b; 151 | background-color: #fbf1f0; 152 | position: relative; 153 | text-align: center; 154 | display: flex; 155 | align-items: center; 156 | } 157 | 158 | .error-message { 159 | width: 100%; 160 | line-height: 18px; 161 | } 162 | 163 | .progress-bar { 164 | position: absolute; 165 | bottom:10%; 166 | left:10%; 167 | width: 80%; 168 | height: 5px; 169 | background-color: grey; 170 | opacity: 0.9; 171 | overflow: hidden; 172 | } 173 | 174 | .bar { 175 | position: absolute; 176 | height: 100%; 177 | background-color: #a4c639; 178 | } 179 | 180 | .drag-overlay { 181 | position: absolute; 182 | top: 0; 183 | left: 0; 184 | width: 100%; 185 | height: 100%; 186 | background-color: yellow; 187 | opacity: 0.3; 188 | } 189 | 190 | /* spinner */ 191 | 192 | .sk-fading-circle { 193 | width: 40px; 194 | height: 40px; 195 | position: relative; 196 | top: 50%; 197 | left: 50%; 198 | transform: translate(-50%, -50%); 199 | } 200 | 201 | .sk-fading-circle .sk-circle { 202 | width: 100%; 203 | height: 100%; 204 | position: absolute; 205 | left: 0; 206 | top: 0; 207 | } 208 | 209 | .sk-fading-circle .sk-circle:before { 210 | content: ''; 211 | display: block; 212 | margin: 0 auto; 213 | width: 15%; 214 | height: 15%; 215 | background-color: #333; 216 | border-radius: 100%; 217 | -webkit-animation: sk-circleFadeDelay 1.2s infinite ease-in-out both; 218 | animation: sk-circleFadeDelay 1.2s infinite ease-in-out both; 219 | } 220 | .sk-fading-circle .sk-circle2 { 221 | -webkit-transform: rotate(30deg); 222 | -ms-transform: rotate(30deg); 223 | transform: rotate(30deg); 224 | } 225 | .sk-fading-circle .sk-circle3 { 226 | -webkit-transform: rotate(60deg); 227 | -ms-transform: rotate(60deg); 228 | transform: rotate(60deg); 229 | } 230 | .sk-fading-circle .sk-circle4 { 231 | -webkit-transform: rotate(90deg); 232 | -ms-transform: rotate(90deg); 233 | transform: rotate(90deg); 234 | } 235 | .sk-fading-circle .sk-circle5 { 236 | -webkit-transform: rotate(120deg); 237 | -ms-transform: rotate(120deg); 238 | transform: rotate(120deg); 239 | } 240 | .sk-fading-circle .sk-circle6 { 241 | -webkit-transform: rotate(150deg); 242 | -ms-transform: rotate(150deg); 243 | transform: rotate(150deg); 244 | } 245 | .sk-fading-circle .sk-circle7 { 246 | -webkit-transform: rotate(180deg); 247 | -ms-transform: rotate(180deg); 248 | transform: rotate(180deg); 249 | } 250 | .sk-fading-circle .sk-circle8 { 251 | -webkit-transform: rotate(210deg); 252 | -ms-transform: rotate(210deg); 253 | transform: rotate(210deg); 254 | } 255 | .sk-fading-circle .sk-circle9 { 256 | -webkit-transform: rotate(240deg); 257 | -ms-transform: rotate(240deg); 258 | transform: rotate(240deg); 259 | } 260 | .sk-fading-circle .sk-circle10 { 261 | -webkit-transform: rotate(270deg); 262 | -ms-transform: rotate(270deg); 263 | transform: rotate(270deg); 264 | } 265 | .sk-fading-circle .sk-circle11 { 266 | -webkit-transform: rotate(300deg); 267 | -ms-transform: rotate(300deg); 268 | transform: rotate(300deg); 269 | } 270 | .sk-fading-circle .sk-circle12 { 271 | -webkit-transform: rotate(330deg); 272 | -ms-transform: rotate(330deg); 273 | transform: rotate(330deg); 274 | } 275 | .sk-fading-circle .sk-circle2:before { 276 | -webkit-animation-delay: -1.1s; 277 | animation-delay: -1.1s; 278 | } 279 | .sk-fading-circle .sk-circle3:before { 280 | -webkit-animation-delay: -1s; 281 | animation-delay: -1s; 282 | } 283 | .sk-fading-circle .sk-circle4:before { 284 | -webkit-animation-delay: -0.9s; 285 | animation-delay: -0.9s; 286 | } 287 | .sk-fading-circle .sk-circle5:before { 288 | -webkit-animation-delay: -0.8s; 289 | animation-delay: -0.8s; 290 | } 291 | .sk-fading-circle .sk-circle6:before { 292 | -webkit-animation-delay: -0.7s; 293 | animation-delay: -0.7s; 294 | } 295 | .sk-fading-circle .sk-circle7:before { 296 | -webkit-animation-delay: -0.6s; 297 | animation-delay: -0.6s; 298 | } 299 | .sk-fading-circle .sk-circle8:before { 300 | -webkit-animation-delay: -0.5s; 301 | animation-delay: -0.5s; 302 | } 303 | .sk-fading-circle .sk-circle9:before { 304 | -webkit-animation-delay: -0.4s; 305 | animation-delay: -0.4s; 306 | } 307 | .sk-fading-circle .sk-circle10:before { 308 | -webkit-animation-delay: -0.3s; 309 | animation-delay: -0.3s; 310 | } 311 | .sk-fading-circle .sk-circle11:before { 312 | -webkit-animation-delay: -0.2s; 313 | animation-delay: -0.2s; 314 | } 315 | .sk-fading-circle .sk-circle12:before { 316 | -webkit-animation-delay: -0.1s; 317 | animation-delay: -0.1s; 318 | } 319 | 320 | @-webkit-keyframes sk-circleFadeDelay { 321 | 0%, 39%, 100% { opacity: 0; } 322 | 40% { opacity: 1; } 323 | } 324 | 325 | @keyframes sk-circleFadeDelay { 326 | 0%, 39%, 100% { opacity: 0; } 327 | 40% { opacity: 1; } 328 | }`; -------------------------------------------------------------------------------- /src/uploaded-file.ts: -------------------------------------------------------------------------------- 1 | export class UploadedFile { 2 | id: string; 3 | status: number; 4 | statusText: string; 5 | progress: number; 6 | originalName: string; 7 | size: number; 8 | response: string; 9 | done: boolean; 10 | error: boolean; 11 | abort: boolean; 12 | 13 | constructor(id: string, originalName: string, size: number) { 14 | this.id = id; 15 | this.originalName = originalName; 16 | this.size = size; 17 | this.progress = 0; 18 | this.done = false; 19 | this.error = false; 20 | this.abort = false; 21 | } 22 | 23 | setError(): void { 24 | this.error = true; 25 | this.done = true; 26 | } 27 | 28 | setAbort(): void { 29 | this.abort = true; 30 | this.done = true; 31 | } 32 | 33 | onFinished(status: number, statusText: string, response: string): void { 34 | this.status = status; 35 | this.statusText = statusText; 36 | this.response = response; 37 | this.done = true; 38 | } 39 | } -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import {ResizeOptions} from './interfaces'; 2 | 3 | export function createImage(url: string, cb: (i: HTMLImageElement) => void) { 4 | var image = new Image(); 5 | image.onload = function () { 6 | cb(image); 7 | }; 8 | image.src = url; 9 | } 10 | 11 | const resizeAreaId = 'imageupload-resize-area'; 12 | 13 | function getResizeArea() { 14 | let resizeArea = document.getElementById(resizeAreaId); 15 | if (!resizeArea) { 16 | resizeArea = document.createElement('canvas'); 17 | resizeArea.id = resizeAreaId; 18 | resizeArea.style.display = 'none'; 19 | document.body.appendChild(resizeArea); 20 | } 21 | 22 | return resizeArea; 23 | } 24 | 25 | export function resizeImage(origImage: HTMLImageElement, { 26 | resizeHeight, 27 | resizeWidth, 28 | resizeQuality = 0.7, 29 | resizeType = 'image/jpeg', 30 | resizeMode = 'fill' 31 | }: ResizeOptions = {}) { 32 | 33 | let canvas = getResizeArea(); 34 | 35 | let height = origImage.height; 36 | let width = origImage.width; 37 | let offsetX = 0; 38 | let offsetY = 0; 39 | 40 | if (resizeMode === 'fill') { 41 | // calculate the width and height, constraining the proportions 42 | if (width / height > resizeWidth / resizeHeight) { 43 | width = Math.round(height * resizeWidth / resizeHeight); 44 | } else { 45 | height = Math.round(width * resizeHeight / resizeWidth); 46 | } 47 | 48 | canvas.width = resizeWidth <= width ? resizeWidth : width; 49 | canvas.height = resizeHeight <= height ? resizeHeight : height; 50 | 51 | offsetX = origImage.width / 2 - width / 2; 52 | offsetY = origImage.height / 2 - height / 2; 53 | 54 | //draw image on canvas 55 | const ctx = canvas.getContext("2d"); 56 | ctx.drawImage(origImage, offsetX, offsetY, width, height, 0, 0, canvas.width, canvas.height); 57 | } else if (resizeMode === 'fit') { 58 | // calculate the width and height, constraining the proportions 59 | if (width > height) { 60 | if (width > resizeWidth) { 61 | height = Math.round(height *= resizeWidth / width); 62 | width = resizeWidth; 63 | } 64 | } else { 65 | if (height > resizeHeight) { 66 | width = Math.round(width *= resizeHeight / height); 67 | height = resizeHeight; 68 | } 69 | } 70 | 71 | canvas.width = width; 72 | canvas.height = height; 73 | 74 | //draw image on canvas 75 | const ctx = canvas.getContext("2d"); 76 | ctx.drawImage(origImage, 0, 0, width, height); 77 | } else { 78 | throw new Error('Unknown resizeMode: ' + resizeMode); 79 | } 80 | 81 | // get the data from canvas as 70% jpg (or specified type). 82 | return canvas.toDataURL(resizeType, resizeQuality); 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "es5", 5 | "module": "es2015", 6 | "moduleResolution": "node", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "sourceMap": false, 10 | "noEmitHelpers": false, 11 | "noImplicitAny": true, 12 | "declaration": true, 13 | "skipLibCheck": true, 14 | "stripInternal": true, 15 | "lib": ["es2015", "dom"], 16 | "types": [ 17 | "node", 18 | "cropperjs" 19 | ] 20 | }, 21 | "exclude": [ 22 | "node_modules", 23 | "demo" 24 | ], 25 | "files": [ 26 | "./index.ts" 27 | ], 28 | "angularCompilerOptions": { 29 | "genDir": "compiled", 30 | "skipTemplateCodegen": true 31 | } 32 | } --------------------------------------------------------------------------------