[ADF-643] upload enhancements (#1949)

* rework folder uploading

- flatterns hierarchy on folder upload
- performs a single traversal for the entire folder heirarchy and ends with a comple file list
- allows now dropping folders on existing folders
- overall code improvements

* fix unit tests

* readme updates

* clean old and unused code

* code cleanup

* limit concurrent uploads

* update code as per review

* fix upload button for Safari

* fixes for Safari

- Safari compatibility
- code updates based on review

* fix code

* fix unit tests
This commit is contained in:
Denys Vuika
2017-06-11 12:02:05 +01:00
committed by Eugenio Romano
parent e7a1f46ac8
commit a02ba4ad71
16 changed files with 393 additions and 631 deletions

View File

@@ -46,11 +46,10 @@
[contextMenuActions]="true"
[contentActions]="true"
[allowDropFiles]="true"
[sorting]="['name', 'desc']"
(error)="onNavigationError($event)"
(success)="resetError()"
(preview)="showFile($event)"
(permissionError)="onPermissionsFailed($event)">
(permissionError)="handlePermissionError($event)">
<data-columns>
<data-column key="$thumbnail" type="image" [sortable]="false"></data-column>
<data-column
@@ -100,22 +99,12 @@
<content-actions>
<!-- folder actions -->
<content-action
target="folder"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.SYSTEM_1' | translate}}"
handler="system1">
</content-action>
<content-action
target="folder"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.CUSTOM' | translate}}"
(execute)="myFolderAction1($event)">
</content-action>
<content-action
target="folder"
permission="delete"
[disableWithNoPermission]="true"
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
(permissionEvent)="onPermissionsFailed($event)"
(permissionEvent)="handlePermissionError($event)"
handler="delete">
</content-action>
<!-- document actions -->
@@ -124,29 +113,14 @@
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
handler="download">
</content-action>
<content-action
target="document"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.SYSTEM_2' | translate}}"
handler="system2">
</content-action>
<content-action
target="document"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.CUSTOM' | translate}}"
(execute)="myCustomAction1($event)">
</content-action>
<content-action
target="document"
permission="delete"
[disableWithNoPermission]="true"
(permissionEvent)="onPermissionsFailed($event)"
(permissionEvent)="handlePermissionError($event)"
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
handler="delete">
</content-action>
<content-action
target="folder"
title="Activiti: View Form"
(execute)="viewActivitiForm($event)">
</content-action>
</content-actions>
</alfresco-document-list>
</alfresco-upload-drag-area>
@@ -195,7 +169,7 @@
[uploadFolders]="folderUpload"
[versioning]="versioning"
[disableWithNoPermission]="disableWithNoPermission"
(permissionEvent)="onUploadPermissionFailed($event)">
(permissionEvent)="handlePermissionError($event)">
</alfresco-upload-button>
</div>
<div *ngIf="acceptedFilesTypeShow">
@@ -209,7 +183,7 @@
[uploadFolders]="folderUpload"
[versioning]="versioning"
[disableWithNoPermission]="disableWithNoPermission"
(permissionEvent)="onUploadPermissionFailed($event)">
(permissionEvent)="handlePermissionError($event)">
</alfresco-upload-button>
</div>
<section>

View File

@@ -15,13 +15,12 @@
* limitations under the License.
*/
import { Component, Input, OnInit, AfterViewInit, Optional, ViewChild, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Component, Input, OnInit, Optional, ViewChild, ChangeDetectorRef } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { MdDialog } from '@angular/material';
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 } from 'ng2-alfresco-upload';
import { AlfrescoContentService, FolderCreatedEvent, NotificationService } from 'ng2-alfresco-core';
import { DocumentListComponent } from 'ng2-alfresco-documentlist';
import { UploadService, FileUploadCompleteEvent } from 'ng2-alfresco-upload';
import { CreateFolderDialog } from '../../dialogs/create-folder.dialog';
@@ -30,7 +29,7 @@ import { CreateFolderDialog } from '../../dialogs/create-folder.dialog';
templateUrl: './files.component.html',
styleUrls: ['./files.component.css']
})
export class FilesComponent implements OnInit, AfterViewInit {
export class FilesComponent implements OnInit {
// The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root-
currentFolderId: string = '-my-';
@@ -38,7 +37,7 @@ export class FilesComponent implements OnInit, AfterViewInit {
fileNodeId: any;
fileShowed: boolean = false;
useCustomToolbar = false;
useCustomToolbar = true;
@Input()
multipleFileUpload: boolean = false;
@@ -64,36 +63,12 @@ export class FilesComponent implements OnInit, AfterViewInit {
@ViewChild(DocumentListComponent)
documentList: DocumentListComponent;
@ViewChild(UploadButtonComponent)
uploadButton: UploadButtonComponent;
@ViewChild(UploadDragAreaComponent)
uploadDragArea: UploadDragAreaComponent;
constructor(private documentActions: DocumentActionsService,
private authService: AlfrescoAuthenticationService,
private formService: FormService,
private logService: LogService,
private changeDetector: ChangeDetectorRef,
private router: Router,
constructor(private changeDetector: ChangeDetectorRef,
private notificationService: NotificationService,
private uploadService: UploadService,
private contentService: AlfrescoContentService,
private dialog: MdDialog,
@Optional() private route: ActivatedRoute) {
documentActions.setHandler('my-handler', this.myDocumentActionHandler.bind(this));
}
myDocumentActionHandler(obj: any) {
window.alert('my custom action handler');
}
myCustomAction1(event) {
alert('Custom document action for ' + event.value.entry.name);
}
myFolderAction1(event) {
alert('Custom folder action for ' + event.value.entry.name);
}
showFile(event) {
@@ -120,36 +95,11 @@ export class FilesComponent implements OnInit, AfterViewInit {
}
});
}
if (this.authService.isBpmLoggedIn()) {
this.formService.getProcessDefinitions().subscribe(
defs => this.setupBpmActions(defs || []),
err => this.logService.error(err)
);
} else {
this.logService.warn('You are not logged in to BPM');
}
this.uploadService.fileUploadComplete.debounceTime(300).subscribe(value => this.onFileUploadComplete(value));
this.contentService.folderCreated.subscribe(value => this.onFolderCreated(value));
}
ngAfterViewInit() {
this.uploadButton.onSuccess
.debounceTime(100)
.subscribe((event) => {
this.reload(event);
});
this.uploadDragArea.onSuccess
.debounceTime(100)
.subscribe((event) => {
this.reload(event);
});
}
viewActivitiForm(event?: any) {
this.router.navigate(['/activiti/tasksnode', event.value.entry.id]);
}
onNavigationError(err: any) {
if (err) {
this.errorMessage = err.message || 'Navigation error';
@@ -160,24 +110,10 @@ export class FilesComponent implements OnInit, AfterViewInit {
this.errorMessage = null;
}
private setupBpmActions(actions: any[]) {
actions.map(def => {
let documentAction = new DocumentActionModel();
documentAction.title = 'Activiti: ' + (def.name || 'Unknown process');
documentAction.handler = this.getBpmActionHandler(def);
this.documentList.actions.push(documentAction);
let folderAction = new FolderActionModel();
folderAction.title = 'Activiti: ' + (def.name || 'Unknown process');
folderAction.handler = this.getBpmActionHandler(def);
this.documentList.actions.push(folderAction);
});
onFileUploadComplete(event: FileUploadCompleteEvent) {
if (event && event.file.options.parentId === this.documentList.currentFolderId) {
this.documentList.reload();
}
private getBpmActionHandler(processDefinition: any): ContentActionHandler {
return function (obj: any, target?: any) {
window.alert(`Starting BPM process: ${processDefinition.id}`);
}.bind(this);
}
onFolderCreated(event: FolderCreatedEvent) {
@@ -188,20 +124,11 @@ export class FilesComponent implements OnInit, AfterViewInit {
}
}
onPermissionsFailed(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);
}
reload(event: any) {
if (event && event.value && event.value.entry && event.value.entry.parentId) {
if (this.documentList.currentFolderId === event.value.entry.parentId) {
this.documentList.reload();
}
}
handlePermissionError(event: any) {
this.notificationService.openSnackMessage(
`You don't have the ${event.permission} permission to ${event.action} the ${event.type} `,
4000
);
}
onCreateFolderClicked(event: Event) {
@@ -209,12 +136,8 @@ export class FilesComponent implements OnInit, AfterViewInit {
dialogRef.afterClosed().subscribe(folderName => {
if (folderName) {
this.contentService.createFolder('', folderName, this.documentList.currentFolderId).subscribe(
node => {
console.log(node);
},
err => {
console.log(err);
}
node => console.log(node),
err => console.log(err)
);
}
});

View File

@@ -17,6 +17,7 @@
import { ElementRef } from '@angular/core';
import { UploadDirective } from './upload.directive';
import { FileInfo } from './../utils/file-utils';
describe('UploadDirective', () => {
@@ -106,30 +107,35 @@ describe('UploadDirective', () => {
expect(event.preventDefault).not.toHaveBeenCalled();
});
it('should raise upload-files event on files drop', () => {
it('should raise upload-files event on files drop', (done) => {
directive.enabled = true;
let files = [<File> {}];
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
spyOn(directive, 'getDataTransfer').and.returnValue({});
spyOn(directive, 'getFilesDropped').and.returnValue(files);
spyOn(nativeElement, 'dispatchEvent').and.stub();
spyOn(directive, 'getFilesDropped').and.returnValue(Promise.resolve([
<FileInfo> {},
<FileInfo> {}
]));
spyOn(nativeElement, 'dispatchEvent').and.callFake(_ => {
done();
});
directive.onDrop(event);
expect(nativeElement.dispatchEvent).toHaveBeenCalled();
});
it('should provide dropped files in upload-files event', () => {
it('should provide dropped files in upload-files event', (done) => {
directive.enabled = true;
let files = [<File> {}];
let files = [
<FileInfo> {}
];
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
spyOn(directive, 'getDataTransfer').and.returnValue({});
spyOn(directive, 'getFilesDropped').and.returnValue(files);
spyOn(directive, 'getFilesDropped').and.returnValue(Promise.resolve(files));
spyOn(nativeElement, 'dispatchEvent').and.callFake(e => {
expect(e.detail.files.length).toBe(1);
expect(e.detail.files[0]).toBe(files[0]);
done();
});
directive.onDrop(event);
expect(nativeElement.dispatchEvent).toHaveBeenCalled();
});
});

View File

@@ -16,6 +16,7 @@
*/
import { Directive, Input, HostListener, ElementRef, Renderer, OnInit, NgZone, OnDestroy } from '@angular/core';
import { FileUtils, FileInfo } from '../utils/file-utils';
@Directive({
selector: '[adf-upload]'
@@ -129,14 +130,16 @@ export class UploadDirective implements OnInit, OnDestroy {
const dataTranfer = this.getDataTransfer(event);
if (dataTranfer) {
const files = this.getFilesDropped(dataTranfer);
this.getFilesDropped(dataTranfer).then(files => {
this.onUploadFiles(files);
});
}
}
return false;
}
onUploadFiles(files: File[]) {
onUploadFiles(files: FileInfo[]) {
if (this.enabled && files.length > 0) {
let e = new CustomEvent('upload-files', {
detail: {
@@ -177,34 +180,55 @@ export class UploadDirective implements OnInit, OnDestroy {
* Extract files from the DataTransfer object used to hold the data that is being dragged during a drag and drop operation.
* @param dataTransfer DataTransfer object
*/
protected getFilesDropped(dataTransfer: DataTransfer): File[] {
const result: File[] = [];
protected getFilesDropped(dataTransfer: DataTransfer): Promise<FileInfo[]> {
return new Promise(resolve => {
const iterations = [];
if (dataTransfer) {
const items: FileList = dataTransfer.files;
if (items && items.length > 0) {
const items = dataTransfer.items;
if (items) {
for (let i = 0; i < items.length; i++) {
result.push(items[i]);
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
let item = items[i].webkitGetAsEntry();
if (item) {
if (item.isFile) {
iterations.push(Promise.resolve(<FileInfo> {
entry: item,
file: items[i].getAsFile(),
relativeFolder: '/'
}));
} else if (item.isDirectory) {
iterations.push(new Promise(resolveFolder => {
FileUtils.flattern(item).then(files => resolveFolder(files));
}));
}
}
} else {
iterations.push(Promise.resolve(<FileInfo>{
entry: null,
file: items[i].getAsFile(),
relativeFolder: '/'
}));
}
}
} else {
// safari or FF
let files = FileUtils
.toFileArray(dataTransfer.files)
.map(file => <FileInfo> {
entry: null,
file: file,
relativeFolder: '/'
});
iterations.push(Promise.resolve(files));
}
}
return result;
}
/**
* Extract files from the FileList object used to hold files that user selected by means of File Dialog.
* @param fileList List of selected files
*/
protected getFilesSelected(fileList: FileList) {
let result: File[] = [];
if (fileList && fileList.length > 0) {
for (let i = 0; i < fileList.length; i++) {
result.push(fileList[i]);
}
}
return result;
Promise.all(iterations).then(result => {
resolve(result.reduce((a, b) => a.concat(b), []));
});
});
}
/**
@@ -214,8 +238,12 @@ export class UploadDirective implements OnInit, OnDestroy {
protected onSelectFiles(e: Event) {
if (this.isClickMode()) {
const input = (<HTMLInputElement>e.currentTarget);
const files = this.getFilesSelected(input.files);
this.onUploadFiles(files);
const files = FileUtils.toFileArray(input.files);
this.onUploadFiles(files.map(file => <FileInfo> {
entry: null,
file: file,
relativeFolder: '/'
}));
}
}
}

View File

@@ -0,0 +1,73 @@
/*!
* @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.
*/
export interface FileInfo {
entry?: WebKitFileEntry;
file?: File;
relativeFolder?: string;
}
export class FileUtils {
static flattern(folder: any): Promise<FileInfo[]> {
let reader = folder.createReader();
let files: FileInfo[] = [];
return new Promise(resolve => {
let iterations = [];
(function traverse() {
reader.readEntries((entries) => {
if (!entries.length) {
Promise.all(iterations).then(result => resolve(files));
} else {
iterations.push(Promise.all(entries.map(entry => {
if (entry.isFile) {
return new Promise(resolveFile => {
entry.file(function (f: File) {
files.push({
entry: entry,
file: f,
relativeFolder: entry.fullPath.replace(/\/[^\/]*$/, '')
});
resolveFile();
});
});
} else {
return FileUtils.flattern(entry).then(result => {
files.push(...result);
});
}
})));
// Try calling traverse() again for the same dir, according to spec
traverse();
}
});
})();
});
}
static toFileArray(fileList: FileList): File[] {
let result = [];
if (fileList && fileList.length > 0) {
for (let i = 0; i < fileList.length; i++) {
result.push(fileList[i]);
}
}
return result;
}
}

View File

@@ -16,3 +16,4 @@
*/
export * from './object-utils';
export * from './file-utils';

View File

@@ -102,70 +102,16 @@ Follow the 3 steps below:
```html
<alfresco-upload-button
[showNotificationBar]="true"
[rootFolderId]="-my-"
[uploadFolders]="true"
[multipleFiles]="false"
[acceptedFilesType]=".jpg,.gif,.png,.svg"
[currentFolderPath]="/Sites/swsdp/documentLibrary"
[versioning]="false"
(onSuccess)="customMethod($event)">
</alfresco-upload-button>
<file-uploading-dialog></file-uploading-dialog>
```
Example of an App that declares upload button component :
```ts
import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { UploadModule } from 'ng2-alfresco-upload';
@Component({
selector: 'alfresco-app-demo',
template: `
<alfresco-upload-button
[showNotificationBar]="true"
[uploadFolders]="false"
[multipleFiles]="false"
[acceptedFilesType]="'.jpg,.gif,.png,.svg'"
(onSuccess)="onSuccess($event)">
</alfresco-upload-button>
<file-uploading-dialog></file-uploading-dialog>
`
})
export class MyDemoApp {
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)
);
}
public onSuccess(event: Object): void {
console.log('File uploaded');
}
}
@NgModule({
imports: [
BrowserModule,
CoreModule.forRoot(),
UploadModule.forRoot()
],
declarations: [ MyDemoApp ],
bootstrap: [ MyDemoApp ]
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
```
### Events
| Name | Description |
@@ -177,11 +123,12 @@ platformBrowserDynamic().bootstrapModule(AppModule);
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | *boolean* | false | Toggle component disabled state |
| `showNotificationBar` | *boolean* | true | Hide/show notification bar |
| **(deprecated)** `showNotificationBar` | *boolean* | true | Hide/show notification bar. **Deprecated in 1.6.0: use UploadService events and NotificationService api instead.** |
| `uploadFolders` | *boolean* | false | Allow/disallow upload folders (only for chrome) |
| `multipleFiles` | *boolean* | false | Allow/disallow multiple files |
| `acceptedFilesType` | *string* | * | array of allowed file extensions , example: ".jpg,.gif,.png,.svg" |
| `currentFolderPath` | *string* | '/Sites/swsdp/documentLibrary' | define the path where the files are uploaded |
| **(deprecated)** `currentFolderPath` | *string* | '/Sites/swsdp/documentLibrary' | define the path where the files are uploaded. **Deprecated in 1.6.0: use rootFolderId instead.** |
| `rootFolderId` | *string* | '-root-' | The ID of the root folder node. |
| `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 |
| `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 |
@@ -199,7 +146,9 @@ You can subscribe to this event from your component and use the NotificationServ
[rootFolderId]="currentFolderId"
(permissionEvent)="onUploadPermissionFailed($event)">
</alfresco-upload-button>
```
```ts
export class MyComponent {
onUploadPermissionFailed(event: any) {
@@ -232,61 +181,22 @@ The UploadButtonComponent provides the property disableWithNoPermission that can
This component, provide a drag and drop are to upload files to alfresco.
```html
<alfresco-upload-drag-area
(onSuccess)="customMethod($event)">
<alfresco-upload-drag-area (onSuccess)="customMethod($event)">
<div style="width: 200px; height: 100px; border: 1px solid #888888">
DRAG HERE
</div>
</alfresco-upload-drag-area>
<file-uploading-dialog></file-uploading-dialog>
```
Example of an App that declares upload drag and drop component:
```ts
import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { UploadModule } from 'ng2-alfresco-upload';
@Component({
selector: 'alfresco-app-demo',
template: `
<alfresco-upload-drag-area (onSuccess)="customMethod($event)" >
<div style="width: 200px; height: 100px; border: 1px solid #888888">
DRAG HERE
</div>
</alfresco-upload-drag-area>
<file-uploading-dialog></file-uploading-dialog>
`
})
export class MyDemoApp {
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)
);
}
export class AppComponent {
public onSuccess(event: Object): void {
console.log('File uploaded');
}
}
@NgModule({
imports: [
BrowserModule,
CoreModule.forRoot(),
UploadModule.forRoot()
],
declarations: [ MyDemoApp ],
bootstrap: [ MyDemoApp ]
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
```
### Events
@@ -300,9 +210,9 @@ platformBrowserDynamic().bootstrapModule(AppModule);
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `enabled` | *boolean* | true | Toggle component enabled state |
| `showNotificationBar` | *boolean* | true | Hide/show notification bar |
| `rootFolderId` | *string* | '-root-' | The ID of the root folder node.
| `currentFolderPath` | *string* | '/' | define the path where the files are uploaded |
| **(deprecated)** `showNotificationBar` | *boolean* | true | Hide/show notification bar. **Deprecated in 1.6.0: use UploadService events and NotificationService api instead.** |
| `rootFolderId` | *string* | '-root-' | The ID of the root folder node. |
| **(deprecated)** `currentFolderPath` | *string* | '/' | define the path where the files are uploaded. **Deprecated in 1.6.0: use rootFolderId instead.** |
| `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 |
## FileUploadingDialogComponent

View File

@@ -67,7 +67,6 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
});
this.uploadService.fileUpload.subscribe(e => {
console.log(e);
this.cd.detectChanges();
});
}

View File

@@ -186,7 +186,7 @@ describe('UploadButtonComponent', () => {
fixture.detectChanges();
component.onFilesAdded(fakeEvent);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/folder-fake', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
it('should call uploadFile with a custom root folder', () => {
@@ -202,7 +202,7 @@ describe('UploadButtonComponent', () => {
fixture.detectChanges();
component.onFilesAdded(fakeEvent);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-my-', '/root-fake-/sites-fake/folder-fake', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
it('should create a folder and emit an File uploaded event', (done) => {
@@ -228,21 +228,6 @@ describe('UploadButtonComponent', () => {
component.onDirectoryAdded(fakeEvent);
});
it('should emit an onError event when the folder already exist', (done) => {
component.rootFolderId = '-my-';
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('Error');
done();
});
component.onDirectoryAdded(fakeEvent);
});
it('should by default the title of the button get from the JSON file', () => {
let compiled = fixture.debugElement.nativeElement;
fixture.detectChanges();

View File

@@ -17,14 +17,12 @@
import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Observable, Subject } from 'rxjs/Rx';
import { AlfrescoApiService, AlfrescoContentService, AlfrescoTranslationService, LogService, NotificationService, AlfrescoSettingsService } from 'ng2-alfresco-core';
import { AlfrescoApiService, AlfrescoContentService, AlfrescoTranslationService, LogService, NotificationService, AlfrescoSettingsService, FileUtils } 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';
const ERROR_FOLDER_ALREADY_EXIST = 409;
@Component({
selector: 'alfresco-upload-button',
templateUrl: './upload-button.component.html',
@@ -32,11 +30,15 @@ const ERROR_FOLDER_ALREADY_EXIST = 409;
})
export class UploadButtonComponent implements OnInit, OnChanges {
private static DEFAULT_ROOT_ID: string = '-root-';
@Input()
disabled: boolean = false;
/**
* @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead.
*
* @type {boolean}
* @memberof UploadButtonComponent
*/
@Input()
showNotificationBar: boolean = true;
@@ -55,11 +57,17 @@ export class UploadButtonComponent implements OnInit, OnChanges {
@Input()
staticTitle: string;
/**
* @deprecated Deprecated in 1.6.0, this property is not used for couple of releases already.
*
* @type {string}
* @memberof UploadDragAreaComponent
*/
@Input()
currentFolderPath: string = '/';
@Input()
rootFolderId: string = UploadButtonComponent.DEFAULT_ROOT_ID;
rootFolderId: string = '-root-';
@Input()
disableWithNoPermission: boolean = false;
@@ -122,16 +130,11 @@ export class UploadButtonComponent implements OnInit, OnChanges {
return !this.hasPermission && this.disableWithNoPermission ? true : undefined;
}
/**
* Method called when files are dropped in the drag area.
*
* @param {File[]} files - files dropped in the drag area.
*/
onFilesAdded($event: any): void {
let files: File[] = this.getFiles($event.currentTarget.files);
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
if (this.hasPermission) {
this.uploadFiles(this.currentFolderPath, files);
this.uploadFiles(files);
} else {
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
}
@@ -139,38 +142,10 @@ export class UploadButtonComponent implements OnInit, OnChanges {
$event.target.value = '';
}
/**
* Method called when a folder is dropped in the drag area.
*
* @param {File[]} files - files of a folder dropped in the drag area.
*/
onDirectoryAdded($event: any): void {
let files: File[] = this.getFiles($event.currentTarget.files);
if (this.hasPermission) {
let hashMapDir = this.convertIntoHashMap(files);
hashMapDir.forEach((filesDir, directoryPath) => {
let directoryName = this.getDirectoryName(directoryPath);
let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath);
this.contentService.createFolder(absolutePath, directoryName, this.rootFolderId)
.subscribe(
_ => {
let relativeDir = this.currentFolderPath + '/' + directoryPath;
this.uploadFiles(relativeDir, filesDir);
},
error => {
let errorMessagePlaceholder = this.getErrorMessage(error.response);
if (errorMessagePlaceholder) {
this.onError.emit({value: errorMessagePlaceholder});
let errorMessage = this.formatString(errorMessagePlaceholder, [directoryName]);
if (errorMessage) {
this.showErrorNotificationBar(errorMessage);
}
}
}
);
});
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
this.uploadFiles(files);
} else {
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
}
@@ -180,78 +155,24 @@ export class UploadButtonComponent implements OnInit, OnChanges {
/**
* Upload a list of file in the specified path
* @param path
* @param files
* @param path
*/
uploadFiles(path: string, files: File[]): void {
if (files.length) {
const latestFilesAdded = files.map(f => new FileModel(f, { newVersion: this.versioning }));
uploadFiles(files: File[]): void {
if (files.length > 0) {
const latestFilesAdded = files.map(file => new FileModel(file, {
newVersion: this.versioning,
parentId: this.rootFolderId,
path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, '')
}));
this.uploadService.addToQueue(...latestFilesAdded);
this.uploadService.uploadFilesInTheQueue(this.rootFolderId, path, this.onSuccess);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
if (this.showNotificationBar) {
this.showUndoNotificationBar(latestFilesAdded);
}
}
}
/**
* It converts the array given as input into a map. The map is a key values pairs, where the key is the directory name and the value are
* all the files that the directory contains.
* @param files - array of files
* @returns {Map}
*/
private convertIntoHashMap(files: File[]): Map<string, File[]> {
let directoryMap = new Map<string, File[]>();
for (let file of files) {
let directory = this.getDirectoryPath(file.webkitRelativePath);
let filesSomeDir = directoryMap.get(directory) || [];
filesSomeDir.push(file);
directoryMap.set(directory, filesSomeDir);
}
return directoryMap;
}
private getFiles(fileList: FileList): File[] {
const result: File[] = [];
if (fileList && fileList.length > 0) {
for (let i = 0; i < fileList.length; i++) {
result.push(fileList[i]);
}
}
return result;
}
/**
* Split the directory path given as input and cut the last directory name
* @param directory
* @returns {string}
*/
private getDirectoryPath(directory: string): string {
let relativeDirPath = '';
let dirPath = directory.split('/');
if (dirPath.length > 1) {
dirPath.pop();
relativeDirPath = '/' + dirPath.join('/');
}
return relativeDirPath;
}
/**
* Split a directory path passed in input and return the first directory name
* @param directory
* @returns {string}
*/
private getDirectoryName(directory: string): string {
let dirPath = directory.split('/');
if (dirPath.length > 1) {
return dirPath.pop();
} else {
return dirPath[0];
}
}
/**
* Show undo notification bar.
*
@@ -267,43 +188,6 @@ export class UploadButtonComponent implements OnInit, OnChanges {
});
}
/**
* Retrive the error message using the error status code
* @param response - object that contain the HTTP response
* @returns {string}
*/
private getErrorMessage(response: any): string {
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';
}
/**
* Show the error inside Notification bar
* @param Error message
* @private
*/
private showErrorNotificationBar(errorMessage: string): void {
this.notificationService.openSnackMessage(errorMessage, 3000);
}
/**
* Replace a placeholder {0} in a message with the input keys
* @param message - the message that conains the placeholder
* @param keys - array of value
* @returns {string} - The message without placeholder
*/
private formatString(message: string, keys: any []): string {
let i = keys.length;
while (i--) {
message = message.replace(new RegExp('\\{' + i + '\\}', 'gm'), keys[i]);
}
return message;
}
checkPermission() {
if (this.rootFolderId) {
this.getFolderNode(this.rootFolderId).subscribe(
@@ -313,6 +197,7 @@ export class UploadButtonComponent implements OnInit, OnChanges {
}
}
// TODO: move to AlfrescoContentService
getFolderNode(nodeId: string): Observable<MinimalNodeEntryEntity> {
let opts: any = {
includeSource: true,
@@ -331,13 +216,9 @@ export class UploadButtonComponent implements OnInit, OnChanges {
}
private hasCreatePermission(node: any): boolean {
if (this.hasPermissions(node)) {
if (node && node.allowableOperations) {
return node.allowableOperations.find(permision => permision === 'create') ? true : false;
}
return false;
}
private hasPermissions(node: any): boolean {
return node && node.allowableOperations ? true : false;
}
}

View File

@@ -67,21 +67,22 @@ describe('UploadDragAreaComponent', () => {
TestBed.resetTestingModule();
});
it('should upload the list of files dropped', () => {
it('should upload the list of files dropped', (done) => {
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
component.onSuccess = null;
component.showNotificationBar = false;
uploadService.addToQueue = jasmine.createSpy('addToQueue');
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
fixture.detectChanges();
const file = <File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
let fileFake = new FileModel(file);
let filesList = [fileFake];
let filesList = [file];
spyOn(uploadService, 'addToQueue').and.callFake((f: FileModel) => {
expect(f.file).toBe(file);
done();
});
component.onFilesDropped(filesList);
expect(uploadService.addToQueue).toHaveBeenCalledWith(fileFake);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/folder-fake', null);
});
it('should show the loading messages in the notification bar when the files are dropped', () => {
@@ -92,11 +93,11 @@ describe('UploadDragAreaComponent', () => {
component.showUndoNotificationBar = jasmine.createSpy('_showUndoNotificationBar');
fixture.detectChanges();
let fileFake = new FileModel(<File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'});
let fileFake = <File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
let filesList = [fileFake];
component.onFilesDropped(filesList);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/folder-fake', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
expect(component.showUndoNotificationBar).toHaveBeenCalled();
});
@@ -119,8 +120,7 @@ describe('UploadDragAreaComponent', () => {
};
component.onFilesEntityDropped(itemEntity);
expect(uploadService.uploadFilesInTheQueue)
.toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/document-library-fake/folder-fake/', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
it('should upload a file with a custom root folder ID when dropped', () => {
@@ -143,7 +143,6 @@ describe('UploadDragAreaComponent', () => {
};
component.onFilesEntityDropped(itemEntity);
expect(uploadService.uploadFilesInTheQueue)
.toHaveBeenCalledWith('-my-', '/root-fake-/sites-fake/document-library-fake/folder-fake/', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
});

View File

@@ -16,12 +16,10 @@
*/
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { AlfrescoTranslationService, AlfrescoContentService, LogService, NotificationService } from 'ng2-alfresco-core';
import { AlfrescoTranslationService, NotificationService, FileUtils, FileInfo } from 'ng2-alfresco-core';
import { UploadService } from '../services/upload.service';
import { FileModel } from '../models/file.model';
const ERROR_FOLDER_ALREADY_EXIST = 409;
@Component({
selector: 'alfresco-upload-drag-area',
templateUrl: './upload-drag-area.component.html',
@@ -29,31 +27,39 @@ const ERROR_FOLDER_ALREADY_EXIST = 409;
})
export class UploadDragAreaComponent {
private static DEFAULT_ROOT_ID: string = '-root-';
@Input()
enabled: boolean = true;
/**
* @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead.
*
* @type {boolean}
* @memberof UploadButtonComponent
*/
@Input()
showNotificationBar: boolean = true;
@Input()
versioning: boolean = false;
/**
* @deprecated Deprecated in 1.6.0, this property is not used for couple of releases already. Use rootFolderId instead.
*
* @type {string}
* @memberof UploadDragAreaComponent
*/
@Input()
currentFolderPath: string = '/';
@Input()
rootFolderId: string = UploadDragAreaComponent.DEFAULT_ROOT_ID;
rootFolderId: string = '-root-';
@Output()
onSuccess = new EventEmitter();
constructor(private uploadService: UploadService,
private translateService: AlfrescoTranslationService,
private logService: LogService,
private notificationService: NotificationService,
private contentService: AlfrescoContentService) {
private notificationService: NotificationService) {
if (translateService) {
translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload');
}
@@ -67,15 +73,18 @@ export class UploadDragAreaComponent {
e.stopPropagation();
e.preventDefault();
if (this.enabled) {
let files: File[] = e.detail.files;
let files: FileInfo[] = e.detail.files;
if (files && files.length > 0) {
const fileModels = files.map(f => new FileModel(f, { newVersion: this.versioning }));
if (e.detail.data.obj.entry.isFolder) {
let id = e.detail.data.obj.entry.id;
this.onFilesDropped(fileModels, id, '/');
} else {
this.onFilesDropped(fileModels);
let parentId = this.rootFolderId;
if (e.detail.data && e.detail.data.obj.entry.isFolder) {
parentId = e.detail.data.obj.entry.id || this.rootFolderId;
}
const fileModels = files.map(fileInfo => new FileModel(fileInfo.file, {
newVersion: this.versioning,
path: fileInfo.relativeFolder,
parentId: parentId
}));
this.uploadFiles(fileModels);
}
}
}
@@ -85,10 +94,15 @@ export class UploadDragAreaComponent {
*
* @param {File[]} files - files dropped in the drag area.
*/
onFilesDropped(files: FileModel[], rootId?: string, directory?: string): void {
onFilesDropped(files: File[]): void {
if (this.enabled && files.length) {
this.uploadService.addToQueue(...files);
this.uploadService.uploadFilesInTheQueue(rootId || this.rootFolderId, directory || this.currentFolderPath, this.onSuccess);
const fileModels = files.map(file => new FileModel(file, {
newVersion: this.versioning,
path: '/',
parentId: this.rootFolderId
}));
this.uploadService.addToQueue(...fileModels);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
let latestFilesAdded = this.uploadService.getQueue();
if (this.showNotificationBar) {
this.showUndoNotificationBar(latestFilesAdded);
@@ -103,11 +117,13 @@ export class UploadDragAreaComponent {
onFilesEntityDropped(item: any): void {
if (this.enabled) {
item.file((file: File) => {
const fileModel = new FileModel(file, { newVersion: this.versioning });
const fileModel = new FileModel(file, {
newVersion: this.versioning,
parentId: this.rootFolderId,
path: item.fullPath.replace(item.name, '')
});
this.uploadService.addToQueue(fileModel);
let path = item.fullPath.replace(item.name, '');
let filePath = this.currentFolderPath + path;
this.uploadService.uploadFilesInTheQueue(this.rootFolderId, filePath, this.onSuccess);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
});
}
}
@@ -118,52 +134,22 @@ export class UploadDragAreaComponent {
*/
onFolderEntityDropped(folder: any): void {
if (this.enabled && folder.isDirectory) {
let relativePath = folder.fullPath.replace(folder.name, '');
relativePath = this.currentFolderPath + relativePath;
this.contentService.createFolder(relativePath, folder.name, this.rootFolderId)
.subscribe(
message => {
this.onSuccess.emit({
value: 'Created folder'
FileUtils.flattern(folder).then(entries => {
let files = entries.map(entry => {
return new FileModel(entry.file, {
newVersion: this.versioning,
parentId: this.rootFolderId,
path: entry.relativeFolder
});
let dirReader = folder.createReader();
dirReader.readEntries((entries: any) => {
for (let i = 0; i < entries.length; i++) {
this._traverseFileTree(entries[i]);
}
});
this.uploadService.addToQueue(...files);
/* @deprecated in 1.6.0 */
if (this.showNotificationBar) {
let latestFilesAdded = this.uploadService.getQueue();
this.showUndoNotificationBar(latestFilesAdded);
}
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
});
},
error => {
let errorMessagePlaceholder = this.getErrorMessage(error.response);
let errorMessage = this.formatString(errorMessagePlaceholder, [folder.name]);
if (this.showNotificationBar) {
this.showErrorNotificationBar(errorMessage);
} else {
this.logService.error(errorMessage);
}
}
);
}
}
/**
* Travers all the files and folders, and create it on the alfresco.
*
* @param {Object} item - can contains files or folders.
*/
private _traverseFileTree(item: any): void {
if (item.isFile) {
this.onFilesEntityDropped(item);
} else {
if (item.isDirectory) {
this.onFolderEntityDropped(item);
}
}
}
@@ -182,41 +168,14 @@ export class UploadDragAreaComponent {
});
}
/**
* Show the error inside Notification bar
* @param Error message
* @private
*/
showErrorNotificationBar(errorMessage: string) {
this.notificationService.openSnackMessage(errorMessage, 3000);
}
/**
* Retrive the error message using the error status code
* @param response - object that contain the HTTP response
* @returns {string}
*/
private getErrorMessage(response: any): string {
if (response.body.error.statusCode === ERROR_FOLDER_ALREADY_EXIST) {
let errorMessage: any;
errorMessage = this.translateService.get('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST');
return errorMessage.value;
private uploadFiles(files: FileModel[]): void {
if (this.enabled && files.length) {
this.uploadService.addToQueue(...files);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
let latestFilesAdded = this.uploadService.getQueue();
if (this.showNotificationBar) {
this.showUndoNotificationBar(latestFilesAdded);
}
}
/**
* Replace a placeholder {0} in a message with the input keys
* @param message - the message that conains the placeholder
* @param keys - array of value
* @returns {string} - The message without placeholder
*/
private formatString(message: string, keys: any []) {
if (message) {
let i = keys.length;
while (i--) {
message = message.replace(new RegExp('\\{' + i + '\\}', 'gm'), keys[i]);
}
}
return message;
}
}

View File

@@ -16,6 +16,7 @@
*/
import { Directive, EventEmitter, Input, Output, OnInit, OnDestroy, ElementRef, NgZone } from '@angular/core';
import { FileUtils } from 'ng2-alfresco-core';
@Directive({
selector: '[file-draggable]'
@@ -28,7 +29,7 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
enabled: boolean = true;
@Output()
onFilesDropped: EventEmitter<any> = new EventEmitter();
onFilesDropped: EventEmitter<File[]> = new EventEmitter<File[]>();
@Output()
onFilesEntityDropped: EventEmitter<any> = new EventEmitter();
@@ -73,16 +74,20 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
let item = items[i].webkitGetAsEntry();
if (item) {
this.traverseFileTree(item);
if (item.isFile) {
this.onFilesEntityDropped.emit(item);
} else if (item.isDirectory) {
this.onFolderEntityDropped.emit(item);
}
}
} else {
let files = event.dataTransfer.files;
let files = FileUtils.toFileArray(event.dataTransfer.files);
this.onFilesDropped.emit(files);
}
}
} else {
// safari or FF
let files = event.dataTransfer.files;
let files = FileUtils.toFileArray(event.dataTransfer.files);
this.onFilesDropped.emit(files);
}
@@ -90,22 +95,6 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
}
}
/**
* Travers all the files and folders, and emit an event for each file or directory.
*
* @param {Object} item - can contains files or folders.
*/
private traverseFileTree(item: any): void {
if (item.isFile) {
let self = this;
self.onFilesEntityDropped.emit(item);
} else {
if (item.isDirectory) {
this.onFolderEntityDropped.emit(item);
}
}
}
/**
* Change the style of the drag area when a file drag in.
*

View File

@@ -23,6 +23,8 @@ export interface FileUploadProgress {
export interface FileUploadOptions {
newVersion?: boolean;
parentId?: string;
path?: string;
}
export enum FileUploadStatus {

View File

@@ -19,7 +19,7 @@ import { EventEmitter } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { UploadService } from './upload.service';
import { FileModel } from '../models/file.model';
import { FileModel, FileUploadOptions } from '../models/file.model';
declare let jasmine: any;
@@ -77,9 +77,12 @@ describe('UploadService', () => {
expect(e.value).toBe('File uploaded');
done();
});
let fileFake = new FileModel(<File>{name: 'fake-name', size: 10});
let fileFake = new FileModel(
<File>{name: 'fake-name', size: 10},
<FileUploadOptions> { parentId: '-root-', path: 'fake-dir' }
);
service.addToQueue(fileFake);
service.uploadFilesInTheQueue('-root-', 'fake-dir', emitter);
service.uploadFilesInTheQueue(emitter);
let request = jasmine.Ajax.requests.mostRecent();
expect(request.url).toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true');
@@ -99,9 +102,12 @@ describe('UploadService', () => {
expect(e.value).toBe('Error file uploaded');
done();
});
let fileFake = new FileModel(<File>{name: 'fake-name', size: 10});
let fileFake = new FileModel(
<File>{name: 'fake-name', size: 10},
<FileUploadOptions> { parentId: '-root-' }
);
service.addToQueue(fileFake);
service.uploadFilesInTheQueue('-root-', '', emitter);
service.uploadFilesInTheQueue(emitter);
expect(jasmine.Ajax.requests.mostRecent().url)
.toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true');
@@ -121,7 +127,7 @@ describe('UploadService', () => {
});
let fileFake = new FileModel(<File>{name: 'fake-name', size: 10});
service.addToQueue(fileFake);
service.uploadFilesInTheQueue('-root-', '', emitter);
service.uploadFilesInTheQueue(emitter);
let file = service.getQueue();
service.cancelUpload(...file);
@@ -132,7 +138,7 @@ describe('UploadService', () => {
const filesFake = new FileModel(<File>{name: 'fake-name', size: 10}, { newVersion: true });
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter);
service.uploadFilesInTheQueue(emitter);
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('autoRename=true')).toBe(false);
expect(jasmine.Ajax.requests.mostRecent().params.has('majorVersion')).toBe(true);
@@ -145,9 +151,12 @@ describe('UploadService', () => {
expect(e.value).toBe('File uploaded');
done();
});
let filesFake = new FileModel(<File>{name: 'fake-name', size: 10});
let filesFake = new FileModel(
<File>{name: 'fake-name', size: 10},
<FileUploadOptions> { parentId: '123', path: 'fake-dir' }
);
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('123', 'fake-dir', emitter);
service.uploadFilesInTheQueue(emitter);
let request = jasmine.Ajax.requests.mostRecent();
expect(request.url).toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/123/children?autoRename=true');

View File

@@ -17,7 +17,7 @@
import { EventEmitter, Injectable } from '@angular/core';
import { Subject } from 'rxjs/Rx';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { AlfrescoApiService } from 'ng2-alfresco-core';
import { FileUploadEvent, FileUploadCompleteEvent } from '../events/file.event';
import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model';
@@ -27,6 +27,7 @@ export class UploadService {
private queue: FileModel[] = [];
private cache: { [key: string]: any } = {};
private totalComplete: number = 0;
private activeTask: Promise<any> = null;
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
@@ -37,8 +38,18 @@ export class UploadService {
fileUploadError: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>();
constructor(private apiService: AlfrescoApiService,
private logService: LogService) {
constructor(private apiService: AlfrescoApiService) {
}
/**
* Checks whether the service is uploading a file.
*
* @returns {boolean}
*
* @memberof UploadService
*/
isUploading(): boolean {
return this.activeTask ? true : false;
}
/**
@@ -67,52 +78,32 @@ export class UploadService {
/**
* Pick all the files in the queue that are not been uploaded yet and upload it into the directory folder.
*
* @param {EventEmitter<any>} emitter @deprecated emitter to invoke on file status change
*
* @memberof UploadService
*/
uploadFilesInTheQueue(rootId: string, directory: string, elementEmit: EventEmitter<any>): void {
const files = this.getFilesToUpload();
files.forEach((file: FileModel) => {
uploadFilesInTheQueue(emitter: EventEmitter<any>): void {
if (!this.activeTask) {
let file = this.queue.find(f => f.status === FileUploadStatus.Pending);
if (file) {
this.onUploadStarting(file);
const opts: any = {
renditions: 'doclib'
const promise = this.beginUpload(file, emitter);
this.activeTask = promise;
this.cache[file.id] = promise;
let next = () => {
this.activeTask = null;
setTimeout(() => this.uploadFilesInTheQueue(emitter), 100);
};
if (file.options.newVersion === true) {
opts.overwrite = true;
opts.majorVersion = true;
} else {
opts.autoRename = true;
promise.then(
() => next(),
() => next()
);
}
}
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);
});
this.cache[file.id] = promise;
});
}
cancelUpload(...files: FileModel[]) {
@@ -131,6 +122,46 @@ export class UploadService {
});
}
private beginUpload(file: FileModel, /* @deprecated */emitter: EventEmitter<any>): any {
let opts: any = {
renditions: 'doclib'
};
if (file.options.newVersion === true) {
opts.overwrite = true;
opts.majorVersion = true;
} else {
opts.autoRename = true;
}
let promise = this.apiService.getInstance().upload.uploadFile(
file.file,
file.options.path,
file.options.parentId,
null,
opts
);
promise.on('progress', (progress: FileUploadProgress) => {
this.onUploadProgress(file, progress);
})
.on('abort', () => {
this.onUploadAborted(file);
emitter.emit({ value: 'File aborted' });
})
.on('error', err => {
this.onUploadError(file, err);
emitter.emit({ value: 'Error file uploaded' });
})
.on('success', data => {
this.onUploadComplete(file);
emitter.emit({ value: data });
})
.catch(err => {
this.onUploadError(file, err);
});
return promise;
}
private onUploadStarting(file: FileModel): void {
if (file) {
file.status = FileUploadStatus.Starting;
@@ -200,11 +231,4 @@ export class UploadService {
this.fileUploadAborted.next(event);
}
}
private getFilesToUpload(): FileModel[] {
let filesToUpload = this.queue.filter(file => {
return file.status === FileUploadStatus.Pending;
});
return filesToUpload;
}
}