mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
@@ -0,0 +1,125 @@
|
||||
<div *ngIf="isDialogActive"
|
||||
class="upload-dialog"
|
||||
[class.upload-dialog--minimized]="isDialogMinimized"
|
||||
[class.upload-dialog--position-left]="position === 'left'"
|
||||
[class.upload-dialog--position-right]="position === 'right'">
|
||||
<header class="upload-dialog__header">
|
||||
<button
|
||||
mat-button
|
||||
color="secondary"
|
||||
[disabled]="isConfirmation"
|
||||
(click)="toggleMinimized()">
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
title="{{ (isDialogMinimized ? 'ADF_FILE_UPLOAD.BUTTON.MAXIMIZE': 'ADF_FILE_UPLOAD.BUTTON.MINIMIZE') | translate }}">
|
||||
{{ isDialogMinimized ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
|
||||
<span
|
||||
class="upload-dialog__title"
|
||||
*ngIf="!uploadList.isUploadCompleted() && !uploadList.isUploadCancelled()">
|
||||
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_PROGRESS'
|
||||
| translate: {
|
||||
completed: totalCompleted,
|
||||
total: filesUploadingList.length
|
||||
}
|
||||
}}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="upload-dialog__title"
|
||||
*ngIf="uploadList.isUploadCompleted()">
|
||||
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_COMPLETED'
|
||||
| translate: {
|
||||
completed: totalCompleted,
|
||||
total: filesUploadingList.length
|
||||
}
|
||||
}}
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="upload-dialog__title"
|
||||
*ngIf="uploadList.isUploadCancelled()">
|
||||
{{ 'FILE_UPLOAD.MESSAGES.UPLOAD_CANCELED' | translate }}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<section
|
||||
class="upload-dialog__info"
|
||||
*ngIf="totalErrors">
|
||||
{{
|
||||
(totalErrors > 1
|
||||
? 'FILE_UPLOAD.MESSAGES.UPLOAD_ERRORS'
|
||||
: 'FILE_UPLOAD.MESSAGES.UPLOAD_ERROR')
|
||||
| translate: { total: totalErrors }
|
||||
}}
|
||||
</section>
|
||||
|
||||
<section
|
||||
class="upload-dialog__content"
|
||||
[class.upload-dialog--padding]="isConfirmation">
|
||||
<adf-file-uploading-list
|
||||
[class.upload-dialog--hide]="isConfirmation"
|
||||
#uploadList
|
||||
[files]="filesUploadingList">
|
||||
<ng-template let-file="$implicit">
|
||||
<adf-file-uploading-list-row
|
||||
[file]="file"
|
||||
(remove)="uploadList.removeFile(file)"
|
||||
(cancel)="uploadList.cancelFile(file)">
|
||||
</adf-file-uploading-list-row>
|
||||
</ng-template>
|
||||
</adf-file-uploading-list>
|
||||
|
||||
<div
|
||||
class="upload-dialog__confirmation"
|
||||
[class.upload-dialog--hide]="!isConfirmation">
|
||||
<p class="upload-dialog__confirmation--title">
|
||||
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TITLE' | translate }}
|
||||
</p>
|
||||
|
||||
<p class="upload-dialog__confirmation--text">
|
||||
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.MESSAGE.TEXT' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer
|
||||
class="upload-dialog__actions"
|
||||
[class.upload-dialog--hide]="isConfirmation">
|
||||
<button
|
||||
color="primary"
|
||||
mat-button
|
||||
*ngIf="!uploadList.isUploadCompleted() && !uploadList.isUploadCancelled()"
|
||||
(click)="toggleConfirmation()">
|
||||
{{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_ALL' | translate }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="uploadList.isUploadCompleted() || uploadList.isUploadCancelled()"
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="close($event)">
|
||||
{{ 'ADF_FILE_UPLOAD.BUTTON.CLOSE' | translate }}
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
<footer
|
||||
class="upload-dialog__actions"
|
||||
[class.upload-dialog--hide]="!isConfirmation">
|
||||
<button
|
||||
color="secondary"
|
||||
mat-button
|
||||
(click)="cancelAllUploads()">
|
||||
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CANCEL' | translate }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="toggleConfirmation()">
|
||||
{{ 'ADF_FILE_UPLOAD.CONFIRMATION.BUTTON.CONTINUE' | translate }}
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
@@ -0,0 +1,95 @@
|
||||
|
||||
@mixin adf-upload-dialog-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.upload-dialog {
|
||||
background: mat-color($background, dialog);
|
||||
color: mat-color($foreground, text, 0.54);
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
width: 40%;
|
||||
box-shadow: 1px 5px 15px #888888;
|
||||
|
||||
&--padding {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
&--hide {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
&--position-left {
|
||||
left: 25px;
|
||||
}
|
||||
|
||||
&--position-right {
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
&--minimized {
|
||||
width: 20%;
|
||||
|
||||
.upload-dialog__content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-left: 0.5em;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__info {
|
||||
padding: 0 1em 1em 1em;
|
||||
}
|
||||
|
||||
&__content {
|
||||
overflow: auto;
|
||||
max-height: 194px;
|
||||
border-top: 1px solid mat-color($foreground, text, 0.14);
|
||||
border-bottom: 1px solid mat-color($foreground, text, 0.14);
|
||||
}
|
||||
|
||||
&__confirmation {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
&__confirmation--title {
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
letter-spacing: -0.4px;
|
||||
color: $black-87-opacity;
|
||||
}
|
||||
|
||||
&__confirmation--text {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 1em;
|
||||
|
||||
& > button {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
& mat-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,212 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FileModel, FileUploadCompleteEvent, FileUploadErrorEvent, UploadService } from '@alfresco/core';
|
||||
import { UploadModule } from '../upload.module';
|
||||
import { FileUploadingDialogComponent } from './file-uploading-dialog.component';
|
||||
|
||||
describe('FileUploadingDialogComponent', () => {
|
||||
let fixture: ComponentFixture<FileUploadingDialogComponent>;
|
||||
let uploadService: UploadService;
|
||||
let component: FileUploadingDialogComponent;
|
||||
let emitter: EventEmitter<any>;
|
||||
let filelist: FileModel[];
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
UploadModule
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileUploadingDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
uploadService = TestBed.get(UploadService);
|
||||
emitter = new EventEmitter();
|
||||
filelist = [
|
||||
new FileModel(<File> { name: 'fake-name', size: 10 }),
|
||||
new FileModel(<File> { name: 'fake-name2', size: 10 })
|
||||
];
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('upload service subscribers', () => {
|
||||
it('should not render dialog when uploading list is empty', () => {
|
||||
uploadService.addToQueue();
|
||||
uploadService.uploadFilesInTheQueue(emitter);
|
||||
|
||||
expect(component.isDialogActive).toBe(false);
|
||||
});
|
||||
|
||||
it('should open dialog when uploading list is not empty', () => {
|
||||
uploadService.addToQueue(...filelist);
|
||||
uploadService.uploadFilesInTheQueue(emitter);
|
||||
|
||||
expect(component.isDialogActive).toBe(true);
|
||||
});
|
||||
|
||||
it('should update uploading file list', () => {
|
||||
uploadService.addToQueue(...filelist);
|
||||
uploadService.uploadFilesInTheQueue(emitter);
|
||||
|
||||
expect(component.filesUploadingList.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should update completed uploaded files count', () => {
|
||||
const completedFiles = 2;
|
||||
const completeEvent = new FileUploadCompleteEvent(null, completedFiles, null, null);
|
||||
uploadService.fileUploadComplete.next(completeEvent);
|
||||
|
||||
expect(component.totalCompleted).toEqual(completedFiles);
|
||||
});
|
||||
|
||||
it('should update error files count', () => {
|
||||
const totalErrors = 2;
|
||||
const errorEvent = new FileUploadErrorEvent(null, null, totalErrors);
|
||||
uploadService.fileUploadError.next(errorEvent);
|
||||
|
||||
expect(component.totalErrors).toEqual(totalErrors);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleConfirmation()', () => {
|
||||
it('should change state to true when false', () => {
|
||||
component.isConfirmation = false;
|
||||
|
||||
component.toggleConfirmation();
|
||||
|
||||
expect(component.isConfirmation).toBe(true);
|
||||
});
|
||||
|
||||
it('should change state to false when true', () => {
|
||||
component.isConfirmation = true;
|
||||
|
||||
component.toggleConfirmation();
|
||||
|
||||
expect(component.isConfirmation).toBe(false);
|
||||
});
|
||||
|
||||
it('should change dialog minimize state to false', () => {
|
||||
component.isDialogMinimized = true;
|
||||
|
||||
component.toggleConfirmation();
|
||||
|
||||
expect(component.isDialogMinimized).toBe(false);
|
||||
});
|
||||
|
||||
it('should not change dialog minimize state', () => {
|
||||
component.isDialogMinimized = false;
|
||||
|
||||
component.toggleConfirmation();
|
||||
|
||||
expect(component.isDialogMinimized).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelAllUploads()', () => {
|
||||
beforeEach(() => {
|
||||
(<any> component).uploadList = {
|
||||
cancelAllFiles: jasmine.createSpy('cancelAllFiles')
|
||||
};
|
||||
});
|
||||
|
||||
it('should toggle confirmation dialog', () => {
|
||||
spyOn(component, 'toggleConfirmation');
|
||||
|
||||
component.cancelAllUploads();
|
||||
|
||||
expect(component.toggleConfirmation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call upload list cancel method', () => {
|
||||
component.cancelAllUploads();
|
||||
|
||||
expect(component.uploadList.cancelAllFiles).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('toggleMinimized()', () => {
|
||||
it('should minimze the dialog', () => {
|
||||
component.isDialogMinimized = true;
|
||||
component.toggleMinimized();
|
||||
|
||||
expect(component.isDialogMinimized).toBe(false);
|
||||
});
|
||||
|
||||
it('should maximize the dialog', () => {
|
||||
component.isDialogMinimized = false;
|
||||
component.toggleMinimized();
|
||||
|
||||
expect(component.isDialogMinimized).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('close()', () => {
|
||||
it('should reset confirmation state', () => {
|
||||
component.isConfirmation = true;
|
||||
component.close();
|
||||
|
||||
expect(component.isConfirmation).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset total files count', () => {
|
||||
component.totalCompleted = 1;
|
||||
component.close();
|
||||
|
||||
expect(component.totalCompleted).toBe(0);
|
||||
});
|
||||
|
||||
it('should reset total errors count', () => {
|
||||
component.totalErrors = 1;
|
||||
component.close();
|
||||
|
||||
expect(component.totalErrors).toBe(0);
|
||||
});
|
||||
|
||||
it('should closes the dialog', () => {
|
||||
component.isDialogActive = true;
|
||||
component.close();
|
||||
|
||||
expect(component.isDialogActive).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset dialog minimize state', () => {
|
||||
component.isDialogMinimized = true;
|
||||
component.close();
|
||||
|
||||
expect(component.isDialogMinimized).toBe(false);
|
||||
});
|
||||
|
||||
it('should reset upload queue', () => {
|
||||
uploadService.addToQueue(...filelist);
|
||||
uploadService.uploadFilesInTheQueue(emitter);
|
||||
component.close();
|
||||
|
||||
expect(uploadService.getQueue().length).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,145 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FileModel, FileUploadCompleteEvent, FileUploadDeleteEvent,
|
||||
FileUploadErrorEvent, FileUploadStatus, UploadService } from '@alfresco/core';
|
||||
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { Observable, Subscription } from 'rxjs/Rx';
|
||||
import { FileUploadingListComponent } from './file-uploading-list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-file-uploading-dialog, file-uploading-dialog',
|
||||
templateUrl: './file-uploading-dialog.component.html',
|
||||
styleUrls: ['./file-uploading-dialog.component.scss']
|
||||
})
|
||||
export class FileUploadingDialogComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(FileUploadingListComponent)
|
||||
uploadList: FileUploadingListComponent;
|
||||
|
||||
@Input()
|
||||
position: string = 'right';
|
||||
|
||||
filesUploadingList: FileModel[] = [];
|
||||
isDialogActive: boolean = false;
|
||||
totalCompleted: number = 0;
|
||||
totalErrors: number = 0;
|
||||
isDialogMinimized: boolean = false;
|
||||
isConfirmation: boolean = false;
|
||||
|
||||
private listSubscription: Subscription;
|
||||
private counterSubscription: Subscription;
|
||||
private fileUploadSubscription: Subscription;
|
||||
private errorSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private uploadService: UploadService,
|
||||
private changeDetecor: ChangeDetectorRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.listSubscription = this.uploadService
|
||||
.queueChanged.subscribe((fileList: FileModel[]) => {
|
||||
this.filesUploadingList = fileList;
|
||||
|
||||
if (this.filesUploadingList.length) {
|
||||
this.isDialogActive = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.counterSubscription = Observable
|
||||
.merge(
|
||||
this.uploadService.fileUploadComplete,
|
||||
this.uploadService.fileUploadDeleted
|
||||
)
|
||||
.subscribe((event: (FileUploadDeleteEvent|FileUploadCompleteEvent)) => {
|
||||
this.totalCompleted = event.totalComplete;
|
||||
this.changeDetecor.detectChanges();
|
||||
});
|
||||
|
||||
this.errorSubscription = this.uploadService.fileUploadError
|
||||
.subscribe((event: FileUploadErrorEvent) => {
|
||||
this.totalErrors = event.totalError;
|
||||
this.changeDetecor.detectChanges();
|
||||
});
|
||||
|
||||
this.fileUploadSubscription = this.uploadService
|
||||
.fileUpload.subscribe(() => {
|
||||
this.changeDetecor.detectChanges();
|
||||
});
|
||||
|
||||
this.uploadService.fileDeleted.subscribe((objId) => {
|
||||
if (this.filesUploadingList) {
|
||||
let file = this.filesUploadingList.find((item) => {
|
||||
return item.data.entry.id === objId;
|
||||
});
|
||||
if (file) {
|
||||
file.status = FileUploadStatus.Cancelled;
|
||||
this.changeDetecor.detectChanges();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle confirmation message.
|
||||
*/
|
||||
toggleConfirmation() {
|
||||
this.isConfirmation = !this.isConfirmation;
|
||||
|
||||
if (this.isDialogMinimized) {
|
||||
this.isDialogMinimized = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel uploads and hide confiramtion
|
||||
*/
|
||||
cancelAllUploads() {
|
||||
this.toggleConfirmation();
|
||||
|
||||
this.uploadList.cancelAllFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle dialog minimized state.
|
||||
*/
|
||||
toggleMinimized(): void {
|
||||
this.isDialogMinimized = !this.isDialogMinimized;
|
||||
this.changeDetecor.detectChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss dialog
|
||||
*/
|
||||
close(): void {
|
||||
this.isConfirmation = false;
|
||||
this.totalCompleted = 0;
|
||||
this.totalErrors = 0;
|
||||
this.filesUploadingList = [];
|
||||
this.isDialogActive = false;
|
||||
this.isDialogMinimized = false;
|
||||
this.uploadService.clearQueue();
|
||||
this.changeDetecor.detectChanges();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.uploadService.clearQueue();
|
||||
this.listSubscription.unsubscribe();
|
||||
this.counterSubscription.unsubscribe();
|
||||
this.fileUploadSubscription.unsubscribe();
|
||||
this.errorSubscription.unsubscribe();
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
<div class="adf-file-uploading-row">
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
class="adf-file-uploading-row__type">
|
||||
insert_drive_file
|
||||
</mat-icon>
|
||||
|
||||
<span
|
||||
class="adf-file-uploading-row__name"
|
||||
title="{{ file.name }}">
|
||||
{{ file.name }}
|
||||
</span>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Progress || file.status === FileUploadStatus.Starting"
|
||||
(click)="onCancel(file)"
|
||||
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle"
|
||||
title="{{ 'ADF_FILE_UPLOAD.BUTTON.CANCEL_FILE' | translate }}">
|
||||
<span class="adf-file-uploading-row__status">
|
||||
{{ file.progress.loaded | adfFileSize }} / {{ file.progress.total | adfFileSize }}
|
||||
</span>
|
||||
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
class="adf-file-uploading-row__action adf-file-uploading-row__action--cancel">
|
||||
clear
|
||||
</mat-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Complete"
|
||||
(click)="onRemove(file)"
|
||||
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle"
|
||||
title="{{ 'ADF_FILE_UPLOAD.BUTTON.REMOVE_FILE' | translate }}">
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
class="adf-file-uploading-row__status adf-file-uploading-row__status--done">
|
||||
check_circle
|
||||
</mat-icon>
|
||||
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
class="adf-file-uploading-row__action adf-file-uploading-row__action--remove">
|
||||
remove_circle
|
||||
</mat-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Pending"
|
||||
(click)="onCancel(file)"
|
||||
class="adf-file-uploading-row__group adf-file-uploading-row__group--toggle">
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
class="adf-file-uploading-row__status adf-file-uploading-row__status--pending">
|
||||
schedule
|
||||
</mat-icon>
|
||||
|
||||
<mat-icon
|
||||
mat-list-icon
|
||||
class="adf-file-uploading-row__action adf-file-uploading-row__action--remove">
|
||||
remove_circle
|
||||
</mat-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Error"
|
||||
class="adf-file-uploading-row__block adf-file-uploading-row__status--error"
|
||||
title="{{ file.response }}">
|
||||
<mat-icon mat-list-icon>
|
||||
report_problem
|
||||
</mat-icon>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="file.status === FileUploadStatus.Cancelled ||
|
||||
file.status === FileUploadStatus.Aborted ||
|
||||
file.status === FileUploadStatus.Deleted"
|
||||
class="adf-file-uploading-row__block adf-file-uploading-row__status--cancelled">
|
||||
{{ 'ADF_FILE_UPLOAD.STATUS.FILE_CANCELED_STATUS' | translate }}
|
||||
</div>
|
||||
<div>
|
@@ -0,0 +1,78 @@
|
||||
|
||||
@mixin adf-file-uploading-row-theme($theme) {
|
||||
$background: map-get($theme, background);
|
||||
$foreground: map-get($theme, foreground);
|
||||
$primary: map-get($theme, primary);
|
||||
$accent: map-get($theme, accent);
|
||||
$warn: map-get($theme, warn);
|
||||
|
||||
$file-uploading-row-hover-color: #eeeeee !default;
|
||||
|
||||
adf-file-uploading-list-row:not(:first-child) {
|
||||
display: block;
|
||||
border-top: 1px solid mat-color($foreground, text, 0.14);
|
||||
}
|
||||
|
||||
.adf-file-uploading-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background: $file-uploading-row-hover-color;
|
||||
}
|
||||
|
||||
&__name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex: 1 1 auto;
|
||||
padding: 0 1em 0 0.5em;
|
||||
}
|
||||
|
||||
&__group, &__block {
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&__group--toggle {
|
||||
cursor: pointer;
|
||||
|
||||
.adf-file-uploading-row__status {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.adf-file-uploading-row__action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.adf-file-uploading-row__status {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.adf-file-uploading-row__action {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__status--done {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
|
||||
&__status--error {
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
&__action--cancel {
|
||||
color: mat-color($warn);
|
||||
}
|
||||
|
||||
&__action--remove {
|
||||
color: mat-color($accent);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FileModel } from '@alfresco/core';
|
||||
import { UploadModule } from '../upload.module';
|
||||
import { FileUploadingListRowComponent } from './file-uploading-list-row.component';
|
||||
|
||||
describe('FileUploadingListRowComponent', () => {
|
||||
let fixture: ComponentFixture<FileUploadingListRowComponent>;
|
||||
let component: FileUploadingListRowComponent;
|
||||
let file = new FileModel(<File> { name: 'fake-name' });
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
UploadModule
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileUploadingListRowComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.file = file;
|
||||
});
|
||||
|
||||
it('emits cancel event', () => {
|
||||
spyOn(component.cancel, 'emit');
|
||||
component.onCancel(component.file);
|
||||
|
||||
expect(component.cancel.emit).toHaveBeenCalledWith(file);
|
||||
});
|
||||
|
||||
it('emits remove event', () => {
|
||||
spyOn(component.remove, 'emit');
|
||||
component.onRemove(component.file);
|
||||
|
||||
expect(component.remove.emit).toHaveBeenCalledWith(file);
|
||||
});
|
||||
});
|
@@ -0,0 +1,45 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FileModel, FileUploadStatus } from '@alfresco/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-file-uploading-list-row',
|
||||
templateUrl: './file-uploading-list-row.component.html',
|
||||
styleUrls: [ './file-uploading-list-row.component.scss' ]
|
||||
})
|
||||
export class FileUploadingListRowComponent {
|
||||
@Input()
|
||||
file: FileModel;
|
||||
|
||||
@Output()
|
||||
cancel: EventEmitter<FileModel> = new EventEmitter<FileModel>();
|
||||
|
||||
@Output()
|
||||
remove: EventEmitter<FileModel> = new EventEmitter<FileModel>();
|
||||
|
||||
FileUploadStatus = FileUploadStatus;
|
||||
|
||||
onCancel(file: FileModel): void {
|
||||
this.cancel.emit(file);
|
||||
}
|
||||
|
||||
onRemove(file: FileModel): void {
|
||||
this.remove.emit(file);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
<div class="upload-list">
|
||||
<ng-template
|
||||
ngFor
|
||||
[ngForOf]="files"
|
||||
[ngForTemplate]="template">
|
||||
</ng-template>
|
||||
</div>
|
@@ -0,0 +1,4 @@
|
||||
:host {
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
}
|
@@ -0,0 +1,289 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslationService, FileUploadStatus, NodesApiService, NotificationService, UploadService } from '@alfresco/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { UploadModule } from '../upload.module';
|
||||
import { FileUploadingListComponent } from './file-uploading-list.component';
|
||||
|
||||
describe('FileUploadingListComponent', () => {
|
||||
let fixture: ComponentFixture<FileUploadingListComponent>;
|
||||
let component: FileUploadingListComponent;
|
||||
let uploadService: UploadService;
|
||||
let nodesApiService: NodesApiService;
|
||||
let notificationService: NotificationService;
|
||||
let translateService: TranslationService;
|
||||
let file: any;
|
||||
|
||||
beforeEach(() => {
|
||||
file = { data: { entry: { id: 'x' } } };
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
UploadModule
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
nodesApiService = TestBed.get(NodesApiService);
|
||||
uploadService = TestBed.get(UploadService);
|
||||
notificationService = TestBed.get(NotificationService);
|
||||
translateService = TestBed.get(TranslationService);
|
||||
fixture = TestBed.createComponent(FileUploadingListComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
spyOn(translateService, 'get').and.returnValue(Observable.of('some error message'));
|
||||
spyOn(notificationService, 'openSnackMessage');
|
||||
spyOn(uploadService, 'cancelUpload');
|
||||
});
|
||||
|
||||
describe('cancelFile()', () => {
|
||||
it('should call uploadService api when cancelling a file', () => {
|
||||
component.cancelFile(file);
|
||||
|
||||
expect(uploadService.cancelUpload).toHaveBeenCalledWith(file);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeFile()', () => {
|
||||
it('should change file status when api returns success', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of(file));
|
||||
|
||||
component.removeFile(file);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(file.status).toBe(FileUploadStatus.Deleted);
|
||||
});
|
||||
|
||||
it('should change file status when api returns error', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(file));
|
||||
|
||||
component.removeFile(file);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(file.status).toBe(FileUploadStatus.Error);
|
||||
});
|
||||
|
||||
it('should notify fail when api returns error', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(file));
|
||||
|
||||
component.removeFile(file);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(notificationService.openSnackMessage).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call uploadService on error', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw(file));
|
||||
|
||||
component.removeFile(file);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(uploadService.cancelUpload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call uploadService on success', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of(file));
|
||||
|
||||
component.removeFile(file);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(uploadService.cancelUpload).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelAllFiles()', () => {
|
||||
beforeEach(() => {
|
||||
component.files = <any> [
|
||||
{
|
||||
data: {
|
||||
entry: { id: '1' }
|
||||
},
|
||||
status: FileUploadStatus.Cancelled
|
||||
},
|
||||
{
|
||||
data: {
|
||||
entry: { id: '2' }
|
||||
},
|
||||
status: FileUploadStatus.Error
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
it('should not call deleteNode if there are no competed uploads', () => {
|
||||
spyOn(nodesApiService, 'deleteNode');
|
||||
|
||||
component.cancelAllFiles();
|
||||
|
||||
expect(nodesApiService.deleteNode).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not call uploadService if there are no uploading files', () => {
|
||||
component.cancelAllFiles();
|
||||
|
||||
expect(uploadService.cancelUpload).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call deleteNode when there are completed uploads', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of({}));
|
||||
|
||||
component.files[0].status = FileUploadStatus.Complete;
|
||||
component.cancelAllFiles();
|
||||
|
||||
expect(nodesApiService.deleteNode).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call uploadService when there are uploading files', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.of({}));
|
||||
|
||||
component.files[0].status = FileUploadStatus.Progress;
|
||||
component.cancelAllFiles();
|
||||
|
||||
expect(uploadService.cancelUpload).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should notify on deleting file error', () => {
|
||||
spyOn(nodesApiService, 'deleteNode').and.returnValue(Observable.throw({}));
|
||||
|
||||
component.files[0].status = FileUploadStatus.Complete;
|
||||
component.cancelAllFiles();
|
||||
|
||||
expect(notificationService.openSnackMessage).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUploadCompleted()', () => {
|
||||
it('should return false when at least one file is in progress', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Progress },
|
||||
{ status: FileUploadStatus.Complete }
|
||||
];
|
||||
|
||||
expect(component.isUploadCompleted()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when at least one file is in pending', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Pending },
|
||||
{ status: FileUploadStatus.Complete }
|
||||
];
|
||||
|
||||
expect(component.isUploadCompleted()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when at least one file is in starting state', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Starting },
|
||||
{ status: FileUploadStatus.Complete }
|
||||
];
|
||||
|
||||
expect(component.isUploadCompleted()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when files are cancelled', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Cancelled },
|
||||
{ status: FileUploadStatus.Cancelled }
|
||||
];
|
||||
|
||||
expect(component.isUploadCompleted()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when there are deleted files', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Complete },
|
||||
{ status: FileUploadStatus.Deleted }
|
||||
];
|
||||
|
||||
expect(component.isUploadCompleted()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when none of the files is in progress', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Error },
|
||||
{ status: FileUploadStatus.Cancelled },
|
||||
{ status: FileUploadStatus.Complete }
|
||||
];
|
||||
|
||||
expect(component.isUploadCompleted()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUploadCancelled()', () => {
|
||||
it('should return false when not all files are cancelled', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Complete },
|
||||
{ status: FileUploadStatus.Cancelled },
|
||||
{ status: FileUploadStatus.Error }
|
||||
];
|
||||
|
||||
expect(component.isUploadCancelled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when there are no cancelled files', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Complete },
|
||||
{ status: FileUploadStatus.Error },
|
||||
{ status: FileUploadStatus.Error }
|
||||
];
|
||||
|
||||
expect(component.isUploadCancelled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when there is at leat one file in progress', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Progress },
|
||||
{ status: FileUploadStatus.Error },
|
||||
{ status: FileUploadStatus.Error }
|
||||
];
|
||||
|
||||
expect(component.isUploadCancelled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when there is at leat one file in pendding', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Pending },
|
||||
{ status: FileUploadStatus.Error },
|
||||
{ status: FileUploadStatus.Error }
|
||||
];
|
||||
|
||||
expect(component.isUploadCancelled()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when all files are aborted', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Aborted }
|
||||
];
|
||||
|
||||
expect(component.isUploadCancelled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when all files are cancelled', () => {
|
||||
component.files = <any> [
|
||||
{ status: FileUploadStatus.Cancelled },
|
||||
{ status: FileUploadStatus.Cancelled },
|
||||
{ status: FileUploadStatus.Aborted }
|
||||
];
|
||||
|
||||
expect(component.isUploadCancelled()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,166 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { FileModel, FileUploadStatus, NodesApiService, NotificationService, TranslationService, UploadService } from '@alfresco/core';
|
||||
import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-file-uploading-list',
|
||||
templateUrl: './file-uploading-list.component.html',
|
||||
styleUrls: ['./file-uploading-list.component.scss']
|
||||
})
|
||||
export class FileUploadingListComponent {
|
||||
|
||||
FileUploadStatus = FileUploadStatus;
|
||||
|
||||
@ContentChild(TemplateRef)
|
||||
template: any;
|
||||
|
||||
@Input()
|
||||
files: FileModel[] = [];
|
||||
|
||||
constructor(
|
||||
private uploadService: UploadService,
|
||||
private nodesApi: NodesApiService,
|
||||
private notificationService: NotificationService,
|
||||
private translateService: TranslationService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel file upload
|
||||
*
|
||||
* @param {FileModel} file File model to cancel upload for.
|
||||
*
|
||||
* @memberOf FileUploadingListComponent
|
||||
*/
|
||||
cancelFile(file: FileModel): void {
|
||||
this.uploadService.cancelUpload(file);
|
||||
}
|
||||
|
||||
removeFile(file: FileModel): void {
|
||||
this.deleteNode(file)
|
||||
.subscribe(() => {
|
||||
if ( file.status === FileUploadStatus.Error) {
|
||||
this.notifyError(file);
|
||||
}
|
||||
|
||||
this.uploadService.cancelUpload(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the appropriate method for each file, depending on state
|
||||
*/
|
||||
cancelAllFiles(): void {
|
||||
this.getUploadingFiles()
|
||||
.forEach((file) => this.uploadService.cancelUpload(file));
|
||||
|
||||
const deletedFiles = this.files
|
||||
.filter((file) => file.status === FileUploadStatus.Complete)
|
||||
.map((file) => this.deleteNode(file));
|
||||
|
||||
Observable.forkJoin(...deletedFiles)
|
||||
.subscribe((files: FileModel[]) => {
|
||||
const errors = files
|
||||
.filter((file) => file.status === FileUploadStatus.Error);
|
||||
|
||||
if (errors.length) {
|
||||
this.notifyError(...errors);
|
||||
}
|
||||
|
||||
this.uploadService.cancelUpload(...files);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all the files are uploaded
|
||||
* @returns {boolean} - false if there is at least one file in Progress | Starting | Pending
|
||||
*/
|
||||
isUploadCompleted(): boolean {
|
||||
return !this.isUploadCancelled() &&
|
||||
Boolean(this.files.length) &&
|
||||
!this.files
|
||||
.some(({status}) =>
|
||||
status === FileUploadStatus.Starting ||
|
||||
status === FileUploadStatus.Progress ||
|
||||
status === FileUploadStatus.Pending
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all the files are Cancelled | Aborted | Error.
|
||||
* @returns {boolean} - false if there is at least one file in uploading states
|
||||
*/
|
||||
isUploadCancelled(): boolean {
|
||||
return !!this.files.length &&
|
||||
this.files
|
||||
.every(({status}) =>
|
||||
status === FileUploadStatus.Aborted ||
|
||||
status === FileUploadStatus.Cancelled ||
|
||||
status === FileUploadStatus.Deleted
|
||||
);
|
||||
}
|
||||
|
||||
private deleteNode(file: FileModel): Observable<FileModel> {
|
||||
const { id } = file.data.entry;
|
||||
|
||||
return this.nodesApi
|
||||
.deleteNode(id, { permanent: true })
|
||||
.map(() => {
|
||||
file.status = FileUploadStatus.Deleted;
|
||||
return file;
|
||||
})
|
||||
.catch((error) => {
|
||||
file.status = FileUploadStatus.Error;
|
||||
return Observable.of(file);
|
||||
});
|
||||
}
|
||||
|
||||
private notifyError(...files: FileModel[]) {
|
||||
let translateSubscription = null;
|
||||
|
||||
if (files.length === 1) {
|
||||
translateSubscription = this.translateService
|
||||
.get(
|
||||
'FILE_UPLOAD.MESSAGES.REMOVE_FILE_ERROR',
|
||||
{ fileName: files[0].name}
|
||||
);
|
||||
} else {
|
||||
translateSubscription = this.translateService
|
||||
.get(
|
||||
'FILE_UPLOAD.MESSAGES.REMOVE_FILES_ERROR',
|
||||
{ total: files.length }
|
||||
);
|
||||
}
|
||||
|
||||
translateSubscription
|
||||
.subscribe(message => this.notificationService.openSnackMessage(message, 4000));
|
||||
}
|
||||
|
||||
private getUploadingFiles() {
|
||||
return this.files.filter((item) => {
|
||||
if (
|
||||
item.status === FileUploadStatus.Pending ||
|
||||
item.status === FileUploadStatus.Progress ||
|
||||
item.status === FileUploadStatus.Starting
|
||||
) {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
<form class="adf-upload-button-file-container">
|
||||
<!--Files Upload-->
|
||||
<a *ngIf="!uploadFolders"
|
||||
[disabled]="isButtonDisabled()"
|
||||
mat-raised-button color="primary">
|
||||
<mat-icon>file_upload</mat-icon>
|
||||
|
||||
<!--Multiple Files Upload-->
|
||||
<span *ngIf="multipleFiles">
|
||||
<label
|
||||
id="upload-multiple-file-label"
|
||||
*ngIf="!staticTitle"
|
||||
for="upload-multiple-files">{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' | translate }}</label>
|
||||
<label
|
||||
id="upload-multiple-file-label-static"
|
||||
*ngIf="staticTitle"
|
||||
for="upload-multiple-files">{{ staticTitle }}</label>
|
||||
<input #uploadFiles
|
||||
id="upload-multiple-files"
|
||||
data-automation-id="upload-multiple-files"
|
||||
type="file"
|
||||
name="uploadFiles"
|
||||
multiple="multiple"
|
||||
accept="{{acceptedFilesType}}"
|
||||
[attr.disabled]="isButtonDisabled()"
|
||||
[title]="tooltip"
|
||||
(change)="onFilesAdded($event)">
|
||||
</span>
|
||||
|
||||
<!--Single Files Upload-->
|
||||
<span *ngIf="!multipleFiles">
|
||||
<label
|
||||
id="upload-single-file-label"
|
||||
*ngIf="!staticTitle"
|
||||
for="upload-single-file">{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' | translate }}</label>
|
||||
<label
|
||||
id="upload-single-file-label-static"
|
||||
*ngIf="staticTitle"
|
||||
for="upload-single-file">{{ staticTitle }}</label>
|
||||
<input #uploadFiles
|
||||
id="upload-single-file"
|
||||
data-automation-id="upload-single-file"
|
||||
type="file"
|
||||
name="uploadFiles"
|
||||
accept="{{acceptedFilesType}}"
|
||||
[attr.disabled]="isButtonDisabled()"
|
||||
[title]="tooltip"
|
||||
(change)="onFilesAdded($event)">
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!--Folders Upload-->
|
||||
<a *ngIf="uploadFolders"
|
||||
[disabled]="isButtonDisabled()" mat-raised-button color="primary">
|
||||
<mat-icon>file_upload</mat-icon>
|
||||
<label
|
||||
id="uploadFolder-label"
|
||||
*ngIf="!staticTitle"
|
||||
for="uploadFolder">{{ 'FILE_UPLOAD.BUTTON.UPLOAD_FOLDER' | translate }}</label>
|
||||
<label
|
||||
id="uploadFolder-label-static"
|
||||
*ngIf="staticTitle"
|
||||
for="uploadFolder">{{ staticTitle }}</label>
|
||||
<input #uploadFolders
|
||||
id="uploadFolder"
|
||||
data-automation-id="uploadFolder"
|
||||
type="file"
|
||||
name="uploadFiles"
|
||||
multiple="multiple"
|
||||
accept="{{acceptedFilesType}}"
|
||||
webkitdirectory directory
|
||||
[attr.disabled]="isButtonDisabled()"
|
||||
[title]="tooltip"
|
||||
(change)="onDirectoryAdded($event)">
|
||||
</a>
|
||||
</form>
|
@@ -0,0 +1,12 @@
|
||||
.adf {
|
||||
&-upload-button-file-container input {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 300px;
|
||||
z-index: 4;
|
||||
}
|
||||
}
|
@@ -0,0 +1,378 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DebugElement, SimpleChange } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MaterialModule } from '../../material.module';
|
||||
import { ContentService, UploadService, TranslationService } from '@alfresco/core';
|
||||
import { Observable } from 'rxjs/Rx';
|
||||
import { UploadButtonComponent } from './upload-button.component';
|
||||
import { TranslationMock } from '@alfresco/core';
|
||||
|
||||
describe('UploadButtonComponent', () => {
|
||||
|
||||
let file = {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
|
||||
let fakeEvent = {
|
||||
currentTarget: {
|
||||
files: [file]
|
||||
},
|
||||
target: {value: 'fake-name-1'}
|
||||
};
|
||||
|
||||
let fakeFolderNodeWithoutPermission = {
|
||||
allowableOperations: [
|
||||
'update'
|
||||
],
|
||||
isFolder: true,
|
||||
name: 'Folder Fake Name',
|
||||
nodeType: 'cm:folder'
|
||||
};
|
||||
|
||||
let fakeFolderNodeWithPermission = {
|
||||
allowableOperations: [
|
||||
'create',
|
||||
'update'
|
||||
],
|
||||
isFolder: true,
|
||||
name: 'Folder Fake Name',
|
||||
nodeType: 'cm:folder'
|
||||
};
|
||||
|
||||
let component: UploadButtonComponent;
|
||||
let fixture: ComponentFixture<UploadButtonComponent>;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
let uploadService: UploadService;
|
||||
let contentService: ContentService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
MaterialModule
|
||||
],
|
||||
declarations: [
|
||||
UploadButtonComponent
|
||||
],
|
||||
providers: [
|
||||
UploadService,
|
||||
{provide: TranslationService, useClass: TranslationMock}
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UploadButtonComponent);
|
||||
uploadService = TestBed.get(UploadService);
|
||||
contentService = TestBed.get(ContentService);
|
||||
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('should render upload-single-file button as default', () => {
|
||||
component.multipleFiles = false;
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-single-file')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render upload-multiple-file button if multipleFiles is true', () => {
|
||||
component.multipleFiles = true;
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-multiple-files')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render an uploadFolder button if uploadFolder is true', () => {
|
||||
component.uploadFolders = true;
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#uploadFolder')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should emit the permissionEvent, without permission and disableWithNoPermission false', (done) => {
|
||||
component.rootFolderId = '-my-';
|
||||
component.disableWithNoPermission = false;
|
||||
|
||||
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
component.permissionEvent.subscribe(permission => {
|
||||
expect(permission).toBeDefined();
|
||||
expect(permission.type).toEqual('content');
|
||||
expect(permission.action).toEqual('upload');
|
||||
expect(permission.permission).toEqual('create');
|
||||
done();
|
||||
});
|
||||
|
||||
component.onFilesAdded(fakeEvent);
|
||||
});
|
||||
|
||||
it('should show the disabled button, without permission and disableWithNoPermission true', () => {
|
||||
component.rootFolderId = '-my-';
|
||||
component.disableWithNoPermission = true;
|
||||
|
||||
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
|
||||
|
||||
component.onFilesAdded(fakeEvent);
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-single-file')).toBeDefined();
|
||||
expect(compiled.querySelector('#upload-single-file').disabled).toBe(true);
|
||||
});
|
||||
|
||||
it('should show the enabled button with permission and disableWithNoPermission true', () => {
|
||||
component.rootFolderId = '-my-';
|
||||
component.disableWithNoPermission = true;
|
||||
|
||||
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
|
||||
|
||||
component.ngOnChanges({rootFolderId: new SimpleChange(null, component.rootFolderId, true)});
|
||||
component.onFilesAdded(fakeEvent);
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-single-file')).toBeDefined();
|
||||
expect(compiled.querySelector('#upload-single-file').disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should show the enabled button with permission and disableWithNoPermission false', () => {
|
||||
component.rootFolderId = '-my-';
|
||||
component.disableWithNoPermission = false;
|
||||
|
||||
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
|
||||
|
||||
component.ngOnChanges({rootFolderId: new SimpleChange(null, component.rootFolderId, true)});
|
||||
component.onFilesAdded(fakeEvent);
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-single-file')).toBeDefined();
|
||||
expect(compiled.querySelector('#upload-single-file').disabled).toBe(false);
|
||||
});
|
||||
|
||||
it('should call uploadFile with the default root folder', () => {
|
||||
component.rootFolderId = '-root-';
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
|
||||
component.success = null;
|
||||
|
||||
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
|
||||
|
||||
component.ngOnChanges({rootFolderId: new SimpleChange(null, component.rootFolderId, true)});
|
||||
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
component.onFilesAdded(fakeEvent);
|
||||
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it('should call uploadFile with a custom root folder', () => {
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
|
||||
component.rootFolderId = '-my-';
|
||||
component.success = null;
|
||||
|
||||
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
|
||||
component.ngOnChanges({rootFolderId: new SimpleChange(null, component.rootFolderId, true)});
|
||||
|
||||
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
|
||||
|
||||
fixture.detectChanges();
|
||||
|
||||
component.onFilesAdded(fakeEvent);
|
||||
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it('should create a folder and emit an File uploaded event', (done) => {
|
||||
component.rootFolderId = '-my-';
|
||||
component.currentFolderPath = '/fake-root-path';
|
||||
|
||||
spyOn(contentService, 'createFolder').and.returnValue(Observable.of(true));
|
||||
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
|
||||
|
||||
component.ngOnChanges({rootFolderId: new SimpleChange(null, component.rootFolderId, true)});
|
||||
fixture.detectChanges();
|
||||
|
||||
component.success.subscribe(e => {
|
||||
expect(e.value).toEqual('File uploaded');
|
||||
done();
|
||||
});
|
||||
|
||||
spyOn(component, 'uploadFiles').and.callFake(() => {
|
||||
component.success.emit({
|
||||
value: 'File uploaded'
|
||||
});
|
||||
});
|
||||
component.onDirectoryAdded(fakeEvent);
|
||||
});
|
||||
|
||||
it('should by default the title of the button get from the JSON file', () => {
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
fixture.detectChanges();
|
||||
component.uploadFolders = false;
|
||||
component.multipleFiles = false;
|
||||
|
||||
expect(compiled.querySelector('#upload-single-file-label').innerText).toEqual('FILE_UPLOAD.BUTTON.UPLOAD_FILE');
|
||||
|
||||
component.multipleFiles = true;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-multiple-file-label').innerText).toEqual('FILE_UPLOAD.BUTTON.UPLOAD_FILE');
|
||||
|
||||
component.uploadFolders = true;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#uploadFolder-label').innerText).toEqual('FILE_UPLOAD.BUTTON.UPLOAD_FOLDER');
|
||||
});
|
||||
|
||||
it('should staticTitle properties change the title of the upload buttons', () => {
|
||||
let compiled = fixture.debugElement.nativeElement;
|
||||
component.staticTitle = 'test-text';
|
||||
component.uploadFolders = false;
|
||||
component.multipleFiles = false;
|
||||
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-single-file-label-static').textContent).toEqual('test-text');
|
||||
|
||||
component.multipleFiles = true;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#upload-multiple-file-label-static').textContent).toEqual('test-text');
|
||||
|
||||
component.uploadFolders = true;
|
||||
fixture.detectChanges();
|
||||
expect(compiled.querySelector('#uploadFolder-label-static').textContent).toEqual('test-text');
|
||||
});
|
||||
|
||||
describe('filesize', () => {
|
||||
|
||||
const files: File[] = [
|
||||
<File> {name: 'bigFile.png', size: 1000},
|
||||
<File> {name: 'smallFile.png', size: 10}
|
||||
];
|
||||
|
||||
let addToQueueSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
addToQueueSpy = spyOn(uploadService, 'addToQueue');
|
||||
});
|
||||
|
||||
it('should filter out file, which are too big if max file size is set', () => {
|
||||
component.maxFilesSize = 100;
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
const filesCalledWith = addToQueueSpy.calls.mostRecent().args;
|
||||
expect(filesCalledWith.length).toBe(1);
|
||||
expect(filesCalledWith[0].name).toBe('smallFile.png');
|
||||
});
|
||||
|
||||
it('should filter out all files if maxFilesSize is 0', () => {
|
||||
component.maxFilesSize = 0;
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
expect(addToQueueSpy.calls.mostRecent()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should filter out all files if maxFilesSize is <0', () => {
|
||||
component.maxFilesSize = -2;
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
expect(addToQueueSpy.calls.mostRecent()).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should output an error when you try to upload a file too big', (done) => {
|
||||
component.maxFilesSize = 100;
|
||||
|
||||
component.error.subscribe(() => {
|
||||
done();
|
||||
});
|
||||
|
||||
component.uploadFiles(files);
|
||||
});
|
||||
|
||||
it('should not filter out files if max file size is not set', () => {
|
||||
component.maxFilesSize = null;
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
const filesCalledWith = addToQueueSpy.calls.mostRecent().args;
|
||||
expect(filesCalledWith.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uploadFiles', () => {
|
||||
|
||||
const files: File[] = [
|
||||
<File> {name: 'phobos.jpg'},
|
||||
<File> {name: 'deimos.png'},
|
||||
<File> {name: 'ganymede.bmp'}
|
||||
];
|
||||
|
||||
let addToQueueSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
addToQueueSpy = spyOn(uploadService, 'addToQueue');
|
||||
});
|
||||
|
||||
it('should filter out file, which is not part of the acceptedFilesType', () => {
|
||||
component.acceptedFilesType = '.jpg';
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
const filesCalledWith = addToQueueSpy.calls.mostRecent().args;
|
||||
expect(filesCalledWith.length).toBe(1, 'Files should contain only one element');
|
||||
expect(filesCalledWith[0].name).toBe('phobos.jpg', 'png file should be filtered out');
|
||||
});
|
||||
|
||||
it('should filter out files, which are not part of the acceptedFilesType', () => {
|
||||
component.acceptedFilesType = '.jpg,.png';
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
const filesCalledWith = addToQueueSpy.calls.mostRecent().args;
|
||||
expect(filesCalledWith.length).toBe(2, 'Files should contain two elements');
|
||||
expect(filesCalledWith[0].name).toBe('phobos.jpg');
|
||||
expect(filesCalledWith[1].name).toBe('deimos.png');
|
||||
});
|
||||
|
||||
it('should not filter out anything if acceptedFilesType is wildcard', () => {
|
||||
component.acceptedFilesType = '*';
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
const filesCalledWith = addToQueueSpy.calls.mostRecent().args;
|
||||
expect(filesCalledWith.length).toBe(3, 'Files should contain all elements');
|
||||
expect(filesCalledWith[0].name).toBe('phobos.jpg');
|
||||
expect(filesCalledWith[1].name).toBe('deimos.png');
|
||||
expect(filesCalledWith[2].name).toBe('ganymede.bmp');
|
||||
});
|
||||
|
||||
it('should not add any file to que if everything is filtered out', () => {
|
||||
component.acceptedFilesType = 'doc';
|
||||
|
||||
component.uploadFiles(files);
|
||||
|
||||
expect(addToQueueSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,287 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
AlfrescoApiService,
|
||||
EXTENDIBLE_COMPONENT,
|
||||
FileModel,
|
||||
FileUtils,
|
||||
LogService,
|
||||
NodePermissionSubject,
|
||||
NotificationService,
|
||||
TranslationService,
|
||||
UploadService
|
||||
} from '@alfresco/core';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
forwardRef,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
|
||||
import { Observable, Subject } from 'rxjs/Rx';
|
||||
import { PermissionModel } from '../../document-list/models/permissions.model';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-upload-button',
|
||||
templateUrl: './upload-button.component.html',
|
||||
styleUrls: ['./upload-button.component.scss'],
|
||||
providers: [
|
||||
{provide: EXTENDIBLE_COMPONENT, useExisting: forwardRef(() => UploadButtonComponent)}
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class UploadButtonComponent implements OnInit, OnChanges, NodePermissionSubject {
|
||||
|
||||
/** @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead. */
|
||||
@Input()
|
||||
showNotificationBar: boolean = true;
|
||||
|
||||
/** @deprecated Deprecated in 1.6.0, this property is not used for couple of releases already. */
|
||||
@Input()
|
||||
currentFolderPath: string = '/';
|
||||
|
||||
/** @deprecated Deprecated in 1.8.0, use the button with combination of adf-node-permission directive */
|
||||
@Input()
|
||||
disableWithNoPermission: boolean = false;
|
||||
|
||||
@Input()
|
||||
disabled: boolean = false;
|
||||
|
||||
@Input()
|
||||
uploadFolders: boolean = false;
|
||||
|
||||
@Input()
|
||||
multipleFiles: boolean = false;
|
||||
|
||||
@Input()
|
||||
versioning: boolean = false;
|
||||
|
||||
@Input()
|
||||
acceptedFilesType: string = '*';
|
||||
|
||||
@Input()
|
||||
maxFilesSize: number;
|
||||
|
||||
@Input()
|
||||
staticTitle: string;
|
||||
|
||||
@Input()
|
||||
tooltip: string = null;
|
||||
|
||||
@Input()
|
||||
rootFolderId: string = '-root-';
|
||||
|
||||
@Output()
|
||||
success = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
error = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
createFolder = new EventEmitter();
|
||||
|
||||
@Output()
|
||||
permissionEvent: EventEmitter<PermissionModel> = new EventEmitter<PermissionModel>();
|
||||
|
||||
private hasPermission: boolean = false;
|
||||
|
||||
private permissionValue: Subject<boolean> = new Subject<boolean>();
|
||||
|
||||
constructor(private uploadService: UploadService,
|
||||
private translateService: TranslationService,
|
||||
private logService: LogService,
|
||||
private notificationService: NotificationService,
|
||||
private apiService: AlfrescoApiService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.permissionValue.subscribe((permission: boolean) => {
|
||||
this.hasPermission = permission;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
let rootFolderId = changes['rootFolderId'];
|
||||
if (rootFolderId && rootFolderId.currentValue) {
|
||||
this.checkPermission();
|
||||
}
|
||||
}
|
||||
|
||||
isButtonDisabled(): boolean {
|
||||
return this.isForceDisable() || this.isDisableWithNoPermission();
|
||||
}
|
||||
|
||||
isForceDisable(): boolean {
|
||||
return this.disabled ? true : undefined;
|
||||
}
|
||||
|
||||
/** @deprecated Deprecated in 1.8.0, use the button with combination of adf-node-permission directive */
|
||||
isDisableWithNoPermission(): boolean {
|
||||
return !this.hasPermission && this.disableWithNoPermission ? true : undefined;
|
||||
}
|
||||
|
||||
onFilesAdded($event: any): void {
|
||||
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
|
||||
|
||||
if (this.hasPermission) {
|
||||
this.uploadFiles(files);
|
||||
} else {
|
||||
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
|
||||
}
|
||||
// reset the value of the input file
|
||||
$event.target.value = '';
|
||||
}
|
||||
|
||||
onDirectoryAdded($event: any): void {
|
||||
if (this.hasPermission) {
|
||||
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
|
||||
this.uploadFiles(files);
|
||||
} else {
|
||||
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
|
||||
}
|
||||
// reset the value of the input file
|
||||
$event.target.value = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a list of file in the specified path
|
||||
* @param files
|
||||
* @param path
|
||||
*/
|
||||
uploadFiles(files: File[]): void {
|
||||
const latestFilesAdded: FileModel[] = files
|
||||
.map<FileModel>(this.createFileModel.bind(this))
|
||||
.filter(this.isFileAcceptable.bind(this))
|
||||
.filter(this.isFileSizeAcceptable.bind(this));
|
||||
|
||||
if (latestFilesAdded.length > 0) {
|
||||
this.uploadService.addToQueue(...latestFilesAdded);
|
||||
this.uploadService.uploadFilesInTheQueue(this.success);
|
||||
if (this.showNotificationBar) {
|
||||
this.showUndoNotificationBar(latestFilesAdded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates FileModel from File
|
||||
*
|
||||
* @param file
|
||||
*/
|
||||
private createFileModel(file: File): FileModel {
|
||||
return new FileModel(file, {
|
||||
newVersion: this.versioning,
|
||||
parentId: this.rootFolderId,
|
||||
path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, '')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given file is allowed by the extension filters
|
||||
*
|
||||
* @param file FileModel
|
||||
*/
|
||||
private isFileAcceptable(file: FileModel): boolean {
|
||||
if (this.acceptedFilesType === '*') {
|
||||
return true;
|
||||
}
|
||||
|
||||
const allowedExtensions = this.acceptedFilesType
|
||||
.split(',')
|
||||
.map(ext => ext.replace(/^\./, ''));
|
||||
|
||||
if (allowedExtensions.indexOf(file.extension) !== -1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given file is an acceptable size
|
||||
*
|
||||
* @param file FileModel
|
||||
*/
|
||||
private isFileSizeAcceptable(file: FileModel): boolean {
|
||||
let acceptableSize = true;
|
||||
|
||||
if ((this.maxFilesSize !== undefined && this.maxFilesSize !== null ) && (this.maxFilesSize <= 0 || file.size > this.maxFilesSize)) {
|
||||
acceptableSize = false;
|
||||
|
||||
this.translateService.get('FILE_UPLOAD.MESSAGES.EXCEED_MAX_FILE_SIZE', {fileName: file.name}).subscribe((message: string) => {
|
||||
this.error.emit(message);
|
||||
});
|
||||
}
|
||||
|
||||
return acceptableSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show undo notification bar.
|
||||
*
|
||||
* @param {FileModel[]} latestFilesAdded - files in the upload queue enriched with status flag and xhr object.
|
||||
*/
|
||||
private showUndoNotificationBar(latestFilesAdded: FileModel[]): void {
|
||||
let messageTranslate: any, actionTranslate: any;
|
||||
messageTranslate = this.translateService.get('FILE_UPLOAD.MESSAGES.PROGRESS');
|
||||
actionTranslate = this.translateService.get('FILE_UPLOAD.ACTION.UNDO');
|
||||
|
||||
this.notificationService.openSnackMessageAction(messageTranslate.value, actionTranslate.value, 3000).onAction().subscribe(() => {
|
||||
this.uploadService.cancelUpload(...latestFilesAdded);
|
||||
});
|
||||
}
|
||||
|
||||
checkPermission() {
|
||||
if (this.rootFolderId) {
|
||||
this.getFolderNode(this.rootFolderId).subscribe(
|
||||
res => this.permissionValue.next(this.hasCreatePermission(res)),
|
||||
error => this.error.emit(error)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move to ContentService
|
||||
getFolderNode(nodeId: string): Observable<MinimalNodeEntryEntity> {
|
||||
let opts: any = {
|
||||
includeSource: true,
|
||||
include: ['allowableOperations']
|
||||
};
|
||||
|
||||
return Observable.fromPromise(this.apiService.getInstance().nodes.getNodeInfo(nodeId, opts))
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
|
||||
private handleError(error: Response) {
|
||||
// in a real world app, we may send the error to some remote logging infrastructure
|
||||
// instead of just logging it to the console
|
||||
this.logService.error(error);
|
||||
return Observable.throw(error || 'Server error');
|
||||
}
|
||||
|
||||
private hasCreatePermission(node: any): boolean {
|
||||
if (node && node.allowableOperations) {
|
||||
return node.allowableOperations.find(permission => permission === 'create') ? true : false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
.upload-border {
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-draggable__input-focus {
|
||||
color: #2196F3;
|
||||
border: 1px dashed #2196F3;
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<div [file-draggable]="isDroppable()" id="UploadBorder" class="upload-border"
|
||||
(filesDropped)="onFilesDropped($event)"
|
||||
(filesEntityDropped)="onFilesEntityDropped($event)"
|
||||
(folderEntityDropped)="onFolderEntityDropped($event)"
|
||||
(upload-files)="onUploadFiles($event)"
|
||||
dropzone="" webkitdropzone="*" #droparea>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
@@ -0,0 +1,322 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { DebugElement } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { FileModel, LogService, UploadService } from '@alfresco/core';
|
||||
|
||||
import { FileDraggableDirective } from '../directives/file-draggable.directive';
|
||||
import { UploadDragAreaComponent } from './upload-drag-area.component';
|
||||
|
||||
function getFakeShareDataRow(allowableOperations = ['delete', 'update', 'create']) {
|
||||
return {
|
||||
obj: {
|
||||
entry: {
|
||||
createdAt: '2017-06-04T04:32:15.597Z',
|
||||
path: {
|
||||
name: '/Company Home/User Homes/Test',
|
||||
isComplete: true,
|
||||
elements: [
|
||||
{
|
||||
id: '94acfc73-7014-4475-9bd9-93a2162f0f8c',
|
||||
name: 'Company Home'
|
||||
},
|
||||
{
|
||||
id: '55052317-7e59-4058-8e07-769f41e615e1',
|
||||
name: 'User Homes'
|
||||
},
|
||||
{
|
||||
id: '70e1cc6a-6918-468a-b84a-1048093b06fd',
|
||||
name: 'Test'
|
||||
}
|
||||
]
|
||||
},
|
||||
isFolder: true,
|
||||
name: 'pippo',
|
||||
id: '7462d28e-bd43-4b91-9e7b-0d71598680ac',
|
||||
nodeType: 'cm:folder',
|
||||
allowableOperations
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
describe('UploadDragAreaComponent', () => {
|
||||
|
||||
let component: UploadDragAreaComponent;
|
||||
let fixture: ComponentFixture<UploadDragAreaComponent>;
|
||||
let debug: DebugElement;
|
||||
let element: HTMLElement;
|
||||
let uploadService: UploadService;
|
||||
let logService: LogService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
|
||||
declarations: [
|
||||
FileDraggableDirective,
|
||||
UploadDragAreaComponent
|
||||
],
|
||||
providers: [
|
||||
UploadService,
|
||||
LogService
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
logService = TestBed.get(LogService);
|
||||
fixture = TestBed.createComponent(UploadDragAreaComponent);
|
||||
uploadService = TestBed.get(UploadService);
|
||||
|
||||
debug = fixture.debugElement;
|
||||
element = fixture.nativeElement;
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
describe('When disabled', () => {
|
||||
|
||||
it('should NOT upload the list of files dropped', () => {
|
||||
component.enabled = false;
|
||||
spyOn(uploadService, 'addToQueue');
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue');
|
||||
fixture.detectChanges();
|
||||
|
||||
const file = <File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
|
||||
let filesList = [file];
|
||||
component.onFilesDropped(filesList);
|
||||
|
||||
expect(uploadService.addToQueue).not.toHaveBeenCalled();
|
||||
expect(uploadService.uploadFilesInTheQueue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT upload the file dropped', () => {
|
||||
component.enabled = false;
|
||||
spyOn(uploadService, 'addToQueue');
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue');
|
||||
fixture.detectChanges();
|
||||
|
||||
let itemEntity = {
|
||||
fullPath: '/folder-fake/file-fake.png',
|
||||
isDirectory: false,
|
||||
isFile: true,
|
||||
name: 'file-fake.png',
|
||||
file: (callbackFile) => {
|
||||
let fileFake = new File(['fakefake'], 'file-fake.png', {type: 'image/png'});
|
||||
callbackFile(fileFake);
|
||||
}
|
||||
};
|
||||
component.onFilesEntityDropped(itemEntity);
|
||||
|
||||
expect(uploadService.addToQueue).not.toHaveBeenCalled();
|
||||
expect(uploadService.uploadFilesInTheQueue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should NOT upload the folder dropped', (done) => {
|
||||
component.enabled = false;
|
||||
spyOn(uploadService, 'addToQueue');
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue');
|
||||
fixture.detectChanges();
|
||||
|
||||
let itemEntity = {
|
||||
isDirectory: true,
|
||||
createReader: () => {
|
||||
return {
|
||||
readEntries: (cb) => {
|
||||
cb([]);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
component.onFolderEntityDropped(itemEntity);
|
||||
|
||||
setTimeout(() => {
|
||||
expect(uploadService.addToQueue).not.toHaveBeenCalled();
|
||||
expect(uploadService.uploadFilesInTheQueue).not.toHaveBeenCalled();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should NOT upload the files', () => {
|
||||
component.enabled = false;
|
||||
spyOn(uploadService, 'addToQueue');
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue');
|
||||
|
||||
let fakeItem = {
|
||||
fullPath: '/folder-fake/file-fake.png',
|
||||
isDirectory: false,
|
||||
isFile: true,
|
||||
name: 'file-fake.png',
|
||||
file: (callbackFile) => {
|
||||
let fileFake = new File(['fakefake'], 'file-fake.png', {type: 'image/png'});
|
||||
callbackFile(fileFake);
|
||||
}
|
||||
};
|
||||
fixture.detectChanges();
|
||||
|
||||
let fakeCustomEvent: CustomEvent = new CustomEvent('CustomEvent', {
|
||||
detail: { data: getFakeShareDataRow([]), files: [fakeItem] }
|
||||
});
|
||||
component.onUploadFiles(fakeCustomEvent);
|
||||
|
||||
expect(uploadService.addToQueue).not.toHaveBeenCalled();
|
||||
expect(uploadService.uploadFilesInTheQueue).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should upload the list of files dropped', (done) => {
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
|
||||
component.success = null;
|
||||
component.showNotificationBar = false;
|
||||
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
|
||||
|
||||
fixture.detectChanges();
|
||||
const file = <File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
|
||||
let filesList = [file];
|
||||
|
||||
spyOn(uploadService, 'addToQueue').and.callFake((f: FileModel) => {
|
||||
expect(f.file).toBe(file);
|
||||
done();
|
||||
});
|
||||
|
||||
component.onFilesDropped(filesList);
|
||||
});
|
||||
|
||||
it('should show the loading messages in the notification bar when the files are dropped', () => {
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
|
||||
component.success = null;
|
||||
component.showNotificationBar = true;
|
||||
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
|
||||
component.showUndoNotificationBar = jasmine.createSpy('_showUndoNotificationBar');
|
||||
|
||||
fixture.detectChanges();
|
||||
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(null);
|
||||
expect(component.showUndoNotificationBar).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should upload a file when dropped', () => {
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/document-library-fake';
|
||||
component.success = null;
|
||||
|
||||
fixture.detectChanges();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue');
|
||||
|
||||
let itemEntity = {
|
||||
fullPath: '/folder-fake/file-fake.png',
|
||||
isDirectory: false,
|
||||
isFile: true,
|
||||
name: 'file-fake.png',
|
||||
file: (callbackFile) => {
|
||||
let fileFake = new File(['fakefake'], 'file-fake.png', {type: 'image/png'});
|
||||
callbackFile(fileFake);
|
||||
}
|
||||
};
|
||||
|
||||
component.onFilesEntityDropped(itemEntity);
|
||||
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it('should upload a file with a custom root folder ID when dropped', () => {
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/document-library-fake';
|
||||
component.rootFolderId = '-my-';
|
||||
component.success = null;
|
||||
|
||||
fixture.detectChanges();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue');
|
||||
|
||||
let itemEntity = {
|
||||
fullPath: '/folder-fake/file-fake.png',
|
||||
isDirectory: false,
|
||||
isFile: true,
|
||||
name: 'file-fake.png',
|
||||
file: (callbackFile) => {
|
||||
let fileFake = new File(['fakefake'], 'file-fake.png', {type: 'image/png'});
|
||||
callbackFile(fileFake);
|
||||
}
|
||||
};
|
||||
|
||||
component.onFilesEntityDropped(itemEntity);
|
||||
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it('should upload a file when user has create permission on target folder', async(() => {
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/document-library-fake';
|
||||
component.rootFolderId = '-my-';
|
||||
component.enabled = false;
|
||||
|
||||
let fakeItem = {
|
||||
fullPath: '/folder-fake/file-fake.png',
|
||||
isDirectory: false,
|
||||
isFile: true,
|
||||
name: 'file-fake.png',
|
||||
file: (callbackFile) => {
|
||||
let fileFake = new File(['fakefake'], 'file-fake.png', {type: 'image/png'});
|
||||
callbackFile(fileFake);
|
||||
}
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue').and.returnValue(Promise.resolve(fakeItem));
|
||||
component.success.subscribe((val) => {
|
||||
expect(val).not.toBeNull();
|
||||
});
|
||||
|
||||
let fakeCustomEvent: CustomEvent = new CustomEvent('CustomEvent', {
|
||||
detail: {
|
||||
data: getFakeShareDataRow(),
|
||||
files: [fakeItem]
|
||||
}
|
||||
});
|
||||
|
||||
component.onUploadFiles(fakeCustomEvent);
|
||||
}));
|
||||
|
||||
it('should show notification bar when a file is dropped', () => {
|
||||
component.currentFolderPath = '/root-fake-/sites-fake/document-library-fake';
|
||||
component.rootFolderId = '-my-';
|
||||
component.success = null;
|
||||
|
||||
fixture.detectChanges();
|
||||
spyOn(uploadService, 'uploadFilesInTheQueue');
|
||||
|
||||
let itemEntity = {
|
||||
fullPath: '/folder-fake/file-fake.png',
|
||||
isDirectory: false,
|
||||
isFile: true,
|
||||
name: 'file-fake.png',
|
||||
file: (callbackFile) => {
|
||||
let fileFake = new File(['fakefake'], 'file-fake.png', {type: 'image/png'});
|
||||
callbackFile(fileFake);
|
||||
}
|
||||
};
|
||||
|
||||
component.onFilesEntityDropped(itemEntity);
|
||||
fixture.detectChanges();
|
||||
expect(document.querySelector('snack-bar-container > simple-snack-bar')).not.toBeNull();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,237 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EXTENDIBLE_COMPONENT,
|
||||
FileInfo,
|
||||
FileModel,
|
||||
FileUtils,
|
||||
NodePermissionSubject,
|
||||
NotificationService,
|
||||
TranslationService,
|
||||
UploadService
|
||||
} from '@alfresco/core';
|
||||
import { Component, EventEmitter, forwardRef, Input, Output, ViewEncapsulation } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-upload-drag-area',
|
||||
templateUrl: './upload-drag-area.component.html',
|
||||
styleUrls: ['./upload-drag-area.component.css'],
|
||||
providers: [
|
||||
{ provide: EXTENDIBLE_COMPONENT, useExisting: forwardRef(() => UploadDragAreaComponent)}
|
||||
],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class UploadDragAreaComponent implements NodePermissionSubject {
|
||||
|
||||
/** @deprecated Deprecated in favor of disabled input property */
|
||||
@Input()
|
||||
set enabled(enabled: boolean) {
|
||||
console.warn('Deprecated: enabled input property should not be used for UploadDragAreaComponent. Please use disabled instead.');
|
||||
this.disabled = !enabled;
|
||||
}
|
||||
|
||||
/** @deprecated Deprecated in favor of disabled input property */
|
||||
get enabled(): boolean {
|
||||
console.warn('Deprecated: enabled input property should not be used for UploadDragAreaComponent. Please use disabled instead.');
|
||||
return !this.disabled;
|
||||
}
|
||||
|
||||
/** @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead. */
|
||||
@Input()
|
||||
showNotificationBar: boolean = true;
|
||||
|
||||
/** @deprecated Deprecated in 1.6.0, this property is not used for couple of releases already. Use rootFolderId instead. */
|
||||
@Input()
|
||||
currentFolderPath: string = '/';
|
||||
|
||||
/** @deprecated Deprecated in 1.6.2, this property is not used for couple of releases already. Use parentId instead. */
|
||||
@Input()
|
||||
rootFolderId: string = '-root-';
|
||||
|
||||
@Input()
|
||||
disabled: boolean = false;
|
||||
|
||||
@Input()
|
||||
versioning: boolean = false;
|
||||
|
||||
@Input()
|
||||
parentId: string;
|
||||
|
||||
@Output()
|
||||
success = new EventEmitter();
|
||||
|
||||
constructor(private uploadService: UploadService,
|
||||
private translateService: TranslationService,
|
||||
private notificationService: NotificationService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called when files are dropped in the drag area.
|
||||
*
|
||||
* @param {File[]} files - files dropped in the drag area.
|
||||
*/
|
||||
onFilesDropped(files: File[]): void {
|
||||
if (!this.disabled && files.length) {
|
||||
const fileModels = files.map(file => new FileModel(file, {
|
||||
newVersion: this.versioning,
|
||||
path: '/',
|
||||
parentId: this.parentId || this.rootFolderId
|
||||
}));
|
||||
this.uploadService.addToQueue(...fileModels);
|
||||
this.uploadService.uploadFilesInTheQueue(this.success);
|
||||
let latestFilesAdded = this.uploadService.getQueue();
|
||||
if (this.showNotificationBar) {
|
||||
this.showUndoNotificationBar(latestFilesAdded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the file are dropped in the drag area
|
||||
*
|
||||
* @param item - FileEntity
|
||||
*/
|
||||
onFilesEntityDropped(item: any): void {
|
||||
if (!this.disabled) {
|
||||
item.file((file: File) => {
|
||||
const fileModel = new FileModel(file, {
|
||||
newVersion: this.versioning,
|
||||
parentId: this.parentId || this.rootFolderId,
|
||||
path: item.fullPath.replace(item.name, '')
|
||||
});
|
||||
this.uploadService.addToQueue(fileModel);
|
||||
this.uploadService.uploadFilesInTheQueue(this.success);
|
||||
});
|
||||
if (this.showNotificationBar) {
|
||||
this.showUndoNotificationBar(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a folder are dropped in the drag area
|
||||
*
|
||||
* @param folder - name of the dropped folder
|
||||
*/
|
||||
onFolderEntityDropped(folder: any): void {
|
||||
if (!this.disabled && folder.isDirectory) {
|
||||
FileUtils.flattern(folder).then(entries => {
|
||||
let files = entries.map(entry => {
|
||||
return new FileModel(entry.file, {
|
||||
newVersion: this.versioning,
|
||||
parentId: this.parentId || this.rootFolderId,
|
||||
path: entry.relativeFolder
|
||||
});
|
||||
});
|
||||
this.uploadService.addToQueue(...files);
|
||||
/* @deprecated in 1.6.0 */
|
||||
if (this.showNotificationBar) {
|
||||
let latestFilesAdded = this.uploadService.getQueue();
|
||||
this.showUndoNotificationBar(latestFilesAdded);
|
||||
}
|
||||
this.uploadService.uploadFilesInTheQueue(this.success);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show undo notification bar.
|
||||
*
|
||||
* @param {FileModel[]} latestFilesAdded - files in the upload queue enriched with status flag and xhr object.
|
||||
*/
|
||||
showUndoNotificationBar(latestFilesAdded: FileModel[]) {
|
||||
let messageTranslate: any, actionTranslate: any;
|
||||
messageTranslate = this.translateService.get('FILE_UPLOAD.MESSAGES.PROGRESS');
|
||||
actionTranslate = this.translateService.get('FILE_UPLOAD.ACTION.UNDO');
|
||||
|
||||
this.notificationService.openSnackMessageAction(messageTranslate.value, actionTranslate.value, 3000).onAction().subscribe(() => {
|
||||
this.uploadService.cancelUpload(...latestFilesAdded);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the error inside Notification bar
|
||||
*
|
||||
* @param Error message
|
||||
* @private
|
||||
*/
|
||||
showErrorNotificationBar(errorMessage: string) {
|
||||
this.notificationService.openSnackMessage(errorMessage, 3000);
|
||||
}
|
||||
|
||||
/** Returns true or false considering the component options and node permissions */
|
||||
isDroppable(): boolean {
|
||||
return !this.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles 'upload-files' events raised by child components.
|
||||
*
|
||||
* @param event DOM event
|
||||
*/
|
||||
onUploadFiles(event: CustomEvent) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let isAllowed: boolean = this.hasCreatePermission(event.detail.data.obj.entry);
|
||||
if (isAllowed) {
|
||||
let files: FileInfo[] = event.detail.files;
|
||||
if (files && files.length > 0) {
|
||||
let parentId = this.parentId || this.rootFolderId;
|
||||
if (event.detail.data && event.detail.data.obj.entry.isFolder) {
|
||||
parentId = event.detail.data.obj.entry.id || this.parentId || this.rootFolderId;
|
||||
}
|
||||
const fileModels = files.map(fileInfo => new FileModel(fileInfo.file, {
|
||||
newVersion: this.versioning,
|
||||
path: fileInfo.relativeFolder,
|
||||
parentId: parentId
|
||||
}));
|
||||
this.uploadFiles(fileModels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual file uploading and show the notification
|
||||
*
|
||||
* @param files
|
||||
*/
|
||||
private uploadFiles(files: FileModel[]): void {
|
||||
if (files.length) {
|
||||
this.uploadService.addToQueue(...files);
|
||||
this.uploadService.uploadFilesInTheQueue(this.success);
|
||||
let latestFilesAdded = this.uploadService.getQueue();
|
||||
if (this.showNotificationBar) {
|
||||
this.showUndoNotificationBar(latestFilesAdded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if "create" permission is present on the given node
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
private hasCreatePermission(node: any): boolean {
|
||||
let isPermitted = false;
|
||||
if (node && node['allowableOperations']) {
|
||||
let permFound = node['allowableOperations'].find(element => element === 'create');
|
||||
isPermitted = permFound ? true : false;
|
||||
}
|
||||
return isPermitted;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user