diff --git a/demo-shell-ng2/app/components/files/files.component.ts b/demo-shell-ng2/app/components/files/files.component.ts index 26430ea5c3..50a3d24b03 100644 --- a/demo-shell-ng2/app/components/files/files.component.ts +++ b/demo-shell-ng2/app/components/files/files.component.ts @@ -21,7 +21,7 @@ import { ActivatedRoute, Params, Router } from '@angular/router'; import { MinimalNodeEntity } from 'alfresco-js-api'; import { AlfrescoApiService, AlfrescoContentService, AlfrescoTranslationService, CreateFolderDialogComponent, - DownloadZipDialogComponent, FileUploadCompleteEvent, FolderCreatedEvent, NotificationService, + DownloadZipDialogComponent, FileUploadEvent, FolderCreatedEvent, NotificationService, SiteModel, UploadService } from 'ng2-alfresco-core'; import { DataColumn, DataRow } from 'ng2-alfresco-datatable'; @@ -136,7 +136,8 @@ export class FilesComponent implements OnInit { }); } - this.uploadService.fileUploadComplete.debounceTime(300).subscribe(value => this.onFileUploadComplete(value)); + this.uploadService.fileUploadComplete.debounceTime(300).subscribe(value => this.onFileUploadEvent(value)); + this.uploadService.fileUploadDeleted.subscribe((value) => this.onFileUploadEvent(value)); this.contentService.folderCreated.subscribe(value => this.onFolderCreated(value)); // this.permissionsStyle.push(new PermissionStyleModel('document-list__create', PermissionsEnum.CREATE)); @@ -161,7 +162,7 @@ export class FilesComponent implements OnInit { this.errorMessage = null; } - onFileUploadComplete(event: FileUploadCompleteEvent) { + onFileUploadEvent(event: FileUploadEvent) { if (event && event.file.options.parentId === this.documentList.currentFolderId) { this.documentList.reload(); } diff --git a/ng2-components/ng2-alfresco-core/src/events/file.event.ts b/ng2-components/ng2-alfresco-core/src/events/file.event.ts index e269b2b943..06cc07a50f 100644 --- a/ng2-components/ng2-alfresco-core/src/events/file.event.ts +++ b/ng2-components/ng2-alfresco-core/src/events/file.event.ts @@ -34,3 +34,19 @@ export class FileUploadCompleteEvent extends FileUploadEvent { } } + +export class FileUploadDeleteEvent extends FileUploadEvent { + + constructor(file: FileModel, public totalComplete: number = 0) { + super(file, FileUploadStatus.Deleted); + } + +} + +export class FileUploadErrorEvent extends FileUploadEvent { + + constructor(file: FileModel, public error: any, public totalError: number = 0) { + super(file, FileUploadStatus.Error); + } + +} diff --git a/ng2-components/ng2-alfresco-core/src/models/file.model.ts b/ng2-components/ng2-alfresco-core/src/models/file.model.ts index 30abb7979e..e8ce370dbb 100644 --- a/ng2-components/ng2-alfresco-core/src/models/file.model.ts +++ b/ng2-components/ng2-alfresco-core/src/models/file.model.ts @@ -34,7 +34,8 @@ export enum FileUploadStatus { Progress = 3, Cancelled = 4, Aborted = 5, - Error = 6 + Error = 6, + Deleted = 7 } export class FileModel { diff --git a/ng2-components/ng2-alfresco-core/src/services/upload.service.spec.ts b/ng2-components/ng2-alfresco-core/src/services/upload.service.spec.ts index e88ba853f4..8ce994cfc2 100644 --- a/ng2-components/ng2-alfresco-core/src/services/upload.service.spec.ts +++ b/ng2-components/ng2-alfresco-core/src/services/upload.service.spec.ts @@ -17,7 +17,7 @@ import { EventEmitter } from '@angular/core'; import { TestBed } from '@angular/core/testing'; -import { FileModel, FileUploadOptions } from '../models/file.model'; +import { FileModel, FileUploadOptions, FileUploadStatus } from '../models/file.model'; import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoSettingsService } from './alfresco-settings.service'; import { AppConfigModule } from './app-config.service'; @@ -214,4 +214,31 @@ describe('UploadService', () => { expect(result.length).toBe(1); expect(result[0]).toBe(file4); }); + + it('should call onUploadDeleted if file was deleted', () => { + const file = ({ status: FileUploadStatus.Deleted }); + spyOn(service.fileUploadDeleted, 'next'); + + service.cancelUpload(file); + + expect(service.fileUploadDeleted.next).toHaveBeenCalled(); + }); + + it('should call fileUploadError if file has error status', () => { + const file = ({ status: FileUploadStatus.Error }); + spyOn(service.fileUploadError, 'next'); + + service.cancelUpload(file); + + expect(service.fileUploadError.next).toHaveBeenCalled(); + }); + + it('should call fileUploadCancelled if file is in pending', () => { + const file = ({ status: FileUploadStatus.Pending }); + spyOn(service.fileUploadCancelled, 'next'); + + service.cancelUpload(file); + + expect(service.fileUploadCancelled.next).toHaveBeenCalled(); + }); }); diff --git a/ng2-components/ng2-alfresco-core/src/services/upload.service.ts b/ng2-components/ng2-alfresco-core/src/services/upload.service.ts index 43e5d5af4c..ea4cabd539 100644 --- a/ng2-components/ng2-alfresco-core/src/services/upload.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/upload.service.ts @@ -18,7 +18,7 @@ import { EventEmitter, Injectable } from '@angular/core'; import * as minimatch from 'minimatch'; import { Subject } from 'rxjs/Rx'; -import { FileUploadCompleteEvent, FileUploadEvent } from '../events/file.event'; +import { FileUploadCompleteEvent, FileUploadDeleteEvent, FileUploadErrorEvent, FileUploadEvent } from '../events/file.event'; import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model'; import { AlfrescoApiService } from './alfresco-api.service'; import { AppConfigService } from './app-config.service'; @@ -30,6 +30,7 @@ export class UploadService { private cache: { [key: string]: any } = {}; private totalComplete: number = 0; private totalAborted: number = 0; + private totalError: number = 0; private activeTask: Promise = null; private excludedFileList: String[] = []; @@ -39,8 +40,9 @@ export class UploadService { fileUploadCancelled: Subject = new Subject(); fileUploadProgress: Subject = new Subject(); fileUploadAborted: Subject = new Subject(); - fileUploadError: Subject = new Subject(); + fileUploadError: Subject = new Subject(); fileUploadComplete: Subject = new Subject(); + fileUploadDeleted: Subject = new Subject(); constructor(private apiService: AlfrescoApiService, private appConfigService: AppConfigService) { this.excludedFileList = this.appConfigService.get('files.excluded'); @@ -123,17 +125,15 @@ export class UploadService { cancelUpload(...files: FileModel[]) { files.forEach(file => { - file.status = FileUploadStatus.Cancelled; - const promise = this.cache[file.id]; + if (promise) { promise.abort(); delete this.cache[file.id]; + } else { + const performAction = this.getAction(file); + performAction(); } - - const event = new FileUploadEvent(file, FileUploadStatus.Cancelled); - this.fileUpload.next(event); - this.fileUploadCancelled.next(event); }); } @@ -141,6 +141,7 @@ export class UploadService { this.queue = []; this.totalComplete = 0; this.totalAborted = 0; + this.totalError = 0; } getUploadPromise(file: FileModel) { @@ -183,7 +184,7 @@ export class UploadService { emitter.emit({ value: data }); }) .catch(err => { - this.onUploadError(file, err); + throw err; }); return promise; @@ -212,13 +213,14 @@ export class UploadService { private onUploadError(file: FileModel, error: any): void { if (file) { file.status = FileUploadStatus.Error; + this.totalError++; const promise = this.cache[file.id]; if (promise) { delete this.cache[file.id]; } - const event = new FileUploadEvent(file, FileUploadStatus.Error, error); + const event = new FileUploadErrorEvent(file, error, this.totalError); this.fileUpload.next(event); this.fileUploadError.next(event); } @@ -257,4 +259,35 @@ export class UploadService { promise.next(); } } + + private onUploadCancelled(file: FileModel): void { + if (file) { + file.status = FileUploadStatus.Cancelled; + + const event = new FileUploadEvent(file, FileUploadStatus.Cancelled); + this.fileUpload.next(event); + this.fileUploadCancelled.next(event); + } + } + + private onUploadDeleted(file: FileModel): void { + if (file) { + file.status = FileUploadStatus.Deleted; + this.totalComplete--; + + const event = new FileUploadDeleteEvent(file, this.totalComplete); + this.fileUpload.next(event); + this.fileUploadDeleted.next(event); + } + } + + private getAction(file) { + const actions = { + [FileUploadStatus.Pending]: () => this.onUploadCancelled(file), + [FileUploadStatus.Deleted]: () => this.onUploadDeleted(file), + [FileUploadStatus.Error]: () => this.onUploadError(file, null) + }; + + return actions[file.status]; + } } diff --git a/ng2-components/ng2-alfresco-core/styles/_all-theme.scss b/ng2-components/ng2-alfresco-core/styles/_all-theme.scss index 251f2cae3f..e9ac602052 100644 --- a/ng2-components/ng2-alfresco-core/styles/_all-theme.scss +++ b/ng2-components/ng2-alfresco-core/styles/_all-theme.scss @@ -43,7 +43,7 @@ @include mat-comment-list-theme($theme); @include mat-start-task-theme($theme); @include mat-people-search-theme($theme); - @include mat-file-uploading-list-theme($theme); + @include mat-file-uploading-row-theme($theme); @include mat-people-theme($theme); @include mat-login-theme($theme); @include mat-accordion-theme($theme); diff --git a/ng2-components/ng2-alfresco-upload/README.md b/ng2-components/ng2-alfresco-upload/README.md index 512953f3c8..0034a6283c 100644 --- a/ng2-components/ng2-alfresco-upload/README.md +++ b/ng2-components/ng2-alfresco-upload/README.md @@ -240,6 +240,7 @@ Note: | fileUploadAborted | FileUploadEvent | Raised when file upload gets aborted by the server. | | fileUploadError | FileUploadEvent | Raised when an error occurs to file upload. | | fileUploadComplete | FileUploadCompleteEvent | Raised when file upload is complete. | +| fileUploadDelete | FileUploadDeleteEvent | Raised when uploaded file is removed from server. | ## Build from sources diff --git a/ng2-components/ng2-alfresco-upload/index.ts b/ng2-components/ng2-alfresco-upload/index.ts index 985951c5d4..3833d62175 100644 --- a/ng2-components/ng2-alfresco-upload/index.ts +++ b/ng2-components/ng2-alfresco-upload/index.ts @@ -26,7 +26,6 @@ import { FileUploadingListComponent } from './src/components/file-uploading-list import { UploadButtonComponent } from './src/components/upload-button.component'; import { UploadDragAreaComponent } from './src/components/upload-drag-area.component'; import { FileDraggableDirective } from './src/directives/file-draggable.directive'; -import { FileUploadService } from './src/services/file-uploading.service'; export * from './src/components/upload-button.component'; export * from './src/components/file-uploading-dialog.component'; @@ -35,7 +34,6 @@ export * from './src/directives/file-draggable.directive'; export * from './src/components/file-uploading-list.component'; export * from './src/components/file-uploading-list-row.component'; export * from './src/models/permissions.model'; -export * from './src/services/file-uploading.service'; export const UPLOAD_DIRECTIVES: any[] = [ FileDraggableDirective, @@ -46,10 +44,6 @@ export const UPLOAD_DIRECTIVES: any[] = [ FileUploadingListRowComponent ]; -export const UPLOAD_PROVIDERS: any[] = [ - FileUploadService -]; - @NgModule({ imports: [ CoreModule, @@ -59,7 +53,6 @@ export const UPLOAD_PROVIDERS: any[] = [ ...UPLOAD_DIRECTIVES ], providers: [ - ...UPLOAD_PROVIDERS, { provide: TRANSLATION_PROVIDER, multi: true, @@ -76,10 +69,7 @@ export const UPLOAD_PROVIDERS: any[] = [ export class UploadModule { static forRoot(): ModuleWithProviders { return { - ngModule: UploadModule, - providers: [ - ...UPLOAD_PROVIDERS - ] + ngModule: UploadModule }; } } diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.html b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.html index 03139467f7..7918babf1b 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.html +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.html @@ -27,7 +27,7 @@ *ngIf="uploadList.isUploadCompleted()"> {{ 'FILE_UPLOAD.MESSAGES.UPLOAD_COMPLETED' | translate: { - completed: (totalCompleted - uploadList.uploadCancelledFiles.length), + completed: totalCompleted, total: filesUploadingList.length } }} @@ -42,12 +42,12 @@
+ *ngIf="totalErrors"> {{ - (uploadList.uploadErrorFiles.length > 1 + (totalErrors > 1 ? 'FILE_UPLOAD.MESSAGES.UPLOAD_ERRORS' : 'FILE_UPLOAD.MESSAGES.UPLOAD_ERROR') - | translate: { total: uploadList.uploadErrorFiles.length } + | translate: { total: totalErrors } }}
@@ -59,7 +59,7 @@ + (cancel)="uploadList.cancelFile(file)"> @@ -70,7 +70,7 @@ color="primary" *ngIf="!uploadList.isUploadCompleted() && !uploadList.isUploadCancelled()" md-button - (click)="uploadList.cancelAllFiles($event)"> + (click)="uploadList.cancelAllFiles()"> {{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_ALL' | translate }} diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts index 0a9e4157b7..55b14562f8 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts @@ -16,8 +16,9 @@ */ import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { FileModel, FileUploadCompleteEvent, UploadService } from 'ng2-alfresco-core'; -import { Subscription } from 'rxjs/Rx'; +import { FileModel, FileUploadCompleteEvent, FileUploadDeleteEvent, + FileUploadErrorEvent, UploadService } from 'ng2-alfresco-core'; +import { Observable, Subscription } from 'rxjs/Rx'; @Component({ selector: 'adf-file-uploading-dialog, file-uploading-dialog', @@ -31,35 +32,47 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { filesUploadingList: FileModel[] = []; isDialogActive: boolean = false; totalCompleted: number = 0; + totalErrors: number = 0; isDialogMinimized: boolean = false; - uploadFilesCompleted: boolean = false; private listSubscription: Subscription; private counterSubscription: Subscription; private fileUploadSubscription: Subscription; + private errorSubscription: Subscription; constructor( private uploadService: UploadService, - private changeDetecor: ChangeDetectorRef) { - } + private changeDetecor: ChangeDetectorRef) {} ngOnInit() { this.listSubscription = this.uploadService .queueChanged.subscribe((fileList: FileModel[]) => { this.filesUploadingList = fileList; - if (this.filesUploadingList.length > 0) { + if (this.filesUploadingList.length) { this.isDialogActive = true; } }); - this.counterSubscription = this.uploadService - .fileUploadComplete.subscribe((event: FileUploadCompleteEvent) => { + this.counterSubscription = Observable + .merge( + this.uploadService.fileUploadComplete, + this.uploadService.fileUploadDeleted + ) + .subscribe((event: (FileUploadDeleteEvent|FileUploadCompleteEvent)) => { this.totalCompleted = event.totalComplete; }); + this.errorSubscription = this.uploadService.fileUploadError + .subscribe((event: FileUploadErrorEvent) => { + this.totalErrors = event.totalError; + this.changeDetecor.detectChanges(); + }); + this.fileUploadSubscription = this.uploadService - .fileUpload.subscribe(() => this.changeDetecor.detectChanges()); + .fileUpload.subscribe(() => { + this.changeDetecor.detectChanges(); + }); } /** @@ -75,6 +88,7 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { */ close(): void { this.totalCompleted = 0; + this.totalErrors = 0; this.filesUploadingList = []; this.isDialogActive = false; this.isDialogMinimized = false; @@ -87,5 +101,6 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { this.listSubscription.unsubscribe(); this.counterSubscription.unsubscribe(); this.fileUploadSubscription.unsubscribe(); + this.errorSubscription.unsubscribe(); } } diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.html b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.html index aa9810e731..aadc9f9653 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.html +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.html @@ -1,28 +1,28 @@ -
+
+ class="adf-file-uploading-row__type"> insert_drive_file {{ file.name }}
- - {{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }} - + + {{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }} + + class="adf-file-uploading-row__action adf-file-uploading-row__action--cancel"> clear
@@ -30,24 +30,41 @@
+ class="adf-file-uploading-row__status adf-file-uploading-row__status--done"> check_circle + class="adf-file-uploading-row__action adf-file-uploading-row__action--remove"> + remove_circle + +
+ +
+ + schedule + + + remove_circle
report_problem @@ -55,8 +72,10 @@
+ *ngIf="file.status === FileUploadStatus.Cancelled || + file.status === FileUploadStatus.Aborted || + file.status === FileUploadStatus.Deleted" + class="adf-file-uploading-row__block adf-file-uploading-row__status--cancelled"> {{ 'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate }}
-
+
\ No newline at end of file diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.scss b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.scss index 04571a1497..9847f302fe 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.scss +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list-row.component.scss @@ -1,74 +1,71 @@ @import 'colors'; -@mixin mat-file-uploading-list-theme($theme) { +@mixin mat-file-uploading-row-theme($theme) { $primary: map-get($theme, primary); $accent: map-get($theme, accent); $warn: map-get($theme, warn); - .adf-file-uploading-list { + .adf-file-uploading-row { display: flex; align-items: center; padding: 0.5em 1em 0.5em 1em; + cursor: default; &:not(:first-child) { border-top: 1px solid ; } - .list-row { - cursor: default; + &__name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1 1 auto; + padding: 0 1em 0 0.5em; + } - &__name { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1 1 auto; - padding: 0 1em 0 0.5em; - } + &__group, &__block { + min-width: 200px; + display: flex; + justify-content: flex-end; + } - &__group, &__block { - min-width: 200px; + &__group--toggle { + cursor: pointer; + + .adf-file-uploading-row__status { display: flex; - justify-content: flex-end; } - &__group--toggle { - cursor: pointer; + .adf-file-uploading-row__action { + display: none; + } - .list-row__status { - display: flex; - } - - .list-row__action { + &:hover { + .adf-file-uploading-row__status { display: none; } - &:hover { - .list-row__status { - display: none; - } - - .list-row__action { - display: flex; - } + .adf-file-uploading-row__action { + display: flex; } } + } - &__status--done { - color: mat-color($accent); - } + &__status--done { + color: mat-color($accent); + } - &__status--error { - color: mat-color($warn); - } + &__status--error { + color: mat-color($warn); + } - &__action--cancel { - color: mat-color($warn); - } + &__action--cancel { + color: mat-color($warn); + } - &__action--remove { - color: mat-color($accent); - } + &__action--remove { + color: mat-color($accent); } } } diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.spec.ts b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.spec.ts index 7e6712eea4..359a7e36f5 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.spec.ts @@ -16,10 +16,9 @@ */ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AlfrescoTranslationService, FileModel, FileUploadStatus, NodesApiService, NotificationService, UploadService } from 'ng2-alfresco-core'; +import { AlfrescoTranslationService, FileUploadStatus, NodesApiService, NotificationService, UploadService } from 'ng2-alfresco-core'; import { Observable } from 'rxjs/Rx'; import { UploadModule } from '../../index'; -import { FileUploadService } from '../services/file-uploading.service'; import { FileUploadingListComponent } from './file-uploading-list.component'; describe('FileUploadingListComponent', () => { @@ -27,10 +26,13 @@ describe('FileUploadingListComponent', () => { let component: FileUploadingListComponent; let uploadService: UploadService; let nodesApiService: NodesApiService; - let fileUploadService: FileUploadService; let notificationService: NotificationService; let translateService: AlfrescoTranslationService; - let file = new FileModel( { name: 'fake-name' }); + let file: any; + + beforeEach(() => { + file = { data: { entry: { id: 'x' } } }; + }); beforeEach(() => { TestBed.configureTestingModule({ @@ -43,66 +45,128 @@ describe('FileUploadingListComponent', () => { beforeEach(() => { nodesApiService = TestBed.get(NodesApiService); uploadService = TestBed.get(UploadService); - fileUploadService = TestBed.get(FileUploadService); notificationService = TestBed.get(NotificationService); translateService = TestBed.get(AlfrescoTranslationService); fixture = TestBed.createComponent(FileUploadingListComponent); component = fixture.componentInstance; - component.files = [ file ]; - file.data = { entry: { id: 'x' } }; spyOn(translateService, 'get').and.returnValue(Observable.of('some error message')); + spyOn(notificationService, 'openSnackMessage'); + spyOn(uploadService, 'cancelUpload'); }); - describe('cancelFileUpload()', () => { + describe('cancelFile()', () => { it('should call uploadService api when cancelling a file', () => { - spyOn(uploadService, 'cancelUpload'); - component.cancelFileUpload(file); + component.cancelFile(file); expect(uploadService.cancelUpload).toHaveBeenCalledWith(file); }); }); describe('removeFile()', () => { - it('should remove file successfully when api returns success', () => { - spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of('success')); - spyOn(fileUploadService, 'emitFileRemoved'); + it('should change file status when api returns success', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of(file)); component.removeFile(file); fixture.detectChanges(); - expect(fileUploadService.emitFileRemoved).toHaveBeenCalledWith(file); + expect(file.status).toBe(FileUploadStatus.Deleted); }); - it('should notify on remove file fail when api returns error', () => { - spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw({})); - spyOn(notificationService, 'openSnackMessage'); + it('should change file status when api returns error', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(file)); + + component.removeFile(file); + fixture.detectChanges(); + + expect(file.status).toBe(FileUploadStatus.Error); + }); + + it('should notify fail when api returns error', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(file)); component.removeFile(file); fixture.detectChanges(); expect(notificationService.openSnackMessage).toHaveBeenCalled(); }); + + it('should call uploadService on error', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(file)); + + component.removeFile(file); + fixture.detectChanges(); + + expect(uploadService.cancelUpload).toHaveBeenCalled(); + }); + + it('should call uploadService on success', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of(file)); + + component.removeFile(file); + fixture.detectChanges(); + + expect(uploadService.cancelUpload).toHaveBeenCalled(); + }); }); describe('cancelAllFiles()', () => { beforeEach(() => { - spyOn(component, 'removeFile'); - spyOn(component, 'cancelFileUpload'); + component.files = [ + { + data: { + entry: { id: '1' } + }, + status: FileUploadStatus.Cancelled + }, + { + data: { + entry: { id: '2' } + }, + status: FileUploadStatus.Error + } + ]; }); - it('should call removeFile() if file was uploaded', () => { - file.status = FileUploadStatus.Complete; - component.cancelAllFiles(null); + it('should not call deleteNode if there are no competed uploads', () => { + spyOn(nodesApiService, 'deleteNode'); - expect(component.removeFile).toHaveBeenCalledWith(file); + component.cancelAllFiles(); + + expect(nodesApiService.deleteNode).not.toHaveBeenCalled(); }); - it('should call cancelFileUpload() if file is being uploaded', () => { - file.status = FileUploadStatus.Progress; - component.cancelAllFiles(null); + it('should not call uploadService if there are no uploading files', () => { + component.cancelAllFiles(); - expect(component.cancelFileUpload).toHaveBeenCalledWith(file); + expect(uploadService.cancelUpload).not.toHaveBeenCalled(); + }); + + it('should call deleteNode when there are completed uploads', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of({})); + + component.files[0].status = FileUploadStatus.Complete; + component.cancelAllFiles(); + + expect(nodesApiService.deleteNode).toHaveBeenCalled(); + }); + + it('should call uploadService when there are uploading files', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of({})); + + component.files[0].status = FileUploadStatus.Progress; + component.cancelAllFiles(); + + expect(uploadService.cancelUpload).toHaveBeenCalled(); + }); + + it('should notify on deleting file error', () => { + spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw({})); + + component.files[0].status = FileUploadStatus.Complete; + component.cancelAllFiles(); + + expect(notificationService.openSnackMessage).toHaveBeenCalled(); }); }); @@ -110,7 +174,6 @@ describe('FileUploadingListComponent', () => { it('should return false when at least one file is in progress', () => { component.files = [ { status: FileUploadStatus.Progress }, - { status: FileUploadStatus.Cancelled }, { status: FileUploadStatus.Complete } ]; @@ -120,23 +183,39 @@ describe('FileUploadingListComponent', () => { it('should return false when at least one file is in pending', () => { component.files = [ { status: FileUploadStatus.Pending }, - { status: FileUploadStatus.Cancelled }, { status: FileUploadStatus.Complete } ]; expect(component.isUploadCompleted()).toBe(false); }); - it('should return false when none of the files is completed', () => { + it('should return false when at least one file is in starting state', () => { component.files = [ - { status: FileUploadStatus.Error }, - { status: FileUploadStatus.Error }, + { status: FileUploadStatus.Starting }, + { status: FileUploadStatus.Complete } + ]; + + expect(component.isUploadCompleted()).toBe(false); + }); + + it('should return false when files are cancelled', () => { + component.files = [ + { status: FileUploadStatus.Cancelled }, { status: FileUploadStatus.Cancelled } ]; expect(component.isUploadCompleted()).toBe(false); }); + it('should return true when there are deleted files', () => { + component.files = [ + { status: FileUploadStatus.Complete }, + { status: FileUploadStatus.Deleted } + ]; + + expect(component.isUploadCompleted()).toBe(true); + }); + it('should return true when none of the files is in progress', () => { component.files = [ { status: FileUploadStatus.Error }, @@ -191,7 +270,6 @@ describe('FileUploadingListComponent', () => { it('should return true when all files are aborted', () => { component.files = [ - { status: FileUploadStatus.Aborted }, { status: FileUploadStatus.Aborted } ]; @@ -202,53 +280,10 @@ describe('FileUploadingListComponent', () => { component.files = [ { status: FileUploadStatus.Cancelled }, { status: FileUploadStatus.Cancelled }, - { status: FileUploadStatus.Error } + { status: FileUploadStatus.Aborted } ]; expect(component.isUploadCancelled()).toBe(true); }); }); - - describe('uploadErrorFiles()', () => { - it('should return array of error files', () => { - component.files = [ - { status: FileUploadStatus.Complete }, - { status: FileUploadStatus.Error }, - { status: FileUploadStatus.Error } - ]; - - expect(component.uploadErrorFiles.length).toEqual(2); - }); - - it('should return empty array when no error files found', () => { - component.files = [ - { status: FileUploadStatus.Complete }, - { status: FileUploadStatus.Pending } - ]; - - expect(component.uploadErrorFiles.length).toEqual(0); - }); - }); - - describe('uploadCancelledFiles()', () => { - it('should return array of cancelled files', () => { - component.files = [ - { status: FileUploadStatus.Cancelled }, - { status: FileUploadStatus.Complete }, - { status: FileUploadStatus.Error } - ]; - - expect(component.uploadCancelledFiles.length).toEqual(1); - }); - - it('should return emty array when no cancelled files found', () => { - component.files = [ - { status: FileUploadStatus.Error }, - { status: FileUploadStatus.Complete }, - { status: FileUploadStatus.Pending } - ]; - - expect(component.uploadCancelledFiles.length).toEqual(0); - }); - }); }); diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts index a3dc8fa45c..f3ee8b4435 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts @@ -17,7 +17,7 @@ import { Component, ContentChild, Input, TemplateRef } from '@angular/core'; import { AlfrescoTranslationService, FileModel, FileUploadStatus, NodesApiService, NotificationService, UploadService } from 'ng2-alfresco-core'; -import { FileUploadService } from '../services/file-uploading.service'; +import { Observable } from 'rxjs/Rx'; @Component({ selector: 'adf-file-uploading-list, alfresco-file-uploading-list', @@ -35,7 +35,6 @@ export class FileUploadingListComponent { files: FileModel[] = []; constructor( - private fileUploadService: FileUploadService, private uploadService: UploadService, private nodesApi: NodesApiService, private notificationService: NotificationService, @@ -49,41 +48,43 @@ export class FileUploadingListComponent { * * @memberOf FileUploadingListComponent */ - cancelFileUpload(file: FileModel): void { + cancelFile(file: FileModel): void { this.uploadService.cancelUpload(file); } removeFile(file: FileModel): void { - const { id } = file.data.entry; - this.nodesApi - .deleteNode(id, { permanent: true }) - .subscribe( - () => this.onRemoveSuccess(file), - () => this.onRemoveFail(file) - ); + this.deleteNode(file) + .subscribe(() => { + if ( file.status === FileUploadStatus.Error) { + this.notifyError(file); + } + + this.uploadService.cancelUpload(file); + }); } /** - * Call the abort method for each file + * Call the appropriate method for each file, depending on state */ - cancelAllFiles(event: Event): void { - if (event) { - event.preventDefault(); - } + cancelAllFiles(): void { + this.getUploadingFiles() + .forEach((file) => this.uploadService.cancelUpload(file)); - this.files.forEach((file) => { - const { status } = file; - const { Complete, Progress, Pending } = FileUploadStatus; + const deletedFiles = this.files + .filter((file) => file.status === FileUploadStatus.Complete) + .map((file) => this.deleteNode(file)); - if (status === Complete) { - this.removeFile(file); - } + Observable.forkJoin(...deletedFiles) + .subscribe((files: FileModel[]) => { + const errors = files + .filter((file) => file.status === FileUploadStatus.Error); - if (status === Progress || status === Pending) { - this.cancelFileUpload(file); - } + if (errors.length) { + this.notifyError(...errors); + } - }); + this.uploadService.cancelUpload(...files); + }); } /** @@ -92,7 +93,7 @@ export class FileUploadingListComponent { */ isUploadCompleted(): boolean { return !this.isUploadCancelled() && - !!this.files.length && + Boolean(this.files.length) && !this.files .some(({status}) => status === FileUploadStatus.Starting || @@ -111,38 +112,55 @@ export class FileUploadingListComponent { .every(({status}) => status === FileUploadStatus.Aborted || status === FileUploadStatus.Cancelled || - status === FileUploadStatus.Error + status === FileUploadStatus.Deleted ); } - /** - * Gets all the files with status Error. - * @returns {boolean} - false if there is none - */ - get uploadErrorFiles(): FileModel[] { - return this.files.filter(({status}) => status === FileUploadStatus.Error); - } + private deleteNode(file: FileModel): Observable { + const { id } = file.data.entry; - /** - * Gets all the files with status Cancelled. - * @returns {boolean} - false if there is none - */ - get uploadCancelledFiles(): FileModel[] { - return this.files.filter(({status}) => status === FileUploadStatus.Cancelled); - } - - private onRemoveSuccess(file: FileModel): void { - const { uploadService, fileUploadService } = this; - - uploadService.cancelUpload(file); - fileUploadService.emitFileRemoved(file); - } - - private onRemoveFail(file: FileModel): void { - this.translateService - .get('FILE_UPLOAD.MESSAGES.REMOVE_FILE_ERROR', { fileName: file.name}) - .subscribe((message) => { - this.notificationService.openSnackMessage(message, 4000); + return this.nodesApi + .deleteNode(id, { permanent: true }) + .map(() => { + file.status = FileUploadStatus.Deleted; + return file; + }) + .catch((error) => { + file.status = FileUploadStatus.Error; + return Observable.of(file); }); } + + private notifyError(...files: FileModel[]) { + let translateSubscription = null; + + if (files.length === 1) { + translateSubscription = this.translateService + .get( + 'FILE_UPLOAD.MESSAGES.REMOVE_FILE_ERROR', + { fileName: files[0].name} + ); + } else { + translateSubscription = this.translateService + .get( + 'FILE_UPLOAD.MESSAGES.REMOVE_FILES_ERROR', + { total: files.length } + ); + } + + translateSubscription + .subscribe(message => this.notificationService.openSnackMessage(message, 4000)); + } + + private getUploadingFiles() { + return this.files.filter((item) => { + if ( + item.status === FileUploadStatus.Pending || + item.status === FileUploadStatus.Progress || + item.status === FileUploadStatus.Starting + ) { + return item; + } + }); + } } diff --git a/ng2-components/ng2-alfresco-upload/src/i18n/en.json b/ng2-components/ng2-alfresco-upload/src/i18n/en.json index 29c94ffc99..aee535f285 100644 --- a/ng2-components/ng2-alfresco-upload/src/i18n/en.json +++ b/ng2-components/ng2-alfresco-upload/src/i18n/en.json @@ -26,7 +26,8 @@ "PROGRESS": "Upload in progress...", "FOLDER_ALREADY_EXIST": "The folder {0} already exist", "FOLDER_NOT_SUPPORTED": "Folder upload isn't supported by your browser", - "REMOVE_FILE_ERROR": "Error removing file {{ fileName }}" + "REMOVE_FILE_ERROR": "Error removing file {{ fileName }}", + "REMOVE_FILES_ERROR": "Error removing {{ total }} files" }, "ACTION": { "UNDO": "Undo" diff --git a/ng2-components/ng2-alfresco-upload/src/services/file-uploading.service.spec.ts b/ng2-components/ng2-alfresco-upload/src/services/file-uploading.service.spec.ts deleted file mode 100644 index c44b442eba..0000000000 --- a/ng2-components/ng2-alfresco-upload/src/services/file-uploading.service.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * @license - * Copyright 2016 Alfresco Software, Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FileModel } from 'ng2-alfresco-core'; -import { FileUploadService } from './file-uploading.service'; - -describe('FileUploadService', () => { - let service: FileUploadService; - let file = new FileModel( { name: 'fake-name' }); - - beforeEach(() => { - service = new FileUploadService(); - }); - - it('emits file remove event', () => { - spyOn(service.remove, 'next'); - service.emitFileRemoved(file); - - expect(service.remove.next).toHaveBeenCalledWith(file); - }); - - it('passes removed file data', () => { - service.emitFileRemoved(file); - - service.remove.subscribe((data) => { - expect(data).toEqual(file); - }); - }); -}); diff --git a/ng2-components/ng2-alfresco-upload/src/services/file-uploading.service.ts b/ng2-components/ng2-alfresco-upload/src/services/file-uploading.service.ts deleted file mode 100644 index 88b8863423..0000000000 --- a/ng2-components/ng2-alfresco-upload/src/services/file-uploading.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * @license - * Copyright 2016 Alfresco Software, Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs/Rx'; - -@Injectable() -export class FileUploadService { - remove = new Subject(); - - emitFileRemoved(item: any) { - this.remove.next(item); - } -}