mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-1403] Upload Dialog - Delete files event (#2234)
* remove provisional service * Error and Delete events * delete status * separate upload events * update demo * files upload counter and errors * pendig files icon and action * remove multiple files error key * handle cancel files * fixed component theme * remove fdescribe * resolved comments * throw error
This commit is contained in:
committed by
Mario Romano
parent
1deaa22570
commit
07ba8bc15f
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -34,7 +34,8 @@ export enum FileUploadStatus {
|
||||
Progress = 3,
|
||||
Cancelled = 4,
|
||||
Aborted = 5,
|
||||
Error = 6
|
||||
Error = 6,
|
||||
Deleted = 7
|
||||
}
|
||||
|
||||
export class FileModel {
|
||||
|
@@ -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 = <any> ({ 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 = <any> ({ 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 = <any> ({ status: FileUploadStatus.Pending });
|
||||
spyOn(service.fileUploadCancelled, 'next');
|
||||
|
||||
service.cancelUpload(file);
|
||||
|
||||
expect(service.fileUploadCancelled.next).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@@ -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<any> = null;
|
||||
private excludedFileList: String[] = [];
|
||||
|
||||
@@ -39,8 +40,9 @@ export class UploadService {
|
||||
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadProgress: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadAborted: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadError: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||
fileUploadError: Subject<FileUploadErrorEvent> = new Subject<FileUploadErrorEvent>();
|
||||
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>();
|
||||
fileUploadDeleted: Subject<FileUploadDeleteEvent> = new Subject<FileUploadDeleteEvent>();
|
||||
|
||||
constructor(private apiService: AlfrescoApiService, private appConfigService: AppConfigService) {
|
||||
this.excludedFileList = <String[]> 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];
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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 @@
|
||||
|
||||
<section
|
||||
class="upload-dialog__info"
|
||||
*ngIf="uploadList.uploadErrorFiles.length">
|
||||
*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 }
|
||||
}}
|
||||
</section>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
<adf-file-uploading-list-row
|
||||
[file]="file"
|
||||
(remove)="uploadList.removeFile(file)"
|
||||
(cancel)="uploadList.cancelFileUpload(file)">
|
||||
(cancel)="uploadList.cancelFile(file)">
|
||||
</adf-file-uploading-list-row>
|
||||
</ng-template>
|
||||
</adf-file-uploading-list>
|
||||
@@ -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 }}
|
||||
</button>
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -1,28 +1,28 @@
|
||||
<div class="adf-file-uploading-list">
|
||||
<div class="adf-file-uploading-row">
|
||||
<md-icon
|
||||
md-list-icon
|
||||
class="list-row__type">
|
||||
class="adf-file-uploading-row__type">
|
||||
insert_drive_file
|
||||
</md-icon>
|
||||
|
||||
<span
|
||||
class="list-row__name"
|
||||
class="adf-file-uploading-row__name"
|
||||
title="{{ file.name }}">
|
||||
{{ file.name }}
|
||||
</span>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Progress"
|
||||
*ngIf="file.status === FileUploadStatus.Progress || file.status === FileUploadStatus.Starting"
|
||||
(click)="onCancel(file)"
|
||||
class="list-row__group list-row__group--toggle"
|
||||
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle"
|
||||
title="{{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_FILE' | translate }}">
|
||||
<span class="list-row__status">
|
||||
{{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }}
|
||||
</span>
|
||||
<span class="adf-file-uploading-row__status">
|
||||
{{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }}
|
||||
</span>
|
||||
|
||||
<md-icon
|
||||
md-list-icon
|
||||
class="list-row__action list-row__action--cancel">
|
||||
class="adf-file-uploading-row__action adf-file-uploading-row__action--cancel">
|
||||
clear
|
||||
</md-icon>
|
||||
</div>
|
||||
@@ -30,24 +30,41 @@
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Complete"
|
||||
(click)="onRemove(file)"
|
||||
class="list-row__group list-row__group--toggle"
|
||||
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle"
|
||||
title="{{ 'ADF_FILE_UPLOAD.BUTTON.REMOVE_FILE' | translate }}">
|
||||
<md-icon
|
||||
md-list-icon
|
||||
class="list-row__status list-row__status--done">
|
||||
class="adf-file-uploading-row__status adf-file-uploading-row__status--done">
|
||||
check_circle
|
||||
</md-icon>
|
||||
|
||||
<md-icon
|
||||
md-list-icon
|
||||
class="list-row__action list-row__action--remove">
|
||||
class="adf-file-uploading-row__action adf-file-uploading-row__action--remove">
|
||||
remove_circle
|
||||
</md-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Pending"
|
||||
(click)="onCancel(file)"
|
||||
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle">
|
||||
<md-icon
|
||||
md-list-icon
|
||||
class="adf-file-uploading-row__status adf-file-uploading-row__status--pending">
|
||||
schedule
|
||||
</md-icon>
|
||||
|
||||
<md-icon
|
||||
md-list-icon
|
||||
class="adf-file-uploading-row__action adf-file-uploading-row__action--remove">
|
||||
remove_circle
|
||||
</md-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Error"
|
||||
class="list-row__block list-row__status--error"
|
||||
class="adf-file-uploading-row__block adf-file-uploading-row__status--error"
|
||||
title="{{ file.response }}">
|
||||
<md-icon md-list-icon>
|
||||
report_problem
|
||||
@@ -55,8 +72,10 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Cancelled || file.status === FileUploadStatus.Aborted"
|
||||
class="list-row__block list-row__status--cancelled">
|
||||
*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 }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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(<File> { 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 = <any> [
|
||||
{
|
||||
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 = <any> [
|
||||
{ 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 = <any> [
|
||||
{ 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 = <any> [
|
||||
{ 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 = <any> [
|
||||
{ status: FileUploadStatus.Cancelled },
|
||||
{ status: FileUploadStatus.Cancelled }
|
||||
];
|
||||
|
||||
expect(component.isUploadCompleted()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when there are deleted files', () => {
|
||||
component.files = <any> [
|
||||
{ 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 = <any> [
|
||||
{ status: FileUploadStatus.Error },
|
||||
@@ -191,7 +270,6 @@ describe('FileUploadingListComponent', () => {
|
||||
|
||||
it('should return true when all files are aborted', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Aborted },
|
||||
{ status: FileUploadStatus.Aborted }
|
||||
];
|
||||
|
||||
@@ -202,53 +280,10 @@ describe('FileUploadingListComponent', () => {
|
||||
component.files = <any> [
|
||||
{ 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 = <any> [
|
||||
{ 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 = <any> [
|
||||
{ status: FileUploadStatus.Complete },
|
||||
{ status: FileUploadStatus.Pending }
|
||||
];
|
||||
|
||||
expect(component.uploadErrorFiles.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadCancelledFiles()', () => {
|
||||
it('should return array of cancelled files', () => {
|
||||
component.files = <any> [
|
||||
{ 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 = <any> [
|
||||
{ status: FileUploadStatus.Error },
|
||||
{ status: FileUploadStatus.Complete },
|
||||
{ status: FileUploadStatus.Pending }
|
||||
];
|
||||
|
||||
expect(component.uploadCancelledFiles.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -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<FileModel> {
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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(<File> { 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);
|
||||
});
|
||||
});
|
||||
});
|
@@ -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<string>();
|
||||
|
||||
emitFileRemoved(item: any) {
|
||||
this.remove.next(item);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user