├── src ├── assets │ ├── .gitkeep │ ├── success.svg │ ├── cancel.svg │ └── pdf.svg ├── app │ ├── app.component.scss │ ├── basic-example │ │ ├── basic-example.component.scss │ │ ├── basic.module.ts │ │ ├── basic-example.component.ts │ │ └── basic-example.component.html │ ├── app.component.ts │ ├── routes.ts │ ├── profile-pic-example │ │ ├── profile-pic-example.component.scss │ │ ├── profile-pic.module.ts │ │ ├── profile-pic-example.component.ts │ │ └── profile-pic-example.component.html │ ├── app.component.html │ └── app.module.ts ├── favicon.ico ├── tsconfig.app.json ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── index.html ├── tsconfig.spec.json ├── tslint.json ├── browserslist ├── main.ts ├── styles.scss ├── test.ts ├── karma.conf.js └── polyfills.ts ├── projects ├── photo │ ├── src │ │ ├── public_api.ts │ │ ├── lib │ │ │ ├── fire-photo.token.ts │ │ │ ├── fire-photo.model.ts │ │ │ ├── fire-photo.service.ts │ │ │ ├── fire-photo.html │ │ │ ├── fire-photo.module.ts │ │ │ ├── fire-photo.scss │ │ │ ├── lazy-image.directive.ts │ │ │ └── fire-photo.component.ts │ │ └── test.ts │ ├── ng-package.prod.json │ ├── ng-package.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ ├── package.json │ └── karma.conf.js ├── manager │ ├── src │ │ ├── lib │ │ │ ├── fire-manager.token.ts │ │ │ ├── safe-style.pipe.ts │ │ │ ├── file-size.pipe.ts │ │ │ ├── fire-manager.model.ts │ │ │ ├── fire-manager.service.ts │ │ │ ├── fire-manager.component.html │ │ │ ├── file-item.component.ts │ │ │ ├── file-item.component.html │ │ │ ├── fire-manager.module.ts │ │ │ ├── fire-manager.component.ts │ │ │ └── fire-manager.scss │ │ ├── public_api.ts │ │ └── test.ts │ ├── ng-package.prod.json │ ├── tsconfig.spec.json │ ├── ng-package.json │ ├── tslint.json │ ├── tsconfig.lib.json │ ├── package.json │ └── karma.conf.js └── core │ ├── src │ ├── public_api.ts │ ├── lib │ │ ├── fire-uploader.token.ts │ │ ├── fire-uploader.default.ts │ │ ├── drop-zone.directive.ts │ │ ├── fire-uploader.module.ts │ │ ├── fire-uploader.service.ts │ │ ├── utils.ts │ │ ├── fire-uploader.model.ts │ │ ├── image-resizer.ts │ │ ├── file-item.ts │ │ └── fire-uploader-ref.ts │ └── test.ts │ ├── tsconfig.spec.json │ ├── ng-package.prod.json │ ├── ng-package.json │ ├── tslint.json │ ├── tsconfig.lib.json │ ├── package.json │ └── karma.conf.js ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.e2e.json └── protractor.conf.js ├── .editorconfig ├── .gitignore ├── .config ├── publish.js ├── version.js ├── styles.js ├── styles.ts ├── utils.ts └── utils.js ├── tsconfig.json ├── LICENSE ├── README.md ├── tslint.json ├── package.json └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MurhafSousli/ngx-fire-uploader/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /projects/photo/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/fire-photo.module'; 2 | export * from './lib/fire-photo.model'; 3 | export * from './lib/fire-photo.component'; 4 | -------------------------------------------------------------------------------- /projects/photo/src/lib/fire-photo.token.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { FirePhotoConfig } from './fire-photo.model'; 3 | 4 | export const CONFIG = new InjectionToken('config'); 5 | -------------------------------------------------------------------------------- /projects/manager/src/lib/fire-manager.token.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { FireManagerConfig } from './fire-manager.model'; 3 | 4 | export const CONFIG = new InjectionToken('config'); 5 | -------------------------------------------------------------------------------- /projects/core/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/fire-uploader.module'; 2 | export * from './lib/fire-uploader.model'; 3 | export * from './lib/fire-uploader-ref'; 4 | export * from './lib/fire-uploader.service'; 5 | export * from './lib/file-item'; 6 | 7 | -------------------------------------------------------------------------------- /projects/manager/src/public_api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/fire-manager.module'; 2 | export * from './lib/fire-manager.model'; 3 | export * from './lib/fire-manager.component'; 4 | export * from './lib/file-item.component'; 5 | export * from './lib/file-size.pipe'; 6 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "es2015", 6 | "types": [] 7 | }, 8 | "exclude": [ 9 | "src/test.ts", 10 | "**/*.spec.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/core/src/lib/fire-uploader.token.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | import { FireUploaderConfig } from './fire-uploader.model'; 3 | 4 | /** FireUploaderConfig token */ 5 | export const UPLOADER_CONFIG = new InjectionToken('config'); 6 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /src/app/basic-example/basic-example.component.scss: -------------------------------------------------------------------------------- 1 | pre { 2 | display: flex; 3 | flex-direction: column; 4 | border: 1px solid lightgray; 5 | padding: 10px 15px; 6 | border-radius: 4px; 7 | background: white; 8 | } 9 | button { 10 | margin-right: 5px; 11 | margin-bottom: 5px; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /projects/photo/ng-package.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/photo", 4 | "lib": { 5 | "entryFile": "src/public_api.ts", 6 | "externals": { 7 | "firebase": "firebase", 8 | "@angular/fire": "@angular/fire" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/manager/ng-package.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/manager", 4 | "lib": { 5 | "entryFile": "src/public_api.ts", 6 | "externals": { 7 | "firebase": "firebase", 8 | "@angular/fire": "@angular/fire" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/core/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/manager/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/photo/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/photo", 4 | "deleteDestPath": false, 5 | "lib": { 6 | "entryFile": "src/public_api.ts", 7 | "externals": { 8 | "firebase": "firebase", 9 | "@angular/fire": "@angular/fire" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /projects/photo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts" 12 | ], 13 | "include": [ 14 | "**/*.spec.ts", 15 | "**/*.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /projects/manager/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/manager", 4 | "deleteDestPath": false, 5 | "lib": { 6 | "entryFile": "src/public_api.ts", 7 | "externals": { 8 | "firebase": "firebase", 9 | "@angular/fire": "@angular/fire" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'], 7 | changeDetection: ChangeDetectionStrategy.OnPush 8 | }) 9 | export class AppComponent { 10 | navbarCollapsed = true; 11 | } 12 | -------------------------------------------------------------------------------- /projects/core/ng-package.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/core", 4 | "lib": { 5 | "entryFile": "src/public_api.ts", 6 | "languageLevel": ["dom", "es2017"], 7 | "externals": { 8 | "firebase": "firebase", 9 | "@angular/fire": "@angular/fire" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/app/routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | export const routes: Routes = [ 4 | { 5 | path: '', 6 | loadChildren: './basic-example/basic.module#BasicModule', 7 | pathMatch: 'full' 8 | }, 9 | { 10 | path: 'profile-pic', 11 | loadChildren: './profile-pic-example/profile-pic.module#ProfilePicModule' 12 | } 13 | ]; 14 | 15 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | firebase: { 4 | apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxx', 5 | authDomain: 'x.firebaseapp.com', 6 | databaseURL: 'https://x.firebaseio.com', 7 | projectId: 'xxxxxx', 8 | storageBucket: 'xxxxx.appspot.com', 9 | messagingSenderId: 'xxxxxxxxxxx' 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgxFireUploader 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "commonjs", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /projects/core/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/core", 4 | "deleteDestPath": false, 5 | "lib": { 6 | "entryFile": "src/public_api.ts", 7 | "languageLevel": ["dom", "es2017"], 8 | "externals": { 9 | "firebase": "firebase", 10 | "@angular/fire": "@angular/fire" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # For IE 9-11 support, please uncomment the last line of the file and adjust as needed 5 | > 0.5% 6 | last 2 versions 7 | Firefox ESR 8 | not dead 9 | # IE 9-11 -------------------------------------------------------------------------------- /projects/core/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/manager/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/photo/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "lib", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "lib", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /projects/manager/src/lib/safe-style.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; 3 | 4 | @Pipe({ name: 'safeStyle' }) 5 | export class SafeStylePipe implements PipeTransform { 6 | constructor(private _sanitizer: DomSanitizer) {} 7 | 8 | transform(value: string): SafeStyle { 9 | return this._sanitizer.bypassSecurityTrustStyle(value); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/photo/src/lib/fire-photo.model.ts: -------------------------------------------------------------------------------- 1 | import { ResizeMethod } from '@ngx-fire-uploader/core'; 2 | 3 | export interface FirePhotoConfig { 4 | paramDir?: string; 5 | paramName?: string; 6 | uniqueName?: boolean; 7 | dropZone?: boolean; 8 | defaultImage?: string; 9 | autoStart?: boolean; 10 | thumbWidth?: number; 11 | thumbHeight?: number; 12 | thumbMethod?: ResizeMethod; 13 | resizeMethod?: ResizeMethod; 14 | resizeWidth?: number; 15 | resizeHeight?: number; 16 | resizeQuality?: number; 17 | maxFileSize?: number; 18 | } 19 | -------------------------------------------------------------------------------- /src/assets/success.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /projects/manager/src/lib/file-size.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({name: 'fileSize'}) 4 | export class FileSizePipe implements PipeTransform { 5 | 6 | private units = [ 7 | 'B', 8 | 'KB', 9 | 'MB', 10 | 'GB' 11 | ]; 12 | 13 | transform(bytes: number = 0, precision: number = 1): string { 14 | if (isNaN(parseFloat(String(bytes))) || !isFinite(bytes)) return '?'; 15 | 16 | let unit = 0; 17 | 18 | while (bytes >= 1024) { 19 | bytes /= 1024; 20 | unit++; 21 | } 22 | return bytes.toFixed(precision) + ' ' + this.units[unit]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import url('https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css'); 3 | @import url('https://cdnjs.cloudflare.com/ajax/libs/open-iconic/1.1.1/font/css/open-iconic-bootstrap.min.css'); 4 | 5 | @import '~@ngx-fire-uploader/manager/fire-manager'; 6 | 7 | 8 | html { 9 | min-height: 100%; 10 | height: 100%; 11 | } 12 | body { 13 | height: 100%; 14 | background: #E6ECF0; 15 | } 16 | 17 | h5 { 18 | display: inline-block; 19 | border-bottom: 1px solid lightgray; 20 | padding-bottom: 10px; 21 | margin-top: 15px; 22 | margin-bottom: 15px; 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | .gitignore 41 | 42 | -------------------------------------------------------------------------------- /.config/publish.js: -------------------------------------------------------------------------------- 1 | const c = require('ansi-colors'); 2 | const { execSync } = require('child_process'); 3 | const { readdirSync, statSync } = require('fs'); 4 | const { join } = require('path'); 5 | 6 | const getPackages = p => 7 | readdirSync(p).filter(f => statSync(join(p, f)).isDirectory()); 8 | 9 | const packages = getPackages('projects'); 10 | 11 | console.log( 12 | 'Publishing packages', 13 | c.cyan(c.symbols.pointerSmall), 14 | c.yellow(packages) 15 | ); 16 | 17 | packages.map(function(package) { 18 | const packagePath = `${__dirname}/../dist/${package}`; 19 | execSync(`cd ${packagePath} && npm publish`); 20 | console.log(c.magenta(package), 'has been published', c.green(c.symbols.check)); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/profile-pic-example/profile-pic-example.component.scss: -------------------------------------------------------------------------------- 1 | .btn-orange { 2 | color: white; 3 | background-color: #F26624; 4 | margin-left: 5px; 5 | &:focus { 6 | box-shadow: 0 0 0 0.2rem rgba(#F26624, .25); 7 | } 8 | } 9 | 10 | ::ng-deep { 11 | #profilePic { 12 | z-index: 100; 13 | margin-top: -6.5em; 14 | position: absolute; 15 | border: 6px solid white; 16 | box-shadow: 0 0 2px rgba(0, 0, 0, 0.5); 17 | height: 200px; 18 | width: 200px; 19 | border-radius: 50%; 20 | .fire-border { 21 | border-radius: 50%; 22 | } 23 | } 24 | 25 | #coverPic { 26 | margin-top: -3em; 27 | width: 100%; 28 | height: 300px; 29 | background-color: #909FA8; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /projects/photo/src/lib/fire-photo.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, Optional } from '@angular/core'; 2 | import { ResizeMethod } from '@ngx-fire-uploader/core'; 3 | import { FirePhotoConfig } from './fire-photo.model'; 4 | import { CONFIG } from './fire-photo.token'; 5 | 6 | const defaultConfig: FirePhotoConfig = { 7 | dropZone: true, 8 | uniqueName: true, 9 | defaultImage: null, 10 | autoStart: false, 11 | thumbMethod: ResizeMethod.Contain, 12 | resizeMethod: ResizeMethod.Crop 13 | }; 14 | 15 | @Injectable() 16 | export class FirePhoto { 17 | 18 | /** Global config */ 19 | config: FirePhotoConfig; 20 | 21 | constructor(@Optional() @Inject(CONFIG) config: FirePhotoConfig) { 22 | this.config = {...defaultConfig, ...config}; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /projects/manager/src/lib/fire-manager.model.ts: -------------------------------------------------------------------------------- 1 | import { ResizeMethod } from '@ngx-fire-uploader/core'; 2 | 3 | export interface FireManagerConfig { 4 | showProgress?: boolean; 5 | showDetails?: boolean; 6 | showRemove?: boolean; 7 | extensions?: any; 8 | dropZone?: boolean; 9 | paramDir?: string; 10 | paramName?: string; 11 | uniqueName?: boolean; 12 | maxFileSize?: number; 13 | maxFilesCount?: number; 14 | parallelUploads?: number; 15 | multiple?: boolean; 16 | accept?: string; 17 | autoStart?: boolean; 18 | thumbs?: boolean; 19 | thumbWidth?: number; 20 | thumbHeight?: number; 21 | thumbMethod?: ResizeMethod; 22 | resizeMethod?: ResizeMethod; 23 | resizeWidth?: number; 24 | resizeHeight?: number; 25 | resizeQuality?: number; 26 | } 27 | -------------------------------------------------------------------------------- /projects/core/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/photo/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /src/app/profile-pic-example/profile-pic.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { CommonModule } from '@angular/common'; 4 | 5 | import { FirePhotoModule } from '@ngx-fire-uploader/photo'; 6 | 7 | import { ProfilePicExampleComponent } from './profile-pic-example.component'; 8 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 9 | 10 | 11 | @NgModule({ 12 | declarations: [ 13 | ProfilePicExampleComponent 14 | ], 15 | imports: [ 16 | CommonModule, 17 | FormsModule, 18 | FirePhotoModule.forRoot(), 19 | RouterModule.forChild([ 20 | { 21 | path: '', 22 | component: ProfilePicExampleComponent 23 | } 24 | ]) 25 | ] 26 | }) 27 | export class ProfilePicModule { 28 | } 29 | -------------------------------------------------------------------------------- /projects/manager/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'core-js/es7/reflect'; 4 | import 'zone.js/dist/zone'; 5 | import 'zone.js/dist/zone-testing'; 6 | import { getTestBed } from '@angular/core/testing'; 7 | import { 8 | BrowserDynamicTestingModule, 9 | platformBrowserDynamicTesting 10 | } from '@angular/platform-browser-dynamic/testing'; 11 | 12 | declare const require: any; 13 | 14 | // First, initialize the Angular testing environment. 15 | getTestBed().initTestEnvironment( 16 | BrowserDynamicTestingModule, 17 | platformBrowserDynamicTesting() 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /projects/core/src/lib/fire-uploader.default.ts: -------------------------------------------------------------------------------- 1 | import { FireUploaderState, FireUploaderConfig, ResizeMethod } from './fire-uploader.model'; 2 | 3 | /** Uploader default config */ 4 | export const DEFAULT_CONFIG: FireUploaderConfig = { 5 | paramName: null, 6 | paramDir: null, 7 | uniqueName: true, 8 | multiple: true, 9 | accept: null, 10 | parallelUploads: 1, 11 | maxFiles: 20, 12 | autoStart: false, 13 | thumbs: true, 14 | thumbMethod: ResizeMethod.Contain, 15 | thumbWidth: 100, 16 | thumbHeight: 100, 17 | resizeMethod: ResizeMethod.Crop, 18 | resizeWidth: null, 19 | resizeHeight: null, 20 | resizeQuality: 1 21 | }; 22 | 23 | export const DEFAULT_STATE: FireUploaderState = { 24 | files: [], 25 | active: false, 26 | progress: { 27 | totalBytes: 0, 28 | bytesTransferred: 0, 29 | percentage: 0 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /projects/manager/src/lib/fire-manager.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, Optional } from '@angular/core'; 2 | import { ResizeMethod } from '@ngx-fire-uploader/core'; 3 | import { FireManagerConfig } from './fire-manager.model'; 4 | import { CONFIG } from './fire-manager.token'; 5 | 6 | const defaultConfig: FireManagerConfig = { 7 | showProgress: true, 8 | showDetails: true, 9 | showRemove: true, 10 | dropZone: true, 11 | autoStart: false, 12 | multiple: true, 13 | uniqueName: true, 14 | thumbMethod: ResizeMethod.Contain, 15 | thumbWidth: 100, 16 | thumbHeight: 100, 17 | resizeMethod: ResizeMethod.Crop, 18 | }; 19 | 20 | @Injectable() 21 | export class FireManager { 22 | 23 | /** Global config */ 24 | config: FireManagerConfig; 25 | 26 | constructor(@Optional() @Inject(CONFIG) config: FireManagerConfig) { 27 | this.config = {...defaultConfig, ...config}; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /projects/core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2015" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "skipTemplateCodegen": true, 23 | "strictMetadataEmit": true, 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true, 26 | "flatModuleId": "AUTOGENERATED", 27 | "flatModuleOutFile": "AUTOGENERATED" 28 | }, 29 | "exclude": [ 30 | "src/test.ts", 31 | "**/*.spec.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /projects/photo/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2015" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "skipTemplateCodegen": true, 23 | "strictMetadataEmit": true, 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true, 26 | "flatModuleId": "AUTOGENERATED", 27 | "flatModuleOutFile": "AUTOGENERATED" 28 | }, 29 | "exclude": [ 30 | "src/test.ts", 31 | "**/*.spec.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /projects/manager/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2015" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "annotateForClosureCompiler": true, 22 | "skipTemplateCodegen": true, 23 | "strictMetadataEmit": true, 24 | "fullTemplateTypeCheck": true, 25 | "strictInjectionParameters": true, 26 | "flatModuleId": "AUTOGENERATED", 27 | "flatModuleOutFile": "AUTOGENERATED" 28 | }, 29 | "exclude": [ 30 | "src/test.ts", 31 | "**/*.spec.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /src/assets/cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /projects/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-fire-uploader/core", 3 | "description": "Angular file uploader for Firebase", 4 | "version": "VERSION", 5 | "homepage": "http://github.com/murhafsousli/ngx-fire-uploader", 6 | "author": { 7 | "name": "Murhaf Sousli", 8 | "url": "https://github.com/murhafsousli" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/murhafsousli/ngx-fire-uploader.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/murhafsousli/ngx-fire-uploader/issues" 16 | }, 17 | "license": "MIT", 18 | "keywords": [ 19 | "angular", 20 | "firebase", 21 | "file", 22 | "images", 23 | "upload", 24 | "uploader", 25 | "dropzone", 26 | "dropfile", 27 | "drop" 28 | ], 29 | "private": false, 30 | "peerDependencies": { 31 | "@angular/common": "^6.0.0", 32 | "@angular/core": "^6.0.0", 33 | "firebase": "^5.7.1", 34 | "@angular/fire": "^5.1.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /projects/photo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-fire-uploader/photo", 3 | "description": "Angular file uploader for Firebase", 4 | "version": "VERSION", 5 | "homepage": "http://github.com/murhafsousli/ngx-fire-uploader", 6 | "author": { 7 | "name": "Murhaf Sousli", 8 | "url": "https://github.com/murhafsousli" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/murhafsousli/ngx-fire-uploader.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/murhafsousli/ngx-fire-uploader/issues" 16 | }, 17 | "license": "MIT", 18 | "keywords": [ 19 | "angular", 20 | "firebase", 21 | "file", 22 | "images", 23 | "upload", 24 | "uploader", 25 | "dropzone", 26 | "dropfile", 27 | "drop" 28 | ], 29 | "private": false, 30 | "peerDependencies": { 31 | "@angular/common": "^6.0.0", 32 | "@angular/core": "^6.0.0", 33 | "firebase": "^5.7.1", 34 | "@angular/fire": "^5.1.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | firebase: { 8 | apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxx', 9 | authDomain: 'x.firebaseapp.com', 10 | databaseURL: 'https://x.firebaseio.com', 11 | projectId: 'xxxxxx', 12 | storageBucket: 'xxxxx.appspot.com', 13 | messagingSenderId: 'xxxxxxxxxxx' 14 | } 15 | }; 16 | 17 | /* 18 | * In development mode, to ignore zone related error stack frames such as 19 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can 20 | * import the following file, but please comment it out in production mode 21 | * because it will have performance impact when throw error 22 | */ 23 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 24 | -------------------------------------------------------------------------------- /projects/manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-fire-uploader/manager", 3 | "description": "Angular file uploader for Firebase", 4 | "version": "VERSION", 5 | "homepage": "http://github.com/murhafsousli/ngx-fire-uploader", 6 | "author": { 7 | "name": "Murhaf Sousli", 8 | "url": "https://github.com/murhafsousli" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/murhafsousli/ngx-fire-uploader.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/murhafsousli/ngx-fire-uploader/issues" 16 | }, 17 | "license": "MIT", 18 | "keywords": [ 19 | "angular", 20 | "firebase", 21 | "file", 22 | "images", 23 | "upload", 24 | "uploader", 25 | "dropzone", 26 | "dropfile", 27 | "drop" 28 | ], 29 | "private": false, 30 | "peerDependencies": { 31 | "@angular/common": "^6.0.0", 32 | "@angular/core": "^6.0.0", 33 | "firebase": "^5.7.1", 34 | "@angular/fire": "^5.1.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ], 19 | "paths": { 20 | "core": [ 21 | "dist/core", 22 | "dist/core", 23 | "dist/core" 24 | ], 25 | "core/*": [ 26 | "dist/core/*", 27 | "dist/core/*", 28 | "dist/core/*" 29 | ], 30 | "photo": [ 31 | "dist/photo" 32 | ], 33 | "photo/*": [ 34 | "dist/photo/*" 35 | ], 36 | "manager": [ 37 | "dist/manager" 38 | ], 39 | "manager/*": [ 40 | "dist/manager/*" 41 | ] 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /projects/manager/src/lib/fire-manager.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | > 13 | 14 | 15 |
19 | 20 |
21 |
22 | Drop files here or click to select 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /projects/manager/src/lib/file-item.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter } from '@angular/core'; 2 | import { FileState } from '@ngx-fire-uploader/core'; 3 | 4 | @Component({ 5 | selector: 'file-item', 6 | preserveWhitespaces: false, 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | templateUrl: './file-item.component.html' 9 | }) 10 | export class FileItemComponent { 11 | @Input() state: FileState; 12 | 13 | /** Show fire-uploader progress bar */ 14 | @Input() showProgress: boolean; 15 | 16 | /** Shows file name and size */ 17 | @Input() showDetails: boolean; 18 | 19 | /** Shows remove button */ 20 | @Input() showRemove: boolean; 21 | 22 | /** To set background based on file extension */ 23 | @Input() extensions: any; 24 | 25 | /** Stream that emits when remove button is clicked */ 26 | @Output() remove = new EventEmitter(); 27 | 28 | removeClicked(e: Event) { 29 | e.stopPropagation(); 30 | this.remove.emit(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /projects/core/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/photo/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/manager/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /projects/core/src/lib/drop-zone.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, EventEmitter, HostListener, Output } from '@angular/core'; 2 | /** 3 | * Create a drop zone that adds files to the uploader queue 4 | */ 5 | @Directive({ 6 | selector: '[dropZone]' 7 | }) 8 | export class DropZoneDirective { 9 | 10 | /** Stream that emits when files are dropped */ 11 | @Output('dropZone') dropZone = new EventEmitter(); 12 | 13 | /** Stream that emits when user drag over */ 14 | @Output() dragOver = new EventEmitter(); 15 | 16 | /** Listen to drop event */ 17 | @HostListener('drop', ['$event']) 18 | onDrop(e: DragEvent) { 19 | e.preventDefault(); 20 | this.dragOver.emit(false); 21 | this.dropZone.emit(e.dataTransfer.files); 22 | } 23 | 24 | /** Listen to dragover event */ 25 | @HostListener('dragover', ['$event']) 26 | onDragOver(e) { 27 | e.preventDefault(); 28 | this.dragOver.emit(true); 29 | } 30 | 31 | /** Listen to dragleave event */ 32 | @HostListener('dragleave', ['$event']) 33 | onDragLeave(e) { 34 | e.preventDefault(); 35 | this.dragOver.emit(false); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /.config/version.js: -------------------------------------------------------------------------------- 1 | const c = require('ansi-colors'); 2 | const { join } = require('path'); 3 | const { 4 | readdirSync, 5 | statSync, 6 | readFileSync, 7 | writeFileSync, 8 | existsSync 9 | } = require('fs'); 10 | 11 | const { exec } = require('child_process'); 12 | 13 | const getPackages = p => 14 | readdirSync(p).filter(f => statSync(join(p, f)).isDirectory()); 15 | 16 | const packages = getPackages('projects'); 17 | 18 | console.log( 19 | 'Updating packages version to', 20 | c.cyan(c.symbols.pointerSmall), 21 | c.yellow(packages) 22 | ); 23 | 24 | // Get the root from the main package.json 25 | const version = JSON.parse(readFileSync('package.json', 'utf8')).version; 26 | 27 | // Updates `VERSION` in package.json for all packages 28 | packages.map(package => { 29 | const packagePath = `dist/${package}/package.json`; 30 | // Check if package directory exists 31 | if (existsSync(packagePath)) { 32 | console.log(c.magenta(`${package}@${version}`), c.green(c.symbols.check)); 33 | package = readFileSync(packagePath, 'utf8'); 34 | writeFileSync(packagePath, package.replace(/VERSION/g, version)); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Murhaf Sousli 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 | -------------------------------------------------------------------------------- /projects/photo/src/lib/fire-photo.html: -------------------------------------------------------------------------------- 1 | 2 |
8 | 9 |
14 |
15 | 16 |
17 | 18 | 19 |
20 | 21 |
22 |
23 |
24 | 25 | 26 |
27 | 28 |
29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /projects/core/src/lib/fire-uploader.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { AngularFireStorage, AngularFireStorageModule } from '@angular/fire/storage'; 3 | 4 | import { FireUploader } from './fire-uploader.service'; 5 | import { FireUploaderConfig } from './fire-uploader.model'; 6 | import { UPLOADER_CONFIG } from './fire-uploader.token'; 7 | import { DropZoneDirective } from './drop-zone.directive'; 8 | 9 | /** Create a FireUploader service */ 10 | export function UploaderFactory(config: FireUploaderConfig, storage: AngularFireStorage) { 11 | return new FireUploader(config, storage); 12 | } 13 | 14 | @NgModule({ 15 | imports: [AngularFireStorageModule], 16 | declarations: [DropZoneDirective], 17 | exports: [DropZoneDirective] 18 | }) 19 | export class FireUploaderModule { 20 | static forRoot(config?: FireUploaderConfig): ModuleWithProviders { 21 | return { 22 | ngModule: FireUploaderModule, 23 | providers: [ 24 | {provide: UPLOADER_CONFIG, useValue: config}, 25 | { 26 | provide: FireUploader, 27 | useFactory: UploaderFactory, 28 | deps: [UPLOADER_CONFIG, AngularFireStorage] 29 | } 30 | ] 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/basic-example/basic.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap/dropdown/dropdown.module'; 6 | import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap/collapse/collapse.module'; 7 | import { NgbProgressbarModule } from '@ng-bootstrap/ng-bootstrap/progressbar/progressbar.module'; 8 | import { SimpleNotificationsModule } from 'angular2-notifications'; 9 | 10 | import { FireManagerModule } from '@ngx-fire-uploader/manager'; 11 | 12 | import { BasicExampleComponent } from './basic-example.component'; 13 | 14 | @NgModule({ 15 | declarations: [BasicExampleComponent], 16 | imports: [ 17 | CommonModule, 18 | FormsModule, 19 | NgbDropdownModule, 20 | NgbCollapseModule, 21 | NgbProgressbarModule, 22 | SimpleNotificationsModule, 23 | FireManagerModule.forRoot({ 24 | extensions: { 25 | pdf: 'url("assets/pdf.svg")', 26 | doc: '#335599' 27 | } 28 | }), 29 | RouterModule.forChild([ 30 | { 31 | path: '', 32 | component: BasicExampleComponent 33 | } 34 | ]) 35 | ] 36 | }) 37 | export class BasicModule {} 38 | -------------------------------------------------------------------------------- /projects/photo/src/lib/fire-photo.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FireUploaderModule } from '@ngx-fire-uploader/core'; 4 | import { FirePhotoComponent } from './fire-photo.component'; 5 | import { LazyImageDirective } from './lazy-image.directive'; 6 | import { FirePhotoConfig } from './fire-photo.model'; 7 | import { FirePhoto } from './fire-photo.service'; 8 | import { CONFIG } from './fire-photo.token'; 9 | 10 | export function firePhotoFactory(config: FirePhotoConfig) { 11 | return new FirePhoto(config); 12 | } 13 | 14 | @NgModule({ 15 | imports: [ 16 | CommonModule, 17 | FireUploaderModule 18 | ], 19 | declarations: [ 20 | FirePhotoComponent, 21 | LazyImageDirective 22 | ], 23 | exports: [ 24 | FireUploaderModule, 25 | FirePhotoComponent, 26 | LazyImageDirective 27 | ] 28 | }) 29 | export class FirePhotoModule { 30 | static forRoot(config?: FirePhotoConfig): ModuleWithProviders { 31 | return { 32 | ngModule: FirePhotoModule, 33 | providers: [ 34 | {provide: CONFIG, useValue: config}, 35 | { 36 | provide: FirePhoto, 37 | useFactory: firePhotoFactory, 38 | deps: [CONFIG] 39 | } 40 | ] 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/profile-pic-example/profile-pic-example.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, QueryList, ViewChildren } from '@angular/core'; 2 | import { FirePhotoComponent } from '@ngx-fire-uploader/photo'; 3 | 4 | const DEFAULT_PROFILE = 5 | 'https://media.wired.com/photos/59268c5dcfe0d93c474309a2/master/w_1300,c_limit/BSP_054.jpg'; 6 | 7 | @Component({ 8 | selector: 'app-profile-pic-example', 9 | templateUrl: './profile-pic-example.component.html', 10 | styleUrls: ['./profile-pic-example.component.scss'], 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class ProfilePicExampleComponent { 14 | coverPhotoDisabled = false; 15 | profilePhotoDisabled = false; 16 | profilePhoto = DEFAULT_PROFILE; 17 | coverPhoto = DEFAULT_PROFILE; 18 | 19 | @ViewChildren(FirePhotoComponent) uploaders: QueryList; 20 | 21 | upload() { 22 | this.uploaders.map((uploader: FirePhotoComponent) => uploader.start()); 23 | this.coverPhotoDisabled = true; 24 | this.profilePhotoDisabled = true; 25 | } 26 | 27 | edit() { 28 | this.coverPhotoDisabled = false; 29 | this.profilePhotoDisabled = false; 30 | } 31 | 32 | cancel() { 33 | this.uploaders.map((uploader: FirePhotoComponent) => uploader.reset()); 34 | this.coverPhotoDisabled = true; 35 | this.profilePhotoDisabled = true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/profile-pic-example/profile-pic-example.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 | Upload Cover Photo 12 | 13 | 14 |
15 | 21 | Upload Profile Photo 22 | 23 | 24 |
25 | 28 | 29 | 32 | 33 | 36 |
37 | 38 |
39 | 40 |
41 | -------------------------------------------------------------------------------- /projects/manager/src/lib/file-item.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 | {{ state.extension }} 7 | 8 |
9 | 10 |
12 | 13 |
15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | {{ state.name }} 32 |
33 | 34 |
35 | {{ state.progress.totalBytes | fileSize }} 36 |
37 | 38 |
39 | 40 |
41 | 44 |
45 | -------------------------------------------------------------------------------- /projects/manager/src/lib/fire-manager.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FireUploaderModule } from '@ngx-fire-uploader/core'; 4 | 5 | import { FileItemComponent } from './file-item.component'; 6 | import { FireManagerComponent } from './fire-manager.component'; 7 | import { FileSizePipe } from './file-size.pipe'; 8 | import { SafeStylePipe } from './safe-style.pipe'; 9 | import { FireManagerConfig } from './fire-manager.model'; 10 | import { FireManager } from './fire-manager.service'; 11 | import { CONFIG } from './fire-manager.token'; 12 | 13 | export function previewerFactory(config: FireManagerConfig) { 14 | return new FireManager(config); 15 | } 16 | 17 | @NgModule({ 18 | imports: [ 19 | CommonModule, 20 | FireUploaderModule.forRoot() 21 | ], 22 | declarations: [ 23 | FireManagerComponent, 24 | FileItemComponent, 25 | FileSizePipe, 26 | SafeStylePipe 27 | ], 28 | exports: [ 29 | FireUploaderModule, 30 | FireManagerComponent, 31 | FileSizePipe 32 | ] 33 | }) 34 | export class FireManagerModule { 35 | static forRoot(config?: FireManagerConfig): ModuleWithProviders { 36 | return { 37 | ngModule: FireManagerModule, 38 | providers: [ 39 | {provide: CONFIG, useValue: config}, 40 | { 41 | provide: FireManager, 42 | useFactory: previewerFactory, 43 | deps: [CONFIG] 44 | } 45 | ] 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap/collapse/collapse.module'; 6 | import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap/dropdown/dropdown.module'; 7 | import { NgbProgressbarModule } from '@ng-bootstrap/ng-bootstrap/progressbar/progressbar.module'; 8 | import { SimpleNotificationsModule } from 'angular2-notifications'; 9 | 10 | import { AngularFireModule } from '@angular/fire'; 11 | import { AngularFirestoreModule } from '@angular/fire/firestore'; 12 | import { AngularFireStorageModule } from '@angular/fire/storage'; 13 | 14 | import { FireUploaderModule } from '@ngx-fire-uploader/core'; 15 | 16 | import { AppComponent } from './app.component'; 17 | import { environment } from '../environments/environment'; 18 | import { routes } from './routes'; 19 | 20 | @NgModule({ 21 | declarations: [ 22 | AppComponent 23 | ], 24 | imports: [ 25 | BrowserModule, 26 | BrowserAnimationsModule, 27 | RouterModule.forRoot(routes, {useHash: true}), 28 | NgbDropdownModule.forRoot(), 29 | NgbCollapseModule.forRoot(), 30 | NgbProgressbarModule.forRoot(), 31 | SimpleNotificationsModule.forRoot(), 32 | FireUploaderModule.forRoot(), 33 | AngularFireModule.initializeApp(environment.firebase), 34 | AngularFirestoreModule, 35 | AngularFireStorageModule 36 | ], 37 | bootstrap: [AppComponent] 38 | }) 39 | export class AppModule { 40 | } 41 | -------------------------------------------------------------------------------- /projects/photo/src/lib/fire-photo.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: inline-block; 3 | z-index: 10; 4 | width: 100%; 5 | height: 100%; 6 | overflow: hidden; 7 | } 8 | 9 | .fire-dropzone { 10 | position: absolute; 11 | left: 0; 12 | right: 0; 13 | top: 0; 14 | bottom: 0; 15 | display: flex; 16 | justify-content: center; 17 | align-items: center; 18 | z-index: 30; 19 | cursor: pointer; 20 | } 21 | 22 | .fire-photo { 23 | position: relative; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | background-size: cover; 28 | overflow: hidden; 29 | width: 100%; 30 | height: 100%; 31 | background-position: center center; 32 | } 33 | .fire-disabled { 34 | .fire-dropzone { 35 | cursor: unset; 36 | } 37 | } 38 | 39 | .fire-border { 40 | transition: all 300ms ease; 41 | position: absolute; 42 | top: 0; 43 | left: 0; 44 | right: 0; 45 | bottom: 0; 46 | &.fire-photo-hover { 47 | box-shadow: inset 0 0 0 4px #488aff; 48 | } 49 | } 50 | 51 | .fire-placeholder, .fire-loading { 52 | display: flex; 53 | align-items: center; 54 | justify-content: center; 55 | width: 100%; 56 | height: 100%; 57 | background-color: rgba(0, 0, 0, 0.3); 58 | color: #fff; 59 | font-weight: bold; 60 | } 61 | 62 | .fire-spinner { 63 | width: 38px; 64 | height: 38px; 65 | border-radius: 50%; 66 | box-sizing: border-box; 67 | animation: spinner-animation 600ms linear infinite; 68 | border: 3px solid transparent; 69 | border-top-color: #fff; 70 | border-left-color: #fff; 71 | } 72 | 73 | @keyframes spinner-animation { 74 | 0% { 75 | transform: rotate(0deg); 76 | } 77 | 100% { 78 | transform: rotate(360deg); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /projects/photo/src/lib/lazy-image.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, Output, OnDestroy, ElementRef, EventEmitter, Renderer2 } from '@angular/core'; 2 | import { Subject, zip, fromEvent } from 'rxjs'; 3 | import { tap, switchMap, filter } from 'rxjs/operators'; 4 | 5 | @Directive({ 6 | selector: '[lazyImage]' 7 | }) 8 | export class LazyImageDirective implements OnDestroy { 9 | 10 | // Lazy load worker 11 | private readonly _worker$ = new Subject(); 12 | 13 | @Input('lazyImage') 14 | set lazyImage(imagePath) { 15 | this.loadImage(imagePath); 16 | } 17 | 18 | @Output() loading = new EventEmitter(); 19 | 20 | constructor(private _el: ElementRef, private _renderer: Renderer2) { 21 | const img = new Image(); 22 | 23 | this._worker$.pipe( 24 | filter((imageSrc: string) => !!imageSrc), 25 | switchMap((imageSrc: string) => { 26 | 27 | // Image is loading 28 | this.loading.emit(true); 29 | 30 | // Stop previously loading 31 | img.src = imageSrc; 32 | 33 | // Image load success 34 | const loadSuccess = fromEvent(img, 'load').pipe( 35 | tap(() => { 36 | this._renderer.setStyle(this._el.nativeElement, 'backgroundImage', `url(${imageSrc})`); 37 | this.loading.emit(false); 38 | }) 39 | ); 40 | 41 | // Image load error 42 | const loadError = fromEvent(img, 'error').pipe(tap(() => this.loading.emit(false))); 43 | 44 | return zip(loadSuccess, loadError); 45 | }) 46 | ).subscribe(); 47 | } 48 | 49 | loadImage(imagePath) { 50 | this._worker$.next(imagePath); 51 | } 52 | 53 | ngOnDestroy() { 54 | this._worker$.complete(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Fire Uploader 2 | 3 | Fire Uploader is a plugin that extends `AngularFireStorage` service to help you manage files upload easily. 4 | 5 | [![demo](https://img.shields.io/badge/demo-online-ed1c46.svg)](https://ngx-fire-uploader.stackblitz.io) 6 | [![npm](https://img.shields.io/npm/v/@ngx-fire-uploader/core.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/@ngx-fire-uploader/core) 7 | [![travis](https://travis-ci.com/MurhafSousli/ngx-fire-uploader.svg?branch=master)](https://travis-ci.org/MurhafSousli/ngx-fire-uploader) 8 | [![downloads](https://img.shields.io/npm/dt/@ngx-fire-uploader/core.svg?maxAge=2592000?style=plastic)](https://www.npmjs.com/package/@ngx-fire-uploader/core) 9 | [![license](https://img.shields.io/npm/l/express.svg?maxAge=2592000)](/LICENSE) 10 | 11 | **This project is divided into 3 modules:** 12 | 13 | - @ngx-fire-uploader/core 14 | - @ngx-fire-uploader/photo 15 | - @ngx-fire-uploader/manager 16 | 17 | The documentation is at the [wiki page](https://github.com/MurhafSousli/ngx-fire-uploader/wiki) 18 | 19 | - [Stackblitz Template](https://stackblitz.com/edit/ngx-fire-uploader) - Remember to set your Firebase configuration in `app/app.module.ts`. 20 | 21 | This project is still in beta, I need your feedback and your contribution is extremely welcome! 22 | 23 | Besides bug reports and feature requests, I would like to know your opinion about: 24 | 25 | - Improvable of options and customization 26 | - Suggestions of adding useful common upload tasks (e.g. renaming, resizing, compressing ...etc). 27 | - Suggestions of removing extra features that you think are unnecessary. 28 | - Improve the documentations and fix my grammar mistakes :) 29 | 30 | ## Author 31 | 32 | **Murhaf Sousli** 33 | 34 | - [github/murhafsousli](https://github.com/MurhafSousli) 35 | - [twitter/murhafsousli](https://twitter.com/MurhafSousli) 36 | -------------------------------------------------------------------------------- /src/assets/pdf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 14 | 16 | 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 | -------------------------------------------------------------------------------- /.config/styles.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var node_sass_1 = require("node-sass"); 4 | var postcss = require("postcss"); 5 | var cssnano = require("cssnano"); 6 | var autoprefixer = require("autoprefixer"); 7 | var stripInlineComments = require("postcss-strip-inline-comments"); 8 | var fs_1 = require("fs"); 9 | var rxjs_1 = require("rxjs"); 10 | var operators_1 = require("rxjs/operators"); 11 | var utils_1 = require("./utils"); 12 | /** Compile SCSS to CSS */ 13 | var compileScssFile = operators_1.mergeMap(function (file) { 14 | var compileSass$ = rxjs_1.bindNodeCallback(node_sass_1.render); 15 | return compileSass$({ file: file.src }).pipe(operators_1.mergeMap(function (res) { return processCss(res.css); }), operators_1.mergeMap(function (result) { return createCssFile(file.distCss, result.css); }), operators_1.tap(function () { return utils_1.logSuccess(file); })); 16 | }); 17 | /** Process CSS file */ 18 | function processCss(cssData) { 19 | var CSS_PROCESSORS = [stripInlineComments, autoprefixer, cssnano]; 20 | var process$ = postcss(CSS_PROCESSORS).process(cssData.toString('utf8')); 21 | return rxjs_1.from(process$); 22 | } 23 | /** Create css file and save it to dist */ 24 | function createCssFile(target, cssContent) { 25 | var cssData = new Buffer(cssContent); 26 | var writeFile$ = rxjs_1.bindNodeCallback(fs_1.writeFile); 27 | // Write css file to dist 28 | return writeFile$(target, cssData); 29 | } 30 | function sendFileToWorker(target) { 31 | worker$.next(new utils_1.WorkFile(target, SRC_DIR, DIST_DIR)); 32 | } 33 | function startTask() { 34 | // check if SRC_DIR exists 35 | if (!fs_1.existsSync(SRC_DIR)) { 36 | utils_1.logError(SRC_DIR + " does not exist!"); 37 | return; 38 | } 39 | utils_1.readFiles(SRC_DIR, '.scss', sendFileToWorker); 40 | } 41 | var worker$ = new rxjs_1.Subject(); 42 | worker$.pipe(utils_1.makeDirectory, utils_1.copyFile, compileScssFile).subscribe(); 43 | /** Read arguments from process */ 44 | var SRC_DIR = process.argv[2]; 45 | var DIST_DIR = process.argv[3]; 46 | if (!SRC_DIR || !DIST_DIR) { 47 | throw new Error('Base dir has not been set!'); 48 | } 49 | startTask(); 50 | -------------------------------------------------------------------------------- /.config/styles.ts: -------------------------------------------------------------------------------- 1 | import { render as renderSass, Result } from 'node-sass'; 2 | import * as postcss from 'postcss'; 3 | import * as cssnano from 'cssnano'; 4 | import * as autoprefixer from 'autoprefixer'; 5 | import * as stripInlineComments from 'postcss-strip-inline-comments'; 6 | 7 | import { existsSync, writeFile } from 'fs'; 8 | import { Subject, bindNodeCallback, from } from 'rxjs'; 9 | import { mergeMap, tap } from 'rxjs/operators'; 10 | 11 | import { WorkFile, copyFile, makeDirectory, logSuccess, logError, readFiles } from './utils'; 12 | 13 | /** Compile SCSS to CSS */ 14 | const compileScssFile = mergeMap((file: WorkFile) => { 15 | const compileSass$: any = bindNodeCallback(renderSass); 16 | return compileSass$({file: file.src}).pipe( 17 | mergeMap((res: Result) => processCss(res.css)), 18 | mergeMap((result: any) => createCssFile(file.distCss, result.css)), 19 | tap(() => logSuccess(file)) 20 | ); 21 | }); 22 | 23 | /** Process CSS file */ 24 | function processCss(cssData: Buffer) { 25 | const CSS_PROCESSORS = [stripInlineComments, autoprefixer, cssnano]; 26 | const process$ = postcss(CSS_PROCESSORS).process(cssData.toString('utf8')); 27 | return from(process$); 28 | } 29 | 30 | /** Create css file and save it to dist */ 31 | function createCssFile(target: string, cssContent: string) { 32 | const cssData = new Buffer(cssContent); 33 | const writeFile$: any = bindNodeCallback(writeFile); 34 | // Write css file to dist 35 | return writeFile$(target, cssData); 36 | } 37 | 38 | function sendFileToWorker(target: string) { 39 | worker$.next(new WorkFile(target, SRC_DIR, DIST_DIR)); 40 | } 41 | 42 | function startTask() { 43 | // check if SRC_DIR exists 44 | if (!existsSync(SRC_DIR)) { 45 | logError( `${SRC_DIR} does not exist!`); 46 | return; 47 | } 48 | readFiles(SRC_DIR, '.scss', sendFileToWorker); 49 | } 50 | 51 | const worker$ = new Subject(); 52 | worker$.pipe( 53 | makeDirectory, 54 | copyFile, 55 | compileScssFile 56 | ).subscribe(); 57 | 58 | /** Read arguments from process */ 59 | const SRC_DIR = process.argv[2]; 60 | const DIST_DIR = process.argv[3]; 61 | if (!SRC_DIR || !DIST_DIR) { 62 | throw new Error('Base dir has not been set!'); 63 | } 64 | startTask(); 65 | -------------------------------------------------------------------------------- /projects/core/src/lib/fire-uploader.service.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable, Optional } from '@angular/core'; 2 | import { AngularFireStorage } from '@angular/fire/storage'; 3 | import { FireUploaderConfig } from './fire-uploader.model'; 4 | import { UPLOADER_CONFIG } from './fire-uploader.token'; 5 | import { DEFAULT_CONFIG } from './fire-uploader.default'; 6 | import { FireUploaderRef } from './fire-uploader-ref'; 7 | 8 | /** 9 | * FireUploader global service that can be used to access FireUploaderRef anywhere in the app 10 | */ 11 | @Injectable() 12 | export class FireUploader { 13 | /** Stores FireUploaderRef instances */ 14 | private readonly _instances = {}; 15 | 16 | /** Global default config */ 17 | config: FireUploaderConfig; 18 | 19 | /** Set default global config */ 20 | constructor(@Optional() @Inject(UPLOADER_CONFIG)config: FireUploaderConfig, private _storage: AngularFireStorage) { 21 | this.config = {...DEFAULT_CONFIG, ...config}; 22 | } 23 | 24 | /** Checks if uploader instance exists */ 25 | hasRef(id = 'root'): boolean { 26 | return this._instances[id] instanceof FireUploaderRef; 27 | } 28 | 29 | /** Get a FireUploaderRef */ 30 | ref(id = 'root', config?: FireUploaderConfig): FireUploaderRef { 31 | if (this.hasRef(this._instances[id])) { 32 | if (config) { 33 | this._instances[id].setConfig({...this.config, ...config}); 34 | } 35 | return this._instances[id]; 36 | } else { 37 | return this._instances[id] = new FireUploaderRef({...this.config, ...config}, this._storage); 38 | } 39 | } 40 | 41 | /** Destroy a uploader instance */ 42 | destroy(id = 'root') { 43 | if (this.hasRef(this._instances[id])) { 44 | this._instances[id].destroy(); 45 | this._instances[id] = null; 46 | } 47 | } 48 | 49 | /** Destroy all uploader instances */ 50 | destroyAll() { 51 | Object.keys(this._instances) 52 | .map((key) => { 53 | this._instances[key].destory(); 54 | this._instances[key] = null; 55 | }); 56 | } 57 | 58 | /** Reset all uploader instances */ 59 | resetAll() { 60 | Object.keys(this._instances) 61 | .map((id = 'root') => this._instances[id].uploader) 62 | .map((uploader: FireUploaderRef) => uploader.reset()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.config/utils.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, readdirSync } from 'fs'; 2 | import { dirname, relative, join, basename, extname } from 'path'; 3 | import { map } from 'rxjs/operators'; 4 | import * as replaceExt from 'replace-ext'; 5 | import * as c from 'ansi-colors'; 6 | 7 | export class WorkFile { 8 | distCss: string; 9 | distScss: string; 10 | 11 | constructor(public src: string, srcDir: string, distDir: string) { 12 | // Subtract src dir from file path 13 | const relativePath = relative(srcDir, src); 14 | // Add baseDist + relativePath 15 | this.distScss = join(distDir, relativePath); 16 | this.distCss = replaceExt(this.distScss, '.css'); 17 | } 18 | } 19 | 20 | /** Loop over all files from directory and sub-directories */ 21 | export function readFiles(filePath: string, fileType: string, callback: Function) { 22 | if (extname(filePath) === fileType) { 23 | callback(filePath); 24 | } else if (lstatSync(filePath).isDirectory()) { 25 | // src is directory 26 | const filesOrDirectories = readdirSync(filePath); 27 | filesOrDirectories.map((fileName: string) => { 28 | const fileOrDirectory = join(filePath, fileName); 29 | readFiles(fileOrDirectory, fileType, callback); 30 | }); 31 | } 32 | } 33 | 34 | /** Creates directories recursively */ 35 | export function dirMaker(target: string) { 36 | // check if parent directory exists 37 | const parentDir = dirname(target); 38 | if (!existsSync(parentDir)) { 39 | dirMaker(parentDir); 40 | } 41 | // check if directory exists 42 | if (!existsSync(target)) { 43 | mkdirSync(target); 44 | } 45 | } 46 | 47 | /** Make directory for the work file */ 48 | export const makeDirectory = map((file: WorkFile): WorkFile => { 49 | dirMaker(dirname(file.distScss)); 50 | return file; 51 | }); 52 | 53 | /** Copy file from src to dist */ 54 | export const copyFile = map((file: WorkFile): WorkFile => { 55 | createReadStream(file.src).pipe(createWriteStream(file.distScss)); 56 | return file; 57 | }); 58 | 59 | export function logError(err: any) { 60 | console.log(c.bgRedBright('[Error]:'), c.red(err)); 61 | } 62 | 63 | export function logSuccess(file: WorkFile) { 64 | console.log(c.magenta(basename(file.distScss)), c.green(c.symbols.check)); 65 | } 66 | -------------------------------------------------------------------------------- /src/app/basic-example/basic-example.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { NotificationsService } from 'angular2-notifications'; 3 | import { FileItem, ResizeMethod, FireUploaderProgress } from '@ngx-fire-uploader/core'; 4 | 5 | @Component({ 6 | selector: 'app-basic-example', 7 | templateUrl: './basic-example.component.html', 8 | styleUrls: ['./basic-example.component.scss'], 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | export class BasicExampleComponent { 12 | 13 | files = []; 14 | links = []; 15 | progress: FireUploaderProgress; 16 | active = false; 17 | 18 | notifOptions = { 19 | timeOut: 5000, 20 | showProgressBar: true, 21 | pauseOnHover: false, 22 | clickToClose: false, 23 | maxLength: 10 24 | }; 25 | 26 | uniqueName = true; 27 | dropZone = true; 28 | multiple = true; 29 | maxFilesCount = 20; 30 | maxFileSize = 5; 31 | paramName; 32 | paramDir; 33 | placeholder = 'Drop files here or click to select'; 34 | accept = null; 35 | parallelUploads = 1; 36 | thumbs = true; 37 | thumbWidth = 100; 38 | thumbHeight = 100; 39 | resizeWidth; 40 | resizeHeight; 41 | resizeMethod = ResizeMethod.Crop; 42 | 43 | constructor(private notifications: NotificationsService) { 44 | } 45 | 46 | onFiles(e) { 47 | this.files = e; 48 | } 49 | 50 | onSuccess(e: FileItem) { 51 | this.notifications.success('File uploaded successfully!', e.state.name, this.notifOptions); 52 | } 53 | 54 | onComplete(e) { 55 | this.links = e.map(file => file.downloadURL); 56 | this.notifications.info('Operation finished!', `${this.links.length} files has been uploaded`, this.notifOptions); 57 | } 58 | 59 | onProgress(e) { 60 | this.progress = e; 61 | } 62 | 63 | onRemove(e: FileItem) { 64 | this.notifications.info('File removed!', e.state.name, this.notifOptions); 65 | } 66 | 67 | onCancel(e: FileItem) { 68 | this.notifications.info('Upload cancelled!', e.state.name, this.notifOptions); 69 | } 70 | 71 | onError(e) { 72 | this.notifications.error('Error!', e.message, this.notifOptions); 73 | } 74 | 75 | onReset() { 76 | this.notifications.alert('Cleared!', 'All items has been removed', this.notifOptions); 77 | } 78 | 79 | onValue(e) { 80 | console.log('value', e); 81 | } 82 | 83 | onActive(e) { 84 | this.active = e; 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /.config/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | exports.__esModule = true; 3 | var fs_1 = require("fs"); 4 | var path_1 = require("path"); 5 | var operators_1 = require("rxjs/operators"); 6 | var replaceExt = require("replace-ext"); 7 | var c = require("ansi-colors"); 8 | var WorkFile = /** @class */ (function () { 9 | function WorkFile(src, srcDir, distDir) { 10 | this.src = src; 11 | // Subtract src dir from file path 12 | var relativePath = path_1.relative(srcDir, src); 13 | // Add baseDist + relativePath 14 | this.distScss = path_1.join(distDir, relativePath); 15 | this.distCss = replaceExt(this.distScss, '.css'); 16 | } 17 | return WorkFile; 18 | }()); 19 | exports.WorkFile = WorkFile; 20 | /** Loop over all files from directory and sub-directories */ 21 | function readFiles(filePath, fileType, callback) { 22 | if (path_1.extname(filePath) === fileType) { 23 | callback(filePath); 24 | } 25 | else if (fs_1.lstatSync(filePath).isDirectory()) { 26 | // src is directory 27 | var filesOrDirectories = fs_1.readdirSync(filePath); 28 | filesOrDirectories.map(function (fileName) { 29 | var fileOrDirectory = path_1.join(filePath, fileName); 30 | readFiles(fileOrDirectory, fileType, callback); 31 | }); 32 | } 33 | } 34 | exports.readFiles = readFiles; 35 | /** Creates directories recursively */ 36 | function dirMaker(target) { 37 | // check if parent directory exists 38 | var parentDir = path_1.dirname(target); 39 | if (!fs_1.existsSync(parentDir)) { 40 | dirMaker(parentDir); 41 | } 42 | // check if directory exists 43 | if (!fs_1.existsSync(target)) { 44 | fs_1.mkdirSync(target); 45 | } 46 | } 47 | exports.dirMaker = dirMaker; 48 | /** Make directory for the work file */ 49 | exports.makeDirectory = operators_1.map(function (file) { 50 | dirMaker(path_1.dirname(file.distScss)); 51 | return file; 52 | }); 53 | /** Copy file from src to dist */ 54 | exports.copyFile = operators_1.map(function (file) { 55 | fs_1.createReadStream(file.src).pipe(fs_1.createWriteStream(file.distScss)); 56 | return file; 57 | }); 58 | function logError(err) { 59 | console.log(c.bgRedBright('[Error]:'), c.red(err)); 60 | } 61 | exports.logError = logError; 62 | function logSuccess(file) { 63 | console.log(c.magenta(path_1.basename(file.distScss)), c.green(c.symbols.check)); 64 | } 65 | exports.logSuccess = logSuccess; 66 | -------------------------------------------------------------------------------- /projects/core/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { Observable, from } from 'rxjs'; 2 | import { debounceTime, map, mergeAll, reduce, switchMap } from 'rxjs/operators'; 3 | import { FileItem } from './file-item'; 4 | import { FileState, FireUploaderState } from './fire-uploader.model'; 5 | 6 | /** Combine the states of all files in a single state */ 7 | export function combineStates(files: FileItem[]): Observable { 8 | 9 | const state$Arr = from(files).pipe(map((item: FileItem) => item.state$)); 10 | const stateArr = from(files).pipe(map((item: FileItem) => item.state)); 11 | 12 | return from(state$Arr).pipe( 13 | mergeAll(), 14 | debounceTime(100), 15 | switchMap(() => from(stateArr).pipe( 16 | reduce((total: FileState, state: FileState) => { 17 | return { 18 | active: total.active || state.active, 19 | progress: { 20 | percentage: (total.progress.bytesTransferred / total.progress.totalBytes) * 100, 21 | bytesTransferred: total.progress.bytesTransferred + state.progress.bytesTransferred, 22 | totalBytes: total.progress.totalBytes + state.progress.totalBytes 23 | } 24 | }; 25 | }) 26 | )) 27 | ); 28 | } 29 | 30 | /** Convert file size to MB */ 31 | export function convertToMB(size: number) { 32 | return size / 1024 / 1024; 33 | } 34 | 35 | /** Splice files array into chunks for parallel upload */ 36 | export function parallizeUploads(files: FileItem[], parallelUploads: number): Observable { 37 | const arr = []; 38 | let i, j; 39 | for (i = 0, j = files.length; i < j; i += parallelUploads) { 40 | arr.push(files.slice(i, i + parallelUploads)); 41 | } 42 | return from(arr); 43 | } 44 | 45 | export function blobToFile(theBlob: Blob, type: string, fileName: string): File { 46 | const blob: any = new Blob([theBlob], {type: type}); 47 | blob.lastModifiedDate = new Date(); 48 | blob.name = fileName; 49 | return blob; 50 | } 51 | 52 | /** Uploader errors */ 53 | export const maxFilesError = (maxFiles: number) => { 54 | return { 55 | type: 'uploader/count_limit_exceeded', 56 | message: `Max files has exceeded, Only ${maxFiles} is accepted.` 57 | }; 58 | }; 59 | 60 | /** Throw error when max file size has exceeded */ 61 | export const maxFileSizeError = (fileName: string) => { 62 | return { 63 | type: 'uploader/size_limit_exceeded', 64 | message: `${fileName} has exceeded the max size allowed.` 65 | }; 66 | }; 67 | 68 | /** Check if a file type is image */ 69 | export const isImage = (file: File) => { 70 | return file.type.split('/')[0] === 'image'; 71 | }; 72 | -------------------------------------------------------------------------------- /projects/core/src/lib/fire-uploader.model.ts: -------------------------------------------------------------------------------- 1 | import { FileItem } from './file-item'; 2 | 3 | /** Upload progress state */ 4 | export interface FireUploaderProgress { 5 | /** Progress percentage */ 6 | percentage?: number; 7 | /** Bytes transferred */ 8 | bytesTransferred?: number; 9 | /** Total size in bytes */ 10 | totalBytes?: number; 11 | } 12 | 13 | /** Uploader state */ 14 | export interface FireUploaderState { 15 | /** Queued files */ 16 | files?: FileItem[]; 17 | /** Total upload progress */ 18 | progress?: FireUploaderProgress; 19 | /** Uploader active state */ 20 | active?: boolean; 21 | } 22 | 23 | /** FileItem state */ 24 | export interface FileState { 25 | /** File name */ 26 | name?: string; 27 | /** File type */ 28 | type?: string; 29 | /** File extension */ 30 | extension?: string; 31 | /** File upload progress */ 32 | progress?: FireUploaderProgress; 33 | /** If file is an image, this will be the image thumbnail */ 34 | thumbnail?: string; 35 | /** File download URL if it has been uploaded */ 36 | downloadURL?: string; 37 | /** File upload task state */ 38 | active?: boolean; 39 | /** File state */ 40 | state?: string; 41 | } 42 | 43 | /** Uploader config */ 44 | export interface FireUploaderConfig { 45 | /** Stores file in directory, e.g. photos/{FileName}. */ 46 | paramDir?: string; 47 | /** Stores file with custom name in the firebase storage. */ 48 | paramName?: string; 49 | /** Adds current date to file's name. */ 50 | uniqueName?: boolean; 51 | /** Enables multiple file select. */ 52 | multiple?: boolean; 53 | /** The accepted files types. */ 54 | accept?: string; 55 | /** Maximum number of files uploading at a time. */ 56 | parallelUploads?: number; 57 | /** Maximum files count in the queue. */ 58 | maxFiles?: number; 59 | /** Maximum file size in MB. */ 60 | maxFileSize?: number; 61 | /** Starts uploading on file select. */ 62 | autoStart?: boolean; 63 | /** Generate thumbnails for image files. */ 64 | thumbs?: boolean; 65 | /** Thumbnail width in px. */ 66 | thumbWidth?: number; 67 | /** Thumbnail height in px. */ 68 | thumbHeight?: number; 69 | /** The method used to generate the thumbnails. */ 70 | thumbMethod?: ResizeMethod; 71 | /** The method used to resize the images before uploading. */ 72 | resizeMethod?: ResizeMethod; 73 | /** Image new width in px. */ 74 | resizeWidth?: number; 75 | /** Image new height in px. */ 76 | resizeHeight?: number; 77 | /** Quality of re-sized image between 0 and 1 (not on Edge). */ 78 | resizeQuality?: number; 79 | } 80 | 81 | /** Resize method options */ 82 | export enum ResizeMethod { 83 | Crop = 'crop', 84 | Contain = 'contain' 85 | } 86 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | import 'core-js/es6/symbol'; 23 | import 'core-js/es6/object'; 24 | import 'core-js/es6/function'; 25 | import 'core-js/es6/parse-int'; 26 | import 'core-js/es6/parse-float'; 27 | import 'core-js/es6/number'; 28 | import 'core-js/es6/math'; 29 | import 'core-js/es6/string'; 30 | import 'core-js/es6/date'; 31 | import 'core-js/es6/array'; 32 | import 'core-js/es6/regexp'; 33 | import 'core-js/es6/map'; 34 | import 'core-js/es6/weak-map'; 35 | import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | /** 56 | * By default, zone.js will patch all possible macroTask and DomEvents 57 | * user can disable parts of macroTask/DomEvents patch by setting following flags 58 | */ 59 | 60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 63 | 64 | /* 65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 67 | */ 68 | // (window as any).__Zone_enable_cross_context_check = true; 69 | 70 | /*************************************************************************************************** 71 | * Zone JS is required by default for Angular itself. 72 | */ 73 | import 'zone.js/dist/zone'; // Included with Angular CLI. 74 | 75 | 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "use-input-property-decorator": true, 121 | "use-output-property-decorator": true, 122 | "use-host-property-decorator": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /projects/core/src/lib/image-resizer.ts: -------------------------------------------------------------------------------- 1 | import { Observable, fromEvent, of } from 'rxjs'; 2 | import { map, switchMap } from 'rxjs/operators'; 3 | import { ResizeMethod } from './fire-uploader.model'; 4 | import { blobToFile } from './utils'; 5 | 6 | interface ImageSize { 7 | width: number; 8 | height: number; 9 | } 10 | 11 | /** 12 | * Image re-sizer function 13 | */ 14 | export function resizeImage(file: File, 15 | maxWidth: number, 16 | maxHeight: number, 17 | method = ResizeMethod.Crop, 18 | quality: number): Observable { 19 | const image = new Image(); 20 | return of({}).pipe( 21 | map(() => initSize(maxWidth, maxHeight)), 22 | switchMap((size: ImageSize) => { 23 | return loadImage(image, file).pipe( 24 | map(() => resize(image, method, size)), 25 | map((newSize: ImageSize) => imageToCanvas(image, newSize.width, newSize.height)), 26 | switchMap((canvas: HTMLCanvasElement) => canvasToFile(canvas, file.name, file.type, quality)) 27 | ); 28 | }) 29 | ); 30 | } 31 | 32 | /** 33 | * Initialize image size 34 | */ 35 | function initSize(maxWidth: number, maxHeight: number): ImageSize { 36 | return { 37 | width: maxWidth ? maxWidth : maxHeight, 38 | height: maxHeight ? maxHeight : maxWidth 39 | }; 40 | } 41 | 42 | /** 43 | * Load Image 44 | */ 45 | function loadImage(image: HTMLImageElement, file: File): Observable { 46 | const loadSuccess = fromEvent(image, 'load'); 47 | image.src = URL.createObjectURL(file); 48 | return loadSuccess; 49 | } 50 | 51 | /** 52 | * Get proper size using contain size or cover size 53 | */ 54 | function resize(image: HTMLImageElement, method: ResizeMethod, maxSize: ImageSize): ImageSize { 55 | switch (method) { 56 | case ResizeMethod.Contain: 57 | if (image.width > image.height) { 58 | return { 59 | width: image.width * (maxSize.height / image.height), 60 | height: maxSize.height 61 | }; 62 | } 63 | return { 64 | width: maxSize.width, 65 | height: image.height * (maxSize.width / image.width) 66 | }; 67 | case ResizeMethod.Crop: 68 | if (image.width > image.height) { 69 | return { 70 | width: maxSize.width, 71 | height: image.height * (maxSize.width / image.width) 72 | }; 73 | } 74 | return { 75 | width: image.width * (maxSize.height / image.height), 76 | height: maxSize.height 77 | }; 78 | } 79 | } 80 | 81 | /** 82 | * Convert image to canvas 83 | */ 84 | function imageToCanvas(image: HTMLImageElement, width: number, height: number): HTMLCanvasElement { 85 | const canvas = document.createElement('canvas'); 86 | canvas.width = width; 87 | canvas.height = height; 88 | const context = canvas.getContext('2d'); 89 | context.drawImage(image, 0, 0, width, height); 90 | return canvas; 91 | } 92 | 93 | /** 94 | * Convert canvas to file 95 | */ 96 | function canvasToFile(canvas: HTMLCanvasElement, name: string, type: string, quality: number): Observable { 97 | if (typeof canvas.toBlob === 'function') { 98 | return new Observable(observer => { 99 | canvas.toBlob((blob: Blob) => { 100 | // Create new image file and set image type 101 | const file = blobToFile(blob, type, name); 102 | observer.next(file); 103 | observer.complete(); 104 | }, type, quality); 105 | }); 106 | } else { 107 | return of(blobToFile(canvas.msToBlob(), type, name)); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngx-fire-uploader/core", 3 | "description": "Angular file uploader for Firebase", 4 | "version": "0.9.9", 5 | "homepage": "http://github.com/murhafsousli/ngx-fire-uploader", 6 | "author": { 7 | "name": "Murhaf Sousli", 8 | "url": "https://github.com/murhafsousli" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/murhafsousli/ngx-fire-uploader.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/murhafsousli/ngx-fire-uploader/issues" 16 | }, 17 | "license": "MIT", 18 | "keywords": [ 19 | "angular", 20 | "firebase", 21 | "file", 22 | "images", 23 | "upload", 24 | "uploader", 25 | "dropzone", 26 | "dropfile", 27 | "drop" 28 | ], 29 | "private": false, 30 | "scripts": { 31 | "ng": "ng", 32 | "start": "ng serve", 33 | "build": "ng build", 34 | "test": "ng test", 35 | "lint": "ng lint", 36 | "e2e": "ng e2e", 37 | "compodoc": "./node_modules/.bin/compodoc -p projects/core/tsconfig.lib.json -w -s", 38 | "sass-ts": "tsc .config/styles.ts --lib es7 && node .config/styles.js projects/manager/src/lib dist/manager", 39 | "sass": "node .config/styles.js projects/manager/src/lib dist/manager", 40 | "init-build": "npm run build-core && npm run version && npm run link-core && npm run build-manager && npm run version && npm run link-manager && npm run build-photo && npm run version && npm run link-photo && npm run sass-ts && npm run version", 41 | "build-all": "npm run build-core && npm run build-manager && npm run build-photo && npm run sass && npm run version", 42 | "build-core": "ng build core --prod", 43 | "build-manager": "ng build manager --prod", 44 | "build-photo": "ng build photo --prod", 45 | "link-core": "npm link dist/core && npm link @ngx-fire-uploader/core", 46 | "link-manager": "npm link dist/manager && npm link @ngx-fire-uploader/manager", 47 | "link-photo": "npm link dist/photo && npm link @ngx-fire-uploader/photo", 48 | "publish-all": "node .config/publish.js", 49 | "version": "node .config/version.js" 50 | }, 51 | "dependencies": { 52 | "@angular/animations": "^6.1.2", 53 | "@angular/common": "^6.1.2", 54 | "@angular/compiler": "^6.1.2", 55 | "@angular/core": "^6.1.2", 56 | "@angular/fire": "^5.1.1", 57 | "@angular/forms": "^6.1.2", 58 | "@angular/http": "^6.1.2", 59 | "@angular/platform-browser": "^6.1.2", 60 | "@angular/platform-browser-dynamic": "^6.1.2", 61 | "@angular/router": "^6.1.2", 62 | "@firebase/app-types": "^0.3.2", 63 | "@ng-bootstrap/ng-bootstrap": "^2.2.2", 64 | "angular2-notifications": "^1.0.2", 65 | "classlist.js": "^1.1.20150312", 66 | "core-js": "^2.5.4", 67 | "firebase": "^5.7.1", 68 | "rxjs": "^6.2.2", 69 | "zone.js": "^0.8.26" 70 | }, 71 | "devDependencies": { 72 | "@angular-devkit/build-angular": "~0.7.3", 73 | "@angular-devkit/build-ng-packagr": "~0.7.3", 74 | "@angular/cli": "^6.1.3", 75 | "@angular/compiler-cli": "^6.1.2", 76 | "@angular/language-service": "^6.1.2", 77 | "@compodoc/compodoc": "^1.1.3", 78 | "@types/bluebird": "^3.5.23", 79 | "@types/core-js": "^2.5.0", 80 | "@types/jasmine": "~2.8.6", 81 | "@types/jasminewd2": "~2.0.3", 82 | "@types/node": "~10.5.8", 83 | "ansi-colors": "^2.0.5", 84 | "autoprefixer": "^9.1.0", 85 | "codelyzer": "~4.4.3", 86 | "cssnano": "^4.0.5", 87 | "jasmine-core": "~3.2.0", 88 | "jasmine-spec-reporter": "~4.2.1", 89 | "karma": "~3.0.0", 90 | "karma-chrome-launcher": "~2.2.0", 91 | "karma-coverage-istanbul-reporter": "~2.0.1", 92 | "karma-jasmine": "~1.1.1", 93 | "karma-jasmine-html-reporter": "^1.2.0", 94 | "ng-packagr": "^3.0.6", 95 | "postcss": "^7.0.2", 96 | "postcss-strip-inline-comments": "^0.1.5", 97 | "protractor": "~5.4.0", 98 | "replace-ext": "^1.0.0", 99 | "ts-node": "~7.0.1", 100 | "tsickle": "0.34.0", 101 | "tslib": "^1.9.3", 102 | "tslint": "~5.11.0", 103 | "typescript": "^2.9.2" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /projects/manager/src/lib/fire-manager.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit, Output, HostBinding, OnChanges, OnDestroy, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { FireUploader, FireUploaderRef, FileItem, ResizeMethod, FireUploaderProgress } from '@ngx-fire-uploader/core'; 3 | import { FireManager } from './fire-manager.service'; 4 | 5 | @Component({ 6 | selector: 'fire-manager', 7 | preserveWhitespaces: false, 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | templateUrl: './fire-manager.component.html' 10 | }) 11 | export class FireManagerComponent implements OnInit, OnChanges, OnDestroy { 12 | @Input() id = 'root'; 13 | @Input() dropZone: boolean = this._manager.config.dropZone; 14 | 15 | @Input() paramName: string = this._manager.config.paramName; 16 | @Input() paramDir: string = this._manager.config.paramDir; 17 | @Input() uniqueName: boolean = this._manager.config.uniqueName; 18 | @Input() maxFilesCount: number = this._manager.config.maxFilesCount; 19 | @Input() maxFileSize: number = this._manager.config.maxFileSize; 20 | @Input() parallelUploads: number = this._manager.config.parallelUploads; 21 | @Input() multiple: boolean = this._manager.config.multiple; 22 | @Input() accept: string = this._manager.config.accept; 23 | @Input() autoStart: boolean = this._manager.config.autoStart; 24 | @Input() thumbs: boolean = this._manager.config.thumbs; 25 | @Input() thumbWidth: number = this._manager.config.thumbWidth; 26 | @Input() thumbHeight: number = this._manager.config.thumbHeight; 27 | @Input() thumbMethod: ResizeMethod = this._manager.config.thumbMethod; 28 | @Input() resizeMethod: ResizeMethod = this._manager.config.resizeMethod; 29 | @Input() resizeWidth: number = this._manager.config.resizeWidth; 30 | @Input() resizeHeight: number = this._manager.config.resizeHeight; 31 | @Input() resizeQuality: number = this._manager.config.resizeQuality; 32 | 33 | // Reference to the uploader 34 | @Input() uploaderRef: FireUploaderRef; 35 | 36 | // Show the fire-uploader progress bar of each file item 37 | @Input() showProgress: boolean = this._manager.config.showProgress; 38 | 39 | // Shows name and size of each file item 40 | @Input() showDetails: boolean = this._manager.config.showDetails; 41 | 42 | // Show remove button 43 | @Input() showRemove: boolean = this._manager.config.showRemove; 44 | 45 | // Set item background based on file extension 46 | @Input() extensions: any = this._manager.config.extensions; 47 | 48 | @Output() itemClick = new EventEmitter(); 49 | @Output() files = new EventEmitter(); 50 | @Output() value = new EventEmitter(); 51 | @Output() complete = new EventEmitter(); 52 | @Output() success = new EventEmitter(); 53 | @Output() progress = new EventEmitter(); 54 | @Output('remove') removeEmitter = new EventEmitter(); 55 | @Output('cancel') cancelEmitter = new EventEmitter(); 56 | @Output('reset') resetEmitter = new EventEmitter(); 57 | @Output() active = new EventEmitter(); 58 | @Output() error = new EventEmitter(); 59 | 60 | @HostBinding('class.dragover') hoverClass; 61 | 62 | constructor(private _uploader: FireUploader, private _manager: FireManager) { 63 | } 64 | 65 | private getConfig() { 66 | return { 67 | paramName: this.paramName, 68 | paramDir: this.paramDir, 69 | uniqueName: this.uniqueName, 70 | maxFiles: this.maxFilesCount, 71 | maxFileSize: this.maxFilesCount, 72 | parallelUploads: this.parallelUploads, 73 | multiple: this.multiple, 74 | accept: this.accept, 75 | autoStart: this.autoStart, 76 | thumbs: this.thumbs, 77 | thumbWidth: this.thumbWidth, 78 | thumbHeight: this.thumbHeight, 79 | thumbMethod: this.thumbMethod, 80 | resizeHeight: this.resizeHeight, 81 | resizeMethod: this.resizeMethod, 82 | resizeWidth: this.resizeWidth, 83 | resizeQuality: this.resizeQuality 84 | }; 85 | } 86 | 87 | ngOnInit() { 88 | this.uploaderRef = this._uploader.ref(this.id, this.getConfig()); 89 | 90 | this.uploaderRef.success$.subscribe((file: FileItem) => this.success.emit(file)); 91 | this.uploaderRef.progress$.subscribe((progress: FireUploaderProgress) => this.progress.emit(progress)); 92 | this.uploaderRef.active$.subscribe((active: boolean) => this.active.emit(active)); 93 | this.uploaderRef.files$.subscribe((files: FileItem[]) => this.files.emit(files)); 94 | this.uploaderRef.value$.subscribe((value: string[]) => this.value.emit(value)); 95 | this.uploaderRef.remove$.subscribe((file: FileItem) => this.removeEmitter.emit(file)); 96 | this.uploaderRef.cancel$.subscribe((file: FileItem) => this.cancelEmitter.emit(file)); 97 | this.uploaderRef.complete$.subscribe((files: FileItem[]) => this.complete.emit(files)); 98 | this.uploaderRef.reset$.subscribe(() => this.resetEmitter.emit()); 99 | } 100 | 101 | ngOnDestroy() { 102 | this._uploader.destroy(this.id); 103 | } 104 | 105 | ngOnChanges() { 106 | if (this.uploaderRef instanceof FireUploaderRef) { 107 | // Update uploader's config when inputs change 108 | this.uploaderRef.setConfig(this.getConfig()); 109 | } 110 | } 111 | 112 | itemClicked(e: Event, file: FileItem) { 113 | e.stopPropagation(); 114 | this.itemClick.emit(file); 115 | } 116 | 117 | remove(file: FileItem) { 118 | this.uploaderRef.removeItem(file); 119 | } 120 | 121 | select() { 122 | this.uploaderRef.select(); 123 | } 124 | 125 | start() { 126 | this.uploaderRef.start(); 127 | } 128 | 129 | pause() { 130 | this.uploaderRef.pause(); 131 | } 132 | 133 | resume() { 134 | this.uploaderRef.resume(); 135 | } 136 | 137 | cancel() { 138 | this.uploaderRef.cancel(); 139 | } 140 | 141 | reset() { 142 | this.uploaderRef.reset(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /projects/core/src/lib/file-item.ts: -------------------------------------------------------------------------------- 1 | import { AngularFireStorage, AngularFireStorageReference, AngularFireUploadTask } from '@angular/fire/storage'; 2 | import { UploadTaskSnapshot } from '@angular/fire/storage/interfaces'; 3 | import { BehaviorSubject, Observable, from, of } from 'rxjs'; 4 | import { filter, switchMap, take, tap } from 'rxjs/operators'; 5 | import { FileState, FireUploaderConfig } from './fire-uploader.model'; 6 | import { isImage } from './utils'; 7 | import { resizeImage } from './image-resizer'; 8 | 9 | /** 10 | * FileItem class for each file in the uploader queue 11 | */ 12 | export class FileItem { 13 | 14 | /** File storage ref */ 15 | private readonly _ref: AngularFireStorageReference; 16 | 17 | /** File path in fire storage */ 18 | private readonly _path: string; 19 | 20 | /** File upload task ref */ 21 | private _task: AngularFireUploadTask; 22 | 23 | /** FileItem state */ 24 | state: FileState = { 25 | active: false, 26 | state: 'init', 27 | progress: { 28 | percentage: 0, 29 | bytesTransferred: 0, 30 | totalBytes: 0 31 | } 32 | }; 33 | 34 | /** Stream that emits FileItem state */ 35 | state$ = new BehaviorSubject(this.state); 36 | 37 | /** Creates FileItem class for each file added to uploader queue */ 38 | constructor(public file: File, public config: FireUploaderConfig, private _storage: AngularFireStorage) { 39 | 40 | // Initialize file item state 41 | this.updateState({ 42 | name: file.name, 43 | type: file.type, 44 | extension: file.name.split('.').pop(), 45 | progress: { 46 | percentage: 0, 47 | bytesTransferred: 0, 48 | totalBytes: file.size 49 | } 50 | }); 51 | 52 | // Set directory name where the file should be stored in the fire cloud 53 | const dirName = this.config.paramDir ? `${this.config.paramDir}/` : ''; 54 | 55 | // Set a prefix to the file name, useful to avoid overriding an existing file 56 | const prefixName = this.config.uniqueName ? `${new Date().getTime()}_` : ''; 57 | 58 | // Set file name to either a custom name or the original file name 59 | const fileName = this.config.paramName || file.name; 60 | 61 | this._path = dirName + prefixName + fileName; 62 | this._ref = this._storage.ref(this._path); 63 | } 64 | 65 | /** Prepare file, functions to be executed when a file is added (Used for image files) */ 66 | prepare(): Observable { 67 | return of({}).pipe( 68 | // Check if file type is image 69 | filter(() => isImage(this.file)), 70 | switchMap(() => this.generateThumb()), 71 | switchMap(() => this.resizeImage()), 72 | take(1) 73 | ); 74 | } 75 | 76 | /** Assign AngularFireUploadTask to FileItem */ 77 | upload(): Observable { 78 | this._task = this._storage.upload(this._path, this.file); 79 | this._task.snapshotChanges().pipe(tap((snapshot: UploadTaskSnapshot) => this.onSnapshotChanges(snapshot))).subscribe(); 80 | return from(this._task).pipe(switchMap((task: UploadTaskSnapshot) => this.onTaskComplete(task))); 81 | } 82 | 83 | /** Delete file after it is uploaded */ 84 | delete(): Observable { 85 | return this._ref.delete(); 86 | } 87 | 88 | /** Pause upload task */ 89 | pause() { 90 | if (this._task) { 91 | this._task.pause(); 92 | } 93 | } 94 | 95 | /** Resume upload task */ 96 | resume() { 97 | if (this._task) { 98 | this._task.resume(); 99 | } 100 | } 101 | 102 | /** Cancel upload task */ 103 | cancel() { 104 | if (this._task) { 105 | this._task.cancel(); 106 | } 107 | } 108 | 109 | destroy() { 110 | this.state$.complete(); 111 | } 112 | 113 | /** Update FileItem state */ 114 | private updateState(state: FileState) { 115 | this.state = {...this.state, ...state}; 116 | this.state$.next(this.state); 117 | } 118 | 119 | /** Update FileItem state when UploadTaskSnapshot changes */ 120 | private onSnapshotChanges(state: UploadTaskSnapshot) { 121 | this.updateState({ 122 | active: state.state === 'running', 123 | state: state.state, 124 | progress: { 125 | percentage: (state.bytesTransferred / state.totalBytes) * 100, 126 | bytesTransferred: state.bytesTransferred, 127 | totalBytes: state.totalBytes 128 | } 129 | }); 130 | } 131 | 132 | /** Update FileItem state when UploadTaskSnapshot completes */ 133 | private onTaskComplete(task: UploadTaskSnapshot) { 134 | return this._ref.getDownloadURL().pipe( 135 | tap((downloadURL: string) => { 136 | this.updateState({ 137 | downloadURL: downloadURL, 138 | active: false, 139 | state: task.state, 140 | progress: { 141 | percentage: 100, 142 | bytesTransferred: task.bytesTransferred, 143 | totalBytes: task.totalBytes 144 | } 145 | }); 146 | }) 147 | ); 148 | } 149 | 150 | /** Generate image thumbnail */ 151 | private generateThumb(): Observable { 152 | if (this.config.thumbs) { 153 | // Update file item state with thumbnail 154 | return resizeImage(this.file, this.config.thumbWidth, this.config.thumbHeight, this.config.thumbMethod, 1).pipe( 155 | tap((blob: Blob) => this.updateState({thumbnail: window.URL.createObjectURL(blob)})) 156 | ); 157 | } 158 | return of({}); 159 | } 160 | 161 | /** Resize image */ 162 | private resizeImage(): Observable { 163 | if (this.config.resizeWidth || this.config.resizeHeight) { 164 | return resizeImage(this.file, this.config.resizeWidth, this.config.resizeHeight, this.config.thumbMethod, 1).pipe( 165 | tap((newFile: File) => { 166 | this.file = newFile; 167 | this.updateState({ 168 | progress: { 169 | percentage: 0, 170 | bytesTransferred: 0, 171 | totalBytes: newFile.size 172 | } 173 | }); 174 | }) 175 | ); 176 | } 177 | return of({}); 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /projects/manager/src/lib/fire-manager.scss: -------------------------------------------------------------------------------- 1 | fire-manager { 2 | position: relative; 3 | z-index: 100; 4 | display: flex; 5 | justify-content: space-around; 6 | width: 100%; 7 | min-height: 150px; 8 | padding: 1em; 9 | } 10 | 11 | .dropzone { 12 | padding: 1em; 13 | cursor: pointer; 14 | } 15 | 16 | .dropzone, .dropzone-placeholder, .overlay-layer { 17 | position: absolute; 18 | left: 0; 19 | right: 0; 20 | top: 0; 21 | bottom: 0; 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | } 26 | 27 | .overlay-layer { 28 | color: #E9ECEF; 29 | box-shadow: inset 0 0 0 3px lightgray; 30 | } 31 | 32 | .running { 33 | .overlay-layer { 34 | color: limegreen; 35 | box-shadow: inset 0 0 0 3px limegreen; 36 | } 37 | } 38 | 39 | .dragover { 40 | .overlay-layer { 41 | color: #1B95E0; 42 | box-shadow: inset 0 0 0 3px #1B95E0; 43 | } 44 | } 45 | 46 | file-item { 47 | display: block; 48 | position: relative; 49 | margin: 6px; 50 | user-select: none; 51 | &:hover { 52 | .file-details { 53 | opacity: 1; 54 | } 55 | .file-thumb { 56 | filter: blur(4px); 57 | } 58 | } 59 | } 60 | 61 | .file-progress-bar { 62 | opacity: 0; 63 | position: absolute; 64 | transform: translate(-50%, -50%); 65 | top: 50%; 66 | left: 50%; 67 | width: 70%; 68 | height: 15%; 69 | border-radius: 5px; 70 | overflow: hidden; 71 | background-color: rgba(white, 0.85); 72 | } 73 | 74 | .file-bar { 75 | position: absolute; 76 | height: 100%; 77 | width: 100%; 78 | transform: translate3d(-100%, 0, 0); 79 | transition: transform linear 0.2s; 80 | background: #1FA2FF; 81 | background: linear-gradient(to right, #A6FFCB, #12D8FA, #1FA2FF); 82 | } 83 | 84 | .file-running, 85 | .file-paused { 86 | .file-progress-bar { 87 | opacity: 1; 88 | } 89 | } 90 | 91 | .file-success { 92 | .file-progress-bar { 93 | animation: hide-progress ease-in-out 1.5s; 94 | animation-fill-mode: forwards; 95 | } 96 | 97 | .file-success-icon { 98 | display: block; 99 | } 100 | } 101 | 102 | .file-error { 103 | .file-error-icon { 104 | display: block; 105 | } 106 | } 107 | 108 | .file-progress-bar, .file-success-icon, .file-error-icon { 109 | animation-iteration-count: 1; 110 | transform-origin: 50% 50%; 111 | animation-fill-mode: forwards; 112 | } 113 | 114 | .file-success-icon, .file-error-icon { 115 | display: none; 116 | background-repeat: no-repeat; 117 | background-size: cover; 118 | height: 100%; 119 | width: 100%; 120 | opacity: 0; 121 | animation: slide-up-fade-in ease-in-out 2s; 122 | } 123 | 124 | .file-success-icon { 125 | background-image: url('assets/success.svg'); 126 | } 127 | 128 | .file-error-icon { 129 | background-image: url('assets/cancel.svg'); 130 | } 131 | 132 | .file-item { 133 | width: 100%; 134 | height: 100%; 135 | overflow: hidden; 136 | position: relative; 137 | border-radius: 4px; 138 | } 139 | 140 | .file-item, .file-no-thumb, .file-overlay { 141 | display: flex; 142 | justify-content: center; 143 | align-items: center; 144 | } 145 | 146 | .file-overlay { 147 | position: absolute; 148 | width: 50%; 149 | height: 50%; 150 | z-index: 10; 151 | } 152 | 153 | .file-no-thumb { 154 | background-color: lightcoral; 155 | span { 156 | color: white; 157 | font-size: 20px; 158 | font-weight: bold; 159 | text-transform: uppercase; 160 | } 161 | } 162 | 163 | .file-thumb-img { 164 | background-color: white; 165 | } 166 | .file-thumb-img, .file-thumb-ext { 167 | background-size: cover; 168 | background-position: center center; 169 | } 170 | 171 | .file-thumb, .file-no-thumb, .file-thumb-img, .file-thumb-ext,.file-details { 172 | position: absolute; 173 | left: 0; 174 | right: 0; 175 | top: 0; 176 | bottom: 0; 177 | } 178 | 179 | .file-details { 180 | z-index: 100; 181 | opacity: 0; 182 | color: black; 183 | display: flex; 184 | flex-direction: column; 185 | justify-content: space-around; 186 | align-items: center; 187 | padding: 0 8px; 188 | span { 189 | padding: 0 0.4em; 190 | border-radius: 3px; 191 | background-color: rgba(255, 255, 255, 0.5); 192 | } 193 | } 194 | 195 | .file-detail { 196 | border-radius: 3px; 197 | font-size: 12px; 198 | max-width: 100%; 199 | white-space: nowrap; 200 | overflow: hidden; 201 | text-overflow: ellipsis; 202 | } 203 | 204 | .file-name:hover { 205 | overflow: visible; 206 | span { 207 | border: 1px solid rgba(200, 200, 200, 0.8); 208 | background-color: rgba(255, 255, 255, 0.8); 209 | } 210 | } 211 | 212 | .file-remove { 213 | cursor: pointer; 214 | z-index: 100; 215 | position: absolute; 216 | right: 0; 217 | top: 0; 218 | transform: translate(50%, -50%); 219 | width: 20px; 220 | height: 20px; 221 | border-radius: 50%; 222 | background-color: #FFF; 223 | box-shadow: 0 1px 3px rgba(#000, 0.6); 224 | overflow: hidden; 225 | } 226 | 227 | .file-remove-button { 228 | background-color: #FFF; 229 | position: absolute; 230 | border: 0; 231 | padding: 0; 232 | width: 100%; 233 | height: 100%; 234 | transition: all linear 0.2s; 235 | cursor: pointer; 236 | &:active { 237 | transform: rotate(180deg) perspective(1px); 238 | } 239 | &:active, 240 | &:focus { 241 | outline: none; 242 | } 243 | } 244 | 245 | .file-remove-icon { 246 | margin: auto; 247 | width: 10px; 248 | height: 10px; 249 | background-image: url('assets/cancel.svg'); 250 | background-size: cover; 251 | background-repeat: no-repeat; 252 | } 253 | 254 | @keyframes hide-progress { 255 | 0% { 256 | opacity: 1; 257 | margin-top: 0; 258 | } 259 | 50% { 260 | opacity: 1; 261 | margin-top: 0; 262 | } 263 | 100% { 264 | opacity: 0; 265 | } 266 | } 267 | 268 | @keyframes slide-up-fade-in { 269 | 0% { 270 | opacity: 0; 271 | transform: translate3d(0, 100%, 0); 272 | } 273 | 25% { 274 | opacity: 0.9; 275 | transform: translate3d(0, 0, 0); 276 | } 277 | 75% { 278 | opacity: 0.9; 279 | transform: translate3d(0, 0, 0); 280 | } 281 | 100% { 282 | opacity: 0; 283 | transform: translate3d(0, -100%, 0); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /projects/photo/src/lib/fire-photo.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | OnInit, 5 | Output, 6 | OnChanges, 7 | OnDestroy, 8 | SimpleChanges, 9 | EventEmitter, 10 | TemplateRef, 11 | ElementRef, 12 | ChangeDetectionStrategy, 13 | forwardRef 14 | } from '@angular/core'; 15 | import { FileState, FireUploaderProgress, FileItem, FireUploaderRef, FireUploader, ResizeMethod } from '@ngx-fire-uploader/core'; 16 | import { BehaviorSubject } from 'rxjs'; 17 | import { tap, map, switchMap, take, filter } from 'rxjs/operators'; 18 | import { FirePhoto } from './fire-photo.service'; 19 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 20 | 21 | export interface FirePhotoState { 22 | loading?: boolean; 23 | photo?: string; 24 | success?: boolean; 25 | disabled?: boolean; 26 | } 27 | 28 | /** For form control usage */ 29 | export const PHOTO_UPLOADER_VALUE_ACCESSOR: any = { 30 | provide: NG_VALUE_ACCESSOR, 31 | useExisting: forwardRef(() => FirePhotoComponent), 32 | multi: true, 33 | }; 34 | 35 | @Component({ 36 | selector: 'fire-photo', 37 | changeDetection: ChangeDetectionStrategy.OnPush, 38 | styleUrls: ['./fire-photo.scss'], 39 | templateUrl: './fire-photo.html', 40 | providers: [PHOTO_UPLOADER_VALUE_ACCESSOR] 41 | }) 42 | export class FirePhotoComponent implements OnInit, OnChanges, OnDestroy, ControlValueAccessor { 43 | 44 | // Local state 45 | readonly state$ = new BehaviorSubject({loading: false, disabled: false}); 46 | 47 | // Fire uploader ref 48 | uploaderRef: FireUploaderRef; 49 | 50 | // Adds '.fire-photo-hover' class to '.fire-border' 51 | hover = false; 52 | 53 | onChange: Function; 54 | 55 | // FirePhoto options 56 | @Input() id = 'root'; 57 | @Input() loadingTemplate: TemplateRef; 58 | @Input() value: string = this._manager.config.defaultImage; 59 | 60 | @Input() dropZone: boolean = this._manager.config.dropZone; 61 | 62 | // UploaderRef options 63 | @Input() paramName: string = this._manager.config.paramName; 64 | @Input() paramDir: string = this._manager.config.paramDir; 65 | @Input() uniqueName: boolean = this._manager.config.uniqueName; 66 | @Input() autoStart: boolean = this._manager.config.autoStart; 67 | @Input() thumbWidth: number = this._manager.config.thumbWidth; 68 | @Input() thumbHeight: number = this._manager.config.thumbHeight; 69 | @Input() thumbMethod: ResizeMethod = this._manager.config.thumbMethod; 70 | @Input() resizeMethod: ResizeMethod = this._manager.config.resizeMethod; 71 | @Input() resizeWidth: number = this._manager.config.resizeWidth; 72 | @Input() resizeHeight: number = this._manager.config.resizeHeight; 73 | @Input() resizeQuality: number = this._manager.config.resizeQuality; 74 | 75 | @Input('disabled') set onDisabled(isDisabled: boolean) { 76 | this.updateState({disabled: isDisabled}); 77 | } 78 | 79 | // UploaderRef events 80 | @Output() progress = new EventEmitter(); 81 | @Output() file = new EventEmitter(); 82 | @Output() valueChange = new EventEmitter(); 83 | @Output() complete = new EventEmitter(); 84 | @Output() active = new EventEmitter(); 85 | @Output() error = new EventEmitter(); 86 | 87 | constructor(private _uploader: FireUploader, private _manager: FirePhoto, private _el: ElementRef) { 88 | } 89 | 90 | private getConfig() { 91 | return { 92 | multiple: false, 93 | accept: 'image/*', 94 | paramName: this.paramName, 95 | paramDir: this.paramDir, 96 | uniqueName: this.uniqueName, 97 | autoStart: this.autoStart, 98 | thumbWidth: this.thumbWidth, 99 | thumbHeight: this.thumbHeight, 100 | thumbMethod: this.thumbMethod, 101 | resizeHeight: this.resizeHeight, 102 | resizeMethod: this.resizeMethod, 103 | resizeWidth: this.resizeWidth, 104 | resizeQuality: this.resizeQuality 105 | }; 106 | } 107 | 108 | ngOnInit() { 109 | 110 | // Auto-set thumb width and height 111 | if (!this.thumbHeight && !this.thumbWidth) { 112 | this.thumbWidth = this._el.nativeElement.clientWidth; 113 | this.thumbHeight = this._el.nativeElement.clientHeight; 114 | } 115 | 116 | // Get uploader ref and set the config 117 | this.uploaderRef = this._uploader.ref(this.id, this.getConfig()); 118 | 119 | // Get generated thumbnail 120 | this.uploaderRef.files$.pipe( 121 | filter((files: FileItem[]) => !!files.length), 122 | map((files: FileItem[]) => files[0]), 123 | switchMap((item: FileItem) => { 124 | this.file.emit(item); 125 | return item.state$.pipe( 126 | filter((state: FileState) => !!state.thumbnail), 127 | take(1), 128 | tap((state: FileState) => 129 | this.updateState({ 130 | photo: state.thumbnail, 131 | loading: false 132 | }) 133 | ) 134 | ); 135 | }) 136 | ).subscribe(); 137 | 138 | this.uploaderRef.value$.subscribe((downloadURLs: string[]) => { 139 | this.updateState({photo: downloadURLs[0], success: true}); 140 | this.valueChange.next(downloadURLs[0]); 141 | // If used as a form control, call onChange function 142 | if (this.onChange) { 143 | this.onChange(downloadURLs[0]); 144 | } 145 | }); 146 | 147 | this.uploaderRef.active$.subscribe((active: boolean) => { 148 | this.updateState({loading: active}); 149 | this.active.next(active); 150 | }); 151 | 152 | this.uploaderRef.complete$.subscribe((files: FileItem[]) => { 153 | this.complete.next(files[0]); 154 | // Reset the uploader on complete 155 | this.uploaderRef.reset(); 156 | }); 157 | 158 | if (this.file.observers.length) { 159 | this.uploaderRef.files$.subscribe((files: FileItem[]) => 160 | this.file.next(files[0]) 161 | ); 162 | } 163 | 164 | if (this.progress.observers.length) { 165 | this.uploaderRef.progress$.subscribe((progress: FireUploaderProgress) => 166 | this.progress.next(progress) 167 | ); 168 | } 169 | 170 | if (this.error.observers.length) { 171 | this.uploaderRef.error$.subscribe((err: any) => this.error.next(err)); 172 | } 173 | } 174 | 175 | ngOnDestroy() { 176 | this._uploader.destroy(this.id); 177 | } 178 | 179 | ngOnChanges(changes: SimpleChanges) { 180 | if (changes['value'] && changes['value'].currentValue !== changes['value'].previousValue) { 181 | this.updateState({photo: this.value}); 182 | } 183 | 184 | if (this.uploaderRef instanceof FireUploaderRef) { 185 | // Update uploader's config when inputs change 186 | this.uploaderRef.setConfig(this.getConfig()); 187 | } 188 | } 189 | 190 | // Open file dialog 191 | select() { 192 | if (!this.state$.value.disabled) { 193 | this.uploaderRef.select(); 194 | } 195 | } 196 | 197 | // Start Uploading 198 | start() { 199 | this.uploaderRef.start(); 200 | } 201 | 202 | // Pause Uploading 203 | pause() { 204 | this.uploaderRef.pause(); 205 | } 206 | 207 | // Resume Uploading 208 | resume() { 209 | this.uploaderRef.resume(); 210 | } 211 | 212 | // Reset 213 | reset() { 214 | this.uploaderRef.reset(); 215 | if (!this.state$.value.success) { 216 | this.updateState({photo: this.value}); 217 | } 218 | } 219 | 220 | updateState(state: FirePhotoState) { 221 | this.state$.next({...this.state$.value, ...state}); 222 | } 223 | 224 | /** 225 | * Custom form control functions 226 | */ 227 | 228 | registerOnChange(fn: any): void { 229 | this.onChange = fn; 230 | } 231 | 232 | registerOnTouched(fn: any): void { 233 | } 234 | 235 | setDisabledState(isDisabled: boolean): void { 236 | this.updateState({disabled: isDisabled}); 237 | } 238 | 239 | writeValue(defaultPhoto: string): void { 240 | if (defaultPhoto) { 241 | this.updateState({photo: defaultPhoto}); 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-fire-uploader": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/ngx-fire-uploader", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.scss" 27 | ], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "fileReplacements": [ 33 | { 34 | "replace": "src/environments/environment.ts", 35 | "with": "src/environments/environment.prod.ts" 36 | } 37 | ], 38 | "optimization": true, 39 | "outputHashing": "all", 40 | "sourceMap": false, 41 | "extractCss": true, 42 | "namedChunks": false, 43 | "aot": true, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true 47 | } 48 | } 49 | }, 50 | "serve": { 51 | "builder": "@angular-devkit/build-angular:dev-server", 52 | "options": { 53 | "browserTarget": "ngx-fire-uploader:build" 54 | }, 55 | "configurations": { 56 | "production": { 57 | "browserTarget": "ngx-fire-uploader:build:production" 58 | } 59 | } 60 | }, 61 | "extract-i18n": { 62 | "builder": "@angular-devkit/build-angular:extract-i18n", 63 | "options": { 64 | "browserTarget": "ngx-fire-uploader:build" 65 | } 66 | }, 67 | "test": { 68 | "builder": "@angular-devkit/build-angular:karma", 69 | "options": { 70 | "main": "src/test.ts", 71 | "polyfills": "src/polyfills.ts", 72 | "tsConfig": "src/tsconfig.spec.json", 73 | "karmaConfig": "src/karma.conf.js", 74 | "styles": [ 75 | "styles.scss" 76 | ], 77 | "scripts": [], 78 | "assets": [ 79 | "src/favicon.ico", 80 | "src/assets" 81 | ] 82 | } 83 | }, 84 | "lint": { 85 | "builder": "@angular-devkit/build-angular:tslint", 86 | "options": { 87 | "tsConfig": [ 88 | "src/tsconfig.app.json", 89 | "src/tsconfig.spec.json" 90 | ], 91 | "exclude": [ 92 | "**/node_modules/**" 93 | ] 94 | } 95 | } 96 | } 97 | }, 98 | "ngx-fire-uploader-e2e": { 99 | "root": "e2e/", 100 | "projectType": "application", 101 | "architect": { 102 | "e2e": { 103 | "builder": "@angular-devkit/build-angular:protractor", 104 | "options": { 105 | "protractorConfig": "e2e/protractor.conf.js", 106 | "devServerTarget": "ngx-fire-uploader:serve" 107 | } 108 | }, 109 | "lint": { 110 | "builder": "@angular-devkit/build-angular:tslint", 111 | "options": { 112 | "tsConfig": "e2e/tsconfig.e2e.json", 113 | "exclude": [ 114 | "**/node_modules/**" 115 | ] 116 | } 117 | } 118 | } 119 | }, 120 | "core": { 121 | "root": "projects/core", 122 | "sourceRoot": "projects/core/src", 123 | "projectType": "library", 124 | "prefix": "lib", 125 | "architect": { 126 | "build": { 127 | "builder": "@angular-devkit/build-ng-packagr:build", 128 | "options": { 129 | "tsConfig": "projects/core/tsconfig.lib.json", 130 | "project": "projects/core/ng-package.json" 131 | }, 132 | "configurations": { 133 | "production": { 134 | "project": "projects/core/ng-package.prod.json" 135 | } 136 | } 137 | }, 138 | "test": { 139 | "builder": "@angular-devkit/build-angular:karma", 140 | "options": { 141 | "main": "projects/core/src/test.ts", 142 | "tsConfig": "projects/core/tsconfig.spec.json", 143 | "karmaConfig": "projects/core/karma.conf.js" 144 | } 145 | }, 146 | "lint": { 147 | "builder": "@angular-devkit/build-angular:tslint", 148 | "options": { 149 | "tsConfig": [ 150 | "projects/core/tsconfig.lib.json", 151 | "projects/core/tsconfig.spec.json" 152 | ], 153 | "exclude": [ 154 | "**/node_modules/**" 155 | ] 156 | } 157 | } 158 | } 159 | }, 160 | "photo": { 161 | "root": "projects/photo", 162 | "sourceRoot": "projects/photo/src", 163 | "projectType": "library", 164 | "prefix": "lib", 165 | "architect": { 166 | "build": { 167 | "builder": "@angular-devkit/build-ng-packagr:build", 168 | "options": { 169 | "tsConfig": "projects/photo/tsconfig.lib.json", 170 | "project": "projects/photo/ng-package.json" 171 | }, 172 | "configurations": { 173 | "production": { 174 | "project": "projects/photo/ng-package.prod.json" 175 | } 176 | } 177 | }, 178 | "test": { 179 | "builder": "@angular-devkit/build-angular:karma", 180 | "options": { 181 | "main": "projects/photo/src/test.ts", 182 | "tsConfig": "projects/photo/tsconfig.spec.json", 183 | "karmaConfig": "projects/photo/karma.conf.js" 184 | } 185 | }, 186 | "lint": { 187 | "builder": "@angular-devkit/build-angular:tslint", 188 | "options": { 189 | "tsConfig": [ 190 | "projects/photo/tsconfig.lib.json", 191 | "projects/photo/tsconfig.spec.json" 192 | ], 193 | "exclude": [ 194 | "**/node_modules/**" 195 | ] 196 | } 197 | } 198 | } 199 | }, 200 | "manager": { 201 | "root": "projects/manager", 202 | "sourceRoot": "projects/manager/src", 203 | "projectType": "library", 204 | "prefix": "lib", 205 | "architect": { 206 | "build": { 207 | "builder": "@angular-devkit/build-ng-packagr:build", 208 | "options": { 209 | "tsConfig": "projects/manager/tsconfig.lib.json", 210 | "project": "projects/manager/ng-package.json" 211 | }, 212 | "configurations": { 213 | "production": { 214 | "project": "projects/manager/ng-package.prod.json" 215 | } 216 | } 217 | }, 218 | "test": { 219 | "builder": "@angular-devkit/build-angular:karma", 220 | "options": { 221 | "main": "projects/manager/src/test.ts", 222 | "tsConfig": "projects/manager/tsconfig.spec.json", 223 | "karmaConfig": "projects/manager/karma.conf.js" 224 | } 225 | }, 226 | "lint": { 227 | "builder": "@angular-devkit/build-angular:tslint", 228 | "options": { 229 | "tsConfig": [ 230 | "projects/manager/tsconfig.lib.json", 231 | "projects/manager/tsconfig.spec.json" 232 | ], 233 | "exclude": [ 234 | "**/node_modules/**" 235 | ] 236 | } 237 | } 238 | } 239 | } 240 | }, 241 | "defaultProject": "ngx-fire-uploader" 242 | } -------------------------------------------------------------------------------- /src/app/basic-example/basic-example.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 32 | 33 |
34 |
35 | 36 |
Uploader Functions
37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | 48 |
49 |
Uploader State
50 | 51 |
 52 |       Files Count: {{ files.length }}
 53 |       Transfer Progress: {{ progress?.bytesTransferred | fileSize }} / {{ progress?.totalBytes | fileSize }}
 54 |       Transfer Percentage: {{ progress?.percentage }}%
 55 |       Active: {{ active }}
 56 |     
57 | 58 | 59 |
60 | 61 |
62 | 63 |

64 | {{link}} 65 |

66 |
67 |
68 | 69 |
Uploader Inputs
70 | 71 |
72 |
73 | 74 |
75 | 76 | 77 |
78 | 79 |
80 | 81 | 84 |
85 | 86 |
87 | 88 | 89 |
90 | 91 |
92 |
93 | File name parameter 94 |
95 | 96 |
97 | 98 |
99 |
100 | File directory parameter 101 |
102 | 103 |
104 | 105 |
106 |
107 | Max files count 108 |
109 | 110 |
111 | 112 |
113 |
114 | Max file 115 |
116 | 117 |
118 | 119 |
120 |
121 | Placeholder 122 |
123 | 124 |
125 |
126 |
127 | Accept 128 |
129 | 130 |
131 | 132 |
133 |
134 | Parallel upload 135 |
136 | 137 |
138 | 139 |
140 | 141 |
142 | 143 |
144 | 145 | 146 |
147 | 148 |
149 |
150 | Thumb width 151 |
152 | 153 |
154 | 155 |
156 |
157 | Thumb height 158 |
159 | 160 |
161 | 162 |
163 |
164 | Resize width 165 |
166 | 167 |
168 | 169 |
170 |
171 | Resize height 172 |
173 | 174 |
175 | 176 |
177 |
178 | 179 |
180 | 184 |
185 | 186 |
187 | 188 | 189 | 191 |
192 | 193 |
194 |
195 | 196 |
197 | -------------------------------------------------------------------------------- /projects/core/src/lib/fire-uploader-ref.ts: -------------------------------------------------------------------------------- 1 | import { AngularFireStorage } from '@angular/fire/storage'; 2 | import { Observable, Subject, BehaviorSubject, Subscription, of, forkJoin, fromEvent, EMPTY, SubscriptionLike } from 'rxjs'; 3 | import { switchMap, concatMap, takeUntil, finalize, tap, catchError, distinctUntilChanged, map } from 'rxjs/operators'; 4 | import { FireUploaderState, FireUploaderConfig, FireUploaderProgress } from './fire-uploader.model'; 5 | import { DEFAULT_STATE } from './fire-uploader.default'; 6 | import { FileItem } from './file-item'; 7 | import { parallizeUploads, maxFilesError, maxFileSizeError, convertToMB, combineStates } from './utils'; 8 | 9 | /** 10 | * FireUploaderRef class for fire uploader ref 11 | */ 12 | export class FireUploaderRef { 13 | 14 | /** File input element used to select files from browser file dialog */ 15 | private readonly _fileInput: HTMLInputElement; 16 | 17 | /** Reference to file input Subscription, used to unsubscribe onDestroy */ 18 | private readonly _inputSelect$: SubscriptionLike = Subscription.EMPTY; 19 | 20 | /** Internal stream that emits to cancel any ongoing task */ 21 | private readonly _cancelUpload$ = new Subject(); 22 | 23 | /** Internal stream that emits to combine all FileItem(s) states into a single state */ 24 | private readonly _refreshState$ = new BehaviorSubject({}); 25 | 26 | /** Uploader state */ 27 | private _state: FireUploaderState = DEFAULT_STATE; 28 | 29 | /** Stream that emits when uploader state is changed */ 30 | state$ = new BehaviorSubject(DEFAULT_STATE); 31 | 32 | /** Stream that emits when files are added */ 33 | files$ = new Subject(); 34 | 35 | /** Stream that emits when files are removed */ 36 | remove$ = new Subject(); 37 | 38 | /** Stream that emits when files are cancel */ 39 | cancel$ = new Subject(); 40 | 41 | /** Stream that emits when file is successfully uploaded */ 42 | success$ = new Subject(); 43 | 44 | /** Stream that emits when uploading has been completed */ 45 | complete$ = new Subject(); 46 | 47 | /** Stream that emits the download URLs array after files are uploaded */ 48 | value$ = new Subject(); 49 | 50 | /** Stream that emits when an error has occurred */ 51 | error$ = new Subject(); 52 | 53 | /** Stream that emits when uploader active state is changed */ 54 | active$ = new Subject(); 55 | 56 | /** Stream that emits when upload progress is changed */ 57 | progress$ = new Subject(); 58 | 59 | /** Stream that emits when uploader has been reset */ 60 | reset$ = new Subject(); 61 | 62 | /** Creates FireUploaderRef with a default config */ 63 | constructor(public config: FireUploaderConfig, private _storage: AngularFireStorage) { 64 | 65 | // Prepare the file input 66 | this._fileInput = document.createElement('input'); 67 | this._fileInput.type = 'file'; 68 | this._inputSelect$ = fromEvent(this._fileInput, 'change').subscribe(() => this.addFiles(this._fileInput.files)); 69 | 70 | this._refreshState$.pipe( 71 | switchMap(() => this._state.files.length ? combineStates(this._state.files) : of(DEFAULT_STATE)), 72 | map((state: FireUploaderState) => { 73 | this.setState(state); 74 | this.progress$.next(state.progress); 75 | return state.active; 76 | }), 77 | // Emit active state only if it has changed 78 | distinctUntilChanged(), 79 | tap((active: boolean) => this.active$.next(active)) 80 | ).subscribe(); 81 | } 82 | 83 | /** Set Config */ 84 | setConfig(config: FireUploaderConfig) { 85 | this.config = {...this.config, ...config}; 86 | } 87 | 88 | /** Destroy uploader ref */ 89 | destroy() { 90 | this.state$.complete(); 91 | this.files$.complete(); 92 | this.value$.complete(); 93 | this.error$.complete(); 94 | this.reset$.complete(); 95 | this.active$.complete(); 96 | this.cancel$.complete(); 97 | this.remove$.complete(); 98 | this.success$.complete(); 99 | this.progress$.complete(); 100 | this._cancelUpload$.complete(); 101 | this._refreshState$.complete(); 102 | this._inputSelect$.unsubscribe(); 103 | } 104 | 105 | /** 106 | * Start uploading 107 | */ 108 | start() { 109 | // Start if there are files added and the uploader is not busy 110 | if (!this._state.active && this._state.files.length) { 111 | of(this._state.files).pipe( 112 | switchMap((files: FileItem[]) => parallizeUploads(files, this.config.parallelUploads)), 113 | concatMap((chunk: FileItem[]) => this.uploadChunk(chunk)), 114 | takeUntil(this._cancelUpload$), 115 | finalize(() => { 116 | const uploadedFiles = this._state.files.filter((item: FileItem) => item.state.state === 'success'); 117 | 118 | // Emits the URLs of the uploaded files. 119 | const downloadURLs = uploadedFiles.map((item: FileItem) => item.state.downloadURL); 120 | this.value$.next(downloadURLs); 121 | 122 | // Emits uploaded files. 123 | this.complete$.next(uploadedFiles); 124 | }) 125 | ).subscribe(); 126 | } 127 | } 128 | 129 | /** 130 | * Select files 131 | */ 132 | select() { 133 | this._fileInput.multiple = this.config.multiple; 134 | this._fileInput.accept = this.config.accept; 135 | this._fileInput.click(); 136 | } 137 | 138 | /** 139 | * Add files to the queue 140 | */ 141 | addFiles(fileList: FileList | File[]) { 142 | this.validateFiles(fileList).subscribe((files: FileItem[]) => { 143 | this.setState({files}); 144 | this.files$.next(files); 145 | 146 | this._refreshState$.next(null); 147 | 148 | // Starts uploading as soon as the file are added 149 | if (this.config.autoStart) { 150 | this.start(); 151 | } 152 | }); 153 | } 154 | 155 | /** 156 | * Remove file 157 | * cancels the file if it is being uploaded 158 | * deletes the file if it has been uploaded 159 | */ 160 | removeItem(item: FileItem) { 161 | if (item.state.state === 'success') { 162 | item.delete().pipe( 163 | tap(() => this.remove$.next(item)), 164 | catchError((err: Error) => this.handleError(err)), 165 | ); 166 | } else if (item.state.state === 'running') { 167 | item.cancel(); 168 | this.cancel$.next(item); 169 | } else { 170 | this.remove$.next(item); 171 | } 172 | 173 | // Destroy file item 174 | item.state$.complete(); 175 | const files = this._state.files.filter((file: FileItem) => file !== item); 176 | this.setState({files}); 177 | 178 | this.files$.next(this._state.files); 179 | this._refreshState$.next(null); 180 | } 181 | 182 | /** 183 | * Resets the uploader 184 | */ 185 | reset(remove = false) { 186 | this.cancel(remove); 187 | this.setState({files: []}); 188 | this._refreshState$.next(null); 189 | this.files$.next([]); 190 | this.reset$.next(); 191 | } 192 | 193 | /** 194 | * Cancel a specific file 195 | */ 196 | cancelItem(item: FileItem) { 197 | item.cancel(); 198 | this._refreshState$.next(null); 199 | } 200 | 201 | /** 202 | * Cancel all upload tasks 203 | */ 204 | cancel(remove = true) { 205 | this._cancelUpload$.next(); 206 | this._state.files.map((item: FileItem) => { 207 | if (remove && item.state.state === 'success') { 208 | item.delete(); 209 | } else { 210 | item.cancel(); 211 | } 212 | }); 213 | } 214 | 215 | /** 216 | * Pause all upload tasks 217 | */ 218 | pause() { 219 | this._state.files.map((file: FileItem) => file.pause()); 220 | } 221 | 222 | /** 223 | * Resume all paused tasks 224 | */ 225 | resume() { 226 | this._state.files.map((file: FileItem) => file.resume()); 227 | } 228 | 229 | private setState(state: FireUploaderState) { 230 | this._state = {...this._state, ...state}; 231 | this.state$.next(this._state); 232 | } 233 | 234 | /** 235 | * Takes files from drop zone or file input 236 | * Validates max file count 237 | * Validates max file size 238 | * Prevents duplication 239 | */ 240 | private validateFiles(fileList: FileList | File[]): Observable { 241 | if (!fileList.length) { 242 | // If user didn't select file, return state's files 243 | return of(this._state.files); 244 | } 245 | 246 | return of({}).pipe( 247 | map(() => { 248 | let files: FileItem[] = []; 249 | 250 | // Validate max files count 251 | const length = this.validateMaxFiles(fileList, this.config.maxFiles); 252 | 253 | for (let i = 0; i < length; i++) { 254 | // Check if file type is accepted 255 | if (this.config.accept && !fileList[i].type.match(this.config.accept)) { 256 | continue; 257 | } 258 | 259 | // Validate max file size 260 | if (convertToMB(fileList[i].size) > this.config.maxFileSize) { 261 | this.error$.next(maxFileSizeError(fileList[i].name)); 262 | continue; 263 | } 264 | 265 | const file = new FileItem(fileList[i], this.config, this._storage); 266 | files = [...files, file]; 267 | } 268 | return files; 269 | }), 270 | switchMap((files: FileItem[]) => { 271 | const prepareFiles = files.map(item => item.prepare()); 272 | return forkJoin(prepareFiles).pipe( 273 | map(() => { 274 | if (this.config.multiple) { 275 | // Combine and filter duplicated files 276 | files = [...this._state.files, ...files].filter((curr, index, self) => 277 | self.findIndex(t => t.file.name === curr.file.name && t.file.size === curr.file.size) === index 278 | ); 279 | } 280 | return files; 281 | }), 282 | catchError((err: Error) => this.handleError(err)), 283 | ); 284 | }) 285 | ); 286 | } 287 | 288 | /** Validate if fileList count exceeded uploader's max count */ 289 | private validateMaxFiles(fileList: FileList | File[], max: number): number { 290 | if (fileList.length > max) { 291 | this.error$.next(maxFilesError(max)); 292 | return max; 293 | } 294 | return fileList.length; 295 | } 296 | 297 | /** 298 | * Iterates over given files 299 | * Starts the uploading task 300 | */ 301 | private uploadChunk(files: FileItem[]): Observable { 302 | const chunk = files.map((item: FileItem) => { 303 | return item.upload().pipe( 304 | catchError((err: Error) => this.handleError(err)), 305 | finalize(() => this.success$.next(item)) 306 | ); 307 | }); 308 | return forkJoin(chunk); 309 | } 310 | 311 | /** 312 | * Handles errors 313 | */ 314 | private handleError(err: Error) { 315 | this.error$.next(err); 316 | return EMPTY; 317 | } 318 | 319 | } 320 | --------------------------------------------------------------------------------