[ACA-1057] Upload dialog - file upload error message (#832)

* Upload dialog

* prettier lint

* changed to Uploaded text

* remove unnecessary implementation

* todos
This commit is contained in:
Cilibiu Bogdan
2018-11-27 18:49:58 +02:00
committed by Denys Vuika
parent cd46070368
commit db8be5c1b1
10 changed files with 677 additions and 4 deletions

View File

@@ -34,5 +34,5 @@
</adf-sidenav-layout> </adf-sidenav-layout>
<adf-file-uploading-dialog position="left"></adf-file-uploading-dialog> <app-file-uploading-dialog position="left"></app-file-uploading-dialog>
</adf-upload-drag-area> </adf-upload-drag-area>

View File

@@ -32,6 +32,7 @@ import { RouterModule } from '@angular/router';
import { AppSidenavModule } from '../sidenav/sidenav.module'; import { AppSidenavModule } from '../sidenav/sidenav.module';
import { AppCommonModule } from '../common/common.module'; import { AppCommonModule } from '../common/common.module';
import { AppHeaderModule } from '../header/header.module'; import { AppHeaderModule } from '../header/header.module';
import { AppUploadingDialogModule } from '../upload-dialog/upload-dialog.module';
import { PageLayoutComponent } from './page-layout/page-layout.component'; import { PageLayoutComponent } from './page-layout/page-layout.component';
import { PageLayoutHeaderComponent } from './page-layout/page-layout-header.component'; import { PageLayoutHeaderComponent } from './page-layout/page-layout-header.component';
import { PageLayoutContentComponent } from './page-layout/page-layout-content.component'; import { PageLayoutContentComponent } from './page-layout/page-layout-content.component';
@@ -47,7 +48,8 @@ import { HttpClientModule } from '@angular/common/http';
AppCommonModule, AppCommonModule,
AppSidenavModule, AppSidenavModule,
AppHeaderModule, AppHeaderModule,
HttpClientModule HttpClientModule,
AppUploadingDialogModule
], ],
declarations: [ declarations: [
AppLayoutComponent, AppLayoutComponent,

View File

@@ -0,0 +1,116 @@
<div *ngIf="isDialogActive"
class="upload-dialog"
id="upload-dialog"
[class.upload-dialog--minimized]="isDialogMinimized"
[class.upload-dialog--position-left]="position === 'left'"
[class.upload-dialog--position-right]="position === 'right'">
<header class="upload-dialog__header">
<button
mat-button
color="secondary"
[disabled]="isConfirmation"
(click)="toggleMinimized()">
<mat-icon
mat-list-icon
title="{{ (isDialogMinimized ? 'ADF_FILE_UPLOAD.BUTTON.MAXIMIZE': 'ADF_FILE_UPLOAD.BUTTON.MINIMIZE') | translate }}">
{{ isDialogMinimized ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}
</mat-icon>
</button>
<span
class="upload-dialog__title"
*ngIf="!uploadList.isUploadCancelled()">
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_COMPLETED'
| translate: {
completed: totalCompleted,
total: filesUploadingList.length
}
}}
</span>
<span
class="upload-dialog__title"
*ngIf="uploadList.isUploadCancelled()">
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_CANCELED' | translate }}
</span>
</header>
<section class="upload-dialog__info"
*ngIf="totalErrors">
{{
(totalErrors > 1
? 'FILE_UPLOAD.MESSAGES.UPLOAD_ERRORS'
: 'FILE_UPLOAD.MESSAGES.UPLOAD_ERROR')
| translate: { total: totalErrors }
}}
</section>
<section class="upload-dialog__content"
[class.upload-dialog--padding]="isConfirmation">
<app-file-uploading-list
[class.upload-dialog--hide]="isConfirmation"
#uploadList
[files]="filesUploadingList">
<ng-template let-file="$implicit">
<app-file-uploading-list-row
[file]="file"
[error]="getFileUploadError(file)"
(remove)="uploadList.removeFile(file)"
(cancel)="uploadList.cancelFile(file)">
</app-file-uploading-list-row>
</ng-template>
</app-file-uploading-list>
<div
class="upload-dialog__confirmation"
[class.upload-dialog--hide]="!isConfirmation">
<p class="upload-dialog__confirmation--title">
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TITLE' | translate }}
</p>
<p class="upload-dialog__confirmation--text">
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TEXT' | translate }}
</p>
</div>
</section>
<footer class="upload-dialog__actions"
*ngIf="!isConfirmation">
<button
id="adf-upload-dialog-cancel-all"
color="primary"
mat-button
*ngIf="!uploadList.isUploadCompleted() && !uploadList.isUploadCancelled()"
(click)="toggleConfirmation()">
{{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_ALL' | translate }}
</button>
<button
id="adf-upload-dialog-close"
*ngIf="uploadList.isUploadCompleted() || uploadList.isUploadCancelled()"
mat-button
color="primary"
(click)="close()">
{{ 'ADF_FILE_UPLOAD.BUTTON.CLOSE' | translate }}
</button>
</footer>
<footer class="upload-dialog__actions"
*ngIf="isConfirmation">
<button
id="adf-upload-dialog-cancel"
color="secondary"
mat-button
(click)="cancelAllUploads()">
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CANCEL' | translate }}
</button>
<button
id="adf-upload-dialog-confirm"
mat-button
color="primary"
(click)="toggleConfirmation()">
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CONTINUE' | translate }}
</button>
</footer>
</div>

View File

@@ -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<any> = 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];
}
}

View File

@@ -0,0 +1,81 @@
<div class="adf-file-uploading-row">
<mat-icon
mat-list-icon
class="adf-file-uploading-row__type">
insert_drive_file
</mat-icon>
<span
class="adf-file-uploading-row__name"
title="{{ file.name }}">
{{ file.name }}
</span>
<div
*ngIf="file.status === FileUploadStatus.Progress || file.status === FileUploadStatus.Starting"
(click)="onCancel(file)"
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle"
title="{{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_FILE' | translate }}">
<span class="adf-file-uploading-row__status">
{{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }}
</span>
<mat-icon
mat-list-icon
class="adf-file-uploading-row__action adf-file-uploading-row__action--cancel">
clear
</mat-icon>
</div>
<div
*ngIf="file.status === FileUploadStatus.Complete"
(click)="onRemove(file)"
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle"
title="{{ 'ADF_FILE_UPLOAD.BUTTON.REMOVE_FILE' | translate }}">
<mat-icon
mat-list-icon
class="adf-file-uploading-row__status adf-file-uploading-row__status--done">
check_circle
</mat-icon>
<mat-icon
mat-list-icon
class="adf-file-uploading-row__action adf-file-uploading-row__action--remove">
remove_circle
</mat-icon>
</div>
<div
*ngIf="file.status === FileUploadStatus.Pending"
(click)="onCancel(file)"
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle">
<mat-icon
mat-list-icon
class="adf-file-uploading-row__status adf-file-uploading-row__status--pending">
schedule
</mat-icon>
<mat-icon
mat-list-icon
class="adf-file-uploading-row__action adf-file-uploading-row__action--remove">
remove_circle
</mat-icon>
</div>
<!--todo: move to ADF ACA-2051 -->
<div
*ngIf="file.status === FileUploadStatus.Error"
class="adf-file-uploading-row__block adf-file-uploading-row__status--error">
<mat-icon mat-list-icon [attr.title]="error?.message | translate">
report_problem
</mat-icon>
</div>
<div
*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>

View File

@@ -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<FileModel> = new EventEmitter<FileModel>();
@Output()
remove: EventEmitter<FileModel> = new EventEmitter<FileModel>();
FileUploadStatus = FileUploadStatus;
onCancel(file: FileModel): void {
this.cancel.emit(file);
}
onRemove(file: FileModel): void {
this.remove.emit(file);
}
}

View File

@@ -0,0 +1,7 @@
<div class="upload-list">
<ng-template
ngFor
[ngForOf]="files"
[ngForTemplate]="template">
</ng-template>
</div>

View File

@@ -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<any> = 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<FileModel> {
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;
}
});
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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 {}

View File

@@ -281,9 +281,12 @@
}, },
"UPLOAD": { "UPLOAD": {
"ERROR": { "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", "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": { "INFO": {