import { Inject , Injectable , NgZone , OnDestroy , Optional } from '@angular/core' ; import { fromEvent , Observable , Subject , Subscription } from 'rxjs' ; import { debounceTime , map } from 'rxjs/operators' ; import { Ajax , AjaxRequestConfig , AjaxResponse , UPLOADX_AJAX } from './ajax' ; import { IdService } from './id.service' ; import { UploadState , UploadxControlEvent } from './interfaces' ; import { iOSPatch , UploadxFactoryOptions , UploadxOptions , UPLOADX_FACTORY_OPTIONS , UPLOADX_OPTIONS } from './options' ; import { store } from './store' ; import { Uploader } from './uploader' ; import { isBrowser , isIOS , onLine , pick } from './utils' ; export const UPLOAD_STATE_KEYS = [ 'file' , 'name' , 'progress' , 'remaining' , 'response' , 'responseHeaders' , 'responseStatus' , 'size' , 'speed' , 'status' , 'uploadId' , 'url' ] ; const DUE_TIME = 5 ; @ Injectable ( { providedIn : 'root' } ) export class UploadxService implements OnDestroy { /** Upload Queue */ queue : Uploader [ ] = [ ] ; options : UploadxFactoryOptions ; private readonly eventsStream : Subject < UploadState > = new Subject ( ) ; private subs : Subscription [ ] = [ ] ; constructor ( @ Optional ( ) @ Inject ( UPLOADX_OPTIONS ) options , @ Inject ( UPLOADX_FACTORY_OPTIONS ) defaults , @ Inject ( UPLOADX_AJAX ) readonly ajax , private ngZone , private idService ) { this . options = Object . assign ( { } , defaults , options ) ; if ( isBrowser ( ) ) { this . subs . push ( fromEvent ( window , 'online' ) . subscribe ( ( ) => this . control ( { action : 'upload' } ) ) , fromEvent ( window , 'offline' ) . subscribe ( ( ) => this . control ( { action : 'pause' } ) ) ) ; } } /** Upload status events */ get events ( ) : Observable < UploadState > { return this . eventsStream . asObservable ( ) ; } param options returns init ( options = { } ) { Object . assign ( this . options , options ) ; return this . events ; } param options returns connect ( options ? ) { return this . init ( options ) . pipe ( map ( ( ) => this . queue ) , debounceTime ( DUE_TIME ) ) ; } /**
     * Terminates all uploads and clears the queue
     */ disconnect ( ) { this . queue . forEach ( uploader => ( uploader . status = 'paused' ) ) ; this . queue = [ ] ; } example state ( ) { return this . queue . map ( uploader => pick ( uploader , UPLOAD_STATE_KEYS ) ) ; } ngOnDestroy ( ) { this . disconnect ( ) ; this . subs . forEach ( sub => sub . unsubscribe ( ) ) ; } /**
     * Creates uploaders for files and adds them to the upload queue
     */ handleFiles ( files , options = { } as UploadxOptions ) { isIOS ( ) && iOSPatch ( options ) ; const instanceOptions = { ... this . options , ... options } ; store . clear ( instanceOptions . storeIncompleteHours ) ; this . options . concurrency = instanceOptions . concurrency ; ( 'name' in files ? [ files ] : Array . from ( files ) ) . forEach ( file => this . addUploaderInstance ( file , instanceOptions ) ) ; } example control ( evt ) { const target = evt . uploadId ? this . queue . filter ( ( { uploadId } ) => uploadId === evt . uploadId ) : this . queue ; target . forEach ( uploader => uploader . configure ( evt ) ) ; } /**
     * Number of active uploads
     */ get activeUploadsCount ( ) : number { return this . queue . filter ( ( { status } ) => status === 'uploading' || status === 'retry' ) . length ; } /**
     * Performs http requests
     */ async request < T = string > ( config ) { config . data ||= config . body ; return this . ajax . request ( config ) ; } private stateChange = ( uploader ) => { this . ngZone . run ( ( ) => this . eventsStream . next ( pick ( uploader , UPLOAD_STATE_KEYS ) ) ) ; if ( uploader . status !== 'uploading' && uploader . status !== 'added' ) { this . ngZone . runOutsideAngular ( ( ) => setTimeout ( ( ) => this . processQueue ( ) ) ) ; } } ; private async addUploaderInstance ( file , options ) { const uploader = new options . uploaderClass ( file , options , this . stateChange , this . ajax ) ; ( uploader as { uploadId : string ; } ) . uploadId = await this . idService . generateId ( uploader ) ; this . queue . push ( uploader ) ; uploader . status = options . autoUpload && onLine ( ) ? 'queue' : 'added' ; } private processQueue ( ) { this . queue = this . queue . filter ( ( { status } ) => status !== 'cancelled' ) ; this . queue . filter ( ( { status } ) => status === 'queue' ) . slice ( 0 , Math . max ( this . options . concurrency - this . activeUploadsCount , 0 ) ) . forEach ( uploader => uploader . upload ( ) ) ; } }