From c2fee79724e9f6908c2a8acfeab7d3d9224e83eb Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Fri, 2 Jun 2017 14:05:55 +0100 Subject: [PATCH] [ADF-571] upload feature rework (#1922) * upload feature rework lots of improvements for upload dialog and underlying services * readme update - readme cleanup - remove some old comments from code - update readme with new events for Upload Service * restore prerequisites section in readme --- .../app/components/files/files.component.ts | 7 +- ng2-components/ng2-alfresco-core/index.ts | 8 +- .../src/events/folder-created.event.ts | 0 .../src/pipes/file-size.pipe.ts | 36 +++ .../src/services/alfresco-content.service.ts | 26 +- ng2-components/ng2-alfresco-upload/README.md | 160 +++++------ ng2-components/ng2-alfresco-upload/index.ts | 24 +- .../file-uploading-dialog.component.css | 2 +- .../file-uploading-dialog.component.spec.ts | 9 +- .../file-uploading-dialog.component.ts | 55 ++-- .../file-uploading-list.component.css | 82 +++--- .../file-uploading-list.component.html | 47 ++-- .../file-uploading-list.component.ts | 33 +-- .../upload-button.component.spec.ts | 60 ++--- .../src/components/upload-button.component.ts | 71 +++-- .../upload-drag-area.component.spec.ts | 91 +------ .../components/upload-drag-area.component.ts | 20 +- .../directives/file-draggable.directive.ts | 11 - .../src/events/file.event.ts | 36 +++ .../ng2-alfresco-upload/src/i18n/en.json | 44 +-- .../src/models/file.model.ts | 172 ++++-------- .../src/services/upload.service.spec.ts | 84 +----- .../src/services/upload.service.ts | 251 ++++++++++-------- 23 files changed, 560 insertions(+), 769 deletions(-) rename ng2-components/{ng2-alfresco-upload => ng2-alfresco-core}/src/events/folder-created.event.ts (100%) create mode 100644 ng2-components/ng2-alfresco-core/src/pipes/file-size.pipe.ts create mode 100644 ng2-components/ng2-alfresco-upload/src/events/file.event.ts diff --git a/demo-shell-ng2/app/components/files/files.component.ts b/demo-shell-ng2/app/components/files/files.component.ts index 37ef65ce3d..5b6ab18b44 100644 --- a/demo-shell-ng2/app/components/files/files.component.ts +++ b/demo-shell-ng2/app/components/files/files.component.ts @@ -17,10 +17,10 @@ import { Component, Input, OnInit, AfterViewInit, Optional, ViewChild, ChangeDetectorRef } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { AlfrescoAuthenticationService, LogService, NotificationService } from 'ng2-alfresco-core'; +import { AlfrescoAuthenticationService, AlfrescoContentService, FolderCreatedEvent, LogService, NotificationService } from 'ng2-alfresco-core'; import { DocumentActionsService, DocumentListComponent, ContentActionHandler, DocumentActionModel, FolderActionModel } from 'ng2-alfresco-documentlist'; import { FormService } from 'ng2-activiti-form'; -import { UploadService, UploadButtonComponent, UploadDragAreaComponent, FolderCreatedEvent } from 'ng2-alfresco-upload'; +import { UploadService, UploadButtonComponent, UploadDragAreaComponent } from 'ng2-alfresco-upload'; @Component({ selector: 'files-component', @@ -73,6 +73,7 @@ export class FilesComponent implements OnInit, AfterViewInit { private router: Router, private notificationService: NotificationService, private uploadService: UploadService, + private contentService: AlfrescoContentService, @Optional() private route: ActivatedRoute) { documentActions.setHandler('my-handler', this.myDocumentActionHandler.bind(this)); } @@ -122,7 +123,7 @@ export class FilesComponent implements OnInit, AfterViewInit { this.logService.warn('You are not logged in to BPM'); } - this.uploadService.folderCreated.subscribe(value => this.onFolderCreated(value)); + this.contentService.folderCreated.subscribe(value => this.onFolderCreated(value)); } ngAfterViewInit() { diff --git a/ng2-components/ng2-alfresco-core/index.ts b/ng2-components/ng2-alfresco-core/index.ts index 89f4c9aec1..000aa15d84 100644 --- a/ng2-components/ng2-alfresco-core/index.ts +++ b/ng2-components/ng2-alfresco-core/index.ts @@ -41,6 +41,7 @@ import { ContentService } from './src/services/index'; +import { FileSizePipe } from './src/pipes/file-size.pipe'; import { UploadDirective } from './src/directives/upload.directive'; import { DataColumnComponent } from './src/components/data-column/data-column.component'; import { DataColumnListComponent } from './src/components/data-column/data-column-list.component'; @@ -57,6 +58,7 @@ export * from './src/directives/upload.directive'; export * from './src/utils/index'; export * from './src/events/base.event'; export * from './src/events/base-ui.event'; +export * from './src/events/folder-created.event'; export const ALFRESCO_CORE_PROVIDERS: any[] = [ NotificationService, @@ -101,7 +103,8 @@ export function createTranslateLoader(http: Http, logService: LogService) { ...COLLAPSABLE_DIRECTIVES, UploadDirective, DataColumnComponent, - DataColumnListComponent + DataColumnListComponent, + FileSizePipe ], providers: [ ...ALFRESCO_CORE_PROVIDERS @@ -119,7 +122,8 @@ export function createTranslateLoader(http: Http, logService: LogService) { UploadDirective, DataColumnComponent, DataColumnListComponent, - MdSnackBarModule + MdSnackBarModule, + FileSizePipe ] }) export class CoreModule { diff --git a/ng2-components/ng2-alfresco-upload/src/events/folder-created.event.ts b/ng2-components/ng2-alfresco-core/src/events/folder-created.event.ts similarity index 100% rename from ng2-components/ng2-alfresco-upload/src/events/folder-created.event.ts rename to ng2-components/ng2-alfresco-core/src/events/folder-created.event.ts diff --git a/ng2-components/ng2-alfresco-core/src/pipes/file-size.pipe.ts b/ng2-components/ng2-alfresco-core/src/pipes/file-size.pipe.ts new file mode 100644 index 0000000000..8be85ed2b4 --- /dev/null +++ b/ng2-components/ng2-alfresco-core/src/pipes/file-size.pipe.ts @@ -0,0 +1,36 @@ +/*! + * @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 { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'adfFileSize' +}) +export class FileSizePipe implements PipeTransform { + + transform(bytes: number = 0, decimals: number = 2): string { + if (bytes === 0) { + return '0 Bytes'; + } + const k = 1024, + dm = decimals || 2, + sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], + i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + } + +} diff --git a/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.ts b/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.ts index ca50c87025..03b950a82f 100644 --- a/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.ts +++ b/ng2-components/ng2-alfresco-core/src/services/alfresco-content.service.ts @@ -16,15 +16,18 @@ */ import { Injectable } from '@angular/core'; - +import { Observable, Subject } from 'rxjs/Rx'; +import { MinimalNodeEntity } from 'alfresco-js-api'; import { AlfrescoAuthenticationService } from './alfresco-authentication.service'; import { AlfrescoApiService } from './alfresco-api.service'; -import { LogService } from './log.service.ts'; -import { Observable } from 'rxjs/Rx'; +import { LogService } from './log.service'; +import { FolderCreatedEvent } from '../events/folder-created.event'; @Injectable() export class AlfrescoContentService { + folderCreated: Subject = new Subject(); + constructor(public authService: AlfrescoAuthenticationService, public apiService: AlfrescoApiService, private logService: LogService) { @@ -70,6 +73,23 @@ export class AlfrescoContentService { })).catch(this.handleError); } + /** + * Create a folder + * @param name - the folder name + */ + createFolder(relativePath: string, name: string, parentId?: string): Observable { + return Observable.fromPromise(this.apiService.getInstance().nodes.createFolder(name, relativePath, parentId)) + .do(data => { + this.folderCreated.next({ + relativePath: relativePath, + name: name, + parentId: parentId, + node: data + }); + }) + .catch(err => this.handleError(err)); + } + private handleError(error: any) { this.logService.error(error); return Observable.throw(error || 'Server error'); diff --git a/ng2-components/ng2-alfresco-upload/README.md b/ng2-components/ng2-alfresco-upload/README.md index fae3b7bbe2..ed6548d593 100644 --- a/ng2-components/ng2-alfresco-upload/README.md +++ b/ng2-components/ng2-alfresco-upload/README.md @@ -18,20 +18,25 @@ license - - alfresco component - - - angular 2 - - - typescript - - - node version -

+## Content + +### Components + +- [FileUploadingDialogComponent](#fileuploadingdialogcomponent) +- FileUploadingListComponent +- [UploadButtonComponent](#uploadbuttoncomponent) +- [UploadDragAreaComponent](#uploaddragareacomponent) + +### Services + +- [UploadService](#uploadservice) + +### Directives + +- FileDraggableDirective + ## Prerequisites Before you start using this development framework, make sure you have installed all required software and done all the @@ -93,19 +98,17 @@ Follow the 3 steps below: Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) . - - -#### Basic usage - +## UploadButtonComponent ```html - + ``` @@ -121,26 +124,27 @@ import { UploadModule } from 'ng2-alfresco-upload'; @Component({ selector: 'alfresco-app-demo', - template: ` - - ` + template: ` + + + + ` }) export class MyDemoApp { - constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + constructor(private authService: AlfrescoAuthenticationService, + private settingsService: AlfrescoSettingsService) { settingsService.ecmHost = 'http://localhost:8080'; this.authService.login('admin', 'admin').subscribe( - ticket => { - console.log(ticket); - }, - error => { - console.log(error); - }); + ticket => console.log(ticket), + error => console.log(error) + ); } public onSuccess(event: Object): void { @@ -160,15 +164,15 @@ export class MyDemoApp { export class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule); - ``` -#### Events + +### Events | Name | Description | | --- | --- | | `onSuccess` | The event is emitted when the file is uploaded | -#### Properties +### Properties | Name | Type | Default | Description | | --- | --- | --- | --- | @@ -182,7 +186,10 @@ platformBrowserDynamic().bootstrapModule(AppModule); | `staticTitle` | *string* | 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' or 'FILE_UPLOAD.BUTTON.UPLOAD_FOLDER' string in the JSON text file | define the text of the upload button | | `disableWithNoPermission` | *boolean* | false | If the value is true and the user doesn't have the permission to delete the node the button will be disabled | -### How to show notification message with no permission +### Advanced usage + +#### How to show notification message with no permission + You can show a notification error when the user doesn't have the right permission to perform the action. The UploadButtonComponent provides the event permissionEvent that is raised when the delete permission is missing You can subscribe to this event from your component and use the NotificationService to show a message. @@ -195,9 +202,11 @@ You can subscribe to this event from your component and use the NotificationServ export class MyComponent { -onUploadPermissionFailed(event: any) { - this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000); -} + onUploadPermissionFailed(event: any) { + this.notificationService.openSnackMessage( + `you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000 + ); + } } ``` @@ -205,6 +214,7 @@ onUploadPermissionFailed(event: any) { ![Upload notification message](docs/assets/upload-notification-message.png) #### How to disable the button when the delete permission is missing + You can easily disable the button when the user doesn't own the permission to perform the action. The UploadButtonComponent provides the property disableWithNoPermission that can be true. In this way the button should be disabled if the delete permission is missing for the node. @@ -217,19 +227,18 @@ The UploadButtonComponent provides the property disableWithNoPermission that can ![Upload disable button](docs/assets/upload-disable-button.png) +## UploadDragAreaComponent - -### Drag and drop This component, provide a drag and drop are to upload files to alfresco. -#### Basic usage - ```html - + + ``` -Example of an App that declares upload drag and drop component : +Example of an App that declares upload drag and drop component: ```ts import { NgModule, Component } from '@angular/core'; @@ -240,25 +249,25 @@ import { UploadModule } from 'ng2-alfresco-upload'; @Component({ selector: 'alfresco-app-demo', - template: ` -
- DRAG HERE -
-
- ` + template: ` + +
+ DRAG HERE +
+
+ + ` }) export class MyDemoApp { - constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) { + constructor(private authService: AlfrescoAuthenticationService, + private settingsService: AlfrescoSettingsService) { settingsService.ecmHost = 'http://localhost:8080'; this.authService.login('admin', 'admin').subscribe( - ticket => { - console.log(ticket); - }, - error => { - console.log(error); - }); + ticket => console.log(ticket), + error => console.log(error) + ); } public onSuccess(event: Object): void { @@ -278,16 +287,15 @@ export class MyDemoApp { export class AppModule { } platformBrowserDynamic().bootstrapModule(AppModule); - ``` -#### Events +### Events | Name | Description | | --- | --- | | `onSuccess` | The event is emitted when the file is uploaded | -#### Properties +### Properties | Name | Type | Default | Description | | --- | --- | --- | --- | @@ -297,27 +305,31 @@ platformBrowserDynamic().bootstrapModule(AppModule); | `currentFolderPath` | *string* | '/' | define the path where the files are uploaded | | `versioning` | *boolean* | false | Versioning false is the default uploader behaviour and it rename using an integer suffix if there is a name clash. Versioning true to indicate that a major version should be created | - -### Files Dialog +## FileUploadingDialogComponent This component provides a dialog that shows all the files uploaded with upload button or drag & drop area components. This component should be used in combination with upload button or drag & drop area. -#### Basic usage - ```html ``` -### UploadService service +## UploadService Provides access to various APIs related to file upload features. -#### Events +### Events | Name | Type | Description | | --- | --- | --- | -| folderCreated | FolderCreatedEvent | Raised when dropped folder gets created | +| queueChanged | FileModel[] | Raised every time the file queue changes. | +| fileUpload | FileUploadEvent | Raised every time a File model changes its state. | +| fileUploadStarting | FileUploadEvent | Raised when upload starts. | +| fileUploadCancelled | FileUploadEvent | Raised when upload gets cancelled by user. | +| fileUploadProgress | FileUploadEvent | Raised during file upload process and contains the current progress for the particular File model. | +| 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. | ## Build from sources @@ -331,7 +343,7 @@ npm run build ### Build the files and keep watching for changes ```sh -$ npm run build:w +npm run build:w ``` ## Running unit tests diff --git a/ng2-components/ng2-alfresco-upload/index.ts b/ng2-components/ng2-alfresco-upload/index.ts index cf80ddf32c..feda25c1a6 100644 --- a/ng2-components/ng2-alfresco-upload/index.ts +++ b/ng2-components/ng2-alfresco-upload/index.ts @@ -16,7 +16,7 @@ */ import { NgModule, ModuleWithProviders } from '@angular/core'; -import { MdIconModule } from '@angular/material'; +import { MdIconModule, MdProgressSpinnerModule, MdButtonModule } from '@angular/material'; import { CoreModule } from 'ng2-alfresco-core'; import { UploadDragAreaComponent } from './src/components/upload-drag-area.component'; @@ -26,22 +26,6 @@ import { FileUploadingDialogComponent } from './src/components/file-uploading-di import { FileUploadingListComponent } from './src/components/file-uploading-list.component'; import { UploadService } from './src/services/upload.service'; -/** - * ng2-alfresco-upload, provide components to upload files to alfresco repository. - * - * Components provided: - * - A button to upload files - * - * - * - * - Drag and drop area to upload files: - * - */ - export * from './src/components/upload-button.component'; export * from './src/components/file-uploading-dialog.component'; export * from './src/components/upload-drag-area.component'; @@ -50,7 +34,7 @@ export * from './src/directives/file-draggable.directive'; export * from './src/components/file-uploading-list.component'; export * from './src/models/file.model'; export * from './src/models/permissions.model'; -export * from './src/events/folder-created.event'; +export * from './src/events/file.event'; export const UPLOAD_DIRECTIVES: any[] = [ FileDraggableDirective, @@ -67,7 +51,9 @@ export const UPLOAD_PROVIDERS: any[] = [ @NgModule({ imports: [ CoreModule, - MdIconModule + MdIconModule, + MdProgressSpinnerModule, + MdButtonModule ], declarations: [ ...UPLOAD_DIRECTIVES diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.css b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.css index 18f439caa6..18c8d8dc65 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.css +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.css @@ -14,7 +14,7 @@ } :host .file-dialog { - width: 700px; + width: 550px; display: none; -webkit-box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .2); box-shadow: -2px -1px 8px 3px rgba(0, 0, 0, .2); diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts index 9f80049247..11dde66c35 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.spec.ts @@ -16,12 +16,14 @@ */ import { DebugElement } from '@angular/core'; +import { MdProgressSpinnerModule } from '@angular/material'; import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { CoreModule } from 'ng2-alfresco-core'; import { FileUploadingDialogComponent } from './file-uploading-dialog.component'; import { FileUploadingListComponent } from './file-uploading-list.component'; import { UploadService } from '../services/upload.service'; import { FileModel } from '../models/file.model'; +import { FileUploadCompleteEvent } from '../events/file.event'; describe('FileUploadingDialogComponent', () => { @@ -35,7 +37,8 @@ describe('FileUploadingDialogComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ - CoreModule.forRoot() + CoreModule.forRoot(), + MdProgressSpinnerModule ], declarations: [ FileUploadingDialogComponent, @@ -48,8 +51,6 @@ describe('FileUploadingDialogComponent', () => { })); beforeEach(() => { - window['componentHandler'] = null; - const fileFake = new File([''], 'fake-name'); file = new FileModel(fileFake); @@ -70,7 +71,7 @@ describe('FileUploadingDialogComponent', () => { }); it('should render completed upload 1 when an element is added to Observer', () => { - uploadService.updateFileCounterStream(1); + uploadService.fileUploadComplete.next(new FileUploadCompleteEvent(null, 1)); fixture.detectChanges(); expect(element.querySelector('#total-upload-completed').innerText).toEqual('1'); diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts index 08de586a3a..bee9dc6f3e 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-dialog.component.ts @@ -15,24 +15,15 @@ * limitations under the License. */ -import { Component, Input, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core'; +import { Component, Input, ChangeDetectorRef, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; import { FileModel } from '../models/file.model'; import { AlfrescoTranslationService } from 'ng2-alfresco-core'; import { UploadService } from '../services/upload.service'; +import { FileUploadCompleteEvent } from '../events/file.event'; -/** - * - * - * This component is a hideable and minimizable wich contains the list of the uploading - * files contained in the filesUploadingList. - * - * @InputParam {FileModel[]} filesUploadingList - list of the uploading files . - * - * - * @returns {FileUploadingDialogComponent} . - */ @Component({ selector: 'file-uploading-dialog', + changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './file-uploading-dialog.component.html', styleUrls: ['./file-uploading-dialog.component.css'] }) @@ -55,27 +46,30 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { if (translateService) { translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload'); } + cd.detach(); } ngOnInit() { - if (this.uploadService.filesUpload$) { - this.listSubscription = this.uploadService.filesUpload$.subscribe((fileList: FileModel[]) => { - this.filesUploadingList = fileList; - if (this.filesUploadingList.length > 0) { - this.isDialogActive = true; - this.cd.detectChanges(); - } - }); - } - if (this.uploadService.totalCompleted$) { - this.counterSubscription = this.uploadService.totalCompleted$.subscribe((total: number) => { - this.totalCompleted = total; - if (this.totalCompleted > 1) { - this.totalCompletedMsg = 'FILE_UPLOAD.MESSAGES.COMPLETED'; - } + this.listSubscription = this.uploadService.queueChanged.subscribe((fileList: FileModel[]) => { + this.filesUploadingList = fileList; + if (this.filesUploadingList.length > 0) { + this.isDialogActive = true; this.cd.detectChanges(); - }); - } + } + }); + + this.counterSubscription = this.uploadService.fileUploadComplete.subscribe((e: FileUploadCompleteEvent) => { + this.totalCompleted = e.totalComplete; + if (this.totalCompleted > 1) { + this.totalCompletedMsg = 'FILE_UPLOAD.MESSAGES.COMPLETED'; + } + this.cd.detectChanges(); + }); + + this.uploadService.fileUpload.subscribe(e => { + console.log(e); + this.cd.detectChanges(); + }); } /** @@ -83,6 +77,7 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { */ toggleVisible(): void { this.isDialogActive = !this.isDialogActive; + this.cd.detectChanges(); } /** @@ -90,11 +85,11 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy { */ toggleMinimized(): void { this.isDialogMinimized = !this.isDialogMinimized; + this.cd.detectChanges(); } ngOnDestroy() { this.listSubscription.unsubscribe(); this.counterSubscription.unsubscribe(); - this.cd.detach(); } } diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.css b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.css index 6226775cad..c9aa522686 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.css +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.css @@ -3,8 +3,8 @@ border: 0px; } -.cursor { - cursor: pointer; +.center { + text-align: center; } .body-dialog-header { @@ -42,62 +42,40 @@ width: 100%; } -:host .truncate { - margin-left: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; +.cancel-upload-button { + cursor: pointer; } -:host .mdl-progress { - width: 150px; -} - -@media (max-device-width: 360px) { - .truncate { - max-width: 50px; - margin-left: 0px; - } -} - -@media (max-device-width: 568px) { - .truncate { - width: 60px; - } - - .mdl-progress { - width: 60px; - } -} - -@media (max-width: 740px) { - .truncate { - max-width: 80px; - } - - .mdl-progress { - max-width: 70px; - } - - .size-column { - display: none; - } -} - -@media (min-width: 740px) { - .truncate { - width: 249px; - } - - .size-column { - display: table-cell; - } +.file-progress-spinner { + height: 24px; + width: 100%; + text-align: center; } .full-width { width: 100%; } -.no-width { - width: 0%; +.ellipsis-cell .cell-container { + height: 1em; +} + +/* visible content */ +.ellipsis-cell .cell-value { + display: block; + position: absolute; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 1em; /* for vertical align of text */ +} + + +/* cell stretching content */ +.ellipsis-cell > div:after { + content: attr(title); + overflow: hidden; + height: 0; + display: block; } diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.html b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.html index 24b3b87887..00fe97a588 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.html +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.html @@ -6,32 +6,37 @@ - - - - + + + + - - - - + + diff --git a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts index dc1285fbc1..2b399be04d 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/file-uploading-list.component.ts @@ -16,18 +16,9 @@ */ import { Component, Input } from '@angular/core'; -import { FileModel } from '../models/file.model'; +import { FileModel, FileUploadStatus } from '../models/file.model'; +import { UploadService } from '../services/upload.service'; -/** - * - * - * This component show a list of the uploading files contained in the filesUploadingList. - * - * @InputParam {FileModel[]} filesUploadingList - list of the uploading files . - * - * - * @returns {FileUploadingListComponent} . - */ @Component({ selector: 'alfresco-file-uploading-list', templateUrl: './file-uploading-list.component.html', @@ -35,9 +26,14 @@ import { FileModel } from '../models/file.model'; }) export class FileUploadingListComponent { + FileUploadStatus = FileUploadStatus; + @Input() files: FileModel[]; + constructor(private uploadService: UploadService) { + } + /** * Cancel file upload * @@ -46,9 +42,7 @@ export class FileUploadingListComponent { * @memberOf FileUploadingListComponent */ cancelFileUpload(file: FileModel): void { - if (file) { - file.emitAbort(); - } + this.uploadService.cancelUpload(file); } /** @@ -58,21 +52,20 @@ export class FileUploadingListComponent { if (event) { event.preventDefault(); } - this.files.forEach((uploadingFileModel: FileModel) => { - uploadingFileModel.emitAbort(); - }); + this.uploadService.cancelUpload(...this.files); } /** - * Verify if all the files are in state done or abort - * @returns {boolean} - false if there is a file in progress + * Check if all the files are not in the Progress state. + * @returns {boolean} - false if there is at least one file in Progress */ isUploadCompleted(): boolean { let isPending = false; let isAllCompleted = true; + for (let i = 0; i < this.files.length && !isPending; i++) { let file = this.files[i]; - if (!file.done && !file.abort) { + if (file.status === FileUploadStatus.Progress) { isPending = true; isAllCompleted = false; } diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts index 4d8dd1fa00..3ade2ab971 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.spec.ts @@ -15,10 +15,10 @@ * limitations under the License. */ +import { DebugElement, SimpleChange } from '@angular/core'; import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { UploadButtonComponent } from './upload-button.component'; -import { DebugElement, SimpleChange } from '@angular/core'; -import { CoreModule, AlfrescoTranslationService, NotificationService } from 'ng2-alfresco-core'; +import { CoreModule, AlfrescoTranslationService, AlfrescoContentService} from 'ng2-alfresco-core'; import { TranslationMock } from '../assets/translation.service.mock'; import { UploadService } from '../services/upload.service'; import { Observable } from 'rxjs/Rx'; @@ -33,27 +33,6 @@ describe('UploadButtonComponent', () => { target: {value: 'fake-name-1'} }; - let fakeResolveRest = { - entry: { - isFile: false, - isFolder: true, - name: 'fake-folder1' - } - }; - let fakeResolvePromise = new Promise(function (resolve, reject) { - resolve(fakeResolveRest); - }); - - let fakeRejectRest = { - response: { - body: { - error: { - statusCode: 409 - } - } - } - }; - let fakeFolderNodeWithoutPermission = { allowableOperations: [ 'update' @@ -73,15 +52,12 @@ describe('UploadButtonComponent', () => { nodeType: 'cm:folder' }; - let fakeRejectPromise = new Promise(function (resolve, reject) { - reject(fakeRejectRest); - }); - let component: UploadButtonComponent; let fixture: ComponentFixture; let debug: DebugElement; let element: HTMLElement; let uploadService: UploadService; + let contentService: AlfrescoContentService; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -93,7 +69,6 @@ describe('UploadButtonComponent', () => { ], providers: [ UploadService, - NotificationService, {provide: AlfrescoTranslationService, useClass: TranslationMock} ] }).compileComponents(); @@ -104,6 +79,7 @@ describe('UploadButtonComponent', () => { fixture = TestBed.createComponent(UploadButtonComponent); uploadService = TestBed.get(UploadService); + contentService = TestBed.get(AlfrescoContentService); debug = fixture.debugElement; element = fixture.nativeElement; @@ -141,7 +117,7 @@ describe('UploadButtonComponent', () => { component.rootFolderId = '-my-'; component.disableWithNoPermission = false; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission)); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission)); fixture.detectChanges(); @@ -160,7 +136,7 @@ describe('UploadButtonComponent', () => { component.rootFolderId = '-my-'; component.disableWithNoPermission = true; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission)); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission)); component.onFilesAdded(fakeEvent); let compiled = fixture.debugElement.nativeElement; @@ -173,7 +149,7 @@ describe('UploadButtonComponent', () => { component.rootFolderId = '-my-'; component.disableWithNoPermission = true; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) }); component.onFilesAdded(fakeEvent); @@ -187,7 +163,7 @@ describe('UploadButtonComponent', () => { component.rootFolderId = '-my-'; component.disableWithNoPermission = false; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) }); component.onFilesAdded(fakeEvent); @@ -202,7 +178,7 @@ describe('UploadButtonComponent', () => { component.currentFolderPath = '/root-fake-/sites-fake/folder-fake'; component.onSuccess = null; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) }); uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue'); @@ -218,7 +194,7 @@ describe('UploadButtonComponent', () => { component.rootFolderId = '-my-'; component.onSuccess = null; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) }); uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue'); @@ -233,11 +209,12 @@ describe('UploadButtonComponent', () => { component.rootFolderId = '-my-'; component.currentFolderPath = '/fake-root-path'; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); - spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeResolvePromise); + spyOn(contentService, 'createFolder').and.returnValue(Observable.of(true)); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) }); fixture.detectChanges(); + component.onSuccess.subscribe(e => { expect(e.value).toEqual('File uploaded'); done(); @@ -245,22 +222,21 @@ describe('UploadButtonComponent', () => { spyOn(component, 'uploadFiles').and.callFake(() => { component.onSuccess.emit({ - value: 'File uploaded' - } - ); + value: 'File uploaded' + }); }); component.onDirectoryAdded(fakeEvent); }); it('should emit an onError event when the folder already exist', (done) => { component.rootFolderId = '-my-'; - spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); - spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeRejectPromise); + spyOn(contentService, 'createFolder').and.returnValue(Observable.throw(new Error(''))); + spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission)); component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) }); component.onError.subscribe(e => { - expect(e.value).toEqual('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST'); + expect(e.value).toEqual('Error'); done(); }); diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts index 3c606a91cd..413b76e58d 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-button.component.ts @@ -16,36 +16,15 @@ */ import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core'; -import { Subject } from 'rxjs/Rx'; -import { AlfrescoTranslationService, LogService, NotificationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { Observable, Subject } from 'rxjs/Rx'; +import { AlfrescoApiService, AlfrescoContentService, AlfrescoTranslationService, LogService, NotificationService, AlfrescoSettingsService } from 'ng2-alfresco-core'; +import { MinimalNodeEntryEntity } from 'alfresco-js-api'; import { UploadService } from '../services/upload.service'; import { FileModel } from '../models/file.model'; import { PermissionModel } from '../models/permissions.model'; -declare let componentHandler: any; - const ERROR_FOLDER_ALREADY_EXIST = 409; -/** - * - * - * - * This component, provide a set of buttons to upload files to alfresco. - * - * @InputParam {boolean} [true] showNotificationBar - hide/show notification bar. - * @InputParam {boolean} [false] versioning - true to indicate that a major version should be created - * @InputParam {boolean} [false] uploadFolders - allow/disallow upload folders (only for chrome). - * @InputParam {boolean} [false] multipleFiles - allow/disallow multiple files. - * @InputParam {string} [*] acceptedFilesType - array of allowed file extensions. - * @InputParam {boolean} [false] versioning - true to indicate that a major version should be created - * @Output - onSuccess - The event is emitted when the file is uploaded - * - * @returns {UploadButtonComponent} . - */ @Component({ selector: 'alfresco-upload-button', templateUrl: './upload-button.component.html', @@ -106,7 +85,9 @@ export class UploadButtonComponent implements OnInit, OnChanges { private translateService: AlfrescoTranslationService, private logService: LogService, private notificationService: NotificationService, - private settingsService: AlfrescoSettingsService) { + private settingsService: AlfrescoSettingsService, + private apiService: AlfrescoApiService, + private contentService: AlfrescoContentService) { if (translateService) { translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload'); } @@ -172,9 +153,9 @@ export class UploadButtonComponent implements OnInit, OnChanges { let directoryName = this.getDirectoryName(directoryPath); let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath); - this.uploadService.createFolder(absolutePath, directoryName, this.rootFolderId) + this.contentService.createFolder(absolutePath, directoryName, this.rootFolderId) .subscribe( - res => { + _ => { let relativeDir = this.currentFolderPath + '/' + directoryPath; this.uploadFiles(relativeDir, filesDir); }, @@ -269,10 +250,8 @@ export class UploadButtonComponent implements OnInit, OnChanges { messageTranslate = this.translateService.get('FILE_UPLOAD.MESSAGES.PROGRESS'); actionTranslate = this.translateService.get('FILE_UPLOAD.ACTION.UNDO'); - this.notificationService.openSnackMessageAction(messageTranslate.value, actionTranslate.value, 3000).afterDismissed().subscribe(() => { - latestFilesAdded.forEach((uploadingFileModel: FileModel) => { - uploadingFileModel.emitAbort(); - }); + this.notificationService.openSnackMessageAction(messageTranslate.value, actionTranslate.value, 3000).onAction().subscribe(() => { + this.uploadService.cancelUpload(...latestFilesAdded); }); } @@ -282,11 +261,12 @@ export class UploadButtonComponent implements OnInit, OnChanges { * @returns {string} */ private getErrorMessage(response: any): string { - if (response.body && response.body.error.statusCode === ERROR_FOLDER_ALREADY_EXIST) { + if (response && response.body && response.body.error.statusCode === ERROR_FOLDER_ALREADY_EXIST) { let errorMessage: any; errorMessage = this.translateService.get('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST'); return errorMessage.value; } + return 'Error'; } /** @@ -314,17 +294,30 @@ export class UploadButtonComponent implements OnInit, OnChanges { checkPermission() { if (this.rootFolderId) { - this.uploadService.getFolderNode(this.rootFolderId).subscribe( - (res) => { - this.permissionValue.next(this.hasCreatePermission(res)); - }, - (error) => { - this.onError.emit(error); - } + this.getFolderNode(this.rootFolderId).subscribe( + res => this.permissionValue.next(this.hasCreatePermission(res)), + error => this.onError.emit(error) ); } } + getFolderNode(nodeId: string): Observable { + let opts: any = { + includeSource: true, + include: ['allowableOperations'] + }; + + return Observable.fromPromise(this.apiService.getInstance().nodes.getNodeInfo(nodeId, opts)) + .catch(err => this.handleError(err)); + } + + private handleError(error: Response) { + // in a real world app, we may send the error to some remote logging infrastructure + // instead of just logging it to the console + this.logService.error(error); + return Observable.throw(error || 'Server error'); + } + private hasCreatePermission(node: any): boolean { if (this.hasPermissions(node)) { return node.allowableOperations.find(permision => permision === 'create') ? true : false; diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts index be61774416..cde3898be4 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.spec.ts @@ -17,7 +17,7 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { EventEmitter, DebugElement } from '@angular/core'; -import { AlfrescoTranslationService, CoreModule, LogService, LogServiceMock, NotificationService } from 'ng2-alfresco-core'; +import { AlfrescoTranslationService, CoreModule, LogService, LogServiceMock } from 'ng2-alfresco-core'; import { UploadDragAreaComponent } from './upload-drag-area.component'; import { FileDraggableDirective } from '../directives/file-draggable.directive'; @@ -45,7 +45,6 @@ describe('UploadDragAreaComponent', () => { ], providers: [ UploadService, - NotificationService, { provide: AlfrescoTranslationService, useClass: TranslationMock }, { provide: LogService, useClass: LogServiceMock } ] @@ -147,92 +146,4 @@ describe('UploadDragAreaComponent', () => { expect(uploadService.uploadFilesInTheQueue) .toHaveBeenCalledWith('-my-', '/root-fake-/sites-fake/document-library-fake/folder-fake/', null); }); - - xit('should throws an exception and show it in the notification bar when the folder already exist', done => { - component.currentFolderPath = '/root-fake-/sites-fake/folder-fake'; - component.showNotificationBar = true; - - fixture.detectChanges(); - let fakeRest = { - response: { - body: { - error: { - statusCode: 409 - } - } - } - }; - let fakePromise = new Promise(function (resolve, reject) { - reject(fakeRest); - }); - spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakePromise); - spyOn(component, 'showErrorNotificationBar').and.callFake( () => { - expect(component.showErrorNotificationBar).toHaveBeenCalledWith('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST'); - done(); - }); - - let folderEntry = { - fullPath: '/folder-duplicate-fake', - isDirectory: true, - isFile: false, - name: 'folder-duplicate-fake' - }; - - component.onFolderEntityDropped(folderEntry); - }); - - it('should create a folder and call onFilesEntityDropped with the file inside the folder', done => { - component.currentFolderPath = '/root-fake-/sites-fake/document-library-fake'; - component.onSuccess = new EventEmitter(); - - fixture.detectChanges(); - - let itemEntity = { - fullPath: '/folder-fake/file-fake.png', - isDirectory: false, - isFile: true, - name: 'file-fake.png', - file: (callbackFile) => { - let fileFake = new File(['fakefake'], 'file-fake.png', {type: 'image/png'}); - callbackFile(fileFake); - } - }; - - let fakeRest = { - entry: { - isFile: false, - isFolder: true, - name: 'folder-fake' - } - }; - let fakePromise = new Promise(function (resolve, reject) { - resolve(fakeRest); - }); - spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakePromise); - spyOn(component, 'onFilesEntityDropped').and.callFake( () => { - expect(component.onFilesEntityDropped).toHaveBeenCalledWith(itemEntity); - }); - - spyOn(component, 'showUndoNotificationBar').and.callFake( () => { - expect(component.showUndoNotificationBar).toHaveBeenCalled(); - done(); - }); - - let folderEntry = { - fullPath: '/folder-fake', - isDirectory: true, - isFile: false, - name: 'folder-fake', - createReader: () => { - return { - readEntries: (callback) => { - let entries = [itemEntity, itemEntity]; - callback(entries); - } - }; - } - }; - - component.onFolderEntityDropped(folderEntry); - }); }); diff --git a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts index 43cf6efa11..799714a9b1 100644 --- a/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts +++ b/ng2-components/ng2-alfresco-upload/src/components/upload-drag-area.component.ts @@ -16,21 +16,12 @@ */ import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { AlfrescoTranslationService, LogService, NotificationService } from 'ng2-alfresco-core'; +import { AlfrescoTranslationService, AlfrescoContentService, LogService, NotificationService } from 'ng2-alfresco-core'; import { UploadService } from '../services/upload.service'; import { FileModel } from '../models/file.model'; const ERROR_FOLDER_ALREADY_EXIST = 409; -/** - * 900) { - size /= 1000; - i++; - } - return Math.round((Math.round(size * 100) / 100)) + ' ' + fSExt[i]; - } - - private generateId(): string { - return 'uploading-file-' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } +export interface FileUploadProgress { + loaded: number; + total: number; + percent: number; } export interface FileUploadOptions { newVersion?: boolean; } + +export enum FileUploadStatus { + Pending = 0, + Complete = 1, + Starting = 2, + Progress = 3, + Cancelled = 4, + Aborted = 5, + Error = 6 +} + +export class FileModel { + readonly id: string; + readonly name: string; + readonly size: number; + readonly file: File; + + status: FileUploadStatus = FileUploadStatus.Pending; + progress: FileUploadProgress; + options: FileUploadOptions; + + constructor(file: File, options?: FileUploadOptions) { + this.file = file; + + this.id = this.generateId(); + this.name = file.name; + this.size = file.size; + + this.progress = { + loaded: 0, + total: 0, + percent: 0 + }; + + this.options = Object.assign({}, { + newVersion: false + }, options); + } + + private generateId(): string { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } +} diff --git a/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts b/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts index 086d536752..6c4f09faa2 100644 --- a/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts +++ b/ng2-components/ng2-alfresco-upload/src/services/upload.service.spec.ts @@ -124,89 +124,7 @@ describe('UploadService', () => { service.uploadFilesInTheQueue('-root-', '', emitter); let file = service.getQueue(); - file[0].emitAbort(); - }); - - it('should make XHR error request after the xhr error is called', (done) => { - let emitter = new EventEmitter(); - - emitter.subscribe(e => { - expect(e.value).toBe('Error file uploaded'); - done(); - }); - let fileFake = new FileModel({name: 'fake-name', size: 10}); - service.addToQueue(fileFake); - service.uploadFilesInTheQueue('-root-', '', emitter); - - let file = service.getQueue(); - file[0].emitError(); - }); - - it('should make XHR progress request after the onprogress is called', (done) => { - let fakeProgress = { - loaded: 500, - total: 1234, - percent: 44 - }; - let filesFake = new FileModel({name: 'fake-name', size: 10}); - service.addToQueue(filesFake); - service.filesUpload$.subscribe((file) => { - expect(file).toBeDefined(); - expect(file[0]).toBeDefined(); - expect(file[0].progress).toEqual(fakeProgress); - done(); - }); - service.uploadFilesInTheQueue('-root-', '', null); - - let file = service.getQueue(); - - file[0].emitProgres(fakeProgress); - }); - - it('should make XHR done request after the folder is created', (done) => { - let fakeRest = { - entry: { - isFile: false, - isFolder: true, - name: 'fake-folder' - } - }; - let fakePromise = new Promise(function (resolve, reject) { - resolve(fakeRest); - }); - spyOn(service, 'callApiCreateFolder').and.returnValue(fakePromise); - let defaultPath = ''; - let folderName = 'fake-folder'; - service.createFolder(defaultPath, folderName).subscribe(res => { - expect(res).toEqual(fakeRest); - done(); - }); - }); - - it('should throws an exception when a folder already exist', (done) => { - let fakeRest = { - response: { - body: { - error: { - statusCode: 409 - } - } - } - }; - let fakePromise = new Promise(function (resolve, reject) { - reject(fakeRest); - }); - spyOn(service, 'callApiCreateFolder').and.returnValue(fakePromise); - let defaultPath = ''; - let folderName = 'folder-duplicate-fake'; - service.createFolder(defaultPath, folderName).subscribe( - res => { - }, - error => { - expect(error).toEqual(fakeRest); - done(); - } - ); + service.cancelUpload(...file); }); it('If versioning is true autoRename should not be present and majorVersion should be a param', () => { diff --git a/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts b/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts index 5131083e1b..0787f476f9 100644 --- a/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts +++ b/ng2-components/ng2-alfresco-upload/src/services/upload.service.ts @@ -16,36 +16,38 @@ */ import { EventEmitter, Injectable } from '@angular/core'; -import { Response } from '@angular/http'; -import { Observer, Observable, Subject } from 'rxjs/Rx'; +import { Subject } from 'rxjs/Rx'; import { AlfrescoApiService, LogService } from 'ng2-alfresco-core'; -import { FolderCreatedEvent } from '../events/folder-created.event'; -import { FileModel } from '../models/file.model'; -import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api'; +import { FileUploadEvent, FileUploadCompleteEvent } from '../events/file.event'; +import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model'; -/** - * - * UploadService keep the queue of the file to upload and uploads them. - * - * @returns {UploadService} . - */ @Injectable() export class UploadService { private queue: FileModel[] = []; - private filesUploadObserverProgressBar: Observer; - private totalCompletedObserver: Observer; + private cache: { [key: string]: any } = {}; + private totalComplete: number = 0; - totalCompleted: number = 0; - filesUpload$: Observable; - totalCompleted$: Observable; - - folderCreated: Subject = new Subject(); + queueChanged: Subject = new Subject(); + fileUpload: Subject = new Subject(); + fileUploadStarting: Subject = new Subject(); + fileUploadCancelled: Subject = new Subject(); + fileUploadProgress: Subject = new Subject(); + fileUploadAborted: Subject = new Subject(); + fileUploadError: Subject = new Subject(); + fileUploadComplete: Subject = new Subject(); constructor(private apiService: AlfrescoApiService, private logService: LogService) { - this.filesUpload$ = new Observable(observer => this.filesUploadObserverProgressBar = observer).share(); - this.totalCompleted$ = new Observable(observer => this.totalCompletedObserver = observer).share(); + } + + /** + * Returns the file Queue + * + * @return {FileModel[]} - files in the upload queue. + */ + getQueue(): FileModel[] { + return this.queue; } /** @@ -59,9 +61,7 @@ export class UploadService { addToQueue(...files: FileModel[]): FileModel[] { const allowedFiles = files.filter(f => !f.name.startsWith('.')); this.queue = this.queue.concat(allowedFiles); - if (this.filesUploadObserverProgressBar) { - this.filesUploadObserverProgressBar.next(this.queue); - } + this.queueChanged.next(this.queue); return allowedFiles; } @@ -69,125 +69,142 @@ export class UploadService { * Pick all the files in the queue that are not been uploaded yet and upload it into the directory folder. */ uploadFilesInTheQueue(rootId: string, directory: string, elementEmit: EventEmitter): void { - let filesToUpload = this.queue.filter((file) => { - return !file.uploading && !file.done && !file.abort && !file.error; - }); + const files = this.getFilesToUpload(); - filesToUpload.forEach((uploadingFileModel: FileModel) => { - uploadingFileModel.setUploading(); + files.forEach((file: FileModel) => { + this.onUploadStarting(file); const opts: any = { renditions: 'doclib' }; - if (uploadingFileModel.options.newVersion === true) { + if (file.options.newVersion === true) { opts.overwrite = true; opts.majorVersion = true; } else { opts.autoRename = true; } - let promiseUpload = this.apiService.getInstance().upload.uploadFile(uploadingFileModel.file, directory, rootId, null, opts) - .on('progress', (progress: any) => { - uploadingFileModel.setProgres(progress); - this.updateFileListStream(this.queue); - }) - .on('abort', () => { - uploadingFileModel.setAbort(); - elementEmit.emit({ - value: 'File aborted' - }); - }) - .on('error', () => { - uploadingFileModel.setError(); - elementEmit.emit({ - value: 'Error file uploaded' - }); - }) - .on('success', (data: any) => { - elementEmit.emit({ - value: data - }); - uploadingFileModel.onFinished( - data.status, - data.statusText, - data.response - ); - - this.updateFileListStream(this.queue); - if (!uploadingFileModel.abort && !uploadingFileModel.error) { - this.updateFileCounterStream(++this.totalCompleted); - } + const promise = this.apiService.getInstance().upload.uploadFile(file.file, directory, rootId, null, opts); + promise.on('progress', (progress: FileUploadProgress) => { + this.onUploadProgress(file, progress); + }) + .on('abort', () => { + this.onUploadAborted(file); + elementEmit.emit({ + value: 'File aborted' }); + }) + .on('error', err => { + this.onUploadError(file, err); + elementEmit.emit({ + value: 'Error file uploaded' + }); + }) + .on('success', data => { + this.onUploadComplete(file); + elementEmit.emit({ + value: data + }); + }) + .catch((err) => { + this.onUploadError(file, err); + }); - uploadingFileModel.setPromiseUpload(promiseUpload); + this.cache[file.id] = promise; }); } - /** - * Return all the files in the uploading queue. - * - * @return {FileModel[]} - files in the upload queue. - */ - getQueue(): FileModel[] { - return this.queue; + 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]; + } + + const event = new FileUploadEvent(file, FileUploadStatus.Cancelled); + this.fileUpload.next(event); + this.fileUploadCancelled.next(event); + }); } - /** - * Create a folder - * @param name - the folder name - */ - createFolder(relativePath: string, name: string, parentId?: string): Observable { - return Observable.fromPromise(this.callApiCreateFolder(relativePath, name, parentId)) - .do(data => { - this.folderCreated.next({ - relativePath: relativePath, - name: name, - parentId: parentId, - node: data - }); - }) - .catch(err => this.handleError(err)); - } - - callApiCreateFolder(relativePath: string, name: string, parentId?: string): Promise { - return this.apiService.getInstance().nodes.createFolder(name, relativePath, parentId); - } - - /** - * Throw the error - * @param error - * @returns {ErrorObservable} - */ - private handleError(error: Response) { - // in a real world app, we may send the error to some remote logging infrastructure - // instead of just logging it to the console - this.logService.error(error); - return Observable.throw(error || 'Server error'); - } - - private updateFileListStream(fileList: FileModel[]) { - if (this.filesUploadObserverProgressBar) { - this.filesUploadObserverProgressBar.next(fileList); + private onUploadStarting(file: FileModel): void { + if (file) { + file.status = FileUploadStatus.Starting; + const event = new FileUploadEvent(file, FileUploadStatus.Starting); + this.fileUpload.next(event); + this.fileUploadStarting.next(event); } } - updateFileCounterStream(total: number) { - if (this.totalCompletedObserver) { - this.totalCompletedObserver.next(total); + private onUploadProgress(file: FileModel, progress: FileUploadProgress): void { + if (file) { + file.progress = progress; + file.status = FileUploadStatus.Progress; + + const event = new FileUploadEvent(file, FileUploadStatus.Progress); + this.fileUpload.next(event); + this.fileUploadProgress.next(event); + + this.queueChanged.next(this.queue); } } - getFolderNode(nodeId: string): Observable { - let opts: any = { - includeSource: true, - include: ['allowableOperations'] - }; + private onUploadError(file: FileModel, error: any): void { + if (file) { + file.status = FileUploadStatus.Error; - return Observable.fromPromise(this.apiService.getInstance().nodes.getNodeInfo(nodeId, opts)) - .map((response: any) => { - return response; - }) - .catch(err => this.handleError(err)); + const promise = this.cache[file.id]; + if (promise) { + delete this.cache[file.id]; + } + + const event = new FileUploadEvent(file, FileUploadStatus.Error, error); + this.fileUpload.next(event); + this.fileUploadError.next(event); + } + } + + private onUploadComplete(file: FileModel): void { + if (file) { + file.status = FileUploadStatus.Complete; + this.totalComplete++; + + const promise = this.cache[file.id]; + if (promise) { + delete this.cache[file.id]; + } + + const event = new FileUploadCompleteEvent(file, this.totalComplete); + this.fileUpload.next(event); + this.fileUploadComplete.next(event); + + this.queueChanged.next(this.queue); + } + } + + private onUploadAborted(file: FileModel): void { + if (file) { + file.status = FileUploadStatus.Aborted; + + const promise = this.cache[file.id]; + if (promise) { + delete this.cache[file.id]; + } + + const event = new FileUploadEvent(file, FileUploadStatus.Aborted); + this.fileUpload.next(event); + this.fileUploadAborted.next(event); + } + } + + private getFilesToUpload(): FileModel[] { + let filesToUpload = this.queue.filter(file => { + return file.status === FileUploadStatus.Pending; + }); + return filesToUpload; } }
{{'FILE_UPLOAD.FILE_INFO.NAME' | translate}}{{'FILE_UPLOAD.FILE_INFO.PROGRESS' | translate}}{{'FILE_UPLOAD.FILE_INFO.SIZE' | translate}}{{'FILE_UPLOAD.FILE_INFO.ACTION' | translate}}{{'ADF_FILE_UPLOAD.FILE_LIST.NAME' | translate}}{{'ADF_FILE_UPLOAD.FILE_LIST.PROGRESS' | translate}}{{'ADF_FILE_UPLOAD.FILE_LIST.SIZE' | translate}}{{'ADF_FILE_UPLOAD.FILE_LIST.ACTION' | translate}}
-
{{file.name}}
-
-
-
-
-
+
+
+
{{file.name}}
{{file.size}} - - done + + error_outline + block + + + + + + {{ file.size | adfFileSize }} + + + done - - remove_circle_outline - - - remove_circle + + remove_circle_outline