upload dialog improvements (#1740)

Various upload dialog improvements

- fix: headers alignment
- fix: Name column alignment
- fix: rename ‘CANCEL’ button to ‘Cancel all’ (as it’s what it does)
- fix: ‘Cancel all’ button still visible after all upload complete
- new: improved layout and api for file upload components
- new: strongly typed methods for UploadService
This commit is contained in:
Denys Vuika 2017-03-21 14:45:48 +00:00 committed by Mario Romano
parent 4df72af86d
commit 4ebe1e77b9
12 changed files with 89 additions and 96 deletions

View File

@ -4,17 +4,17 @@
<span id="total-upload-completed">{{totalCompleted}}</span> {{ totalCompletedMsg | translate}} <span id="total-upload-completed">{{totalCompleted}}</span> {{ totalCompletedMsg | translate}}
</div> </div>
<div class="buttons"> <div class="buttons">
<div class="minimize-button" [ngClass]="{active: _isDialogMinimized}" (keyup.enter)="toggleDialogMinimize()" (click)="toggleDialogMinimize()" tabindex="0"> <div class="minimize-button" [ngClass]="{active: isDialogMinimized}" (keyup.enter)="toggleMinimized()" (click)="toggleMinimized()" tabindex="0">
<i class="material-icons down" title="minimize upload list">keyboard_arrow_down</i> <i class="material-icons down" title="minimize upload list">keyboard_arrow_down</i>
<i class="material-icons up" title="expand upload list">keyboard_arrow_up</i> <i class="material-icons up" title="expand upload list">keyboard_arrow_up</i>
</div> </div>
<div class="close-button" (click)="toggleShowDialog()" (keyup.enter)="toggleShowDialog()" tabindex="0" title="close upload list"> <div class="close-button" (click)="toggleVisible()" (keyup.enter)="toggleVisible()" tabindex="0" title="close upload list">
<i class="material-icons">clear</i> <i class="material-icons">clear</i>
</div> </div>
</div> </div>
</div> </div>
<div class="body-dialog" *ngIf="filesUploadingList" [ngClass]="{hide: _isDialogMinimized}"> <div class="body-dialog" *ngIf="filesUploadingList" [ngClass]="{hide: isDialogMinimized}">
<alfresco-file-uploading-list [filesUploadingList]="filesUploadingList"></alfresco-file-uploading-list> <alfresco-file-uploading-list [files]="filesUploadingList"></alfresco-file-uploading-list>
</div> </div>
</div> </div>

View File

@ -80,7 +80,7 @@ describe('FileUploadingDialogComponent', () => {
}); });
it('should render dialog box with css class show when an element is added to Observer', () => { it('should render dialog box with css class show when an element is added to Observer', () => {
uploadService.addToQueue([file]); uploadService.addToQueue([<File> { name: 'file' }]);
component.filesUploadingList = [file]; component.filesUploadingList = [file];
fixture.detectChanges(); fixture.detectChanges();
@ -88,8 +88,8 @@ describe('FileUploadingDialogComponent', () => {
expect(element.querySelector('.file-dialog').getAttribute('class')).toEqual('file-dialog show'); expect(element.querySelector('.file-dialog').getAttribute('class')).toEqual('file-dialog show');
}); });
it('should render dialog box with css class show when the toggleShowDialog is called', () => { it('should render dialog box with css class show when the toggleVisible is called', () => {
component.toggleShowDialog(); component.toggleVisible();
fixture.detectChanges(); fixture.detectChanges();
expect(element.querySelector('.file-dialog').getAttribute('class')).toEqual('file-dialog show'); expect(element.querySelector('.file-dialog').getAttribute('class')).toEqual('file-dialog show');
@ -98,7 +98,7 @@ describe('FileUploadingDialogComponent', () => {
it('should render dialog box with css class hide', () => { it('should render dialog box with css class hide', () => {
component.isDialogActive = true; component.isDialogActive = true;
component.toggleShowDialog(); component.toggleVisible();
fixture.detectChanges(); fixture.detectChanges();
expect(element.querySelector('.file-dialog').getAttribute('class')).toEqual('file-dialog'); expect(element.querySelector('.file-dialog').getAttribute('class')).toEqual('file-dialog');
@ -107,7 +107,7 @@ describe('FileUploadingDialogComponent', () => {
it('should render minimize dialog as default', () => { it('should render minimize dialog as default', () => {
component.isDialogActive = true; component.isDialogActive = true;
component.toggleDialogMinimize(); component.toggleMinimized();
fixture.detectChanges(); fixture.detectChanges();
expect(element.querySelector('.minimize-button').getAttribute('class')).toEqual('minimize-button active'); expect(element.querySelector('.minimize-button').getAttribute('class')).toEqual('minimize-button active');

View File

@ -15,13 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core'; import { Component, Input, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { FileModel } from '../models/file.model'; import { FileModel } from '../models/file.model';
import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { UploadService } from '../services/upload.service'; import { UploadService } from '../services/upload.service';
/** /**
* <file-uploading-dialog [filesUploadingList]="FileModel[]" ></file-uploading-dialog> * <file-uploading-dialog [filesUploadingList]="FileModel[]"></file-uploading-dialog>
* *
* This component is a hideable and minimizable wich contains the list of the uploading * This component is a hideable and minimizable wich contains the list of the uploading
* files contained in the filesUploadingList. * files contained in the filesUploadingList.
@ -35,20 +35,17 @@ import { UploadService } from '../services/upload.service';
selector: 'file-uploading-dialog', selector: 'file-uploading-dialog',
moduleId: module.id, moduleId: module.id,
templateUrl: './file-uploading-dialog.component.html', templateUrl: './file-uploading-dialog.component.html',
styleUrls: ['./file-uploading-dialog.component.css'], styleUrls: ['./file-uploading-dialog.component.css']
host: {'[class.dialog-show]': 'toggleShowDialog'}
}) })
export class FileUploadingDialogComponent implements OnInit, OnDestroy { export class FileUploadingDialogComponent implements OnInit, OnDestroy {
isDialogActive: boolean = false; @Input()
filesUploadingList: FileModel []; filesUploadingList: FileModel [];
isDialogActive: boolean = false;
totalCompleted: number = 0; totalCompleted: number = 0;
totalCompletedMsg: string = 'FILE_UPLOAD.MESSAGES.SINGLE_COMPLETED'; totalCompletedMsg: string = 'FILE_UPLOAD.MESSAGES.SINGLE_COMPLETED';
isDialogMinimized: boolean = false;
private _isDialogMinimized: boolean = false;
private listSubscription: any; private listSubscription: any;
private counterSubscription: any; private counterSubscription: any;
@ -83,17 +80,17 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
} }
/** /**
* Display and hide the dialog component. * Toggle dialog visibility state.
*/ */
toggleShowDialog() { toggleVisible(): void {
this.isDialogActive = !this.isDialogActive; this.isDialogActive = !this.isDialogActive;
} }
/** /**
* Minimize and expand the dialog component. * Toggle dialog minimized state.
*/ */
toggleDialogMinimize() { toggleMinimized(): void {
this._isDialogMinimized = !this._isDialogMinimized; this.isDialogMinimized = !this.isDialogMinimized;
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -42,7 +42,7 @@
width: 100%; width: 100%;
} }
:host .truncate { :host .truncate {
margin-left: auto; margin-left: 0;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -1,32 +1,38 @@
<div [ngClass]="{hide: isUploadCompleted()}" [ngClass]="{show: !isUploadCompleted()}" <div class="body-dialog-header" *ngIf="!isUploadCompleted()">
class="body-dialog-header">
<div class="body-dialog-action"></div> <div class="body-dialog-action"></div>
<div class="body-dialog-cancel"><a data-automation-id="cancel_upload_all" href="#" (click)="cancelAllFiles($event)">{{'FILE_UPLOAD.BUTTON.CANCEL' | translate}}</a></div> <div class="body-dialog-cancel">
<a data-automation-id="cancel_upload_all" href="#" (click)="cancelAllFiles($event)">{{'FILE_UPLOAD.BUTTON.CANCEL_ALL' | translate}}</a>
</div>
</div> </div>
<table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp"> <table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
<tr> <tr>
<th>{{'FILE_UPLOAD.FILE_INFO.NAME' | translate}}</th> <th class="mdl-data-table__cell--non-numeric">{{'FILE_UPLOAD.FILE_INFO.NAME' | translate}}</th>
<th>{{'FILE_UPLOAD.FILE_INFO.PROGRESS' | translate}}</th> <th class="mdl-data-table__cell--non-numeric">{{'FILE_UPLOAD.FILE_INFO.PROGRESS' | translate}}</th>
<th class="mdl-cell--hide-phone size-column">{{'FILE_UPLOAD.FILE_INFO.SIZE' | translate}}</th> <th class="mdl-data-table__cell--non-numeric mdl-cell--hide-phone size-column">{{'FILE_UPLOAD.FILE_INFO.SIZE' | translate}}</th>
<th>{{'FILE_UPLOAD.FILE_INFO.ACTION' | translate}}</th> <th class="mdl-data-table__cell--non-numeric">{{'FILE_UPLOAD.FILE_INFO.ACTION' | translate}}</th>
</tr> </tr>
<tr *ngFor="let file of filesUploadingList" tabindex="0"> <tr *ngFor="let file of files" tabindex="0">
<td attr.data-automation-id="dialog_{{file.name}}" class="mdl-data-table__cell--non-numeric"><div class="truncate">{{file.name}}</div></td> <td class="mdl-data-table__cell--non-numeric" attr.data-automation-id="dialog_{{file.name}}">
<td _ngcontent-hvq-3=""> <div class="truncate">{{file.name}}</div>
<div _ngcontent-hvq-3="" class="mdl-progress mdl-js-progress is-upgraded" id="{{file.id}}" </td>
data-upgraded=",MaterialProgress"> <td class="mdl-data-table__cell--non-numeric">
<div class="mdl-progress mdl-js-progress is-upgraded" id="{{file.id}}">
<div class="progressbar bar bar1" attr.data-automation-id="dialog_progress_{{file.name}}" [style.width.%]="file.progress.percent"></div> <div class="progressbar bar bar1" attr.data-automation-id="dialog_progress_{{file.name}}" [style.width.%]="file.progress.percent"></div>
<div class="bufferbar bar bar2" style="width: 100%;"></div> <div class="bufferbar bar bar2" style="width: 100%;"></div>
<div class="auxbar bar bar3" style="width: 0%;"></div> <div class="auxbar bar bar3" style="width: 0%;"></div>
</div> </div>
</td> </td>
<td class="mdl-data-table__cell--non-numeric mdl-cell--hide-phone size-column" attr.data-automation-id="{{file.name}}_filesize">{{file.size}}</td> <td class="mdl-data-table__cell--non-numeric mdl-cell--hide-phone size-column" attr.data-automation-id="{{file.name}}_filesize">{{file.size}}</td>
<td> <td class="mdl-data-table__cell--non-numeric">
<span *ngIf="file.done && !file.abort" ><i data-automation-id="done_icon" class="material-icons action-icons">done</i></span> <span *ngIf="file.done && !file.abort">
<span *ngIf="file.uploading" (click)="abort(file.id)" class="cursor" tabindex="0"><i data-automation-id="abort_cancel_upload" <i data-automation-id="done_icon" class="material-icons action-icons">done</i>
class="material-icons action-icons"> </span>
remove_circle_outline</i></span> <span *ngIf="file.uploading" (click)="cancelFileUpload(file)" class="cursor" tabindex="0">
<span *ngIf="file.abort"><i class="material-icons action-icons" data-automation-id="upload_stopped" tabindex="0">remove_circle</i></span> <i data-automation-id="abort_cancel_upload" class="material-icons action-icons">remove_circle_outline</i>
</span>
<span *ngIf="file.abort">
<i class="material-icons action-icons" data-automation-id="upload_stopped" tabindex="0">remove_circle</i>
</span>
</td> </td>
</tr> </tr>
</table> </table>

View File

@ -15,11 +15,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, ElementRef, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { FileModel } from '../models/file.model'; import { FileModel } from '../models/file.model';
/** /**
* <alfresco-file-uploading-list [filesUploadingList]="FileModel[]" ></alfresco-file-uploading-list> * <alfresco-file-uploading-list [files]="files"></alfresco-file-uploading-list>
* *
* This component show a list of the uploading files contained in the filesUploadingList. * This component show a list of the uploading files contained in the filesUploadingList.
* *
@ -37,31 +37,29 @@ import { FileModel } from '../models/file.model';
export class FileUploadingListComponent { export class FileUploadingListComponent {
@Input() @Input()
filesUploadingList: FileModel []; files: FileModel[];
constructor(public el: ElementRef) {
}
/** /**
* Abort the in progress uploading of a specific file. * Cancel file upload
* *
* @param {string} id - FileModel id of the file to abort. * @param {FileModel} file File model to cancel upload for.
*
* @memberOf FileUploadingListComponent
*/ */
abort(id: string): void { cancelFileUpload(file: FileModel): void {
let file = this.filesUploadingList.filter((uploadingFileModel) => { if (file) {
return uploadingFileModel.id === id; file.emitAbort();
}); }
file[0].emitAbort();
} }
/** /**
* Call the abort method for each file * Call the abort method for each file
*/ */
cancelAllFiles($event) { cancelAllFiles(event: Event): void {
if ($event) { if (event) {
$event.preventDefault(); event.preventDefault();
} }
this.filesUploadingList.forEach((uploadingFileModel: FileModel) => { this.files.forEach((uploadingFileModel: FileModel) => {
uploadingFileModel.emitAbort(); uploadingFileModel.emitAbort();
}); });
} }
@ -70,12 +68,12 @@ export class FileUploadingListComponent {
* Verify if all the files are in state done or abort * Verify if all the files are in state done or abort
* @returns {boolean} - false if there is a file in progress * @returns {boolean} - false if there is a file in progress
*/ */
isUploadCompleted() { isUploadCompleted(): boolean {
let isPending = false; let isPending = false;
let isAllCompleted = true; let isAllCompleted = true;
for (let i = 0; i < this.filesUploadingList.length && !isPending; i++) { for (let i = 0; i < this.files.length && !isPending; i++) {
let uploadingFileModel = this.filesUploadingList[i]; let file = this.files[i];
if (!uploadingFileModel.done && !uploadingFileModel.abort) { if (!file.done && !file.abort) {
isPending = true; isPending = true;
isAllCompleted = false; isAllCompleted = false;
} }

View File

@ -1,4 +1,4 @@
<div file-draggable id='UploadBorder' class="upload-border" <div file-draggable id="UploadBorder" class="upload-border"
(onFilesDropped)="onFilesDropped($event)" (onFilesDropped)="onFilesDropped($event)"
(onFilesEntityDropped)="onFilesEntityDropped($event)" (onFilesEntityDropped)="onFilesEntityDropped($event)"
(onFolderEntityDropped)="onFolderEntityDropped($event)" (onFolderEntityDropped)="onFolderEntityDropped($event)"

View File

@ -3,7 +3,7 @@
"BUTTON": { "BUTTON": {
"UPLOAD_FILE": "Upload file", "UPLOAD_FILE": "Upload file",
"UPLOAD_FOLDER": "Upload folder", "UPLOAD_FOLDER": "Upload folder",
"CANCEL": "CANCEL" "CANCEL_ALL": "Cancell all"
}, },
"MESSAGES": { "MESSAGES": {
"SINGLE_COMPLETED": "upload complete", "SINGLE_COMPLETED": "upload complete",

View File

@ -3,7 +3,7 @@
"BUTTON": { "BUTTON": {
"UPLOAD_FILE": "Carica un file", "UPLOAD_FILE": "Carica un file",
"UPLOAD_FOLDER": "Carica una cartella", "UPLOAD_FOLDER": "Carica una cartella",
"CANCEL": "CANCELLA" "CANCEL_ALL": "CANCELLA"
}, },
"MESSAGES": { "MESSAGES": {
"SINGLE_COMPLETED": "caricamento completato", "SINGLE_COMPLETED": "caricamento completato",
@ -22,4 +22,4 @@
"UNDO": "Annulla" "UNDO": "Annulla"
} }
} }
} }

View File

@ -3,7 +3,7 @@
"BUTTON": { "BUTTON": {
"UPLOAD_FILE": "Загрузить файл", "UPLOAD_FILE": "Загрузить файл",
"UPLOAD_FOLDER": "Загрузить папку", "UPLOAD_FOLDER": "Загрузить папку",
"CANCEL": "ОТМЕНА" "CANCEL_ALL": "Отменить все"
}, },
"MESSAGES": { "MESSAGES": {
"SINGLE_COMPLETED": "файл загружен", "SINGLE_COMPLETED": "файл загружен",

View File

@ -59,14 +59,17 @@ describe('UploadService', () => {
it('should add an element in the queue and returns it', () => { it('should add an element in the queue and returns it', () => {
service.setOptions(options, false); service.setOptions(options, false);
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
expect(service.getQueue().length).toEqual(1); expect(service.getQueue().length).toEqual(1);
}); });
it('should add two elements in the queue and returns them', () => { it('should add two elements in the queue and returns them', () => {
service.setOptions(options, false); service.setOptions(options, false);
let filesFake = [{name: 'fake-name', size: 10}, {name: 'fake-name2', size: 20}]; let filesFake = [
<File>{name: 'fake-name', size: 10},
<File>{name: 'fake-name2', size: 20}
];
service.addToQueue(filesFake); service.addToQueue(filesFake);
expect(service.getQueue().length).toEqual(2); expect(service.getQueue().length).toEqual(2);
}); });
@ -79,7 +82,7 @@ describe('UploadService', () => {
done(); done();
}); });
service.setOptions(options, false); service.setOptions(options, false);
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', 'fake-dir', emitter); service.uploadFilesInTheQueue('-root-', 'fake-dir', emitter);
@ -102,7 +105,7 @@ describe('UploadService', () => {
done(); done();
}); });
service.setOptions(options, false); service.setOptions(options, false);
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter); service.uploadFilesInTheQueue('-root-', '', emitter);
expect(jasmine.Ajax.requests.mostRecent().url) expect(jasmine.Ajax.requests.mostRecent().url)
@ -123,7 +126,7 @@ describe('UploadService', () => {
done(); done();
}); });
service.setOptions(options, false); service.setOptions(options, false);
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter); service.uploadFilesInTheQueue('-root-', '', emitter);
@ -139,7 +142,7 @@ describe('UploadService', () => {
done(); done();
}); });
service.setOptions(options, false); service.setOptions(options, false);
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter); service.uploadFilesInTheQueue('-root-', '', emitter);
@ -154,7 +157,7 @@ describe('UploadService', () => {
total: 1234, total: 1234,
percent: 44 percent: 44
}; };
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.filesUpload$.subscribe((file) => { service.filesUpload$.subscribe((file) => {
expect(file).toBeDefined(); expect(file).toBeDefined();
@ -222,7 +225,7 @@ describe('UploadService', () => {
let enableVersioning = true; let enableVersioning = true;
service.setOptions(options, enableVersioning); service.setOptions(options, enableVersioning);
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter); service.uploadFilesInTheQueue('-root-', '', emitter);
@ -238,7 +241,7 @@ describe('UploadService', () => {
done(); done();
}); });
service.setOptions(options, false); service.setOptions(options, false);
let filesFake = [{name: 'fake-name', size: 10}]; let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake); service.addToQueue(filesFake);
service.uploadFilesInTheQueue('123', 'fake-dir', emitter); service.uploadFilesInTheQueue('123', 'fake-dir', emitter);

View File

@ -66,20 +66,18 @@ export class UploadService {
* *
* return {FileModel[]} - return the file added to the queue in this call. * return {FileModel[]} - return the file added to the queue in this call.
*/ */
addToQueue(files: any[]): FileModel[] { addToQueue(files: File[]): FileModel[] {
let latestFilesAdded: FileModel[] = []; const result: FileModel[] = [];
for (let file of files) { for (let file of files) {
if (this.isFile(file)) { let uploadingFileModel = new FileModel(file);
let uploadingFileModel = new FileModel(file); result.push(uploadingFileModel);
latestFilesAdded.push(uploadingFileModel); this.queue.push(uploadingFileModel);
this.queue.push(uploadingFileModel); if (this.filesUploadObserverProgressBar) {
if (this.filesUploadObserverProgressBar) { this.filesUploadObserverProgressBar.next(this.queue);
this.filesUploadObserverProgressBar.next(this.queue);
}
} }
} }
return latestFilesAdded; return result;
} }
/** /**
@ -149,15 +147,6 @@ export class UploadService {
return this.queue; return this.queue;
} }
/**
* Check if an item is a file.
*
* @return {boolean}
*/
private isFile(file: any): boolean {
return file !== null && (file instanceof Blob || (file.name && file.size));
}
/** /**
* Create a folder * Create a folder
* @param name - the folder name * @param name - the folder name