From db8be5c1b1f93eaf3ff56bb023e7e2204514beb9 Mon Sep 17 00:00:00 2001 From: Cilibiu Bogdan Date: Tue, 27 Nov 2018 18:49:58 +0200 Subject: [PATCH] [ACA-1057] Upload dialog - file upload error message (#832) * Upload dialog * prettier lint * changed to Uploaded text * remove unnecessary implementation * todos --- .../app-layout/app-layout.component.html | 2 +- src/app/components/layout/layout.module.ts | 4 +- .../file-uploading-dialog.component.html | 116 +++++++++++ .../file-uploading-dialog.component.ts | 191 ++++++++++++++++++ .../file-uploading-list-row.component.html | 81 ++++++++ .../file-uploading-list-row.component.ts | 47 +++++ .../file-uploading-list.component.html | 7 + .../file-uploading-list.component.ts | 180 +++++++++++++++++ .../upload-dialog/upload-dialog.module.ts | 46 +++++ src/assets/i18n/en.json | 7 +- 10 files changed, 677 insertions(+), 4 deletions(-) create mode 100644 src/app/components/upload-dialog/file-uploading-dialog.component.html create mode 100644 src/app/components/upload-dialog/file-uploading-dialog.component.ts create mode 100644 src/app/components/upload-dialog/file-uploading-list-row.component.html create mode 100644 src/app/components/upload-dialog/file-uploading-list-row.component.ts create mode 100644 src/app/components/upload-dialog/file-uploading-list.component.html create mode 100644 src/app/components/upload-dialog/file-uploading-list.component.ts create mode 100644 src/app/components/upload-dialog/upload-dialog.module.ts diff --git a/src/app/components/layout/app-layout/app-layout.component.html b/src/app/components/layout/app-layout/app-layout.component.html index 20a5f53b5..63b169c00 100644 --- a/src/app/components/layout/app-layout/app-layout.component.html +++ b/src/app/components/layout/app-layout/app-layout.component.html @@ -34,5 +34,5 @@ - + diff --git a/src/app/components/layout/layout.module.ts b/src/app/components/layout/layout.module.ts index 78959e455..4ce8ba5d0 100644 --- a/src/app/components/layout/layout.module.ts +++ b/src/app/components/layout/layout.module.ts @@ -32,6 +32,7 @@ import { RouterModule } from '@angular/router'; import { AppSidenavModule } from '../sidenav/sidenav.module'; import { AppCommonModule } from '../common/common.module'; import { AppHeaderModule } from '../header/header.module'; +import { AppUploadingDialogModule } from '../upload-dialog/upload-dialog.module'; import { PageLayoutComponent } from './page-layout/page-layout.component'; import { PageLayoutHeaderComponent } from './page-layout/page-layout-header.component'; import { PageLayoutContentComponent } from './page-layout/page-layout-content.component'; @@ -47,7 +48,8 @@ import { HttpClientModule } from '@angular/common/http'; AppCommonModule, AppSidenavModule, AppHeaderModule, - HttpClientModule + HttpClientModule, + AppUploadingDialogModule ], declarations: [ AppLayoutComponent, diff --git a/src/app/components/upload-dialog/file-uploading-dialog.component.html b/src/app/components/upload-dialog/file-uploading-dialog.component.html new file mode 100644 index 000000000..c4f246079 --- /dev/null +++ b/src/app/components/upload-dialog/file-uploading-dialog.component.html @@ -0,0 +1,116 @@ +
+
+ + + + {{ 'FILE_UPLOAD.MESSAGES.UPLOAD_COMPLETED' + | translate: { + completed: totalCompleted, + total: filesUploadingList.length + } + }} + + + + {{ 'FILE_UPLOAD.MESSAGES.UPLOAD_CANCELED' | translate }} + +
+ +
+ {{ + (totalErrors > 1 + ? 'FILE_UPLOAD.MESSAGES.UPLOAD_ERRORS' + : 'FILE_UPLOAD.MESSAGES.UPLOAD_ERROR') + | translate: { total: totalErrors } + }} +
+ +
+ + + + + + + +
+

+ {{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TITLE' | translate }} +

+ +

+ {{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TEXT' | translate }} +

+
+
+ +
+ + + +
+ +
+ + + +
+
diff --git a/src/app/components/upload-dialog/file-uploading-dialog.component.ts b/src/app/components/upload-dialog/file-uploading-dialog.component.ts new file mode 100644 index 000000000..ed7780543 --- /dev/null +++ b/src/app/components/upload-dialog/file-uploading-dialog.component.ts @@ -0,0 +1,191 @@ +/*! + * @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, + FileUploadCompleteEvent, + FileUploadDeleteEvent, + FileUploadErrorEvent, + FileUploadStatus, + UploadService +} from '@alfresco/adf-core'; +import { + ChangeDetectorRef, + Component, + Input, + Output, + EventEmitter, + OnDestroy, + OnInit, + ViewChild +} from '@angular/core'; +import { Subscription, merge } from 'rxjs'; +import { FileUploadingListComponent } from './file-uploading-list.component'; + +// @deprecated file-uploading-dialog TODO remove in 3.0.0 +@Component({ + selector: 'app-file-uploading-dialog', + templateUrl: './file-uploading-dialog.component.html' +}) +export class FileUploadingDialogComponent implements OnInit, OnDestroy { + @ViewChild('uploadList') + uploadList: FileUploadingListComponent; + + /** Dialog position. Can be 'left' or 'right'. */ + @Input() + position = 'right'; + + /** Emitted when a file in the list has an error. */ + @Output() + error: EventEmitter = new EventEmitter(); + + filesUploadingList: FileModel[] = []; + isDialogActive = false; + totalCompleted = 0; + totalErrors = 0; + isDialogMinimized = false; + isConfirmation = false; + + private listSubscription: Subscription; + private counterSubscription: Subscription; + private fileUploadSubscription: Subscription; + private errorSubscription: Subscription; + private errors = []; + + constructor( + private uploadService: UploadService, + private changeDetector: ChangeDetectorRef + ) {} + + ngOnInit() { + this.listSubscription = this.uploadService.queueChanged.subscribe( + (fileList: FileModel[]) => { + this.filesUploadingList = fileList; + + if (this.filesUploadingList.length) { + this.isDialogActive = true; + } + } + ); + + this.counterSubscription = merge( + this.uploadService.fileUploadComplete, + this.uploadService.fileUploadDeleted + ).subscribe((event: FileUploadDeleteEvent | FileUploadCompleteEvent) => { + this.totalCompleted = event.totalComplete; + this.changeDetector.detectChanges(); + }); + + // todo: move to ADF ACA-2051 + this.errorSubscription = this.uploadService.fileUploadError.subscribe( + (event: FileUploadErrorEvent) => { + this.errors.push({ + fileName: event.file.name, + status: event.error.response.statusCode, + message: this.getUploadErrorMessage(event.error.response.statusCode) + }); + this.totalErrors = event.totalError; + this.changeDetector.detectChanges(); + } + ); + + this.fileUploadSubscription = this.uploadService.fileUpload.subscribe( + event => { + this.changeDetector.detectChanges(); + } + ); + + this.uploadService.fileDeleted.subscribe(objId => { + if (this.filesUploadingList) { + const file = this.filesUploadingList.find(item => { + return item.data.entry.id === objId; + }); + if (file) { + file.status = FileUploadStatus.Cancelled; + this.changeDetector.detectChanges(); + } + } + }); + } + + getFileUploadError(file) { + return this.errors.find(error => (error.fileName = file.name)); + } + + /** + * Toggle confirmation message. + */ + toggleConfirmation() { + this.isConfirmation = !this.isConfirmation; + + if (this.isDialogMinimized) { + this.isDialogMinimized = false; + } + } + + /** + * Cancel uploads and hide confirmation + */ + cancelAllUploads() { + this.toggleConfirmation(); + + this.uploadList.cancelAllFiles(); + } + + /** + * Toggle dialog minimized state. + */ + toggleMinimized(): void { + this.isDialogMinimized = !this.isDialogMinimized; + this.changeDetector.detectChanges(); + } + + /** + * Dismiss dialog + */ + close(): void { + this.isConfirmation = false; + this.totalCompleted = 0; + this.totalErrors = 0; + this.filesUploadingList = []; + this.isDialogActive = false; + this.isDialogMinimized = false; + this.uploadService.clearQueue(); + this.changeDetector.detectChanges(); + this.errors = []; + } + + ngOnDestroy() { + this.uploadService.clearQueue(); + this.listSubscription.unsubscribe(); + this.counterSubscription.unsubscribe(); + this.fileUploadSubscription.unsubscribe(); + this.errorSubscription.unsubscribe(); + } + + // todo: move to ADF ACA-2051 + private getUploadErrorMessage(status) { + const messages = { + 500: 'APP.MESSAGES.UPLOAD.ERROR.500', + 504: 'APP.MESSAGES.UPLOAD.ERROR.504', + 403: 'APP.MESSAGES.UPLOAD.ERROR.403', + 404: 'APP.MESSAGES.UPLOAD.ERROR.404' + }; + + return messages[status]; + } +} diff --git a/src/app/components/upload-dialog/file-uploading-list-row.component.html b/src/app/components/upload-dialog/file-uploading-list-row.component.html new file mode 100644 index 000000000..57cba25fd --- /dev/null +++ b/src/app/components/upload-dialog/file-uploading-list-row.component.html @@ -0,0 +1,81 @@ +
+ + insert_drive_file + + + + {{ file.name }} + + +
+ + {{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }} + + + + clear + +
+ +
+ + check_circle + + + + remove_circle + +
+ +
+ + schedule + + + + remove_circle + +
+ + +
+ + report_problem + +
+ +
+ {{ 'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate }} +
+
diff --git a/src/app/components/upload-dialog/file-uploading-list-row.component.ts b/src/app/components/upload-dialog/file-uploading-list-row.component.ts new file mode 100644 index 000000000..7bb564ecc --- /dev/null +++ b/src/app/components/upload-dialog/file-uploading-list-row.component.ts @@ -0,0 +1,47 @@ +/*! + * @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, FileUploadStatus } from '@alfresco/adf-core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'app-file-uploading-list-row', + templateUrl: './file-uploading-list-row.component.html' +}) +export class FileUploadingListRowComponent { + @Input() + file: FileModel; + + @Input() + error: any; + + @Output() + cancel: EventEmitter = new EventEmitter(); + + @Output() + remove: EventEmitter = new EventEmitter(); + + FileUploadStatus = FileUploadStatus; + + onCancel(file: FileModel): void { + this.cancel.emit(file); + } + + onRemove(file: FileModel): void { + this.remove.emit(file); + } +} diff --git a/src/app/components/upload-dialog/file-uploading-list.component.html b/src/app/components/upload-dialog/file-uploading-list.component.html new file mode 100644 index 000000000..91a4d358d --- /dev/null +++ b/src/app/components/upload-dialog/file-uploading-list.component.html @@ -0,0 +1,7 @@ +
+ + +
diff --git a/src/app/components/upload-dialog/file-uploading-list.component.ts b/src/app/components/upload-dialog/file-uploading-list.component.ts new file mode 100644 index 000000000..f70e07e03 --- /dev/null +++ b/src/app/components/upload-dialog/file-uploading-list.component.ts @@ -0,0 +1,180 @@ +/*! + * @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, + FileUploadStatus, + NodesApiService, + TranslationService, + UploadService +} from '@alfresco/adf-core'; +import { + Component, + ContentChild, + Input, + Output, + TemplateRef, + EventEmitter +} from '@angular/core'; +import { Observable, forkJoin, of } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; + +@Component({ + selector: 'app-file-uploading-list', + templateUrl: './file-uploading-list.component.html' +}) +export class FileUploadingListComponent { + FileUploadStatus = FileUploadStatus; + + @ContentChild(TemplateRef) + template: any; + + @Input() + files: FileModel[] = []; + + /** Emitted when a file in the list has an error. */ + @Output() + error: EventEmitter = new EventEmitter(); + + constructor( + private uploadService: UploadService, + private nodesApi: NodesApiService, + private translateService: TranslationService + ) {} + + /** + * Cancel file upload + * + * @param file File model to cancel upload for. + * + * @memberOf FileUploadingListComponent + */ + cancelFile(file: FileModel): void { + this.uploadService.cancelUpload(file); + } + + removeFile(file: FileModel): void { + this.deleteNode(file).subscribe(() => { + if (file.status === FileUploadStatus.Error) { + this.notifyError(file); + } + + this.uploadService.cancelUpload(file); + }); + } + + /** + * Call the appropriate method for each file, depending on state + */ + cancelAllFiles(): void { + this.getUploadingFiles().forEach(file => + this.uploadService.cancelUpload(file) + ); + + const deletedFiles = this.files + .filter(file => file.status === FileUploadStatus.Complete) + .map(file => this.deleteNode(file)); + + forkJoin(...deletedFiles).subscribe((files: FileModel[]) => { + const errors = files.filter( + file => file.status === FileUploadStatus.Error + ); + + if (errors.length) { + this.notifyError(...errors); + } + + this.uploadService.cancelUpload(...files); + }); + } + + /** + * Checks if all the files are uploaded false if there is at least one file in Progress | Starting | Pending + */ + isUploadCompleted(): boolean { + return ( + !this.isUploadCancelled() && + Boolean(this.files.length) && + !this.files.some( + ({ status }) => + status === FileUploadStatus.Starting || + status === FileUploadStatus.Progress || + status === FileUploadStatus.Pending + ) + ); + } + + /** + * Check if all the files are Cancelled | Aborted | Error. false if there is at least one file in uploading states + */ + isUploadCancelled(): boolean { + return ( + !!this.files.length && + this.files.every( + ({ status }) => + status === FileUploadStatus.Aborted || + status === FileUploadStatus.Cancelled || + status === FileUploadStatus.Deleted + ) + ); + } + + private deleteNode(file: FileModel): Observable { + const { id } = file.data.entry; + + return this.nodesApi.deleteNode(id, { permanent: true }).pipe( + map(() => { + file.status = FileUploadStatus.Deleted; + return file; + }), + catchError(() => { + file.status = FileUploadStatus.Error; + return of(file); + }) + ); + } + + private notifyError(...files: FileModel[]) { + let messageError: string = null; + + if (files.length === 1) { + messageError = this.translateService.instant( + 'FILE_UPLOAD.MESSAGES.REMOVE_FILE_ERROR', + { fileName: files[0].name } + ); + } else { + messageError = this.translateService.instant( + 'FILE_UPLOAD.MESSAGES.REMOVE_FILES_ERROR', + { total: files.length } + ); + } + + this.error.emit(messageError); + } + + 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/src/app/components/upload-dialog/upload-dialog.module.ts b/src/app/components/upload-dialog/upload-dialog.module.ts new file mode 100644 index 000000000..24606c41e --- /dev/null +++ b/src/app/components/upload-dialog/upload-dialog.module.ts @@ -0,0 +1,46 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2018 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CoreModule } from '@alfresco/adf-core'; +import { FileUploadingDialogComponent } from './file-uploading-dialog.component'; +import { FileUploadingListRowComponent } from './file-uploading-list-row.component'; +import { FileUploadingListComponent } from './file-uploading-list.component'; + +@NgModule({ + imports: [CommonModule, CoreModule.forChild()], + declarations: [ + FileUploadingDialogComponent, + FileUploadingListRowComponent, + FileUploadingListComponent + ], + exports: [ + FileUploadingDialogComponent, + FileUploadingListRowComponent, + FileUploadingListComponent + ] +}) +export class AppUploadingDialogModule {} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index cf27c22b5..bdde88df9 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -281,9 +281,12 @@ }, "UPLOAD": { "ERROR": { - "GENERIC": "There was a problem", + "GENERIC": "Upload unsuccessful. Contact IT if this problem persists", "CONFLICT": "New version not uploaded, another file with the same name already exists", - "500": "There was a problem while uploading" + "500": "Internal server error, try again or contact IT support [500]", + "504": "The server timed out, try again or contact IT support [504]", + "403": "Insufficient permissions to upload in this location [403]", + "404": "Upload location no longer exists [404]" } }, "INFO": {