mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
[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:
committed by
Eugenio Romano
parent
e7a1f46ac8
commit
a02ba4ad71
@@ -46,11 +46,10 @@
|
|||||||
[contextMenuActions]="true"
|
[contextMenuActions]="true"
|
||||||
[contentActions]="true"
|
[contentActions]="true"
|
||||||
[allowDropFiles]="true"
|
[allowDropFiles]="true"
|
||||||
[sorting]="['name', 'desc']"
|
|
||||||
(error)="onNavigationError($event)"
|
(error)="onNavigationError($event)"
|
||||||
(success)="resetError()"
|
(success)="resetError()"
|
||||||
(preview)="showFile($event)"
|
(preview)="showFile($event)"
|
||||||
(permissionError)="onPermissionsFailed($event)">
|
(permissionError)="handlePermissionError($event)">
|
||||||
<data-columns>
|
<data-columns>
|
||||||
<data-column key="$thumbnail" type="image" [sortable]="false"></data-column>
|
<data-column key="$thumbnail" type="image" [sortable]="false"></data-column>
|
||||||
<data-column
|
<data-column
|
||||||
@@ -100,22 +99,12 @@
|
|||||||
|
|
||||||
<content-actions>
|
<content-actions>
|
||||||
<!-- folder 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
|
<content-action
|
||||||
target="folder"
|
target="folder"
|
||||||
permission="delete"
|
permission="delete"
|
||||||
[disableWithNoPermission]="true"
|
[disableWithNoPermission]="true"
|
||||||
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
|
title="{{'DOCUMENT_LIST.ACTIONS.FOLDER.DELETE' | translate}}"
|
||||||
(permissionEvent)="onPermissionsFailed($event)"
|
(permissionEvent)="handlePermissionError($event)"
|
||||||
handler="delete">
|
handler="delete">
|
||||||
</content-action>
|
</content-action>
|
||||||
<!-- document actions -->
|
<!-- document actions -->
|
||||||
@@ -124,29 +113,14 @@
|
|||||||
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
|
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DOWNLOAD' | translate}}"
|
||||||
handler="download">
|
handler="download">
|
||||||
</content-action>
|
</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
|
<content-action
|
||||||
target="document"
|
target="document"
|
||||||
permission="delete"
|
permission="delete"
|
||||||
[disableWithNoPermission]="true"
|
[disableWithNoPermission]="true"
|
||||||
(permissionEvent)="onPermissionsFailed($event)"
|
(permissionEvent)="handlePermissionError($event)"
|
||||||
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
|
title="{{'DOCUMENT_LIST.ACTIONS.DOCUMENT.DELETE' | translate}}"
|
||||||
handler="delete">
|
handler="delete">
|
||||||
</content-action>
|
</content-action>
|
||||||
<content-action
|
|
||||||
target="folder"
|
|
||||||
title="Activiti: View Form"
|
|
||||||
(execute)="viewActivitiForm($event)">
|
|
||||||
</content-action>
|
|
||||||
</content-actions>
|
</content-actions>
|
||||||
</alfresco-document-list>
|
</alfresco-document-list>
|
||||||
</alfresco-upload-drag-area>
|
</alfresco-upload-drag-area>
|
||||||
@@ -195,7 +169,7 @@
|
|||||||
[uploadFolders]="folderUpload"
|
[uploadFolders]="folderUpload"
|
||||||
[versioning]="versioning"
|
[versioning]="versioning"
|
||||||
[disableWithNoPermission]="disableWithNoPermission"
|
[disableWithNoPermission]="disableWithNoPermission"
|
||||||
(permissionEvent)="onUploadPermissionFailed($event)">
|
(permissionEvent)="handlePermissionError($event)">
|
||||||
</alfresco-upload-button>
|
</alfresco-upload-button>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="acceptedFilesTypeShow">
|
<div *ngIf="acceptedFilesTypeShow">
|
||||||
@@ -209,7 +183,7 @@
|
|||||||
[uploadFolders]="folderUpload"
|
[uploadFolders]="folderUpload"
|
||||||
[versioning]="versioning"
|
[versioning]="versioning"
|
||||||
[disableWithNoPermission]="disableWithNoPermission"
|
[disableWithNoPermission]="disableWithNoPermission"
|
||||||
(permissionEvent)="onUploadPermissionFailed($event)">
|
(permissionEvent)="handlePermissionError($event)">
|
||||||
</alfresco-upload-button>
|
</alfresco-upload-button>
|
||||||
</div>
|
</div>
|
||||||
<section>
|
<section>
|
||||||
|
@@ -15,13 +15,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, OnInit, AfterViewInit, Optional, ViewChild, ChangeDetectorRef } from '@angular/core';
|
import { Component, Input, OnInit, Optional, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { MdDialog } from '@angular/material';
|
import { MdDialog } from '@angular/material';
|
||||||
import { AlfrescoAuthenticationService, AlfrescoContentService, FolderCreatedEvent, LogService, NotificationService } from 'ng2-alfresco-core';
|
import { AlfrescoContentService, FolderCreatedEvent, NotificationService } from 'ng2-alfresco-core';
|
||||||
import { DocumentActionsService, DocumentListComponent, ContentActionHandler, DocumentActionModel, FolderActionModel } from 'ng2-alfresco-documentlist';
|
import { DocumentListComponent } from 'ng2-alfresco-documentlist';
|
||||||
import { FormService } from 'ng2-activiti-form';
|
import { UploadService, FileUploadCompleteEvent } from 'ng2-alfresco-upload';
|
||||||
import { UploadService, UploadButtonComponent, UploadDragAreaComponent } from 'ng2-alfresco-upload';
|
|
||||||
|
|
||||||
import { CreateFolderDialog } from '../../dialogs/create-folder.dialog';
|
import { CreateFolderDialog } from '../../dialogs/create-folder.dialog';
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ import { CreateFolderDialog } from '../../dialogs/create-folder.dialog';
|
|||||||
templateUrl: './files.component.html',
|
templateUrl: './files.component.html',
|
||||||
styleUrls: ['./files.component.css']
|
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-
|
// The identifier of a node. You can also use one of these well-known aliases: -my- | -shared- | -root-
|
||||||
currentFolderId: string = '-my-';
|
currentFolderId: string = '-my-';
|
||||||
|
|
||||||
@@ -38,7 +37,7 @@ export class FilesComponent implements OnInit, AfterViewInit {
|
|||||||
fileNodeId: any;
|
fileNodeId: any;
|
||||||
fileShowed: boolean = false;
|
fileShowed: boolean = false;
|
||||||
|
|
||||||
useCustomToolbar = false;
|
useCustomToolbar = true;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
multipleFileUpload: boolean = false;
|
multipleFileUpload: boolean = false;
|
||||||
@@ -64,36 +63,12 @@ export class FilesComponent implements OnInit, AfterViewInit {
|
|||||||
@ViewChild(DocumentListComponent)
|
@ViewChild(DocumentListComponent)
|
||||||
documentList: DocumentListComponent;
|
documentList: DocumentListComponent;
|
||||||
|
|
||||||
@ViewChild(UploadButtonComponent)
|
constructor(private changeDetector: ChangeDetectorRef,
|
||||||
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,
|
|
||||||
private notificationService: NotificationService,
|
private notificationService: NotificationService,
|
||||||
private uploadService: UploadService,
|
private uploadService: UploadService,
|
||||||
private contentService: AlfrescoContentService,
|
private contentService: AlfrescoContentService,
|
||||||
private dialog: MdDialog,
|
private dialog: MdDialog,
|
||||||
@Optional() private route: ActivatedRoute) {
|
@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) {
|
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));
|
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) {
|
onNavigationError(err: any) {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.errorMessage = err.message || 'Navigation error';
|
this.errorMessage = err.message || 'Navigation error';
|
||||||
@@ -160,24 +110,10 @@ export class FilesComponent implements OnInit, AfterViewInit {
|
|||||||
this.errorMessage = null;
|
this.errorMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupBpmActions(actions: any[]) {
|
onFileUploadComplete(event: FileUploadCompleteEvent) {
|
||||||
actions.map(def => {
|
if (event && event.file.options.parentId === this.documentList.currentFolderId) {
|
||||||
let documentAction = new DocumentActionModel();
|
this.documentList.reload();
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getBpmActionHandler(processDefinition: any): ContentActionHandler {
|
|
||||||
return function (obj: any, target?: any) {
|
|
||||||
window.alert(`Starting BPM process: ${processDefinition.id}`);
|
|
||||||
}.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFolderCreated(event: FolderCreatedEvent) {
|
onFolderCreated(event: FolderCreatedEvent) {
|
||||||
@@ -188,20 +124,11 @@ export class FilesComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onPermissionsFailed(event: any) {
|
handlePermissionError(event: any) {
|
||||||
this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000);
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCreateFolderClicked(event: Event) {
|
onCreateFolderClicked(event: Event) {
|
||||||
@@ -209,12 +136,8 @@ export class FilesComponent implements OnInit, AfterViewInit {
|
|||||||
dialogRef.afterClosed().subscribe(folderName => {
|
dialogRef.afterClosed().subscribe(folderName => {
|
||||||
if (folderName) {
|
if (folderName) {
|
||||||
this.contentService.createFolder('', folderName, this.documentList.currentFolderId).subscribe(
|
this.contentService.createFolder('', folderName, this.documentList.currentFolderId).subscribe(
|
||||||
node => {
|
node => console.log(node),
|
||||||
console.log(node);
|
err => console.log(err)
|
||||||
},
|
|
||||||
err => {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import { ElementRef } from '@angular/core';
|
import { ElementRef } from '@angular/core';
|
||||||
import { UploadDirective } from './upload.directive';
|
import { UploadDirective } from './upload.directive';
|
||||||
|
import { FileInfo } from './../utils/file-utils';
|
||||||
|
|
||||||
describe('UploadDirective', () => {
|
describe('UploadDirective', () => {
|
||||||
|
|
||||||
@@ -106,30 +107,35 @@ describe('UploadDirective', () => {
|
|||||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
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;
|
directive.enabled = true;
|
||||||
let files = [<File> {}];
|
|
||||||
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
||||||
spyOn(directive, 'getDataTransfer').and.returnValue({});
|
spyOn(directive, 'getDataTransfer').and.returnValue({});
|
||||||
spyOn(directive, 'getFilesDropped').and.returnValue(files);
|
spyOn(directive, 'getFilesDropped').and.returnValue(Promise.resolve([
|
||||||
spyOn(nativeElement, 'dispatchEvent').and.stub();
|
<FileInfo> {},
|
||||||
|
<FileInfo> {}
|
||||||
|
]));
|
||||||
|
spyOn(nativeElement, 'dispatchEvent').and.callFake(_ => {
|
||||||
|
done();
|
||||||
|
});
|
||||||
directive.onDrop(event);
|
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;
|
directive.enabled = true;
|
||||||
let files = [<File> {}];
|
let files = [
|
||||||
|
<FileInfo> {}
|
||||||
|
];
|
||||||
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopPropagation']);
|
||||||
spyOn(directive, 'getDataTransfer').and.returnValue({});
|
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 => {
|
spyOn(nativeElement, 'dispatchEvent').and.callFake(e => {
|
||||||
expect(e.detail.files.length).toBe(1);
|
expect(e.detail.files.length).toBe(1);
|
||||||
expect(e.detail.files[0]).toBe(files[0]);
|
expect(e.detail.files[0]).toBe(files[0]);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
directive.onDrop(event);
|
directive.onDrop(event);
|
||||||
expect(nativeElement.dispatchEvent).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Directive, Input, HostListener, ElementRef, Renderer, OnInit, NgZone, OnDestroy } from '@angular/core';
|
import { Directive, Input, HostListener, ElementRef, Renderer, OnInit, NgZone, OnDestroy } from '@angular/core';
|
||||||
|
import { FileUtils, FileInfo } from '../utils/file-utils';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[adf-upload]'
|
selector: '[adf-upload]'
|
||||||
@@ -129,14 +130,16 @@ export class UploadDirective implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const dataTranfer = this.getDataTransfer(event);
|
const dataTranfer = this.getDataTransfer(event);
|
||||||
if (dataTranfer) {
|
if (dataTranfer) {
|
||||||
const files = this.getFilesDropped(dataTranfer);
|
this.getFilesDropped(dataTranfer).then(files => {
|
||||||
this.onUploadFiles(files);
|
this.onUploadFiles(files);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onUploadFiles(files: File[]) {
|
onUploadFiles(files: FileInfo[]) {
|
||||||
if (this.enabled && files.length > 0) {
|
if (this.enabled && files.length > 0) {
|
||||||
let e = new CustomEvent('upload-files', {
|
let e = new CustomEvent('upload-files', {
|
||||||
detail: {
|
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.
|
* 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
|
* @param dataTransfer DataTransfer object
|
||||||
*/
|
*/
|
||||||
protected getFilesDropped(dataTransfer: DataTransfer): File[] {
|
protected getFilesDropped(dataTransfer: DataTransfer): Promise<FileInfo[]> {
|
||||||
const result: File[] = [];
|
return new Promise(resolve => {
|
||||||
|
const iterations = [];
|
||||||
|
|
||||||
if (dataTransfer) {
|
if (dataTransfer) {
|
||||||
const items: FileList = dataTransfer.files;
|
const items = dataTransfer.items;
|
||||||
|
if (items) {
|
||||||
if (items && items.length > 0) {
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
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;
|
Promise.all(iterations).then(result => {
|
||||||
}
|
resolve(result.reduce((a, b) => a.concat(b), []));
|
||||||
|
});
|
||||||
/**
|
});
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -214,8 +238,12 @@ export class UploadDirective implements OnInit, OnDestroy {
|
|||||||
protected onSelectFiles(e: Event) {
|
protected onSelectFiles(e: Event) {
|
||||||
if (this.isClickMode()) {
|
if (this.isClickMode()) {
|
||||||
const input = (<HTMLInputElement>e.currentTarget);
|
const input = (<HTMLInputElement>e.currentTarget);
|
||||||
const files = this.getFilesSelected(input.files);
|
const files = FileUtils.toFileArray(input.files);
|
||||||
this.onUploadFiles(files);
|
this.onUploadFiles(files.map(file => <FileInfo> {
|
||||||
|
entry: null,
|
||||||
|
file: file,
|
||||||
|
relativeFolder: '/'
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
73
ng2-components/ng2-alfresco-core/src/utils/file-utils.ts
Normal file
73
ng2-components/ng2-alfresco-core/src/utils/file-utils.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -16,3 +16,4 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './object-utils';
|
export * from './object-utils';
|
||||||
|
export * from './file-utils';
|
||||||
|
@@ -102,70 +102,16 @@ Follow the 3 steps below:
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
<alfresco-upload-button
|
<alfresco-upload-button
|
||||||
[showNotificationBar]="true"
|
[rootFolderId]="-my-"
|
||||||
[uploadFolders]="true"
|
[uploadFolders]="true"
|
||||||
[multipleFiles]="false"
|
[multipleFiles]="false"
|
||||||
[acceptedFilesType]=".jpg,.gif,.png,.svg"
|
[acceptedFilesType]=".jpg,.gif,.png,.svg"
|
||||||
[currentFolderPath]="/Sites/swsdp/documentLibrary"
|
|
||||||
[versioning]="false"
|
[versioning]="false"
|
||||||
(onSuccess)="customMethod($event)">
|
(onSuccess)="customMethod($event)">
|
||||||
</alfresco-upload-button>
|
</alfresco-upload-button>
|
||||||
<file-uploading-dialog></file-uploading-dialog>
|
<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
|
### Events
|
||||||
|
|
||||||
| Name | Description |
|
| Name | Description |
|
||||||
@@ -177,11 +123,12 @@ platformBrowserDynamic().bootstrapModule(AppModule);
|
|||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `disabled` | *boolean* | false | Toggle component disabled state |
|
| `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) |
|
| `uploadFolders` | *boolean* | false | Allow/disallow upload folders (only for chrome) |
|
||||||
| `multipleFiles` | *boolean* | false | Allow/disallow multiple files |
|
| `multipleFiles` | *boolean* | false | Allow/disallow multiple files |
|
||||||
| `acceptedFilesType` | *string* | * | array of allowed file extensions , example: ".jpg,.gif,.png,.svg" |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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"
|
[rootFolderId]="currentFolderId"
|
||||||
(permissionEvent)="onUploadPermissionFailed($event)">
|
(permissionEvent)="onUploadPermissionFailed($event)">
|
||||||
</alfresco-upload-button>
|
</alfresco-upload-button>
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
export class MyComponent {
|
export class MyComponent {
|
||||||
|
|
||||||
onUploadPermissionFailed(event: any) {
|
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.
|
This component, provide a drag and drop are to upload files to alfresco.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<alfresco-upload-drag-area
|
|
||||||
(onSuccess)="customMethod($event)">
|
|
||||||
</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)">
|
<alfresco-upload-drag-area (onSuccess)="customMethod($event)">
|
||||||
<div style="width: 200px; height: 100px; border: 1px solid #888888">
|
<div style="width: 200px; height: 100px; border: 1px solid #888888">
|
||||||
DRAG HERE
|
DRAG HERE
|
||||||
</div>
|
</div>
|
||||||
</alfresco-upload-drag-area>
|
</alfresco-upload-drag-area>
|
||||||
<file-uploading-dialog></file-uploading-dialog>
|
<file-uploading-dialog></file-uploading-dialog>
|
||||||
`
|
```
|
||||||
})
|
|
||||||
export class MyDemoApp {
|
|
||||||
|
|
||||||
constructor(private authService: AlfrescoAuthenticationService,
|
```ts
|
||||||
private settingsService: AlfrescoSettingsService) {
|
export class AppComponent {
|
||||||
settingsService.ecmHost = 'http://localhost:8080';
|
|
||||||
|
|
||||||
this.authService.login('admin', 'admin').subscribe(
|
|
||||||
ticket => console.log(ticket),
|
|
||||||
error => console.log(error)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public onSuccess(event: Object): void {
|
public onSuccess(event: Object): void {
|
||||||
console.log('File uploaded');
|
console.log('File uploaded');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
BrowserModule,
|
|
||||||
CoreModule.forRoot(),
|
|
||||||
UploadModule.forRoot()
|
|
||||||
],
|
|
||||||
declarations: [ MyDemoApp ],
|
|
||||||
bootstrap: [ MyDemoApp ]
|
|
||||||
})
|
|
||||||
export class AppModule { }
|
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Events
|
### Events
|
||||||
@@ -300,9 +210,9 @@ platformBrowserDynamic().bootstrapModule(AppModule);
|
|||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `enabled` | *boolean* | true | Toggle component enabled state |
|
| `enabled` | *boolean* | true | Toggle component enabled 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.** |
|
||||||
| `rootFolderId` | *string* | '-root-' | The ID of the root folder node.
|
| `rootFolderId` | *string* | '-root-' | The ID of the root folder node. |
|
||||||
| `currentFolderPath` | *string* | '/' | define the path where the files are uploaded |
|
| **(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 |
|
| `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
|
## FileUploadingDialogComponent
|
||||||
|
@@ -67,7 +67,6 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.uploadService.fileUpload.subscribe(e => {
|
this.uploadService.fileUpload.subscribe(e => {
|
||||||
console.log(e);
|
|
||||||
this.cd.detectChanges();
|
this.cd.detectChanges();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -186,7 +186,7 @@ describe('UploadButtonComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
component.onFilesAdded(fakeEvent);
|
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', () => {
|
it('should call uploadFile with a custom root folder', () => {
|
||||||
@@ -202,7 +202,7 @@ describe('UploadButtonComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
component.onFilesAdded(fakeEvent);
|
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) => {
|
it('should create a folder and emit an File uploaded event', (done) => {
|
||||||
@@ -228,21 +228,6 @@ describe('UploadButtonComponent', () => {
|
|||||||
component.onDirectoryAdded(fakeEvent);
|
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', () => {
|
it('should by default the title of the button get from the JSON file', () => {
|
||||||
let compiled = fixture.debugElement.nativeElement;
|
let compiled = fixture.debugElement.nativeElement;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@@ -17,14 +17,12 @@
|
|||||||
|
|
||||||
import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { Observable, Subject } from 'rxjs/Rx';
|
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 { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||||
import { UploadService } from '../services/upload.service';
|
import { UploadService } from '../services/upload.service';
|
||||||
import { FileModel } from '../models/file.model';
|
import { FileModel } from '../models/file.model';
|
||||||
import { PermissionModel } from '../models/permissions.model';
|
import { PermissionModel } from '../models/permissions.model';
|
||||||
|
|
||||||
const ERROR_FOLDER_ALREADY_EXIST = 409;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'alfresco-upload-button',
|
selector: 'alfresco-upload-button',
|
||||||
templateUrl: './upload-button.component.html',
|
templateUrl: './upload-button.component.html',
|
||||||
@@ -32,11 +30,15 @@ const ERROR_FOLDER_ALREADY_EXIST = 409;
|
|||||||
})
|
})
|
||||||
export class UploadButtonComponent implements OnInit, OnChanges {
|
export class UploadButtonComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
private static DEFAULT_ROOT_ID: string = '-root-';
|
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
disabled: boolean = false;
|
disabled: boolean = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof UploadButtonComponent
|
||||||
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
showNotificationBar: boolean = true;
|
showNotificationBar: boolean = true;
|
||||||
|
|
||||||
@@ -55,11 +57,17 @@ export class UploadButtonComponent implements OnInit, OnChanges {
|
|||||||
@Input()
|
@Input()
|
||||||
staticTitle: string;
|
staticTitle: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Deprecated in 1.6.0, this property is not used for couple of releases already.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof UploadDragAreaComponent
|
||||||
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
currentFolderPath: string = '/';
|
currentFolderPath: string = '/';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
rootFolderId: string = UploadButtonComponent.DEFAULT_ROOT_ID;
|
rootFolderId: string = '-root-';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
disableWithNoPermission: boolean = false;
|
disableWithNoPermission: boolean = false;
|
||||||
@@ -122,16 +130,11 @@ export class UploadButtonComponent implements OnInit, OnChanges {
|
|||||||
return !this.hasPermission && this.disableWithNoPermission ? true : undefined;
|
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 {
|
onFilesAdded($event: any): void {
|
||||||
let files: File[] = this.getFiles($event.currentTarget.files);
|
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
|
||||||
|
|
||||||
if (this.hasPermission) {
|
if (this.hasPermission) {
|
||||||
this.uploadFiles(this.currentFolderPath, files);
|
this.uploadFiles(files);
|
||||||
} else {
|
} else {
|
||||||
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
|
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
|
||||||
}
|
}
|
||||||
@@ -139,38 +142,10 @@ export class UploadButtonComponent implements OnInit, OnChanges {
|
|||||||
$event.target.value = '';
|
$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 {
|
onDirectoryAdded($event: any): void {
|
||||||
let files: File[] = this.getFiles($event.currentTarget.files);
|
|
||||||
if (this.hasPermission) {
|
if (this.hasPermission) {
|
||||||
let hashMapDir = this.convertIntoHashMap(files);
|
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
|
||||||
|
this.uploadFiles(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
|
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
|
* Upload a list of file in the specified path
|
||||||
* @param path
|
|
||||||
* @param files
|
* @param files
|
||||||
|
* @param path
|
||||||
*/
|
*/
|
||||||
uploadFiles(path: string, files: File[]): void {
|
uploadFiles(files: File[]): void {
|
||||||
if (files.length) {
|
if (files.length > 0) {
|
||||||
const latestFilesAdded = files.map(f => new FileModel(f, { newVersion: this.versioning }));
|
const latestFilesAdded = files.map(file => new FileModel(file, {
|
||||||
|
newVersion: this.versioning,
|
||||||
|
parentId: this.rootFolderId,
|
||||||
|
path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, '')
|
||||||
|
}));
|
||||||
this.uploadService.addToQueue(...latestFilesAdded);
|
this.uploadService.addToQueue(...latestFilesAdded);
|
||||||
this.uploadService.uploadFilesInTheQueue(this.rootFolderId, path, this.onSuccess);
|
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
|
||||||
if (this.showNotificationBar) {
|
if (this.showNotificationBar) {
|
||||||
this.showUndoNotificationBar(latestFilesAdded);
|
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.
|
* 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() {
|
checkPermission() {
|
||||||
if (this.rootFolderId) {
|
if (this.rootFolderId) {
|
||||||
this.getFolderNode(this.rootFolderId).subscribe(
|
this.getFolderNode(this.rootFolderId).subscribe(
|
||||||
@@ -313,6 +197,7 @@ export class UploadButtonComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to AlfrescoContentService
|
||||||
getFolderNode(nodeId: string): Observable<MinimalNodeEntryEntity> {
|
getFolderNode(nodeId: string): Observable<MinimalNodeEntryEntity> {
|
||||||
let opts: any = {
|
let opts: any = {
|
||||||
includeSource: true,
|
includeSource: true,
|
||||||
@@ -331,13 +216,9 @@ export class UploadButtonComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private hasCreatePermission(node: any): boolean {
|
private hasCreatePermission(node: any): boolean {
|
||||||
if (this.hasPermissions(node)) {
|
if (node && node.allowableOperations) {
|
||||||
return node.allowableOperations.find(permision => permision === 'create') ? true : false;
|
return node.allowableOperations.find(permision => permision === 'create') ? true : false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private hasPermissions(node: any): boolean {
|
|
||||||
return node && node.allowableOperations ? true : false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -67,21 +67,22 @@ describe('UploadDragAreaComponent', () => {
|
|||||||
TestBed.resetTestingModule();
|
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.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
|
||||||
component.onSuccess = null;
|
component.onSuccess = null;
|
||||||
component.showNotificationBar = false;
|
component.showNotificationBar = false;
|
||||||
uploadService.addToQueue = jasmine.createSpy('addToQueue');
|
|
||||||
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
|
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const file = <File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
|
const file = <File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
|
||||||
let fileFake = new FileModel(file);
|
let filesList = [file];
|
||||||
let filesList = [fileFake];
|
|
||||||
|
spyOn(uploadService, 'addToQueue').and.callFake((f: FileModel) => {
|
||||||
|
expect(f.file).toBe(file);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
component.onFilesDropped(filesList);
|
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', () => {
|
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');
|
component.showUndoNotificationBar = jasmine.createSpy('_showUndoNotificationBar');
|
||||||
|
|
||||||
fixture.detectChanges();
|
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];
|
let filesList = [fileFake];
|
||||||
|
|
||||||
component.onFilesDropped(filesList);
|
component.onFilesDropped(filesList);
|
||||||
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/folder-fake', null);
|
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
|
||||||
expect(component.showUndoNotificationBar).toHaveBeenCalled();
|
expect(component.showUndoNotificationBar).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -119,8 +120,7 @@ describe('UploadDragAreaComponent', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
component.onFilesEntityDropped(itemEntity);
|
component.onFilesEntityDropped(itemEntity);
|
||||||
expect(uploadService.uploadFilesInTheQueue)
|
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
|
||||||
.toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/document-library-fake/folder-fake/', null);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should upload a file with a custom root folder ID when dropped', () => {
|
it('should upload a file with a custom root folder ID when dropped', () => {
|
||||||
@@ -143,7 +143,6 @@ describe('UploadDragAreaComponent', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
component.onFilesEntityDropped(itemEntity);
|
component.onFilesEntityDropped(itemEntity);
|
||||||
expect(uploadService.uploadFilesInTheQueue)
|
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
|
||||||
.toHaveBeenCalledWith('-my-', '/root-fake-/sites-fake/document-library-fake/folder-fake/', null);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -16,12 +16,10 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
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 { UploadService } from '../services/upload.service';
|
||||||
import { FileModel } from '../models/file.model';
|
import { FileModel } from '../models/file.model';
|
||||||
|
|
||||||
const ERROR_FOLDER_ALREADY_EXIST = 409;
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'alfresco-upload-drag-area',
|
selector: 'alfresco-upload-drag-area',
|
||||||
templateUrl: './upload-drag-area.component.html',
|
templateUrl: './upload-drag-area.component.html',
|
||||||
@@ -29,31 +27,39 @@ const ERROR_FOLDER_ALREADY_EXIST = 409;
|
|||||||
})
|
})
|
||||||
export class UploadDragAreaComponent {
|
export class UploadDragAreaComponent {
|
||||||
|
|
||||||
private static DEFAULT_ROOT_ID: string = '-root-';
|
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof UploadButtonComponent
|
||||||
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
showNotificationBar: boolean = true;
|
showNotificationBar: boolean = true;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
versioning: boolean = false;
|
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()
|
@Input()
|
||||||
currentFolderPath: string = '/';
|
currentFolderPath: string = '/';
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
rootFolderId: string = UploadDragAreaComponent.DEFAULT_ROOT_ID;
|
rootFolderId: string = '-root-';
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
onSuccess = new EventEmitter();
|
onSuccess = new EventEmitter();
|
||||||
|
|
||||||
constructor(private uploadService: UploadService,
|
constructor(private uploadService: UploadService,
|
||||||
private translateService: AlfrescoTranslationService,
|
private translateService: AlfrescoTranslationService,
|
||||||
private logService: LogService,
|
private notificationService: NotificationService) {
|
||||||
private notificationService: NotificationService,
|
|
||||||
private contentService: AlfrescoContentService) {
|
|
||||||
if (translateService) {
|
if (translateService) {
|
||||||
translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload');
|
translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload');
|
||||||
}
|
}
|
||||||
@@ -67,15 +73,18 @@ export class UploadDragAreaComponent {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
let files: File[] = e.detail.files;
|
let files: FileInfo[] = e.detail.files;
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
const fileModels = files.map(f => new FileModel(f, { newVersion: this.versioning }));
|
let parentId = this.rootFolderId;
|
||||||
if (e.detail.data.obj.entry.isFolder) {
|
if (e.detail.data && e.detail.data.obj.entry.isFolder) {
|
||||||
let id = e.detail.data.obj.entry.id;
|
parentId = e.detail.data.obj.entry.id || this.rootFolderId;
|
||||||
this.onFilesDropped(fileModels, id, '/');
|
|
||||||
} else {
|
|
||||||
this.onFilesDropped(fileModels);
|
|
||||||
}
|
}
|
||||||
|
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.
|
* @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) {
|
if (this.enabled && files.length) {
|
||||||
this.uploadService.addToQueue(...files);
|
const fileModels = files.map(file => new FileModel(file, {
|
||||||
this.uploadService.uploadFilesInTheQueue(rootId || this.rootFolderId, directory || this.currentFolderPath, this.onSuccess);
|
newVersion: this.versioning,
|
||||||
|
path: '/',
|
||||||
|
parentId: this.rootFolderId
|
||||||
|
}));
|
||||||
|
this.uploadService.addToQueue(...fileModels);
|
||||||
|
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
|
||||||
let latestFilesAdded = this.uploadService.getQueue();
|
let latestFilesAdded = this.uploadService.getQueue();
|
||||||
if (this.showNotificationBar) {
|
if (this.showNotificationBar) {
|
||||||
this.showUndoNotificationBar(latestFilesAdded);
|
this.showUndoNotificationBar(latestFilesAdded);
|
||||||
@@ -103,11 +117,13 @@ export class UploadDragAreaComponent {
|
|||||||
onFilesEntityDropped(item: any): void {
|
onFilesEntityDropped(item: any): void {
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
item.file((file: File) => {
|
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);
|
this.uploadService.addToQueue(fileModel);
|
||||||
let path = item.fullPath.replace(item.name, '');
|
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
|
||||||
let filePath = this.currentFolderPath + path;
|
|
||||||
this.uploadService.uploadFilesInTheQueue(this.rootFolderId, filePath, this.onSuccess);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,52 +134,22 @@ export class UploadDragAreaComponent {
|
|||||||
*/
|
*/
|
||||||
onFolderEntityDropped(folder: any): void {
|
onFolderEntityDropped(folder: any): void {
|
||||||
if (this.enabled && folder.isDirectory) {
|
if (this.enabled && folder.isDirectory) {
|
||||||
let relativePath = folder.fullPath.replace(folder.name, '');
|
FileUtils.flattern(folder).then(entries => {
|
||||||
relativePath = this.currentFolderPath + relativePath;
|
let files = entries.map(entry => {
|
||||||
|
return new FileModel(entry.file, {
|
||||||
this.contentService.createFolder(relativePath, folder.name, this.rootFolderId)
|
newVersion: this.versioning,
|
||||||
.subscribe(
|
parentId: this.rootFolderId,
|
||||||
message => {
|
path: entry.relativeFolder
|
||||||
this.onSuccess.emit({
|
|
||||||
value: 'Created folder'
|
|
||||||
});
|
});
|
||||||
let dirReader = folder.createReader();
|
});
|
||||||
dirReader.readEntries((entries: any) => {
|
this.uploadService.addToQueue(...files);
|
||||||
for (let i = 0; i < entries.length; i++) {
|
/* @deprecated in 1.6.0 */
|
||||||
this._traverseFileTree(entries[i]);
|
|
||||||
}
|
|
||||||
if (this.showNotificationBar) {
|
if (this.showNotificationBar) {
|
||||||
let latestFilesAdded = this.uploadService.getQueue();
|
let latestFilesAdded = this.uploadService.getQueue();
|
||||||
this.showUndoNotificationBar(latestFilesAdded);
|
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 {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private uploadFiles(files: FileModel[]): void {
|
||||||
* Show the error inside Notification bar
|
if (this.enabled && files.length) {
|
||||||
* @param Error message
|
this.uploadService.addToQueue(...files);
|
||||||
* @private
|
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
|
||||||
*/
|
let latestFilesAdded = this.uploadService.getQueue();
|
||||||
showErrorNotificationBar(errorMessage: string) {
|
if (this.showNotificationBar) {
|
||||||
this.notificationService.openSnackMessage(errorMessage, 3000);
|
this.showUndoNotificationBar(latestFilesAdded);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Directive, EventEmitter, Input, Output, OnInit, OnDestroy, ElementRef, NgZone } from '@angular/core';
|
import { Directive, EventEmitter, Input, Output, OnInit, OnDestroy, ElementRef, NgZone } from '@angular/core';
|
||||||
|
import { FileUtils } from 'ng2-alfresco-core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[file-draggable]'
|
selector: '[file-draggable]'
|
||||||
@@ -28,7 +29,7 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
|
|||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
onFilesDropped: EventEmitter<any> = new EventEmitter();
|
onFilesDropped: EventEmitter<File[]> = new EventEmitter<File[]>();
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
onFilesEntityDropped: EventEmitter<any> = new EventEmitter();
|
onFilesEntityDropped: EventEmitter<any> = new EventEmitter();
|
||||||
@@ -73,16 +74,20 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
|
|||||||
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
|
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
|
||||||
let item = items[i].webkitGetAsEntry();
|
let item = items[i].webkitGetAsEntry();
|
||||||
if (item) {
|
if (item) {
|
||||||
this.traverseFileTree(item);
|
if (item.isFile) {
|
||||||
|
this.onFilesEntityDropped.emit(item);
|
||||||
|
} else if (item.isDirectory) {
|
||||||
|
this.onFolderEntityDropped.emit(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let files = event.dataTransfer.files;
|
let files = FileUtils.toFileArray(event.dataTransfer.files);
|
||||||
this.onFilesDropped.emit(files);
|
this.onFilesDropped.emit(files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// safari or FF
|
// safari or FF
|
||||||
let files = event.dataTransfer.files;
|
let files = FileUtils.toFileArray(event.dataTransfer.files);
|
||||||
this.onFilesDropped.emit(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.
|
* Change the style of the drag area when a file drag in.
|
||||||
*
|
*
|
||||||
|
@@ -23,6 +23,8 @@ export interface FileUploadProgress {
|
|||||||
|
|
||||||
export interface FileUploadOptions {
|
export interface FileUploadOptions {
|
||||||
newVersion?: boolean;
|
newVersion?: boolean;
|
||||||
|
parentId?: string;
|
||||||
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum FileUploadStatus {
|
export enum FileUploadStatus {
|
||||||
|
@@ -19,7 +19,7 @@ import { EventEmitter } from '@angular/core';
|
|||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
import { CoreModule } from 'ng2-alfresco-core';
|
import { CoreModule } from 'ng2-alfresco-core';
|
||||||
import { UploadService } from './upload.service';
|
import { UploadService } from './upload.service';
|
||||||
import { FileModel } from '../models/file.model';
|
import { FileModel, FileUploadOptions } from '../models/file.model';
|
||||||
|
|
||||||
declare let jasmine: any;
|
declare let jasmine: any;
|
||||||
|
|
||||||
@@ -77,9 +77,12 @@ describe('UploadService', () => {
|
|||||||
expect(e.value).toBe('File uploaded');
|
expect(e.value).toBe('File uploaded');
|
||||||
done();
|
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.addToQueue(fileFake);
|
||||||
service.uploadFilesInTheQueue('-root-', 'fake-dir', emitter);
|
service.uploadFilesInTheQueue(emitter);
|
||||||
|
|
||||||
let request = jasmine.Ajax.requests.mostRecent();
|
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');
|
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');
|
expect(e.value).toBe('Error file uploaded');
|
||||||
done();
|
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.addToQueue(fileFake);
|
||||||
service.uploadFilesInTheQueue('-root-', '', emitter);
|
service.uploadFilesInTheQueue(emitter);
|
||||||
expect(jasmine.Ajax.requests.mostRecent().url)
|
expect(jasmine.Ajax.requests.mostRecent().url)
|
||||||
.toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true');
|
.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});
|
let fileFake = new FileModel(<File>{name: 'fake-name', size: 10});
|
||||||
service.addToQueue(fileFake);
|
service.addToQueue(fileFake);
|
||||||
service.uploadFilesInTheQueue('-root-', '', emitter);
|
service.uploadFilesInTheQueue(emitter);
|
||||||
|
|
||||||
let file = service.getQueue();
|
let file = service.getQueue();
|
||||||
service.cancelUpload(...file);
|
service.cancelUpload(...file);
|
||||||
@@ -132,7 +138,7 @@ describe('UploadService', () => {
|
|||||||
|
|
||||||
const filesFake = new FileModel(<File>{name: 'fake-name', size: 10}, { newVersion: true });
|
const filesFake = new FileModel(<File>{name: 'fake-name', size: 10}, { newVersion: true });
|
||||||
service.addToQueue(filesFake);
|
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().url.endsWith('autoRename=true')).toBe(false);
|
||||||
expect(jasmine.Ajax.requests.mostRecent().params.has('majorVersion')).toBe(true);
|
expect(jasmine.Ajax.requests.mostRecent().params.has('majorVersion')).toBe(true);
|
||||||
@@ -145,9 +151,12 @@ describe('UploadService', () => {
|
|||||||
expect(e.value).toBe('File uploaded');
|
expect(e.value).toBe('File uploaded');
|
||||||
done();
|
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.addToQueue(filesFake);
|
||||||
service.uploadFilesInTheQueue('123', 'fake-dir', emitter);
|
service.uploadFilesInTheQueue(emitter);
|
||||||
|
|
||||||
let request = jasmine.Ajax.requests.mostRecent();
|
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');
|
expect(request.url).toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/123/children?autoRename=true');
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import { EventEmitter, Injectable } from '@angular/core';
|
import { EventEmitter, Injectable } from '@angular/core';
|
||||||
import { Subject } from 'rxjs/Rx';
|
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 { FileUploadEvent, FileUploadCompleteEvent } from '../events/file.event';
|
||||||
import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model';
|
import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model';
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@ export class UploadService {
|
|||||||
private queue: FileModel[] = [];
|
private queue: FileModel[] = [];
|
||||||
private cache: { [key: string]: any } = {};
|
private cache: { [key: string]: any } = {};
|
||||||
private totalComplete: number = 0;
|
private totalComplete: number = 0;
|
||||||
|
private activeTask: Promise<any> = null;
|
||||||
|
|
||||||
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
|
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
|
||||||
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||||
@@ -37,8 +38,18 @@ export class UploadService {
|
|||||||
fileUploadError: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
fileUploadError: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
|
||||||
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>();
|
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>();
|
||||||
|
|
||||||
constructor(private apiService: AlfrescoApiService,
|
constructor(private apiService: AlfrescoApiService) {
|
||||||
private logService: LogService) {
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* 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 {
|
uploadFilesInTheQueue(emitter: EventEmitter<any>): void {
|
||||||
const files = this.getFilesToUpload();
|
if (!this.activeTask) {
|
||||||
|
let file = this.queue.find(f => f.status === FileUploadStatus.Pending);
|
||||||
files.forEach((file: FileModel) => {
|
if (file) {
|
||||||
this.onUploadStarting(file);
|
this.onUploadStarting(file);
|
||||||
|
|
||||||
const opts: any = {
|
const promise = this.beginUpload(file, emitter);
|
||||||
renditions: 'doclib'
|
this.activeTask = promise;
|
||||||
|
this.cache[file.id] = promise;
|
||||||
|
|
||||||
|
let next = () => {
|
||||||
|
this.activeTask = null;
|
||||||
|
setTimeout(() => this.uploadFilesInTheQueue(emitter), 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (file.options.newVersion === true) {
|
promise.then(
|
||||||
opts.overwrite = true;
|
() => next(),
|
||||||
opts.majorVersion = true;
|
() => next()
|
||||||
} else {
|
);
|
||||||
opts.autoRename = true;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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[]) {
|
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 {
|
private onUploadStarting(file: FileModel): void {
|
||||||
if (file) {
|
if (file) {
|
||||||
file.status = FileUploadStatus.Starting;
|
file.status = FileUploadStatus.Starting;
|
||||||
@@ -200,11 +231,4 @@ export class UploadService {
|
|||||||
this.fileUploadAborted.next(event);
|
this.fileUploadAborted.next(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFilesToUpload(): FileModel[] {
|
|
||||||
let filesToUpload = this.queue.filter(file => {
|
|
||||||
return file.status === FileUploadStatus.Pending;
|
|
||||||
});
|
|
||||||
return filesToUpload;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user