* fix upload area snackbar behaviour

* SASS support for components

ability to use '.scss' files from within components

* [ADF-610] Upload button and DnD area should not upload hidden files and folders (#1908)

[ADF-610]  upload cleanup
- more strongly typing
- api improvements

* Upload cleanup and api improvements

- remove old unused settings (formFields variable)
- individual options for uploaded files (i.e. versioning)
- upload button and drag-and-drop area now set individual settings for file versioning

* exclude hidden files from upload

* [ADF-640] reload document list on folder upload (#1895)

* reload document list on folder upload

- extend UploadService with 'folderCreated' event to be able reacting on folder uploads globally
- extend Demo Shell to reload document list on UploadService events (folderCreated)

* readme updates

* [ADF-621] show drop effect on folders only (#1897)

* show drop effect on folders only

- fix `hasValue` api for data rows (avoid 'false' value to be evaluated as missing value)
- support for evaluating drop support for rows
- document list enables upload zones for folders only

* api improvements as per code review

* [ADF-242] Fixed behaviour for saving/deleting reports (#1905)

* [ADF-242] - fix for deleting - saving a report

* [ADF - 242] added test for fixed feature on reports save - delete

* Added translation key

* [ADF-604] Upgrade @angular/material to latest version (#1909)

* update dependencies and module imports

* fix template warnings and fix import issues

* migrate Activiti Form to MdTabsModule

* fix unit tests

* fix tests

* fix unit tests

* fix unit tests

* disable test that fails only on travis

* upgrade activiti form component to angular/material

* fix test (remove MDL class check)

* [ADF-613] Add plain text viewer (#1873)

* add plain text viewer

* different devices optimizations

* returns types

* [ADF-573] support for toggling enabled state (#1912)

* [ADF-602] Accordion component - Add basic documentation (#1913)

* Add basic documentation Accordion component

* Update README.md

* [ADF-680] Added previous page check when page has no more elements (#1911)

* [ADF-242] - fix for deleting - saving a report

* [ADF - 242] added test for fixed feature on reports save - delete

* [ADF-680] - Fixed behaviour when deleting all files on last page of document list

* Start adding test for documentlist check

* Added test for check double page load

* [ADF-680] - removed commented test code

* [ADF-680] Added changes from peer review

* [ADF-680] added return type

* [ADF-667] selection mode and row styles (#1914)

* selection mode and row styles

- single/multiple/none selection modes for DataTable component (and Document List)
- support for custom row styles (inline and classname values)
- fix karma config (material themes)
- readme updates
- package-lock.json files for NPM5 support
- updated DataTable demo to demonstrate selection modes and row styles

* remove package lock files

* move demo projects to webpack (#1915)

* wav and Mp3 enabling viewer (#1916)

* add option only demo shell version change for update version script

* ADF-402 add the show diagram button (#1917)

* [ADF-707] Ability to select a row on a dynamic table (#1921)

* [ADF-710] Create an Process Attachment List component (#1919)

* added new component to list the process attachments with view, download and delete functionality

* added unit test cases for activit-process-attachment-list component

* exported new process attachment list component

* added documentation for process-attachment-list component

* [ADF-712] Task Attachment - Provide a way to attach a new content (#1898)

* create button, download, view functionality added in task attachment list component

* created sevice to attach document to task

* added new component to create/uplaod attachment to task

* added new component to create/uplaod attachment to task

* added test case for create task attachment component

* added test case for create task attachment component

* added input to block upload document  to ECM

* fixed create task attachment spec file issue

* changed alfresco-upload to alfresco-core upload directive

* removed attachCreate button and emitter from task-attachment-list

* removed uploadToEcm input and checkValidity method from alfresco-upload

* added documentation for task-attachment-list and create-task-attachment components

* [ADF-696] Entire accordion group header should be clickable (#1918)

* #ADF-696 Added new input to show/hide expand icon, click event is activated for the complete heading

* #ADF-696 tslint fix

* #ADF-696 Added documentation for new input and removed unwanted div

* [ADF-721] Fix translation reference for dev task (#1923)

* move translation files in the bundles folder

* fix after review
ripristinate tslint and remove override tsconfig

* [ADF-709] add autofocus when a new row is added on dynamic table widget (#1927)

* [ADF-709] add autofocus when a new row is added on dynamic table widget

* [ADF-709] removed wrong reference for template

* [ADF-713] Process Attachment - Provide a way to attach a new content (#1920)

* added service to get all the related content of the process instance

* added new component to create/upload attachment for process instance

* added unit test cases for create-process-attachment component

* exported create-process-attachment component

* added documentation for create-process-attachment component

* Add data-automation-id to multi select checkbox (#1924)

* [ADF-571] upload feature rework (#1922)

* upload feature rework

lots of improvements for upload dialog and underlying services

* readme update

- readme cleanup
- remove some old comments from code
- update readme with new events for Upload Service

* restore prerequisites section in readme

* fix i18n issue with webpack

* exported report and chart models (#1925)

* fix file upload bug (#1928)

- proper extraction of File objects from the FileList

* lock files for npm 5 (#1930)

add lock files for npm v5; does nothing for earlier versions, so is not harmful

* Source Mapping is not working on test debugging (#1931)

* coverage single components run fix

* remove spec.ts from coverage

* make the coverage and the istanbul-instrumenter-loader works only over the console test because a problem on the remapping for the browser test

* move tslint on the main folder of any component

* remove build:w from readme

* stop build tslint error also in spec files

* clear karma file from unnecessary files

* add set -f for build all script in order to accept *

* fix lint problem and failing tests

* fix failing test search component

* add loader test for viewer

* fix tslint error userinfo

* --max_old_space_size=2048 remove

* fix tslint error uploader unused EventEmitter

* remove spec|index|.*mock|.*model|.*event from coverage

* move coverage separate file and get component to calculate coverage as input

* remove old 'banned' demo from login screen (#1929)

* add sleep time flag in publish script

* rollback demo tag

* fix pacakge.json tag

* [ADF-686] add blobFile as input (#1933)

* coverage fix (#1934)

* [ADF-702] Task/Process Filter - Provide a way to change the default filter (#1932)

* [ADF-702] Task/Process Filter - Provide a way to change the default filter

* Provide a way to select a custom menu filter

* Improve activiti process filter

* Add internal link

* Change link name

* add link

* [ADF-744] Attachment List is not displayed within Processes. (#1937)

* Use the adf process attachment list indise demo shell

* Change documentation

* support for healdess chrome (#1939)

* #ADF-696 Now accordion opens/closes on click of group header along with emitting heading click event (#1936)

* add info and link on current last git commit (#1940)

* [ADF-754] toolbar component (#1938)

* toolbar component

- simple toolbar component (core lib)
- readme updates (core lib)
- update demo shell with toolbar component demo (document list)

* update unit tests

* [ADF-763] Add Chrome default browser for karma chrome launcher for Chrome versi… (#1941)

* Add Chrome default browser for karma chrome launcher for Chrome version<59

* Fixing intermittently failing test in ng2-activiti-analytics component

* Adding new icon (sent) for Bootstrap to Material icon mapping (#1943)

* fix blob input in text viewer (#1942)

* GitHub issue & pull request template change (#1945)

* Update ISSUE_TEMPLATE.md

* Update PULL_REQUEST_TEMPLATE.md

* [ADF-689] Fix alfresco-document-menu-action styling (z-index) (#1944)

* fix translation wrong folder creation issue

* [ADF-773] Fix datatable custom template render (#1947)

* [ADF-780] centralised call for process filters api (#1950)

* [ADF-780] centralised call for process filters api

* [ADF-780] updated conversion to string

* [ADF-741] Add the create task attachment component to the demo shell (#1946)

* Add the create task attachment component to the demo shell

* Add translation keys

* Add return to methods

* fix thumbnail task process list (#1951)

* [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

* [ADF-589] Login component different bug fixes (#1953)

* Basic style changes

* Further design changes

* Responsive design fixes

* Different sign in button style for the different login steps

* #ADF-780 Fixed getProcessFilterByName to get the correct filter for the given appId and name (#1952)

* fix issues with the require keyword (angular cli) (#1954)

* [ADF-799] add HappyPack to webpack conf (#1956)

* update npm5 lock files

* [ADF-740] Add button for process attachment list (#1955)

* [ADF-740] adding button to allow user to upload related content on process instance

* [ADF-740] add button for attachment content list for process

* changed locatin for translation

* [ADF-740] added test for add button for process attach

* [ADF-740] added PR request changes

* [ADF-802] fix error on uploading file to attachment list (#1957)

* [ADF-802] fix error on uploading file to attachment list

* [ADF-802] improved for loop

* [ADF-797] remove dist folder from npm distributed package , leave src and bundles (#1961)

* [ADF-804] webpack proxy setup to avoid CORS problem (#1960)

* package lock update

* update travis to node 8 (#1965)

* upload service exposes created nodes (#1964)

* [ADF-591] documentation refinements (#1959)

* refine ng2-activiti-analytics

* refine ng2-activiti-diagrams docs

* refine ng2-activiti-form

* refine ng2-activiti-processlist

* refine ng2-activiti-tasklist

* refine ng2-alfresco-core

* refine ng2-alfresco-datatable

* refine ng2-alfresco-datatable

* refine gn2-alfresco-login

* refine ng2-alfresco-search

* refine ng2-alfresco-social

* refine ng2-alfresco-tag

* refine ng2-alfresco-upload

* refine ng2-alfresco-userinfo

* refine ng2-alfresco-viewer

* refine ng2-alfresco-webscript

* various readme cleanups

* fix builds related to node-sass library (#1966)

* update dependencies and remove old lock files

* update sass loader

* updated lock files

* [ADF-578] Remember me functionality (#1962)

* Remember me functionality

* Happy pack to ng2-components' package.json

* Build fix

* Adding tabindices to viewer control elements (#1968)

* karma conf all single browser

* Fix current page number issue (#1970)

* [ADF-524] Datatable loading state (#1958)

* loading state datatable

* modify readme after review

* [ADF-78]  Update CORS help (#1973)

* Fix host configuration in demo-shell when no port is present (#1971)

* remove brachet

* [ADF-494] fixed readonly rendering for forms (#1972)

* [ADF-494] improved disabling for form

* [ADF-494] fixed readonly rendering for forms

* [ADF-814] application configuration service (#1969)

* app configuration service

* api improvements and readme update

* extend readme and add defaults

* unit tests for app config service

* [ADF-716] Task Header - Use a custom view inside the component (#1967)

* Use a generic custom view inside the task header

* Move the component into core component and change name

* Missing file

* Fix unit test

* fix unit test component name

* fix issue with shared Code settings

- remove obsolete rules for .js/.ts
- hide .happypack folder in the project tree

* [ADF-810] fix default value radio button (#1975)

* [ADF-510] Drag&Drop check permission to allow user to upload a file (#1948)

* [ADF-510] added permission check for drag&drop

* Improved code for drag and drop side

* Added test for drag and drop upload area changes

* Added test for document list permissions check

* [ADF-510] rebased branch after changes applied to upload

* [ADF-510] rebased branch and fixed tests

* [ADF-717] upgrade i18n and charting dependencies (#1976)

* remove app-specific polyfill dependencies

remove polyfill dependencies never used by component libraries

* upgrade i18n dependencies

* upgrade ng2-charts dependency

* fix unit tests

* update demo projects

* [ADF-524] Fix empty state after the loading introduction (#1980)

* fix empty state after the loading introduction

* Update document-list.component.spec.ts

remove typo

* [ADF-838] Table of content automatic creation (#1981)

* readonly value set

* Table of content automatic creation (#1982)

* add missing intl dependency for demo shell (#1984)

* [ADF-833] DataTable -  improve the single and double click event (#1979)

* Improve the single and double click event

* Fix unit test

* Task header basic documentation (#1985)

* Disable upload attachment when the task is completed (#1987)

* [ADF-847] upgrade to use application configuration service (#1986)

* migrate core lib to use server-side app config

* fix unit tests

* update Search tests

- update tests
- upgrade tests to use TestBed

* update UserInfo tests

* update Social tests

* update tests

* update unit tests

* cleanup old code

* update about page

* update demo shell readme

* dev and prod configurations

* updated package-lock file and removed duplicates in package.json

* [ADF-851] execute-outcome event for form service (#1989)

* execute-outcome event for form service

* readme updates

* fix loading state excluding other state during the loading (#1991)

* Fix compilation error (#1993)

* [ADF-883] Fix build errors (#1992)

* [ADF-793] Ability to create PDF renditions in case of non supported formats (#1994)

* Style changes and button

* Convert to PDF button

* Convert to PDF button part II.

* Convert within the Not Supported Format component

* Rendition loading skeleton

* Conversion is working.

* Convert button behaviour tests

* Rebasing fix.

* app settings page (#1997)

- custom app setttings service to use isolated storage (demo shell)
- restore settings UI
- redirect angular and rxjs to the same version as components use.

* [ADF-822] Added the npm-prepublish script (#1978)

* added the npm prepublish script

* changed permissions to prepublish script

* changed to npm run prepublish

* prepare the pr

* removed useless code to the script

* remove flags lib from demo shell (#1983)

* remove flags lib from demo shell

greatly reduce demo shell webpack resources by switching off flags (only 3 icons were displayed in the past)

* merge package.json

* add icons

* Fix typo error

* [ADF-794] Add people assignment component (#1977)

* Add people component

* exported people service

* added people-list component to show the involved user list

* changed people-search component layout

* changed people-list usage in people component

* changed people-list data table from custom template to data adapter

* changes people-search component related to people-list

* changes in activiti-people related to people-list and people-search component

* changed data adapter to direct data column setting to data-table

* removed ngChanges and added User and UserEvent models

* added User and UserEvent model in emitter and other emitter handler

* added user event model

* changed activiti-people component with latest UX changes

* addedand changed translate keys to the components

* added hasUser method to check the condition in html

* fixed tslint issue and test case issue in activiti-people component

* added test case for actviti-people-list component

* test case added for activiti-people-search component

* changed activiti-people test cases according to latest UX changes

* added description for activiti-people component

* changed test case to fix component.upgradeElement issue

* changes requested by Vito Albano #1

* splitted getDisplayUser into getDisplayUser and getInitialUsername

* introduce check type definition

* [ADF-897] - ActivitiPeopleList - use the adf prefix (#2001)

* Use the adf as prexif instead of activiti

* Fix typo

* Fix wrong import

* support binding [form] data directly (#1996)

- ability to bind [form] data directly inside `<activiti-form>` component
- ability to parse forms with FormService
- demo of the custom form in demo shell

* [ADF-778] cancel window for upload dialog shows only on complete (#2003)

* [ADF-778] Added new behaviour to upload dialog

* [ADF-778] cancel window for upload dialog shows only on complete

* [ADF-778] changed variable name to showCloseButton

* Create task/process attachment Compilation error (#2004)

* fix tslint errors

minor fix for "Unnecessary semicolon" TSLint rule

* [ADF-842] Fixed type for taskdetails (#2009)

* fix type definition (#2002)

* Use the activiti people with the new look and feel inside the demo shell (#2008)

* add rxjs and @angular in tsconfig.json

* [ADF-843] Form events bus (#1990)

* form events bus

* event test bus fix

* fix test after code review

* fix types errors

* change to public formservice

* make optional formservice

* [ADF-915] Add option to change the JS-API with different version in the update package

* Missing keys (#2011)

* [ADF-845] breadcrumb root option added and style review (#1999)

* breadcrumb root option added and style review

* new breadcrumbs

* split onchange in a method

* update readme with a note for old pefix tag

* fix tslint errors

* fix breadcrumb test

* [ADF-922] Regenerate package-lock.json files for every package and create script for doing that in the future (#2012)

* Updated package-lock.json files

* npm-relock-pkgs.sh

* Update README.md

* Fix ng2-alfresco-search sass problem

* SASS version update (#2013)

* sass update

* update sass loader

* vjsapi option prepublish

* prepublish script deprecation in favour of prepublishOnly node 8 (#2010)

* modify prePublish script with preoPublishOnly

* install rimraf globally

* fix clean scripts demo folders

* move appveyor to node 8

* Appveyor test (#1998)

* reduce memory

* remove max-old-space

* remove increase memory

* create new TaskDetailsModel in loadNextTask (#2017)

* Fix readme document list

* [ADF-907] - Form reacts to data added in input (#2016)

* [ADF-907] Enable activiti form to react on value data changes

* [ADF-907] - Form reacts to data added in input]

* [ADF - 907] added mock json for form

* [ADF-907] added new event of the form to the event list

* [ADF - 907] Added return column to README

* [ADF - 907] Added return column to README

* Script add pkg and clean update

* install globally pkg pre build

* Fix upload related content (#2019)

* regeneration TOC and add automatic list component generator (#2022)

* Fix upload process attachment (#2024)

* update typescript (#2026)

* update viewer readme

* fix type definition variables

* NgZone type passed parameter

* fix tslint error in tasklist

* Add screenshot (#2028)

* fix search miss typing

* bump version 1.6.0 (#2027)
This commit is contained in:
Eugenio Romano
2017-06-29 15:57:37 +01:00
committed by GitHub
parent c6f6227da7
commit b15ab3b988
822 changed files with 168022 additions and 13865 deletions

View File

@@ -14,7 +14,7 @@
}
:host .file-dialog {
width: 700px;
width: 550px;
display: none;
-webkit-box-shadow: 0 2px 8px 0 rgba(0, 0, 0, .2);
box-shadow: -2px -1px 8px 3px rgba(0, 0, 0, .2);

View File

@@ -9,7 +9,7 @@
<i class="material-icons up" title="expand upload list">keyboard_arrow_up</i>
</div>
<div class="close-button" (click)="toggleVisible()" (keyup.enter)="toggleVisible()" tabindex="0" title="close upload list">
<div *ngIf="showCloseButton" id="button-close-upload-list" class="close-button" (click)="toggleVisible()" (keyup.enter)="toggleVisible()" tabindex="0" title="close upload list">
<i class="material-icons">clear</i>
</div>
</div>

View File

@@ -16,12 +16,14 @@
*/
import { DebugElement } from '@angular/core';
import { MdProgressSpinnerModule } from '@angular/material';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { FileUploadingDialogComponent } from './file-uploading-dialog.component';
import { FileUploadingListComponent } from './file-uploading-list.component';
import { UploadService } from '../services/upload.service';
import { FileModel } from '../models/file.model';
import { FileModel, FileUploadStatus } from '../models/file.model';
import { FileUploadCompleteEvent, FileUploadEvent } from '../events/file.event';
describe('FileUploadingDialogComponent', () => {
@@ -35,7 +37,8 @@ describe('FileUploadingDialogComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreModule.forRoot()
CoreModule.forRoot(),
MdProgressSpinnerModule
],
declarations: [
FileUploadingDialogComponent,
@@ -48,12 +51,7 @@ describe('FileUploadingDialogComponent', () => {
}));
beforeEach(() => {
window['componentHandler'] = null;
let fileFake = {
id: 'fake-id',
name: 'fake-name'
};
const fileFake = new File([''], 'fake-name');
file = new FileModel(fileFake);
fixture = TestBed.createComponent(FileUploadingDialogComponent);
@@ -73,14 +71,14 @@ describe('FileUploadingDialogComponent', () => {
});
it('should render completed upload 1 when an element is added to Observer', () => {
uploadService.updateFileCounterStream(1);
uploadService.fileUploadComplete.next(new FileUploadCompleteEvent(null, 1));
fixture.detectChanges();
expect(element.querySelector('#total-upload-completed').innerText).toEqual('1');
});
it('should render dialog box with css class show when an element is added to Observer', () => {
uploadService.addToQueue([<File> { name: 'file' }]);
uploadService.addToQueue(new FileModel(<File> { name: 'file' }));
component.filesUploadingList = [file];
fixture.detectChanges();
@@ -112,4 +110,38 @@ describe('FileUploadingDialogComponent', () => {
expect(element.querySelector('.minimize-button').getAttribute('class')).toEqual('minimize-button active');
});
it('should show the close button when the file upload is completed', async(() => {
component.isDialogActive = true;
uploadService.addToQueue(new FileModel(<File> { name: 'file' }));
fixture.detectChanges();
fixture.whenStable().then(() => {
let closeButton = element.querySelector('#button-close-upload-list');
expect(closeButton).not.toBeNull();
});
uploadService.fileUpload.next(new FileUploadCompleteEvent(file, 1, { status: FileUploadStatus.Complete }, 0));
}));
it('should show the close button when the file upload is in error', async(() => {
component.isDialogActive = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
let closeButton = element.querySelector('#button-close-upload-list');
expect(closeButton).not.toBeNull();
});
uploadService.fileUpload.next(new FileUploadEvent(file, FileUploadStatus.Error));
}));
it('should show the close button when the file upload is cancelled', async(() => {
component.isDialogActive = true;
fixture.detectChanges();
fixture.whenStable().then(() => {
let closeButton = element.querySelector('#button-close-upload-list');
expect(closeButton).not.toBeNull();
});
uploadService.fileUpload.next(new FileUploadEvent(file, FileUploadStatus.Cancelled));
}));
});

View File

@@ -15,24 +15,15 @@
* limitations under the License.
*/
import { Component, Input, ChangeDetectorRef, OnInit, OnDestroy } from '@angular/core';
import { FileModel } from '../models/file.model';
import { Component, Input, ChangeDetectorRef, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { FileModel, FileUploadStatus } from '../models/file.model';
import { AlfrescoTranslationService } from 'ng2-alfresco-core';
import { UploadService } from '../services/upload.service';
import { FileUploadCompleteEvent } from '../events/file.event';
/**
* <file-uploading-dialog [filesUploadingList]="FileModel[]"></file-uploading-dialog>
*
* This component is a hideable and minimizable wich contains the list of the uploading
* files contained in the filesUploadingList.
*
* @InputParam {FileModel[]} filesUploadingList - list of the uploading files .
*
*
* @returns {FileUploadingDialogComponent} .
*/
@Component({
selector: 'file-uploading-dialog',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './file-uploading-dialog.component.html',
styleUrls: ['./file-uploading-dialog.component.css']
})
@@ -48,34 +39,41 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
private listSubscription: any;
private counterSubscription: any;
private showCloseButton: boolean = false;
constructor(private cd: ChangeDetectorRef,
translateService: AlfrescoTranslationService,
private uploadService: UploadService) {
if (translateService) {
translateService.addTranslationFolder('ng2-alfresco-upload', 'node_modules/ng2-alfresco-upload/src');
translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload');
}
cd.detach();
}
ngOnInit() {
if (this.uploadService.filesUpload$) {
this.listSubscription = this.uploadService.filesUpload$.subscribe((fileList: FileModel[]) => {
this.filesUploadingList = fileList;
if (this.filesUploadingList.length > 0) {
this.isDialogActive = true;
this.cd.detectChanges();
}
});
}
if (this.uploadService.totalCompleted$) {
this.counterSubscription = this.uploadService.totalCompleted$.subscribe((total: number) => {
this.totalCompleted = total;
if (this.totalCompleted > 1) {
this.totalCompletedMsg = 'FILE_UPLOAD.MESSAGES.COMPLETED';
}
this.listSubscription = this.uploadService.queueChanged.subscribe((fileList: FileModel[]) => {
this.filesUploadingList = fileList;
if (this.filesUploadingList.length > 0) {
this.isDialogActive = true;
this.cd.detectChanges();
});
}
}
this.showCloseButton = false;
});
this.counterSubscription = this.uploadService.fileUploadComplete.subscribe((event: FileUploadCompleteEvent) => {
this.totalCompleted = event.totalComplete;
if (this.totalCompleted > 1) {
this.totalCompletedMsg = 'FILE_UPLOAD.MESSAGES.COMPLETED';
}
this.cd.detectChanges();
});
this.uploadService.fileUpload.subscribe((event: FileUploadCompleteEvent) => {
if (event.status !== FileUploadStatus.Progress) {
this.isUploadProcessCompleted(event);
}
this.cd.detectChanges();
});
}
/**
@@ -83,6 +81,8 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
*/
toggleVisible(): void {
this.isDialogActive = !this.isDialogActive;
this.uploadService.clearQueue();
this.cd.detectChanges();
}
/**
@@ -90,11 +90,31 @@ export class FileUploadingDialogComponent implements OnInit, OnDestroy {
*/
toggleMinimized(): void {
this.isDialogMinimized = !this.isDialogMinimized;
this.cd.detectChanges();
}
ngOnDestroy() {
this.listSubscription.unsubscribe();
this.counterSubscription.unsubscribe();
this.cd.detach();
}
private isUploadProcessCompleted(event: FileUploadCompleteEvent) {
if (this.isAllFileUploadEnded(event) && this.isUploadStateCompleted(event.status)) {
this.showCloseDialogButton();
} else if (event.status === FileUploadStatus.Error || event.status === FileUploadStatus.Cancelled) {
this.showCloseDialogButton();
}
}
private showCloseDialogButton() {
this.showCloseButton = true;
}
private isAllFileUploadEnded(event: FileUploadCompleteEvent) {
return event.totalComplete === this.uploadService.getQueue().length - event.totalAborted;
}
private isUploadStateCompleted(state): boolean {
return FileUploadStatus.Complete === state;
}
}

View File

@@ -3,8 +3,8 @@
border: 0px;
}
.cursor {
cursor: pointer;
.center {
text-align: center;
}
.body-dialog-header {
@@ -42,62 +42,40 @@
width: 100%;
}
:host .truncate {
margin-left: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.cancel-upload-button {
cursor: pointer;
}
:host .mdl-progress {
width: 150px;
}
@media (max-device-width: 360px) {
.truncate {
max-width: 50px;
margin-left: 0px;
}
}
@media (max-device-width: 568px) {
.truncate {
width: 60px;
}
.mdl-progress {
width: 60px;
}
}
@media (max-width: 740px) {
.truncate {
max-width: 80px;
}
.mdl-progress {
max-width: 70px;
}
.size-column {
display: none;
}
}
@media (min-width: 740px) {
.truncate {
width: 249px;
}
.size-column {
display: table-cell;
}
.file-progress-spinner {
height: 24px;
width: 100%;
text-align: center;
}
.full-width {
width: 100%;
}
.no-width {
width: 0%;
.ellipsis-cell .cell-container {
height: 1em;
}
/* visible content */
.ellipsis-cell .cell-value {
display: block;
position: absolute;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1em; /* for vertical align of text */
}
/* cell stretching content */
.ellipsis-cell > div:after {
content: attr(title);
overflow: hidden;
height: 0;
display: block;
}

View File

@@ -6,32 +6,37 @@
</div>
<table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
<tr>
<th class="mdl-data-table__cell--non-numeric">{{'FILE_UPLOAD.FILE_INFO.NAME' | translate}}</th>
<th class="mdl-data-table__cell--non-numeric">{{'FILE_UPLOAD.FILE_INFO.PROGRESS' | translate}}</th>
<th class="mdl-data-table__cell--non-numeric mdl-cell--hide-phone size-column">{{'FILE_UPLOAD.FILE_INFO.SIZE' | translate}}</th>
<th class="mdl-data-table__cell--non-numeric">{{'FILE_UPLOAD.FILE_INFO.ACTION' | translate}}</th>
<th class="mdl-data-table__cell--non-numeric full-width">{{'ADF_FILE_UPLOAD.FILE_LIST.NAME' | translate}}</th>
<th class="mdl-data-table__cell center">{{'ADF_FILE_UPLOAD.FILE_LIST.PROGRESS' | translate}}</th>
<th class="mdl-data-table__cell mdl-cell--hide-phone size-column center">{{'ADF_FILE_UPLOAD.FILE_LIST.SIZE' | translate}}</th>
<th class="mdl-data-table__cell center">{{'ADF_FILE_UPLOAD.FILE_LIST.ACTION' | translate}}</th>
</tr>
<tr *ngFor="let file of files" tabindex="0">
<td class="mdl-data-table__cell--non-numeric" attr.data-automation-id="dialog_{{file.name}}">
<div class="truncate">{{file.name}}</div>
</td>
<td class="mdl-data-table__cell--non-numeric">
<div class="mdl-progress mdl-js-progress is-upgraded" id="{{file.id}}">
<div class="progressbar bar bar1" attr.data-automation-id="dialog_progress_{{file.name}}" [style.width.%]="file.progress.percent"></div>
<div class="bufferbar bar bar2" class="full-width"></div>
<div class="auxbar bar bar3" class="no-width"></div>
<td class="mdl-data-table__cell--non-numeric full-width ellipsis-cell" attr.data-automation-id="dialog_{{file.name}}">
<div class="cell-container">
<div class="cell-value" [title]="file.name">{{file.name}}</div>
</div>
</td>
<td class="mdl-data-table__cell--non-numeric mdl-cell--hide-phone size-column" attr.data-automation-id="{{file.name}}_filesize">{{file.size}}</td>
<td class="mdl-data-table__cell--non-numeric">
<span *ngIf="file.done && !file.abort">
<i data-automation-id="done_icon" class="material-icons action-icons">done</i>
<td class="mdl-data-table__cell center">
<md-icon *ngIf="file.status === FileUploadStatus.Error || file.status === FileUploadStatus.Aborted">error_outline</md-icon>
<md-icon *ngIf="file.status === FileUploadStatus.Cancelled">block</md-icon>
<ng-container *ngIf="file.status === FileUploadStatus.Progress">
<md-progress-spinner
class="file-progress-spinner"
[mode]="'determinate'"
[value]="file.progress.percent">
</md-progress-spinner>
</ng-container>
</td>
<td class="mdl-data-table__cell mdl-cell--hide-phone size-column center" attr.data-automation-id="{{file.name}}_filesize">
{{ file.size | adfFileSize }}
</td>
<td class="mdl-data-table__cell center">
<span *ngIf="file.status === FileUploadStatus.Complete">
<md-icon>done</md-icon>
</span>
<span *ngIf="file.uploading" (click)="cancelFileUpload(file)" class="cursor" tabindex="0">
<i data-automation-id="abort_cancel_upload" class="material-icons action-icons">remove_circle_outline</i>
</span>
<span *ngIf="file.abort">
<i class="material-icons action-icons" data-automation-id="upload_stopped" tabindex="0">remove_circle</i>
<span *ngIf="file.status === FileUploadStatus.Progress" (click)="cancelFileUpload(file)" tabindex="0" class="cancel-upload-button">
<md-icon>remove_circle_outline</md-icon>
</span>
</td>
</tr>

View File

@@ -16,18 +16,9 @@
*/
import { Component, Input } from '@angular/core';
import { FileModel } from '../models/file.model';
import { FileModel, FileUploadStatus } from '../models/file.model';
import { UploadService } from '../services/upload.service';
/**
* <alfresco-file-uploading-list [files]="files"></alfresco-file-uploading-list>
*
* This component show a list of the uploading files contained in the filesUploadingList.
*
* @InputParam {FileModel[]} filesUploadingList - list of the uploading files .
*
*
* @returns {FileUploadingListComponent} .
*/
@Component({
selector: 'alfresco-file-uploading-list',
templateUrl: './file-uploading-list.component.html',
@@ -35,9 +26,14 @@ import { FileModel } from '../models/file.model';
})
export class FileUploadingListComponent {
FileUploadStatus = FileUploadStatus;
@Input()
files: FileModel[];
constructor(private uploadService: UploadService) {
}
/**
* Cancel file upload
*
@@ -46,9 +42,7 @@ export class FileUploadingListComponent {
* @memberOf FileUploadingListComponent
*/
cancelFileUpload(file: FileModel): void {
if (file) {
file.emitAbort();
}
this.uploadService.cancelUpload(file);
}
/**
@@ -58,21 +52,20 @@ export class FileUploadingListComponent {
if (event) {
event.preventDefault();
}
this.files.forEach((uploadingFileModel: FileModel) => {
uploadingFileModel.emitAbort();
});
this.uploadService.cancelUpload(...this.files);
}
/**
* Verify if all the files are in state done or abort
* @returns {boolean} - false if there is a file in progress
* Check if all the files are not in the Progress state.
* @returns {boolean} - false if there is at least one file in Progress
*/
isUploadCompleted(): boolean {
let isPending = false;
let isAllCompleted = true;
for (let i = 0; i < this.files.length && !isPending; i++) {
let file = this.files[i];
if (!file.done && !file.abort) {
if (file.status === FileUploadStatus.Progress) {
isPending = true;
isAllCompleted = false;
}

View File

@@ -15,10 +15,10 @@
* limitations under the License.
*/
import { DebugElement, SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { UploadButtonComponent } from './upload-button.component';
import { DebugElement, SimpleChange } from '@angular/core';
import { CoreModule, AlfrescoTranslationService, NotificationService } from 'ng2-alfresco-core';
import { CoreModule, AlfrescoTranslationService, AlfrescoContentService} from 'ng2-alfresco-core';
import { TranslationMock } from '../assets/translation.service.mock';
import { UploadService } from '../services/upload.service';
import { Observable } from 'rxjs/Rx';
@@ -33,27 +33,6 @@ describe('UploadButtonComponent', () => {
target: {value: 'fake-name-1'}
};
let fakeResolveRest = {
entry: {
isFile: false,
isFolder: true,
name: 'fake-folder1'
}
};
let fakeResolvePromise = new Promise(function (resolve, reject) {
resolve(fakeResolveRest);
});
let fakeRejectRest = {
response: {
body: {
error: {
statusCode: 409
}
}
}
};
let fakeFolderNodeWithoutPermission = {
allowableOperations: [
'update'
@@ -73,15 +52,12 @@ describe('UploadButtonComponent', () => {
nodeType: 'cm:folder'
};
let fakeRejectPromise = new Promise(function (resolve, reject) {
reject(fakeRejectRest);
});
let component: UploadButtonComponent;
let fixture: ComponentFixture<UploadButtonComponent>;
let debug: DebugElement;
let element: HTMLElement;
let uploadService: UploadService;
let contentService: AlfrescoContentService;
beforeEach(async(() => {
TestBed.configureTestingModule({
@@ -93,7 +69,6 @@ describe('UploadButtonComponent', () => {
],
providers: [
UploadService,
NotificationService,
{provide: AlfrescoTranslationService, useClass: TranslationMock}
]
}).compileComponents();
@@ -104,6 +79,7 @@ describe('UploadButtonComponent', () => {
fixture = TestBed.createComponent(UploadButtonComponent);
uploadService = TestBed.get(UploadService);
contentService = TestBed.get(AlfrescoContentService);
debug = fixture.debugElement;
element = fixture.nativeElement;
@@ -141,7 +117,7 @@ describe('UploadButtonComponent', () => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = false;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
fixture.detectChanges();
@@ -160,7 +136,7 @@ describe('UploadButtonComponent', () => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = true;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithoutPermission));
component.onFilesAdded(fakeEvent);
let compiled = fixture.debugElement.nativeElement;
@@ -173,7 +149,7 @@ describe('UploadButtonComponent', () => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = true;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) });
component.onFilesAdded(fakeEvent);
@@ -187,7 +163,7 @@ describe('UploadButtonComponent', () => {
component.rootFolderId = '-my-';
component.disableWithNoPermission = false;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) });
component.onFilesAdded(fakeEvent);
@@ -202,7 +178,7 @@ describe('UploadButtonComponent', () => {
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
component.onSuccess = null;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) });
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
@@ -210,7 +186,7 @@ describe('UploadButtonComponent', () => {
fixture.detectChanges();
component.onFilesAdded(fakeEvent);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/folder-fake', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
it('should call uploadFile with a custom root folder', () => {
@@ -218,7 +194,7 @@ describe('UploadButtonComponent', () => {
component.rootFolderId = '-my-';
component.onSuccess = null;
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) });
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
@@ -226,18 +202,19 @@ describe('UploadButtonComponent', () => {
fixture.detectChanges();
component.onFilesAdded(fakeEvent);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-my-', '/root-fake-/sites-fake/folder-fake', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
it('should create a folder and emit an File uploaded event', (done) => {
component.rootFolderId = '-my-';
component.currentFolderPath = '/fake-root-path';
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeResolvePromise);
spyOn(contentService, 'createFolder').and.returnValue(Observable.of(true));
spyOn(component, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) });
fixture.detectChanges();
component.onSuccess.subscribe(e => {
expect(e.value).toEqual('File uploaded');
done();
@@ -245,28 +222,12 @@ describe('UploadButtonComponent', () => {
spyOn(component, 'uploadFiles').and.callFake(() => {
component.onSuccess.emit({
value: 'File uploaded'
}
);
value: 'File uploaded'
});
});
component.onDirectoryAdded(fakeEvent);
});
it('should emit an onError event when the folder already exist', (done) => {
component.rootFolderId = '-my-';
spyOn(uploadService, 'getFolderNode').and.returnValue(Observable.of(fakeFolderNodeWithPermission));
spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakeRejectPromise);
component.ngOnChanges({ rootFolderId: new SimpleChange(null, component.rootFolderId, true) });
component.onError.subscribe(e => {
expect(e.value).toEqual('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST');
done();
});
component.onDirectoryAdded(fakeEvent);
});
it('should by default the title of the button get from the JSON file', () => {
let compiled = fixture.debugElement.nativeElement;
fixture.detectChanges();

View File

@@ -16,36 +16,13 @@
*/
import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Subject } from 'rxjs/Rx';
import { AlfrescoTranslationService, LogService, NotificationService, AlfrescoSettingsService } from 'ng2-alfresco-core';
import { Observable, Subject } from 'rxjs/Rx';
import { AlfrescoApiService, AlfrescoContentService, AlfrescoTranslationService, LogService, NotificationService, FileUtils } from 'ng2-alfresco-core';
import { MinimalNodeEntryEntity } from 'alfresco-js-api';
import { UploadService } from '../services/upload.service';
import { FileModel } from '../models/file.model';
import { PermissionModel } from '../models/permissions.model';
declare let componentHandler: any;
const ERROR_FOLDER_ALREADY_EXIST = 409;
/**
* <alfresco-upload-button [showNotificationBar]="boolean"
* [uploadFolders]="boolean"
* [multipleFiles]="boolean"
* [acceptedFilesType]="string"
* (onSuccess)="customMethod($event)">
* </alfresco-upload-button>
*
* This component, provide a set of buttons to upload files to alfresco.
*
* @InputParam {boolean} [true] showNotificationBar - hide/show notification bar.
* @InputParam {boolean} [false] versioning - true to indicate that a major version should be created
* @InputParam {boolean} [false] uploadFolders - allow/disallow upload folders (only for chrome).
* @InputParam {boolean} [false] multipleFiles - allow/disallow multiple files.
* @InputParam {string} [*] acceptedFilesType - array of allowed file extensions.
* @InputParam {boolean} [false] versioning - true to indicate that a major version should be created
* @Output - onSuccess - The event is emitted when the file is uploaded
*
* @returns {UploadButtonComponent} .
*/
@Component({
selector: 'alfresco-upload-button',
templateUrl: './upload-button.component.html',
@@ -53,11 +30,15 @@ const ERROR_FOLDER_ALREADY_EXIST = 409;
})
export class UploadButtonComponent implements OnInit, OnChanges {
private static DEFAULT_ROOT_ID: string = '-root-';
@Input()
disabled: boolean = false;
/**
* @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead.
*
* @type {boolean}
* @memberof UploadButtonComponent
*/
@Input()
showNotificationBar: boolean = true;
@@ -76,11 +57,17 @@ export class UploadButtonComponent implements OnInit, OnChanges {
@Input()
staticTitle: string;
/**
* @deprecated Deprecated in 1.6.0, this property is not used for couple of releases already.
*
* @type {string}
* @memberof UploadDragAreaComponent
*/
@Input()
currentFolderPath: string = '/';
@Input()
rootFolderId: string = UploadButtonComponent.DEFAULT_ROOT_ID;
rootFolderId: string = '-root-';
@Input()
disableWithNoPermission: boolean = false;
@@ -106,17 +93,14 @@ export class UploadButtonComponent implements OnInit, OnChanges {
private translateService: AlfrescoTranslationService,
private logService: LogService,
private notificationService: NotificationService,
private settingsService: AlfrescoSettingsService) {
private apiService: AlfrescoApiService,
private contentService: AlfrescoContentService) {
if (translateService) {
translateService.addTranslationFolder('ng2-alfresco-upload', 'node_modules/ng2-alfresco-upload/src');
translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload');
}
}
ngOnInit() {
this.settingsService.ecmHostSubject.subscribe((hostEcm: string) => {
this.checkPermission();
});
this.permissionValue.subscribe((permission: boolean) => {
this.hasPermission = permission;
});
@@ -127,8 +111,6 @@ export class UploadButtonComponent implements OnInit, OnChanges {
if (rootFolderId && rootFolderId.currentValue) {
this.checkPermission();
}
let formFields = this.createFormFields();
this.uploadService.setOptions(formFields, this.versioning);
}
isButtonDisabled(): boolean {
@@ -143,16 +125,11 @@ export class UploadButtonComponent implements OnInit, OnChanges {
return !this.hasPermission && this.disableWithNoPermission ? true : undefined;
}
/**
* Method called when files are dropped in the drag area.
*
* @param {File[]} files - files dropped in the drag area.
*/
onFilesAdded($event: any): void {
let files = $event.currentTarget.files;
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
if (this.hasPermission) {
this.uploadFiles(this.currentFolderPath, files);
this.uploadFiles(files);
} else {
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
}
@@ -160,38 +137,10 @@ export class UploadButtonComponent implements OnInit, OnChanges {
$event.target.value = '';
}
/**
* Method called when a folder is dropped in the drag area.
*
* @param {File[]} files - files of a folder dropped in the drag area.
*/
onDirectoryAdded($event: any): void {
let files = $event.currentTarget.files;
if (this.hasPermission) {
let hashMapDir = this.convertIntoHashMap(files);
hashMapDir.forEach((filesDir, directoryPath) => {
let directoryName = this.getDirectoryName(directoryPath);
let absolutePath = this.currentFolderPath + this.getDirectoryPath(directoryPath);
this.uploadService.createFolder(absolutePath, directoryName, this.rootFolderId)
.subscribe(
res => {
let relativeDir = this.currentFolderPath + '/' + directoryPath;
this.uploadFiles(relativeDir, filesDir);
},
error => {
let errorMessagePlaceholder = this.getErrorMessage(error.response);
if (errorMessagePlaceholder) {
this.onError.emit({value: errorMessagePlaceholder});
let errorMessage = this.formatString(errorMessagePlaceholder, [directoryName]);
if (errorMessage) {
this._showErrorNotificationBar(errorMessage);
}
}
}
);
});
let files: File[] = FileUtils.toFileArray($event.currentTarget.files);
this.uploadFiles(files);
} else {
this.permissionEvent.emit(new PermissionModel({type: 'content', action: 'upload', permission: 'create'}));
}
@@ -201,147 +150,70 @@ export class UploadButtonComponent implements OnInit, OnChanges {
/**
* Upload a list of file in the specified path
* @param path
* @param files
* @param path
*/
uploadFiles(path: string, files: any[]) {
if (files.length) {
let latestFilesAdded = this.uploadService.addToQueue(files);
this.uploadService.uploadFilesInTheQueue(this.rootFolderId, path, this.onSuccess);
uploadFiles(files: File[]): void {
if (files.length > 0) {
const latestFilesAdded = files.map(file => new FileModel(file, {
newVersion: this.versioning,
parentId: this.rootFolderId,
path: (file.webkitRelativePath || '').replace(/\/[^\/]*$/, '')
}));
this.uploadService.addToQueue(...latestFilesAdded);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
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: any[]) {
let directoryMap = new Map<string, Object[]>();
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;
}
/**
* Split the directory path given as input and cut the last directory name
* @param directory
* @returns {string}
*/
private getDirectoryPath(directory: 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) {
let dirPath = directory.split('/');
if (dirPath.length > 1) {
return dirPath.pop();
} else {
return dirPath[0];
}
}
/**
* Show undo notification bar.
*
* @param {FileModel[]} latestFilesAdded - files in the upload queue enriched with status flag and xhr object.
*/
private _showUndoNotificationBar(latestFilesAdded: FileModel[]) {
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).afterDismissed().subscribe(() => {
latestFilesAdded.forEach((uploadingFileModel: FileModel) => {
uploadingFileModel.emitAbort();
});
this.notificationService.openSnackMessageAction(messageTranslate.value, actionTranslate.value, 3000).onAction().subscribe(() => {
this.uploadService.cancelUpload(...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 && response.body.error.statusCode === ERROR_FOLDER_ALREADY_EXIST) {
let errorMessage: any;
errorMessage = this.translateService.get('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST');
return errorMessage.value;
}
}
/**
* Show the error inside Notification bar
* @param Error message
* @private
*/
private _showErrorNotificationBar(errorMessage: string) {
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 []) {
let i = keys.length;
while (i--) {
message = message.replace(new RegExp('\\{' + i + '\\}', 'gm'), keys[i]);
}
return message;
}
private createFormFields(): any {
return {
formFields: {
overwrite: true
}
};
}
checkPermission() {
if (this.rootFolderId) {
this.uploadService.getFolderNode(this.rootFolderId).subscribe(
(res) => {
this.permissionValue.next(this.hasCreatePermission(res));
},
(error) => {
this.onError.emit(error);
}
this.getFolderNode(this.rootFolderId).subscribe(
res => this.permissionValue.next(this.hasCreatePermission(res)),
error => this.onError.emit(error)
);
}
}
// TODO: move to AlfrescoContentService
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 (this.hasPermissions(node)) {
if (node && node.allowableOperations) {
return node.allowableOperations.find(permision => permision === 'create') ? true : false;
}
return false;
}
private hasPermissions(node: any): boolean {
return node && node.allowableOperations ? true : false;
}
}

View File

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

View File

@@ -16,12 +16,49 @@
*/
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { EventEmitter, DebugElement } from '@angular/core';
import { AlfrescoTranslationService, CoreModule, LogService, LogServiceMock, NotificationService } from 'ng2-alfresco-core';
import { DebugElement } from '@angular/core';
import { AlfrescoTranslationService, CoreModule, LogService, LogServiceMock } from 'ng2-alfresco-core';
import { UploadDragAreaComponent } from './upload-drag-area.component';
import { FileDraggableDirective } from '../directives/file-draggable.directive';
import { TranslationMock } from '../assets/translation.service.mock';
import { UploadService } from '../services/upload.service';
import { FileModel } from '../models/file.model';
let fakeShareDataRow = {
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: [
'delete',
'update',
'create'
]
}
}
};
describe('UploadDragAreaComponent', () => {
@@ -38,11 +75,11 @@ describe('UploadDragAreaComponent', () => {
CoreModule.forRoot()
],
declarations: [
FileDraggableDirective,
UploadDragAreaComponent
],
providers: [
UploadService,
NotificationService,
{ provide: AlfrescoTranslationService, useClass: TranslationMock },
{ provide: LogService, useClass: LogServiceMock }
]
@@ -65,20 +102,22 @@ describe('UploadDragAreaComponent', () => {
TestBed.resetTestingModule();
});
it('should upload the list of files dropped', () => {
it('should upload the list of files dropped', (done) => {
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
component.onSuccess = null;
component.showNotificationBar = false;
uploadService.addToQueue = jasmine.createSpy('addToQueue');
uploadService.uploadFilesInTheQueue = jasmine.createSpy('uploadFilesInTheQueue');
fixture.detectChanges();
let fileFake = <File> {name: 'fake-name-1', size: 10, webkitRelativePath: 'fake-folder1/fake-name-1.json'};
let filesList = [fileFake];
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);
expect(uploadService.addToQueue).toHaveBeenCalledWith(filesList);
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', () => {
@@ -93,7 +132,7 @@ describe('UploadDragAreaComponent', () => {
let filesList = [fileFake];
component.onFilesDropped(filesList);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/folder-fake', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
expect(component.showUndoNotificationBar).toHaveBeenCalled();
});
@@ -116,8 +155,7 @@ describe('UploadDragAreaComponent', () => {
};
component.onFilesEntityDropped(itemEntity);
expect(uploadService.uploadFilesInTheQueue)
.toHaveBeenCalledWith('-root-', '/root-fake-/sites-fake/document-library-fake/folder-fake/', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
it('should upload a file with a custom root folder ID when dropped', () => {
@@ -140,50 +178,15 @@ describe('UploadDragAreaComponent', () => {
};
component.onFilesEntityDropped(itemEntity);
expect(uploadService.uploadFilesInTheQueue)
.toHaveBeenCalledWith('-my-', '/root-fake-/sites-fake/document-library-fake/folder-fake/', null);
expect(uploadService.uploadFilesInTheQueue).toHaveBeenCalledWith(null);
});
xit('should throws an exception and show it in the notification bar when the folder already exist', done => {
component.currentFolderPath = '/root-fake-/sites-fake/folder-fake';
component.showNotificationBar = true;
fixture.detectChanges();
let fakeRest = {
response: {
body: {
error: {
statusCode: 409
}
}
}
};
let fakePromise = new Promise(function (resolve, reject) {
reject(fakeRest);
});
spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakePromise);
spyOn(component, 'showErrorNotificationBar').and.callFake( () => {
expect(component.showErrorNotificationBar).toHaveBeenCalledWith('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST');
done();
});
let folderEntry = {
fullPath: '/folder-duplicate-fake',
isDirectory: true,
isFile: false,
name: 'folder-duplicate-fake'
};
component.onFolderEntityDropped(folderEntry);
});
it('should create a folder and call onFilesEntityDropped with the file inside the folder', done => {
it('should upload a file when user has create permission on target folder', async(() => {
component.currentFolderPath = '/root-fake-/sites-fake/document-library-fake';
component.onSuccess = new EventEmitter();
component.rootFolderId = '-my-';
component.enabled = false;
fixture.detectChanges();
let itemEntity = {
let fakeItem = {
fullPath: '/folder-fake/file-fake.png',
isDirectory: false,
isFile: true,
@@ -194,41 +197,20 @@ describe('UploadDragAreaComponent', () => {
}
};
let fakeRest = {
entry: {
isFile: false,
isFolder: true,
name: 'folder-fake'
fixture.detectChanges();
spyOn(uploadService, 'uploadFilesInTheQueue').and.returnValue(Promise.resolve(fakeItem));
component.onSuccess.subscribe((val) => {
expect(val).not.toBeNull();
});
let fakeCustomEvent: CustomEvent = new CustomEvent('CustomEvent', {
detail: {
data: fakeShareDataRow,
files: [fakeItem]
}
};
let fakePromise = new Promise(function (resolve, reject) {
resolve(fakeRest);
});
spyOn(uploadService, 'callApiCreateFolder').and.returnValue(fakePromise);
spyOn(component, 'onFilesEntityDropped').and.callFake( () => {
expect(component.onFilesEntityDropped).toHaveBeenCalledWith(itemEntity);
});
spyOn(component, 'showUndoNotificationBar').and.callFake( () => {
expect(component.showUndoNotificationBar).toHaveBeenCalled();
done();
});
component.onUploadFiles(fakeCustomEvent);
}));
let folderEntry = {
fullPath: '/folder-fake',
isDirectory: true,
isFile: false,
name: 'folder-fake',
createReader: () => {
return {
readEntries: (callback) => {
let entries = [itemEntity, itemEntity];
callback(entries);
}
};
}
};
component.onFolderEntityDropped(folderEntry);
});
});

View File

@@ -16,23 +16,10 @@
*/
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { AlfrescoTranslationService, LogService, NotificationService } from 'ng2-alfresco-core';
import { AlfrescoTranslationService, NotificationService, FileUtils, FileInfo } from 'ng2-alfresco-core';
import { UploadService } from '../services/upload.service';
import { FileModel } from '../models/file.model';
declare let componentHandler: any;
const ERROR_FOLDER_ALREADY_EXIST = 409;
/**
* <alfresco-upload-drag-area (onSuccess)="customMethod($event)></alfresco-upload-drag-area>
*
* This component, provide a drag and drop are to upload files to alfresco.
*
* @Output - onSuccess - The event is emitted when the file is uploaded
*
* @returns {UploadDragAreaComponent} .
*/
@Component({
selector: 'alfresco-upload-drag-area',
templateUrl: './upload-drag-area.component.html',
@@ -40,52 +27,65 @@ const ERROR_FOLDER_ALREADY_EXIST = 409;
})
export class UploadDragAreaComponent {
private static DEFAULT_ROOT_ID: string = '-root-';
@Input()
enabled: boolean = true;
/**
* @deprecated Deprecated in 1.6.0, you can use UploadService events and NotificationService api instead.
*
* @type {boolean}
* @memberof UploadButtonComponent
*/
@Input()
showNotificationBar: boolean = true;
@Input()
versioning: boolean = false;
/**
* @deprecated Deprecated in 1.6.0, this property is not used for couple of releases already. Use rootFolderId instead.
*
* @type {string}
* @memberof UploadDragAreaComponent
*/
@Input()
currentFolderPath: string = '/';
@Input()
rootFolderId: string = UploadDragAreaComponent.DEFAULT_ROOT_ID;
rootFolderId: string = '-root-';
@Output()
onSuccess = new EventEmitter();
constructor(private uploadService: UploadService,
private translateService: AlfrescoTranslationService,
private logService: LogService,
private notificationService: NotificationService) {
if (translateService) {
translateService.addTranslationFolder('ng2-alfresco-upload', 'node_modules/ng2-alfresco-upload/src');
translateService.addTranslationFolder('ng2-alfresco-upload', 'assets/ng2-alfresco-upload');
}
}
ngOnChanges(changes) {
let formFields = this.createFormFields();
this.uploadService.setOptions(formFields, this.versioning);
}
/**
* Handles 'upload-files' events raised by child components.
* @param e DOM event
* @param event DOM event
*/
onUploadFiles(e: CustomEvent) {
e.stopPropagation();
e.preventDefault();
let files = e.detail.files;
if (files && files.length > 0) {
if (e.detail.data.obj.entry.isFolder) {
let id = e.detail.data.obj.entry.id;
this.onFilesDropped(files, id, '/');
} else {
this.onFilesDropped(files);
onUploadFiles(event: CustomEvent) {
event.stopPropagation();
event.preventDefault();
let isAllowed: boolean = this.isAllowed(event.detail.data.obj.entry);
if (isAllowed) {
let files: FileInfo[] = event.detail.files;
if (files && files.length > 0) {
let parentId = this.rootFolderId;
if (event.detail.data && event.detail.data.obj.entry.isFolder) {
parentId = event.detail.data.obj.entry.id || this.rootFolderId;
}
const fileModels = files.map(fileInfo => new FileModel(fileInfo.file, {
newVersion: this.versioning,
path: fileInfo.relativeFolder,
parentId: parentId
}));
this.uploadFiles(fileModels, isAllowed);
}
}
}
@@ -95,10 +95,15 @@ export class UploadDragAreaComponent {
*
* @param {File[]} files - files dropped in the drag area.
*/
onFilesDropped(files: File[], rootId?: string, directory?: string): void {
if (files.length) {
this.uploadService.addToQueue(files);
this.uploadService.uploadFilesInTheQueue(rootId || this.rootFolderId, directory || this.currentFolderPath, this.onSuccess);
onFilesDropped(files: File[]): void {
if (this.enabled && files.length) {
const fileModels = files.map(file => new FileModel(file, {
newVersion: this.versioning,
path: '/',
parentId: this.rootFolderId
}));
this.uploadService.addToQueue(...fileModels);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
let latestFilesAdded = this.uploadService.getQueue();
if (this.showNotificationBar) {
this.showUndoNotificationBar(latestFilesAdded);
@@ -111,12 +116,17 @@ export class UploadDragAreaComponent {
* @param item - FileEntity
*/
onFilesEntityDropped(item: any): void {
item.file((file: any) => {
this.uploadService.addToQueue([file]);
let path = item.fullPath.replace(item.name, '');
let filePath = this.currentFolderPath + path;
this.uploadService.uploadFilesInTheQueue(this.rootFolderId, filePath, this.onSuccess);
});
if (this.enabled) {
item.file((file: File) => {
const fileModel = new FileModel(file, {
newVersion: this.versioning,
parentId: this.rootFolderId,
path: item.fullPath.replace(item.name, '')
});
this.uploadService.addToQueue(fileModel);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
});
}
}
/**
@@ -124,53 +134,23 @@ export class UploadDragAreaComponent {
* @param folder - name of the dropped folder
*/
onFolderEntityDropped(folder: any): void {
if (folder.isDirectory) {
let relativePath = folder.fullPath.replace(folder.name, '');
relativePath = this.currentFolderPath + relativePath;
this.uploadService.createFolder(relativePath, folder.name, this.rootFolderId)
.subscribe(
message => {
this.onSuccess.emit({
value: 'Created folder'
});
let dirReader = folder.createReader();
dirReader.readEntries((entries: any) => {
for (let i = 0; i < entries.length; i++) {
this._traverseFileTree(entries[i]);
}
if (this.showNotificationBar) {
let latestFilesAdded = this.uploadService.getQueue();
this.showUndoNotificationBar(latestFilesAdded);
}
});
},
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);
}
if (this.enabled && folder.isDirectory) {
FileUtils.flattern(folder).then(entries => {
let files = entries.map(entry => {
return new FileModel(entry.file, {
newVersion: this.versioning,
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.onSuccess);
});
}
}
@@ -184,10 +164,8 @@ export class UploadDragAreaComponent {
messageTranslate = this.translateService.get('FILE_UPLOAD.MESSAGES.PROGRESS');
actionTranslate = this.translateService.get('FILE_UPLOAD.ACTION.UNDO');
this.notificationService.openSnackMessageAction(messageTranslate.value, actionTranslate.value, 3000).afterDismissed().subscribe(() => {
latestFilesAdded.forEach((uploadingFileModel: FileModel) => {
uploadingFileModel.emitAbort();
});
this.notificationService.openSnackMessageAction(messageTranslate.value, actionTranslate.value, 3000).onAction().subscribe(() => {
this.uploadService.cancelUpload(...latestFilesAdded);
});
}
@@ -200,40 +178,27 @@ export class UploadDragAreaComponent {
this.notificationService.openSnackMessage(errorMessage, 3000);
}
/**
* Retrive the error message using the error status code
* @param response - object that contain the HTTP response
* @returns {string}
*/
private getErrorMessage(response: any): string {
if (response.body.error.statusCode === ERROR_FOLDER_ALREADY_EXIST) {
let errorMessage: any;
errorMessage = this.translateService.get('FILE_UPLOAD.MESSAGES.FOLDER_ALREADY_EXIST');
return errorMessage.value;
private uploadFiles(files: FileModel[], isAllowed: boolean): void {
if (isAllowed && files.length) {
this.uploadService.addToQueue(...files);
this.uploadService.uploadFilesInTheQueue(this.onSuccess);
let latestFilesAdded = this.uploadService.getQueue();
if (this.showNotificationBar) {
this.showUndoNotificationBar(latestFilesAdded);
}
}
}
/**
* Replace a placeholder {0} in a message with the input keys
* @param message - the message that conains the placeholder
* @param keys - array of value
* @returns {string} - The message without placeholder
*/
private formatString(message: string, keys: any []) {
if (message) {
let i = keys.length;
while (i--) {
message = message.replace(new RegExp('\\{' + i + '\\}', 'gm'), keys[i]);
}
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 message;
return isPermitted;
}
private createFormFields(): any {
return {
formFields: {
overwrite: true
}
};
private isAllowed(node: any) {
return this.enabled || this.hasCreatePermission(node);
}
}

View File

@@ -15,6 +15,7 @@
* limitations under the License.
*/
import { ElementRef } from '@angular/core';
import { FileDraggableDirective } from '../directives/file-draggable.directive';
describe('FileDraggableDirective', () => {
@@ -22,7 +23,22 @@ describe('FileDraggableDirective', () => {
let component: FileDraggableDirective;
beforeEach( () => {
component = new FileDraggableDirective(null, null);
let el = new ElementRef(null);
component = new FileDraggableDirective(el, null);
});
it('should always be enabled by default', () => {
expect(component.enabled).toBeTruthy();
});
it('should not allow drad and drop when disabled', () => {
component.enabled = false;
let event = new CustomEvent('custom-event');
spyOn(event, 'preventDefault').and.stub();
component.onDropFiles(event);
component.onDragEnter(event);
component.onDragLeave(event);
expect(event.preventDefault).not.toHaveBeenCalled();
});
/*

View File

@@ -15,19 +15,9 @@
* limitations under the License.
*/
import { Directive, EventEmitter, 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';
/**
* [file-draggable]
*
* This directive, provide a drag and drop area for files and folders.
*
* @OutputEvent {EventEmitter} onFilesDropped(File)- event fired fot each file dropped
* in the drag and drop area.
*
*
* @returns {FileDraggableDirective} .
*/
@Directive({
selector: '[file-draggable]'
})
@@ -35,8 +25,11 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
files: File [];
@Input('file-draggable')
enabled: boolean = true;
@Output()
onFilesDropped: EventEmitter<any> = new EventEmitter();
onFilesDropped: EventEmitter<File[]> = new EventEmitter<File[]>();
@Output()
onFilesEntityDropped: EventEmitter<any> = new EventEmitter();
@@ -72,7 +65,7 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
* @param event DOM event.
*/
onDropFiles(event: any): void {
if (!event.defaultPrevented) {
if (this.enabled && !event.defaultPrevented) {
this.preventDefault(event);
let items = event.dataTransfer.items;
@@ -81,16 +74,20 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
if (typeof items[i].webkitGetAsEntry !== 'undefined') {
let item = items[i].webkitGetAsEntry();
if (item) {
this.traverseFileTree(item);
if (item.isFile) {
this.onFilesEntityDropped.emit(item);
} else if (item.isDirectory) {
this.onFolderEntityDropped.emit(item);
}
}
} else {
let files = event.dataTransfer.files;
let files = FileUtils.toFileArray(event.dataTransfer.files);
this.onFilesDropped.emit(files);
}
}
} else {
// safari or FF
let files = event.dataTransfer.files;
let files = FileUtils.toFileArray(event.dataTransfer.files);
this.onFilesDropped.emit(files);
}
@@ -98,29 +95,13 @@ 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.
*
* @param {event} event - DOM event.
*/
onDragEnter(event: Event): void {
if (!event.defaultPrevented) {
if (this.enabled && !event.defaultPrevented) {
this.preventDefault(event);
this.element.classList.add(this.cssClassName);
}
@@ -132,7 +113,7 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
* @param {event} event - DOM event.
*/
onDragLeave(event: Event): void {
if (!event.defaultPrevented) {
if (this.enabled && !event.defaultPrevented) {
this.preventDefault(event);
this.element.classList.remove(this.cssClassName);
}
@@ -144,7 +125,7 @@ export class FileDraggableDirective implements OnInit, OnDestroy {
* @param event
*/
onDragOver(event: Event): void {
if (!event.defaultPrevented) {
if (this.enabled && !event.defaultPrevented) {
this.preventDefault(event);
this.element.classList.add(this.cssClassName);
}

View File

@@ -0,0 +1,36 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { FileModel, FileUploadStatus } from '../models/file.model';
export class FileUploadEvent {
constructor(
public readonly file: FileModel,
public readonly status: FileUploadStatus = FileUploadStatus.Pending,
public readonly error: any = null) {
}
}
export class FileUploadCompleteEvent extends FileUploadEvent {
constructor(file: FileModel, public totalComplete: number = 0, public data?: any, public totalAborted: number = 0) {
super(file, FileUploadStatus.Complete);
}
}

View File

@@ -1,25 +1,27 @@
{
"FILE_UPLOAD": {
"BUTTON": {
"UPLOAD_FILE": "Upload file",
"UPLOAD_FOLDER": "Upload folder",
"CANCEL_ALL": "Cancell all"
"ADF_FILE_UPLOAD": {
"FILE_LIST": {
"NAME": "Name",
"PROGRESS": "Progress",
"SIZE": "Size",
"ACTION": "Action"
}
},
"MESSAGES": {
"SINGLE_COMPLETED": "upload complete",
"COMPLETED": "uploads complete",
"PROGRESS": "Upload in progress...",
"FOLDER_ALREADY_EXIST": "The folder {0} already exist",
"FOLDER_NOT_SUPPORTED": "Folder upload isn't supported by your browser"
},
"FILE_INFO": {
"NAME": "File name",
"PROGRESS": "File progress",
"SIZE": "File size",
"ACTION": "Actions"
},
"ACTION": {
"UNDO": "Undo"
"FILE_UPLOAD": {
"BUTTON": {
"UPLOAD_FILE": "Upload file",
"UPLOAD_FOLDER": "Upload folder",
"CANCEL_ALL": "Cancell all"
},
"MESSAGES": {
"SINGLE_COMPLETED": "upload complete",
"COMPLETED": "uploads complete",
"PROGRESS": "Upload in progress...",
"FOLDER_ALREADY_EXIST": "The folder {0} already exist",
"FOLDER_NOT_SUPPORTED": "Folder upload isn't supported by your browser"
},
"ACTION": {
"UNDO": "Undo"
}
}
}
}

View File

@@ -15,126 +15,60 @@
* limitations under the License.
*/
/**
*
* This object represent the status of an uploading file.
*
*
* @returns {FileModel} .
*/
export class FileModel {
id: string;
status: number;
statusText: string;
progress: Object;
name: string;
size: string;
response: string;
done: boolean = false;
error: boolean = false;
abort: boolean = false;
uploading: boolean = false;
file: any;
promiseUpload: any;
export interface FileUploadProgress {
loaded: number;
total: number;
percent: number;
}
constructor(file: any) {
export interface FileUploadOptions {
newVersion?: boolean;
parentId?: string;
path?: string;
}
export enum FileUploadStatus {
Pending = 0,
Complete = 1,
Starting = 2,
Progress = 3,
Cancelled = 4,
Aborted = 5,
Error = 6
}
export class FileModel {
readonly id: string;
readonly name: string;
readonly size: number;
readonly file: File;
status: FileUploadStatus = FileUploadStatus.Pending;
progress: FileUploadProgress;
options: FileUploadOptions;
constructor(file: File, options?: FileUploadOptions) {
this.file = file;
this.id = this._generateId();
this.id = this.generateId();
this.name = file.name;
this.size = this._getFileSize(file.size);
this.size = file.size;
this.progress = {
loaded: 0,
total: 0,
percent: 0
};
this.options = Object.assign({}, {
newVersion: false
}, options);
}
setProgres(progress: any): void {
this.progress = progress;
}
/**
* Emit an event progress on the promise
*/
emitProgres(progress: any): void {
this.setProgres(progress);
this.promiseUpload.emit('progress', progress);
}
setError(): void {
this.error = true;
}
/**
* Emit an event progress on the promise
*/
emitError(): void {
this.setError();
this.promiseUpload.emit('error');
}
setUploading() {
this.uploading = true;
}
setPromiseUpload(promiseUpload: any) {
this.promiseUpload = promiseUpload;
}
/**
* Stop the uploading of the file.
*/
setAbort(): void {
if (!this.done && !this.error) {
this.abort = true;
this.uploading = false;
}
}
/**
* Emit an event abort on the promise
*/
emitAbort(): void {
this.setAbort();
this.promiseUpload.abort();
}
/**
* Update status of the file when upload finish or is ended.
*/
onFinished(status: number, statusText: string, response: string): void {
this.status = status;
this.statusText = statusText;
this.response = response;
this.done = true;
this.uploading = false;
}
/**
* Calculate the size of the file in kb,mb and gb.
*
* @param {number} sizeinbytes - size in bytes of the file.
*/
private _getFileSize(sizeinbytes: number): string {
let fSExt = new Array('Bytes', 'KB', 'MB', 'GB');
let size = sizeinbytes;
let i = 0;
while (size > 900) {
size /= 1000;
i++;
}
return Math.round((Math.round(size * 100) / 100)) + ' ' + fSExt[i];
}
/**
* Calculate the size of the file in kb,mb and gb.
*
* @return {string} - return a unique file uploading id.
*/
private _generateId(): string {
return 'uploading-file-' + 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
private generateId(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
let r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
}

View File

@@ -19,22 +19,13 @@ import { EventEmitter } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { CoreModule } from 'ng2-alfresco-core';
import { UploadService } from './upload.service';
import { FileModel, FileUploadOptions } from '../models/file.model';
declare let jasmine: any;
describe('UploadService', () => {
let service: UploadService;
let options = {
host: 'fakehost',
url: '/some/cool/url',
baseUrlPath: 'fakebasepath',
formFields: {
siteid: 'fakeSite',
containerid: 'fakeFolder'
}
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
@@ -53,27 +44,32 @@ describe('UploadService', () => {
});
it('should return an empty queue if no elements are added', () => {
service.setOptions(options, false);
expect(service.getQueue().length).toEqual(0);
});
it('should add an element in the queue and returns it', () => {
service.setOptions(options, false);
let filesFake = [<File>{name: 'fake-name', size: 10}];
let filesFake = new FileModel(<File>{ name: 'fake-name', size: 10 });
service.addToQueue(filesFake);
expect(service.getQueue().length).toEqual(1);
});
it('should add two elements in the queue and returns them', () => {
service.setOptions(options, false);
let filesFake = [
<File>{name: 'fake-name', size: 10},
<File>{name: 'fake-name2', size: 20}
new FileModel(<File>{ name: 'fake-name', size: 10 }),
new FileModel(<File>{ name: 'fake-name2', size: 20 })
];
service.addToQueue(filesFake);
service.addToQueue(...filesFake);
expect(service.getQueue().length).toEqual(2);
});
it('should skip hidden macOS files', () => {
const file1 = new FileModel(new File([''], '.git'));
const file2 = new FileModel(new File([''], 'readme.md'));
const result = service.addToQueue(file1, file2);
expect(result.length).toBe(1);
expect(result[0]).toBe(file2);
});
it('should make XHR done request after the file is added in the queue', (done) => {
let emitter = new EventEmitter();
@@ -81,13 +77,15 @@ describe('UploadService', () => {
expect(e.value).toBe('File uploaded');
done();
});
service.setOptions(options, false);
let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', 'fake-dir', emitter);
let fileFake = new FileModel(
<File>{ name: 'fake-name', size: 10 },
<FileUploadOptions> { parentId: '-root-', path: 'fake-dir' }
);
service.addToQueue(fileFake);
service.uploadFilesInTheQueue(emitter);
let request = jasmine.Ajax.requests.mostRecent();
expect(request.url).toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true');
expect(request.url).toBe('http://localhost:3000/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true');
expect(request.method).toBe('POST');
jasmine.Ajax.requests.mostRecent().respondWith({
@@ -104,12 +102,14 @@ describe('UploadService', () => {
expect(e.value).toBe('Error file uploaded');
done();
});
service.setOptions(options, false);
let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter);
let fileFake = new FileModel(
<File>{ name: 'fake-name', size: 10 },
<FileUploadOptions> { parentId: '-root-' }
);
service.addToQueue(fileFake);
service.uploadFilesInTheQueue(emitter);
expect(jasmine.Ajax.requests.mostRecent().url)
.toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true');
.toBe('http://localhost:3000/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/-root-/children?autoRename=true');
jasmine.Ajax.requests.mostRecent().respondWith({
'status': 404,
@@ -125,109 +125,20 @@ describe('UploadService', () => {
expect(e.value).toEqual('File aborted');
done();
});
service.setOptions(options, false);
let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter);
let fileFake = new FileModel(<File>{ name: 'fake-name', size: 10 });
service.addToQueue(fileFake);
service.uploadFilesInTheQueue(emitter);
let file = service.getQueue();
file[0].emitAbort();
});
it('should make XHR error request after the xhr error is called', (done) => {
let emitter = new EventEmitter();
emitter.subscribe(e => {
expect(e.value).toBe('Error file uploaded');
done();
});
service.setOptions(options, false);
let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter);
let file = service.getQueue();
file[0].emitError();
});
it('should make XHR progress request after the onprogress is called', (done) => {
service.setOptions(options, false);
let fakeProgress = {
loaded: 500,
total: 1234,
percent: 44
};
let filesFake = [<File>{name: 'fake-name', size: 10}];
service.addToQueue(filesFake);
service.filesUpload$.subscribe((file) => {
expect(file).toBeDefined();
expect(file[0]).toBeDefined();
expect(file[0].progress).toEqual(fakeProgress);
done();
});
service.uploadFilesInTheQueue('-root-', '', null);
let file = service.getQueue();
file[0].emitProgres(fakeProgress);
});
it('should make XHR done request after the folder is created', (done) => {
let fakeRest = {
entry: {
isFile: false,
isFolder: true,
name: 'fake-folder'
}
};
let fakePromise = new Promise(function (resolve, reject) {
resolve(fakeRest);
});
spyOn(service, 'callApiCreateFolder').and.returnValue(fakePromise);
service.setOptions(options, false);
let defaultPath = '';
let folderName = 'fake-folder';
service.createFolder(defaultPath, folderName).subscribe(res => {
expect(res).toEqual(fakeRest);
done();
});
});
it('should throws an exception when a folder already exist', (done) => {
let fakeRest = {
response: {
body: {
error: {
statusCode: 409
}
}
}
};
let fakePromise = new Promise(function (resolve, reject) {
reject(fakeRest);
});
spyOn(service, 'callApiCreateFolder').and.returnValue(fakePromise);
service.setOptions(options, false);
let defaultPath = '';
let folderName = 'folder-duplicate-fake';
service.createFolder(defaultPath, folderName).subscribe(
res => {
},
error => {
expect(error).toEqual(fakeRest);
done();
}
);
service.cancelUpload(...file);
});
it('If versioning is true autoRename should not be present and majorVersion should be a param', () => {
let emitter = new EventEmitter();
let enableVersioning = true;
service.setOptions(options, enableVersioning);
let filesFake = [<File>{name: 'fake-name', size: 10}];
const filesFake = new FileModel(<File>{ name: 'fake-name', size: 10 }, { newVersion: true });
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('-root-', '', emitter);
service.uploadFilesInTheQueue(emitter);
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('autoRename=true')).toBe(false);
expect(jasmine.Ajax.requests.mostRecent().params.has('majorVersion')).toBe(true);
@@ -240,13 +151,15 @@ describe('UploadService', () => {
expect(e.value).toBe('File uploaded');
done();
});
service.setOptions(options, false);
let filesFake = [<File>{name: 'fake-name', size: 10}];
let filesFake = new FileModel(
<File>{ name: 'fake-name', size: 10 },
<FileUploadOptions> { parentId: '123', path: 'fake-dir' }
);
service.addToQueue(filesFake);
service.uploadFilesInTheQueue('123', 'fake-dir', emitter);
service.uploadFilesInTheQueue(emitter);
let request = jasmine.Ajax.requests.mostRecent();
expect(request.url).toBe('http://localhost:8080/alfresco/api/-default-/public/alfresco/versions/1/nodes/123/children?autoRename=true');
expect(request.url).toBe('http://localhost:3000/ecm/alfresco/api/-default-/public/alfresco/versions/1/nodes/123/children?autoRename=true');
expect(request.method).toBe('POST');
jasmine.Ajax.requests.mostRecent().respondWith({
@@ -255,4 +168,26 @@ describe('UploadService', () => {
responseText: 'File uploaded'
});
});
it('should start downloading the next one if a file of the list is aborted', (done) => {
let emitter = new EventEmitter();
service.fileUploadAborted.subscribe(e => {
expect(e).not.toBeNull();
});
service.fileUploadCancelled.subscribe(e => {
expect(e).not.toBeNull();
done();
});
let fileFake1 = new FileModel(<File>{ name: 'fake-name1', size: 10 });
let fileFake2 = new FileModel(<File>{ name: 'fake-name2', size: 10 });
let filelist = [fileFake1, fileFake2];
service.addToQueue(...filelist);
service.uploadFilesInTheQueue(emitter);
let file = service.getQueue();
service.cancelUpload(...file);
});
});

View File

@@ -16,130 +16,45 @@
*/
import { EventEmitter, Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { Observer, Observable } from 'rxjs/Rx';
import { AlfrescoApiService, LogService } from 'ng2-alfresco-core';
import { FileModel } from '../models/file.model';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from 'alfresco-js-api';
import { Subject } from 'rxjs/Rx';
import { AlfrescoApiService } from 'ng2-alfresco-core';
import { FileUploadEvent, FileUploadCompleteEvent } from '../events/file.event';
import { FileModel, FileUploadProgress, FileUploadStatus } from '../models/file.model';
/**
*
* UploadService keep the queue of the file to upload and uploads them.
*
* @returns {UploadService} .
*/
@Injectable()
export class UploadService {
private formFields: Object = {};
private queue: FileModel[] = [];
private versioning: boolean = false;
private filesUploadObserverProgressBar: Observer<FileModel[]>;
private totalCompletedObserver: Observer<number>;
private cache: { [key: string]: any } = {};
private totalComplete: number = 0;
private totalAborted: number = 0;
private activeTask: Promise<any> = null;
totalCompleted: number = 0;
filesUpload$: Observable<FileModel[]>;
totalCompleted$: Observable<any>;
queueChanged: Subject<FileModel[]> = new Subject<FileModel[]>();
fileUpload: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadStarting: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadCancelled: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadProgress: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadAborted: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadError: Subject<FileUploadEvent> = new Subject<FileUploadEvent>();
fileUploadComplete: Subject<FileUploadCompleteEvent> = new Subject<FileUploadCompleteEvent>();
constructor(private apiService: AlfrescoApiService,
private logService: LogService) {
this.filesUpload$ = new Observable<FileModel[]>(observer => this.filesUploadObserverProgressBar = observer).share();
this.totalCompleted$ = new Observable<number>(observer => this.totalCompletedObserver = observer).share();
constructor(private apiService: AlfrescoApiService) {
}
/**
* Configure the service
* Checks whether the service is uploading a file.
*
* @param {Object} - options formFields to init the object
* @param {boolean} - versioning true to indicate that a major version should be created
* @returns {boolean}
*
* @memberof UploadService
*/
setOptions(options: any, versioning: boolean): void {
this.formFields = options.formFields != null ? options.formFields : this.formFields;
this.versioning = versioning != null ? versioning : this.versioning;
isUploading(): boolean {
return this.activeTask ? true : false;
}
/**
* Add files to the uploading queue to be uploaded.
*
* @param {File[]} - files to add to the upload queue.
*
* return {FileModel[]} - return the file added to the queue in this call.
*/
addToQueue(files: File[]): FileModel[] {
const result: FileModel[] = [];
for (let file of files) {
let uploadingFileModel = new FileModel(file);
result.push(uploadingFileModel);
this.queue.push(uploadingFileModel);
if (this.filesUploadObserverProgressBar) {
this.filesUploadObserverProgressBar.next(this.queue);
}
}
return result;
}
/**
* Pick all the files in the queue that are not been uploaded yet and upload it into the directory folder.
*/
uploadFilesInTheQueue(rootId: string, directory: string, elementEmit: EventEmitter<any>): void {
let filesToUpload = this.queue.filter((uploadingFileModel) => {
return !uploadingFileModel.uploading && !uploadingFileModel.done && !uploadingFileModel.abort && !uploadingFileModel.error;
});
let opts: any = {};
opts.renditions = 'doclib';
if (this.versioning) {
opts.overwrite = true;
opts.majorVersion = true;
} else {
opts.autoRename = true;
}
filesToUpload.forEach((uploadingFileModel: FileModel) => {
uploadingFileModel.setUploading();
let promiseUpload = this.apiService.getInstance().upload.uploadFile(uploadingFileModel.file, directory, rootId, null, opts)
.on('progress', (progress: any) => {
uploadingFileModel.setProgres(progress);
this.updateFileListStream(this.queue);
})
.on('abort', () => {
uploadingFileModel.setAbort();
elementEmit.emit({
value: 'File aborted'
});
})
.on('error', () => {
uploadingFileModel.setError();
elementEmit.emit({
value: 'Error file uploaded'
});
})
.on('success', (data: any) => {
elementEmit.emit({
value: data
});
uploadingFileModel.onFinished(
data.status,
data.statusText,
data.response
);
this.updateFileListStream(this.queue);
if (!uploadingFileModel.abort && !uploadingFileModel.error) {
this.updateFileCounterStream(++this.totalCompleted);
}
});
uploadingFileModel.setPromiseUpload(promiseUpload);
});
}
/**
* Return all the files in the uploading queue.
* Returns the file Queue
*
* @return {FileModel[]} - files in the upload queue.
*/
@@ -148,53 +63,183 @@ export class UploadService {
}
/**
* Create a folder
* @param name - the folder name
* Add files to the uploading queue to be uploaded.
*
* Examples:
* addToQueue(file); // pass one file
* addToQueue(file1, file2, file3); // pass multiple files
* addToQueue(...[file1, file2, file3]); // pass an array of files
*/
createFolder(relativePath: string, name: string, parentId?: string) {
return Observable.fromPromise(this.callApiCreateFolder(relativePath, name, parentId))
.do(data => this.logService.info('Node data', data)) // eyeball results in the console
.catch(err => this.handleError(err));
}
callApiCreateFolder(relativePath: string, name: string, parentId?: string): Promise<MinimalNodeEntity> {
return this.apiService.getInstance().nodes.createFolder(name, relativePath, parentId);
addToQueue(...files: FileModel[]): FileModel[] {
const allowedFiles = files.filter(f => !f.name.startsWith('.'));
this.queue = this.queue.concat(allowedFiles);
this.queueChanged.next(this.queue);
return allowedFiles;
}
/**
* Throw the error
* @param error
* @returns {ErrorObservable}
* 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
*/
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');
}
uploadFilesInTheQueue(emitter: EventEmitter<any>): void {
if (!this.activeTask) {
let file = this.queue.find(f => f.status === FileUploadStatus.Pending);
if (file) {
this.onUploadStarting(file);
private updateFileListStream(fileList: FileModel[]) {
if (this.filesUploadObserverProgressBar) {
this.filesUploadObserverProgressBar.next(fileList);
const promise = this.beginUpload(file, emitter);
this.activeTask = promise;
this.cache[file.id] = promise;
let next = () => {
this.activeTask = null;
setTimeout(() => this.uploadFilesInTheQueue(emitter), 100);
};
promise.next = next;
promise.then(
() => next(),
() => next()
);
}
}
}
updateFileCounterStream(total: number) {
if (this.totalCompletedObserver) {
this.totalCompletedObserver.next(total);
}
cancelUpload(...files: FileModel[]) {
files.forEach(file => {
file.status = FileUploadStatus.Cancelled;
const promise = this.cache[file.id];
if (promise) {
promise.abort();
delete this.cache[file.id];
}
const event = new FileUploadEvent(file, FileUploadStatus.Cancelled);
this.fileUpload.next(event);
this.fileUploadCancelled.next(event);
});
}
getFolderNode(nodeId: string): Observable<MinimalNodeEntryEntity> {
clearQueue() {
this.queue = [];
this.totalComplete = 0;
this.totalAborted = 0;
}
private beginUpload(file: FileModel, /* @deprecated */emitter: EventEmitter<any>): any {
let opts: any = {
includeSource: true,
include: ['allowableOperations']
};
renditions: 'doclib'
};
return Observable.fromPromise(this.apiService.getInstance().nodes.getNodeInfo(nodeId, opts))
.map((response: any) => {
return response;
})
.catch(err => this.handleError(err));
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, data);
emitter.emit({ value: data });
})
.catch(err => {
this.onUploadError(file, err);
});
return promise;
}
private onUploadStarting(file: FileModel): void {
if (file) {
file.status = FileUploadStatus.Starting;
const event = new FileUploadEvent(file, FileUploadStatus.Starting);
this.fileUpload.next(event);
this.fileUploadStarting.next(event);
}
}
private onUploadProgress(file: FileModel, progress: FileUploadProgress): void {
if (file) {
file.progress = progress;
file.status = FileUploadStatus.Progress;
const event = new FileUploadEvent(file, FileUploadStatus.Progress);
this.fileUpload.next(event);
this.fileUploadProgress.next(event);
this.queueChanged.next(this.queue);
}
}
private onUploadError(file: FileModel, error: any): void {
if (file) {
file.status = FileUploadStatus.Error;
const promise = this.cache[file.id];
if (promise) {
delete this.cache[file.id];
}
const event = new FileUploadEvent(file, FileUploadStatus.Error, error);
this.fileUpload.next(event);
this.fileUploadError.next(event);
}
}
private onUploadComplete(file: FileModel, data: any): void {
if (file) {
file.status = FileUploadStatus.Complete;
this.totalComplete++;
const promise = this.cache[file.id];
if (promise) {
delete this.cache[file.id];
}
const event = new FileUploadCompleteEvent(file, this.totalComplete, data, this.totalAborted);
this.fileUpload.next(event);
this.fileUploadComplete.next(event);
this.queueChanged.next(this.queue);
}
}
private onUploadAborted(file: FileModel): void {
if (file) {
file.status = FileUploadStatus.Aborted;
this.totalAborted++;
const promise = this.cache[file.id];
if (promise) {
delete this.cache[file.id];
}
const event = new FileUploadEvent(file, FileUploadStatus.Aborted);
this.fileUpload.next(event);
this.fileUploadAborted.next(event);
promise.next();
}
}
}