* 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

@@ -8,13 +8,13 @@ dist
src/**/*.js
src/**/*.js.map
src/**/*.d.ts
demo/**/*.js
demo/**/*.js.map
demo/**/*.d.ts
index.js
index.js.map
index.js.map.gitignore
!systemjs.config.js
*.tgz
/package/
/bundles/
index.d.ts
/.happypack

View File

@@ -4,6 +4,7 @@ npm-debug.log
coverage/
demo/
dist/
node_modules
typings/
fonts/
@@ -15,3 +16,4 @@ fonts/
/karma.conf.js
/gulpfile.ts
/.npmignore
/.happypack

View File

@@ -1,188 +1,105 @@
# Alfresco Upload Component for Angular
# Alfresco Upload Component
<p>
<a title='Build Status Travis' href="https://travis-ci.org/Alfresco/alfresco-ng2-components">
<img src='https://travis-ci.org/Alfresco/alfresco-ng2-components.svg?branch=master' alt='travis
Status' />
</a>
<a title='Build Status AppVeyor' href="https://ci.appveyor.com/project/alfresco/alfresco-ng2-components">
<img src='https://ci.appveyor.com/api/projects/status/github/Alfresco/alfresco-ng2-components' alt='travis
Status' />
</a>
<a href='https://codecov.io/gh/Alfresco/alfresco-ng2-components'>
<img src='https://img.shields.io/codecov/c/github/Alfresco/alfresco-ng2-components/master.svg?maxAge=2592000' alt='Coverage Status' />
</a>
<a href='https://www.npmjs.com/package/ng2-alfresco-upload'>
<img src='https://img.shields.io/npm/dt/ng2-alfresco-upload.svg' alt='npm downloads' />
</a>
<a href='https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE'>
<img src='https://img.shields.io/hexpm/l/plug.svg' alt='license' />
</a>
<a href='https://www.alfresco.com/'>
<img src='https://img.shields.io/badge/style-component-green.svg?label=alfresco' alt='alfresco component' />
</a>
<a href='https://angular.io/'>
<img src='https://img.shields.io/badge/style-2-red.svg?label=angular' alt='angular 2' />
</a>
<a href='https://www.typescriptlang.org/docs/tutorial.html'>
<img src='https://img.shields.io/badge/style-lang-blue.svg?label=typescript' alt='typescript' />
</a>
<a href='https://www.alfresco.com/'>
<img src='https://img.shields.io/badge/style-%3E5.0.0-blue.svg?label=node%20version' alt='node version' />
</a>
</p>
<!-- markdown-toc start - Don't edit this section. npm run toc to generate it-->
<!-- toc -->
- [Content](#content)
* [Components](#components)
* [Services](#services)
* [Directives](#directives)
- [Prerequisites](#prerequisites)
- [Install](#install)
- [UploadButtonComponent](#uploadbuttoncomponent)
* [Properties](#properties)
* [Events](#events)
* [Advanced usage](#advanced-usage)
+ [How to show notification message with no permission](#how-to-show-notification-message-with-no-permission)
+ [How to disable the button when the delete permission is missing](#how-to-disable-the-button-when-the-delete-permission-is-missing)
- [UploadDragAreaComponent](#uploaddragareacomponent)
* [Properties](#properties-1)
* [Events](#events-1)
- [FileUploadingDialogComponent](#fileuploadingdialogcomponent)
- [UploadService](#uploadservice)
* [Events](#events-2)
- [Build from sources](#build-from-sources)
- [NPM scripts](#npm-scripts)
- [Demo](#demo)
- [License](#license)
<!-- tocstop -->
<!-- markdown-toc end -->
## Content
### Components
- [FileUploadingDialogComponent](#fileuploadingdialogcomponent)
- FileUploadingListComponent
- [UploadButtonComponent](#uploadbuttoncomponent)
- [UploadDragAreaComponent](#uploaddragareacomponent)
### Services
- [UploadService](#uploadservice)
### Directives
- FileDraggableDirective
## Prerequisites
Before you start using this development framework, make sure you have installed all required software and done all the
necessary configuration [prerequisites](https://github.com/Alfresco/alfresco-ng2-components/blob/master/PREREQUISITES.md).
> If you plan using this component with projects generated by Angular CLI, please refer to the following article: [Using ADF with Angular CLI](https://github.com/Alfresco/alfresco-ng2-components/wiki/Angular-CLI)
## Install
Follow the 3 steps below:
1. Npm
```sh
npm install ng2-alfresco-upload --save
```
2. Html
Include these dependencies in your index.html page:
```html
<!-- Google Material Design Lite -->
<link rel="stylesheet" href="node_modules/material-design-lite/material.min.css">
<script src="node_modules/material-design-lite/material.min.js"></script>
<link rel="stylesheet" href="node_modules/material-design-icons/iconfont/material-icons.css">
<!-- Load the Angular Material 2 stylesheet -->
<link href="node_modules/@angular/material/core/theming/prebuilt/deeppurple-amber.css" rel="stylesheet">
<!-- Polyfill(s) for Safari (pre-10.x) -->
<script src="node_modules/intl/dist/Intl.min.js"></script>
<script src="node_modules/intl/locale-data/jsonp/en.js"></script>
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/dom4/1.8.3/dom4.js"></script>
<script src="node_modules/element.scrollintoviewifneeded-polyfill/index.js"></script>
<!-- Polyfill(s) for dialogs -->
<script src="node_modules/dialog-polyfill/dialog-polyfill.js"></script>
<link rel="stylesheet" type="text/css" href="node_modules/dialog-polyfill/dialog-polyfill.css" />
<style>._dialog_overlay { position: static !important; } </style>
<!-- Modules -->
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
```
3. SystemJs
Add the following components to your systemjs.config.js file:
- ng2-translate
- alfresco-js-api
- ng2-alfresco-core
- ng2-alfresco-upload
Please refer to the following example file: [systemjs.config.js](demo/systemjs.config.js) .
#### Basic usage
```sh
npm install ng2-alfresco-upload
```
## UploadButtonComponent
```html
<alfresco-upload-button [showNotificationBar]="true"
[uploadFolders]="true"
[multipleFiles]="false"
[acceptedFilesType]=".jpg,.gif,.png,.svg"
[currentFolderPath]="/Sites/swsdp/documentLibrary"
[versioning]="false"
(onSuccess)="customMethod($event)">
<alfresco-upload-button
[rootFolderId]="-my-"
[uploadFolders]="true"
[multipleFiles]="false"
[acceptedFilesType]=".jpg,.gif,.png,.svg"
[versioning]="false"
(onSuccess)="customMethod($event)">
</alfresco-upload-button>
<file-uploading-dialog></file-uploading-dialog>
```
Example of an App that declares upload button component :
```ts
import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { UploadModule } from 'ng2-alfresco-upload';
@Component({
selector: 'alfresco-app-demo',
template: `<alfresco-upload-button [showNotificationBar]="true"
[uploadFolders]="false"
[multipleFiles]="false"
[acceptedFilesType]="'.jpg,.gif,.png,.svg'"
(onSuccess)="onSuccess($event)">
</alfresco-upload-button>
<file-uploading-dialog></file-uploading-dialog>`
})
export class MyDemoApp {
constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) {
settingsService.ecmHost = 'http://localhost:8080';
this.authService.login('admin', 'admin').subscribe(
ticket => {
console.log(ticket);
},
error => {
console.log(error);
});
}
public onSuccess(event: Object): void {
console.log('File uploaded');
}
}
@NgModule({
imports: [
BrowserModule,
CoreModule.forRoot(),
UploadModule.forRoot()
],
declarations: [ MyDemoApp ],
bootstrap: [ MyDemoApp ]
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
```
#### Events
| Name | Description |
| --- | --- |
| `onSuccess` | The event is emitted when the file is uploaded |
#### Properties
### Properties
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | *boolean* | false | Toggle component disabled state |
| `showNotificationBar` | *boolean* | true | Hide/show notification bar |
| `uploadFolders` | *boolean* | false | Allow/disallow upload folders (only for chrome) |
| `multipleFiles` | *boolean* | false | Allow/disallow multiple files |
| `acceptedFilesType` | *string* | * | array of allowed file extensions , example: ".jpg,.gif,.png,.svg" |
| `currentFolderPath` | *string* | '/Sites/swsdp/documentLibrary' | define the path where the files are uploaded |
| `versioning` | *boolean* | false | Versioning false is the default uploader behaviour and it rename using an integer suffix if there is a name clash. Versioning true to indicate that a major version should be created |
| `staticTitle` | *string* | 'FILE_UPLOAD.BUTTON.UPLOAD_FILE' or 'FILE_UPLOAD.BUTTON.UPLOAD_FOLDER' string in the JSON text file | define the text of the upload button |
| `disableWithNoPermission` | *boolean* | false | If the value is true and the user doesn't have the permission to delete the node the button will be disabled |
| disabled | boolean | false | Toggle component disabled state |
| **(deprecated)** showNotificationBar | boolean | true | Hide/show notification bar. **Deprecated in 1.6.0: use UploadService events and NotificationService api instead.** |
| uploadFolders | boolean | false | Allow/disallow upload folders (only for chrome) |
| multipleFiles | boolean | false | Allow/disallow multiple files |
| acceptedFilesType | string | * | array of allowed file extensions , example: ".jpg,.gif,.png,.svg" |
| **(deprecated)** currentFolderPath | string | '/Sites/swsdp/documentLibrary' | define the path where the files are uploaded. **Deprecated in 1.6.0: use rootFolderId instead.** |
| rootFolderId | string | '-root-' | The ID of the root folder node. |
| versioning | boolean | false | Versioning false is the default uploader behaviour and it rename using an integer suffix if there is a name clash. Versioning true to indicate that a major version should be created |
| staticTitle | string | (predefined) | define the text of the upload button |
| disableWithNoPermission | boolean | false | If the value is true and the user doesn't have the permission to delete the node the button will be disabled |
### Events
| Name | Description |
| --- | --- |
| onSuccess | Raised when the file is uploaded |
### Advanced usage
#### How to show notification message with no permission
### How to show notification message with no permission
You can show a notification error when the user doesn't have the right permission to perform the action.
The UploadButtonComponent provides the event permissionEvent that is raised when the delete permission is missing
You can subscribe to this event from your component and use the NotificationService to show a message.
@@ -192,12 +109,16 @@ You can subscribe to this event from your component and use the NotificationServ
[rootFolderId]="currentFolderId"
(permissionEvent)="onUploadPermissionFailed($event)">
</alfresco-upload-button>
```
```ts
export class MyComponent {
onUploadPermissionFailed(event: any) {
this.notificationService.openSnackMessage(`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000);
}
onUploadPermissionFailed(event: any) {
this.notificationService.openSnackMessage(
`you don't have the ${event.permission} permission to ${event.action} the ${event.type} `, 4000
);
}
}
```
@@ -205,6 +126,7 @@ onUploadPermissionFailed(event: any) {
![Upload notification message](docs/assets/upload-notification-message.png)
#### How to disable the button when the delete permission is missing
You can easily disable the button when the user doesn't own the permission to perform the action.
The UploadButtonComponent provides the property disableWithNoPermission that can be true. In this way the button should be disabled if the delete permission is missing for the node.
@@ -217,141 +139,82 @@ The UploadButtonComponent provides the property disableWithNoPermission that can
![Upload disable button](docs/assets/upload-disable-button.png)
## UploadDragAreaComponent
### Drag and drop
This component, provide a drag and drop are to upload files to alfresco.
#### Basic usage
```html
<alfresco-upload-drag-area (onSuccess)="customMethod($event)"></alfresco-upload-drag-area>
<alfresco-upload-drag-area (onSuccess)="customMethod($event)">
<div style="width: 200px; height: 100px; border: 1px solid #888888">
DRAG HERE
</div>
</alfresco-upload-drag-area>
<file-uploading-dialog></file-uploading-dialog>
```
Example of an App that declares upload drag and drop component :
```ts
import { NgModule, Component } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { CoreModule, AlfrescoSettingsService, AlfrescoAuthenticationService } from 'ng2-alfresco-core';
import { UploadModule } from 'ng2-alfresco-upload';
@Component({
selector: 'alfresco-app-demo',
template: `<alfresco-upload-drag-area (onSuccess)="customMethod($event)" >
<div style="width: 200px; height: 100px; border: 1px solid #888888">
DRAG HERE
</div>
</alfresco-upload-drag-area>
<file-uploading-dialog></file-uploading-dialog>`
})
export class MyDemoApp {
constructor(private authService: AlfrescoAuthenticationService, private settingsService: AlfrescoSettingsService) {
settingsService.ecmHost = 'http://localhost:8080';
this.authService.login('admin', 'admin').subscribe(
ticket => {
console.log(ticket);
},
error => {
console.log(error);
});
}
export class AppComponent {
public onSuccess(event: Object): void {
console.log('File uploaded');
}
}
@NgModule({
imports: [
BrowserModule,
CoreModule.forRoot(),
UploadModule.forRoot()
],
declarations: [ MyDemoApp ],
bootstrap: [ MyDemoApp ]
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
```
#### Events
| Name | Description |
| --- | --- |
| `onSuccess` | The event is emitted when the file is uploaded |
#### Propertoes
### Properties
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `showNotificationBar` | *boolean* | true | Hide/show notification bar |
| `currentFolderPath` | *string* | '/Sites/swsdp/documentLibrary' | define the path where the files are uploaded |
| `versioning` | *boolean* | false | Versioning false is the default uploader behaviour and it rename using an integer suffix if there is a name clash. Versioning true to indicate that a major version should be created |
| enabled | boolean | true | Toggle component enabled state |
| **(deprecated)** showNotificationBar | boolean | true | Hide/show notification bar. **Deprecated in 1.6.0: use UploadService events and NotificationService api instead.** |
| rootFolderId | string | '-root-' | The ID of the root folder node. |
| **(deprecated)** currentFolderPath | string | '/' | define the path where the files are uploaded. **Deprecated in 1.6.0: use rootFolderId instead.** |
| versioning | boolean | false | Versioning false is the default uploader behaviour and it rename using an integer suffix if there is a name clash. Versioning true to indicate that a major version should be created |
### Events
### Files Dialog
| Name | Description |
| --- | --- |
| onSuccess | Raised when the file is uploaded |
## FileUploadingDialogComponent
This component provides a dialog that shows all the files uploaded with upload button or drag & drop area components.
This component should be used in combination with upload button or drag & drop area.
#### Basic usage
```html
<file-uploading-dialog></file-uploading-dialog>
```
## UploadService
Provides access to various APIs related to file upload features.
### Events
| Name | Type | Description |
| --- | --- | --- |
| queueChanged | FileModel[] | Raised every time the file queue changes. |
| fileUpload | FileUploadEvent | Raised every time a File model changes its state. |
| fileUploadStarting | FileUploadEvent | Raised when upload starts. |
| fileUploadCancelled | FileUploadEvent | Raised when upload gets cancelled by user. |
| fileUploadProgress | FileUploadEvent | Raised during file upload process and contains the current progress for the particular File model. |
| fileUploadAborted | FileUploadEvent | Raised when file upload gets aborted by the server. |
| fileUploadError | FileUploadEvent | Raised when an error occurs to file upload. |
| fileUploadComplete | FileUploadCompleteEvent | Raised when file upload is complete. |
## Build from sources
Alternatively you can build component from sources with the following commands:
You can build component from sources with the following commands:
```sh
npm install
npm run build
```
### Build the files and keep watching for changes
```sh
$ npm run build:w
```
## Running unit tests
```sh
npm test
```
### Running unit tests in browser
```sh
npm test-browser
```
This task rebuilds all the code, runs tslint, license checks and other quality check tools
before performing unit testing.
### Code coverage
```sh
npm run coverage
```
## Demo
If you want have a demo of how the component works, please check the demo folder :
```sh
cd demo
npm install
npm start
```
> The `build` task rebuilds all the code, runs tslint, license checks
> and other quality check tools before performing unit testing.
## NPM scripts
@@ -362,6 +225,16 @@ npm start
| npm run test-browser | Run unit tests in the browser
| npm run coverage | Run unit tests and display code coverage report |
## Demo
Please check the demo folder for a demo project
```sh
cd demo
npm install
npm start
```
## License
[Apache Version 2.0](https://github.com/Alfresco/alfresco-ng2-components/blob/master/LICENSE)

View File

@@ -0,0 +1,35 @@
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
module.exports = webpackMerge(commonConfig, {
devtool: 'cheap-module-source-map',
externals: [
/^\@angular\//,
/^rxjs\//,
'moment',
'raphael',
'ng2-charts',
'alfresco-js-api',
'ng2-alfresco-core',
'ng2-alfresco-datatable',
'ng2-activiti-analytics',
'ng2-activiti-diagrams',
'ng2-activiti-form',
"ng2-activiti-tasklist",
'ng2-alfresco-documentlist'
],
output: {
filename: './bundles/[name].js',
library: '[name]',
libraryTarget: 'umd',
chunkFilename: '[id].chunk.js'
},
entry: {
"ng2-alfresco-upload": "./index.ts"
}
});

View File

@@ -2,6 +2,11 @@ const webpack = require('webpack');
const helpers = require('./helpers');
const fs = require('fs');
const path = require('path');
const CopyWebpackPlugin = require('copy-webpack-plugin');
var HappyPack = require('happypack');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
module.exports = {
@@ -12,22 +17,14 @@ module.exports = {
}
},
// require those dependencies but don't bundle them
externals: [
/^\@angular\//,
/^rxjs\//,
'moment',
'raphael',
'ng2-charts',
'alfresco-js-api',
'ng2-alfresco-core',
'ng2-alfresco-datatable',
'ng2-activiti-analytics',
'ng2-activiti-diagrams',
'ng2-activiti-form',
"ng2-activiti-tasklist",
'ng2-alfresco-documentlist'
],
resolve: {
alias: {
"ng2-alfresco-core": helpers.root('../ng2-alfresco-core/index.ts')
},
extensions: ['.ts', '.js'],
symlinks: false,
modules: [helpers.root('../../ng2-components'), helpers.root('node_modules')]
},
module: {
rules: [
@@ -37,25 +34,19 @@ module.exports = {
loader: 'source-map-loader',
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
enforce: 'pre',
test: /\.ts$/,
use: 'source-map-loader',
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
enforce: 'pre',
test: /\.ts$/,
loader: 'tslint-loader',
options: {
emitErrors: true,
configFile: path.resolve(__dirname, './assets/tslint.json')
failOnHint: true
},
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
test: /\.ts$/,
use: ['ts-loader', 'angular2-template-loader'],
loader: ['happypack/loader?id=ts', 'angular2-template-loader'],
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
@@ -67,7 +58,13 @@ module.exports = {
test: /\.css$/,
loader: ['to-string-loader', 'css-loader'],
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},{
},
{
test: /\.component.scss$/,
use: ['to-string-loader', 'raw-loader', 'sass-loader'],
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
enforce: 'pre',
test: /\.ts$/,
loader: 'license-check',
@@ -95,15 +92,29 @@ module.exports = {
]
},
resolve: {
extensions: ['.ts', '.js'],
symlinks: false,
modules: [
'../ng2-components', 'node_modules'
]
},
plugins: [
new ForkTsCheckerWebpackPlugin(),
new HappyPack({
id: 'ts',
threads: 8,
loaders: [
{
path: 'ts-loader',
query: {
happyPackMode: true,
"compilerOptions": {
"paths": {}
}
}
}
]
}),
new CopyWebpackPlugin([{
from: `src/i18n/`,
to: `bundles/assets/${path.basename(helpers.root(''))}/i18n/`
}]),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.BannerPlugin(fs.readFileSync(path.resolve(__dirname, './assets/license_header_add.txt'), 'utf8')),
@@ -112,11 +123,19 @@ module.exports = {
/angular(\\|\/)core(\\|\/)@angular/,
helpers.root('./src'),
{}
)
),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
}),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
minimize: false // workaround for ng2
}
})
],
devtool: 'cheap-module-source-map',
node: {
fs: 'empty',
module: false

View File

@@ -0,0 +1,22 @@
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const testConfig = require('./webpack.test.js');
const helpers = require('./helpers');
module.exports = webpackMerge(testConfig, {
module: {
rules: [
{
enforce: 'post',
test: /^(?!(.*spec|index|.*mock|.*model|.*event)).*\.ts?$/,
include: [helpers.root('src')],
loader: 'istanbul-instrumenter-loader',
exclude: [
/node_modules/,
/test/
]
}
]
}
});

View File

@@ -1,85 +1,8 @@
const webpack = require('webpack');
const helpers = require('./helpers');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
module.exports = webpackMerge(commonConfig, {
module.exports = {
devtool: 'inline-source-map',
resolve: {
extensions: ['.ts', '.js'],
symlinks: false,
modules: [helpers.root('../ng2-components'), helpers.root('node_modules')]
},
module: {
rules: [
{
enforce: 'pre',
test: /\.js$/,
loader: 'source-map-loader',
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
test: /\.ts$/,
loaders: ['ts-loader?' + JSON.stringify({ transpileOnly: true}), 'angular2-template-loader'],
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
test: /\.html$/,
loader: 'html-loader',
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
test: /\.css$/,
loader: ['to-string-loader', 'css-loader'],
exclude: [/node_modules/, /bundles/, /dist/, /demo/]
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico|pdf)$/,
loader: 'file-loader',
query: {
name: '[path][name].[ext]',
outputPath: (url)=> {
return url.replace('src', 'dist');
}
}
},
{
enforce: 'post',
test: /\.ts$/,
loader: 'istanbul-instrumenter-loader',
exclude: [
/node_modules/,
/test/
]
}
]
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)@angular/,
helpers.root('./src'),
{}
),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
}),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
minimize: false // workaround for ng2
}
})
],
node: {
fs: 'empty',
module: false
}
};
devtool: 'inline-source-map'
});

View File

@@ -0,0 +1,10 @@
var path = require('path');
var _root = path.resolve(__dirname, '..');
function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [_root].concat(args));
}
exports.root = root;

View File

@@ -0,0 +1,133 @@
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const helpers = require('./helpers');
const path = require('path');
const alfrescoLibs = [
'ng2-alfresco-upload'
];
module.exports = {
entry: {
'polyfills': './src/polyfills.ts',
'vendor': './src/vendor.ts',
'dist': './src/main.ts'
},
module: {
rules: [
{
enforce: 'pre',
test: /\.js$/,
include: [helpers.root('src'), helpers.root('../ng2-components')],
loader: 'source-map-loader',
exclude: [ /node_modules/, /public/, /resources/, /dist/]
},
{
test: /\.ts$/,
include: [helpers.root('src'), helpers.root('..')],
loader: [
'ts-loader',
'angular2-template-loader'
],
exclude: [ /node_modules/, /public/, /resources/, /dist/]
},
{
enforce: 'pre',
test: /\.ts$/,
loader: 'tslint-loader',
include: [helpers.root('src')],
options: {
emitErrors: true
},
exclude: [ /node_modules/, /public/, /resources/, /dist/]
},
{
enforce: 'pre',
test: /\.ts$/,
use: 'source-map-loader',
exclude: [ /public/, /resources/, /dist/]
},
{
test: /\.html$/,
loader: 'html-loader',
exclude: [ /node_modules/, /public/, /resources/, /dist/]
},
{
test: /\.css$/,
exclude: [helpers.root('src'), helpers.root('../ng2-components')],
loader: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader?sourceMap'
})
},
{
test: /\.css$/,
include: [helpers.root('src'), helpers.root('../ng2-components')],
loader: 'raw-loader'
},
{
test: /\.component.scss$/,
use: ['to-string-loader', 'raw-loader', 'sass-loader']
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader?name=assets/[name].[hash].[ext]'
}
]
},
plugins: [
// Workaround for angular/angular#11580
new webpack.ContextReplacementPlugin(
// The (\\|\/) piece accounts for path separators in *nix and Windows
/angular(\\|\/)core(\\|\/)@angular/,
helpers.root('./src'), // location of your src
{} // a map of your routes
),
new HtmlWebpackPlugin({
template: './index.html'
}),
new CopyWebpackPlugin([
... alfrescoLibs.map(lib => {
return {
context: `../ng2-components/${lib}/bundles/assets/` ,
from: '**/*',
to: `assets/`
}
}),
{
context: 'resources/i18n',
from: '**/*.json',
to: 'resources/i18n'
},
... alfrescoLibs.map(lib => {
return {
context: 'node_modules',
from: `${lib}/src/i18n/*.json`,
to: 'node_modules'
}
})
]),
new webpack.optimize.CommonsChunkPlugin({
name: ['src', 'vendor', 'polyfills']
})
],
devServer: {
contentBase: helpers.root('dist'),
compress: true,
port: 3000,
historyApiFallback: true,
host: '0.0.0.0',
inline: true
},
node: {
fs: 'empty'
}
};

View File

@@ -0,0 +1,36 @@
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const commonConfig = require('./webpack.common.js');
const helpers = require('./helpers');
const path = require('path');
module.exports = webpackMerge(commonConfig, {
devtool: 'cheap-module-eval-source-map',
output: {
path: helpers.root('dist'),
filename: '[name].js',
chunkFilename: '[id].chunk.js'
},
resolve: {
alias: {
"ng2-alfresco-core$": path.resolve(__dirname, '../../ng2-alfresco-core/index.ts'),
"ng2-alfresco-upload$": path.resolve(__dirname, '../../ng2-alfresco-upload/index.ts')
},
extensions: ['.ts', '.js'],
modules: [path.resolve(__dirname, '../node_modules')]
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
minimize: false // workaround for ng2
}
})
]
});

View File

@@ -0,0 +1,65 @@
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const commonConfig = require('./webpack.common.js');
const helpers = require('./helpers');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
const alfrescoLibs = [
'ng2-alfresco-upload'
];
module.exports = webpackMerge(commonConfig, {
devtool: 'source-map',
output: {
path: helpers.root('dist'),
publicPath: '/',
filename: '[name].[hash].js',
chunkFilename: '[id].[hash].chunk.js'
},
resolve: {
extensions: ['.ts', '.js'],
modules: [helpers.root('node_modules')]
},
plugins: [
new CopyWebpackPlugin([
... alfrescoLibs.map(lib => {
return {
context: `node_modules/${lib}/bundles/assets/` ,
from: '**/*',
to: `assets/`
}
})
]),
new webpack.NoEmitOnErrorsPlugin(),
new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
mangle: {
keep_fnames: true
},
compress: {
warnings: false
},
output: {
comments: false
},
sourceMap: true
}),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
}
}),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
minimize: false // workaround for ng2
}
})
]
});

View File

@@ -7,38 +7,7 @@
<title>Alfresco Angular 2 Upload - Demo</title>
<base href="./">
<!-- Google Material Design Lite -->
<link rel="stylesheet" href="node_modules/material-design-lite/material.min.css">
<script src="node_modules/material-design-lite/material.min.js"></script>
<link rel="stylesheet" href="node_modules/material-design-icons/iconfont/material-icons.css">
<!-- Polyfill(s) for Safari (pre-10.x) -->
<script src="node_modules/intl/dist/Intl.min.js"></script>
<script src="node_modules/intl/locale-data/jsonp/en.js"></script>
<!-- Polyfill(s) for older browsers -->
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/dom4/1.8.3/dom4.js"></script>
<script src="node_modules/element.scrollintoviewifneeded-polyfill/index.js"></script>
<!-- Polyfill(s) for dialogs -->
<script src="node_modules/dialog-polyfill/dialog-polyfill.js"></script>
<link rel="stylesheet" type="text/css" href="node_modules/dialog-polyfill/dialog-polyfill.css" />
<style>._dialog_overlay { position: static !important; } </style>
<!-- Modules -->
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<!-- Load the Angular Material 2 stylesheet -->
<link href="node_modules/@angular/material/core/theming/prebuilt/deeppurple-amber.css" rel="stylesheet">
<script src="systemjs.config.js"></script>
<script>
System.import('app').catch(function(err){ console.error(err); });
</script>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>

View File

@@ -0,0 +1,79 @@
{
"name": "ng2-alfresco-upload-demo",
"version": "0.1.0",
"lockfileVersion": 1,
"dependencies": {
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"brace-expansion": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"rimraf": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz",
"integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=",
"dev": true
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
}

View File

@@ -3,19 +3,16 @@
"description": "Alfresco Angular2 Upload Component - Demo",
"version": "0.1.0",
"author": "Alfresco Software, Ltd.",
"main": "index.js",
"scripts": {
"clean": "npm run clean-build && rimraf dist node_modules typings dist",
"clean-build" : "rimraf 'src/{,**/}**.js' 'src/{,**/}**.js.map' 'src/{,**/}**.d.ts'",
"postinstall": "npm run build",
"start": "npm run build && concurrently \"npm run tsc:w\" \"npm run server\" ",
"server": "wsrv -o -s -l",
"build": "npm run tslint && npm run clean-build && npm run tsc",
"build:w": "npm run tslint && rimraf dist && npm run tsc:w",
"travis": "npm link ng2-alfresco-core ng2-alfresco-upload",
"tsc": "tsc",
"tsc:w": "tsc -w",
"tslint": "tslint -c tslint.json *.ts && tslint -c tslint.json src/{,**/}**.ts -e '{,**/}**.d.ts'"
"build": "rimraf dist && npm run webpack -- --config config/webpack.prod.js --progress --profile --bail",
"build:dev": "rimraf dist && npm run webpack -- --config config/webpack.dev.js --progress --profile --bail",
"start:dist": "wsrv -s dist/ -p 3000 -a 0.0.0.0",
"start": "npm run webpack-dev-server -- --config config/webpack.prod.js --progress --content-base app/",
"start:dev": "npm run webpack-dev-server -- --config config/webpack.dev.js --progress --content-base app/",
"clean": "npm run clean-build && rimraf dist node_modules typings dist",
"clean-build": "rimraf 'app/{,**/}**.js' 'app/{,**/}**.js.map' 'app/{,**/}**.d.ts'",
"webpack-dev-server": "node --max_old_space_size=4096 node_modules/webpack-dev-server/bin/webpack-dev-server.js",
"webpack": "webpack"
},
"license": "Apache-2.0",
"contributors": [
@@ -56,32 +53,75 @@
"@angular/platform-browser": "~4.0.0",
"@angular/platform-browser-dynamic": "~4.0.0",
"@angular/router": "~4.0.0",
"@angular/material": "2.0.0-beta.1",
"alfresco-js-api": "~1.5.0",
"alfresco-js-api": "~1.6.0",
"core-js": "2.4.1",
"hammerjs": "2.0.8",
"ng2-alfresco-core": "1.5.0",
"ng2-translate": "5.0.0",
"ng2-alfresco-core": "1.6.0",
"@ngx-translate/core": "^7.0.0",
"reflect-metadata": "0.1.10",
"rxjs": "5.1.0",
"systemjs": "0.19.27",
"zone.js": "0.7.6",
"intl": "1.2.4",
"dialog-polyfill": "0.4.7",
"element.scrollintoviewifneeded-polyfill": "1.0.1",
"material-design-icons": "2.2.3",
"material-design-lite": "1.2.1",
"ng2-alfresco-upload": "1.5.0"
"ng2-alfresco-upload": "1.6.0"
},
"devDependencies": {
"@types/jasmine": "^2.2.33",
"@types/node": "^6.0.42",
"concurrently": "^2.2.0",
"rimraf": "2.5.2",
"tslint": "3.15.1",
"typescript": "^2.0.3",
"wsrv": "^0.1.5"
"@types/hammerjs": "^2.0.34",
"@types/jasmine": "2.5.35",
"@types/node": "6.0.45",
"angular2-template-loader": "^0.6.2",
"autoprefixer": "^6.5.4",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^0.23.1",
"css-to-string-loader": "^0.1.2",
"cssnano": "^3.8.1",
"extract-text-webpack-plugin": "^2.0.0-rc.3",
"file-loader": "0.11.1",
"html-loader": "^0.4.4",
"html-webpack-plugin": "^2.28.0",
"istanbul-instrumenter-loader": "0.2.0",
"jasmine-ajax": "^3.2.0",
"jasmine-core": "2.4.1",
"karma": "^0.13.22",
"karma-chrome-launcher": "~1.0.1",
"karma-coverage": "^1.1.1",
"karma-jasmine": "~1.0.2",
"karma-jasmine-ajax": "^0.1.13",
"karma-jasmine-html-reporter": "0.2.0",
"karma-mocha-reporter": "^2.2.2",
"karma-remap-istanbul": "^0.6.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-systemjs": "^0.16.0",
"karma-webpack": "^2.0.2",
"loader-utils": "^1.1.0",
"merge-stream": "^1.0.1",
"node-sass": "^3.13.1",
"null-loader": "^0.1.1",
"package-json-merge": "0.0.1",
"raw-loader": "^0.5.1",
"remap-istanbul": "^0.6.3",
"rimraf": "^2.6.1",
"run-sequence": "^1.2.2",
"sass-loader": "6.0.2",
"script-loader": "0.7.0",
"source-map-loader": "^0.1.6",
"style-loader": "^0.13.1",
"systemjs-builder": "^0.15.34",
"to-string-loader": "^1.1.4",
"traceur": "^0.0.91",
"ts-loader": "^2.0.0",
"ts-node": "^1.7.0",
"tslint": "^4.4.2",
"tslint-loader": "^3.3.0",
"typescript": "^2.1.6",
"webpack": "^2.2.1",
"webpack-dev-server": "^2.3.0",
"webpack-merge": "2.6.1",
"wsrv": "^0.1.7"
}
}

View File

@@ -0,0 +1,17 @@
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'intl';
require('zone.js/dist/zone'); // IE 8-11
require('element.scrollintoviewifneeded-polyfill'); // IE/FF
if (process.env.ENV === 'production') {
// Production
} else {
// Development
Error['stackTraceLimit'] = Infinity;
require('zone.js/dist/long-stack-trace-zone');
}

View File

@@ -0,0 +1,26 @@
// Angular
import '@angular/platform-browser';
import '@angular/platform-browser-dynamic';
import '@angular/core';
import '@angular/common';
import '@angular/http';
import '@angular/router';
// RxJS
import 'rxjs';
// hammerjs
import 'hammerjs';
// Alfresco
import 'alfresco-js-api';
import 'ng2-alfresco-upload';
// Google Material Design Lite
import 'material-design-lite/material.js';
import 'material-design-lite/dist/material.orange-blue.min.css';
import 'material-design-icons/iconfont/material-icons.css';
// Polyfill(s) for dialogs
require('script-loader!dialog-polyfill/dialog-polyfill');
import 'dialog-polyfill/dialog-polyfill.css';

View File

@@ -1,51 +0,0 @@
/**
* System configuration for Angular 2 samples
* Adjust as necessary for your application needs.
*/
(function (global) {
System.config({
paths: {
// paths serve as alias
'npm:': 'node_modules/'
},
// map tells the System loader where to look for things
map: {
// our app is within the app folder
app: 'src',
// angular bundles
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/material': 'npm:@angular/material/bundles/material.umd.js',
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.min.js',
'@angular/animations/browser':'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
// other libraries
'rxjs': 'npm:rxjs',
'ng2-translate': 'npm:ng2-translate',
'alfresco-js-api': 'npm:alfresco-js-api/dist',
'ng2-alfresco-core': 'npm:ng2-alfresco-core',
'ng2-alfresco-upload': 'npm:ng2-alfresco-upload'
},
// packages tells the System loader how to load when no filename and/or no extension
packages: {
app: {
main: './main.js',
defaultExtension: 'js'
},
rxjs: {
defaultExtension: 'js'
},
'ng2-translate': { defaultExtension: 'js' },
'alfresco-js-api': { main: './alfresco-js-api.js', defaultExtension: 'js'},
'ng2-alfresco-core': {main: './bundles/ng2-alfresco-core.js', defaultExtension: 'js'},
'ng2-alfresco-upload': {main: './bundles/ng2-alfresco-upload.js', defaultExtension: 'js'}
}
});
})(this);

View File

@@ -1,5 +1,6 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
@@ -16,6 +17,7 @@
"noFallthroughCasesInSwitch": true,
"removeComments": true,
"declaration": true,
"outDir": "./dist",
"lib": [
"es2015",
"dom"
@@ -23,7 +25,9 @@
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"node_modules"
"demo",
"node_modules",
"dist"
],
"angularCompilerOptions": {
"strictMetadataEmit": false,

View File

@@ -1,124 +1,118 @@
{
"rules": {
"align": [
true,
"parameters",
"arguments",
"statements"
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space",
"check-lowercase"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"interface-name": false,
"jsdoc-format": true,
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
180
],
"member-ordering": [
true,
"public-before-private",
"static-before-instance",
"variables-before-functions"
],
"no-any": false,
"no-arg": true,
"no-bitwise": false,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-constructor-vars": false,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-require-imports": false,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unreachable": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"no-var-requires": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"radix": true,
"semicolon": true,
"switch-default": true,
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef": false,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"use-strict": false,
"variable-name": [
true,
"check-format",
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-operator",
"check-separator",
"check-type",
"check-module",
"check-decl"
]
}
"rules": {
"align": [
true,
"parameters",
"statements"
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"interface-name": false,
"jsdoc-format": true,
"label-position": true,
"max-line-length": [
true,
180
],
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-any": false,
"no-arg": true,
"no-bitwise": false,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-constructor-vars": false,
"no-debugger": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-require-imports": false,
"no-shadowed-variable": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"no-var-requires": false,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"radix": true,
"semicolon": true,
"switch-default": true,
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef": false,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"use-strict": false,
"variable-name": [
true,
"check-format",
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-operator",
"check-separator",
"check-type",
"check-module",
"check-decl"
]
}
}

View File

@@ -0,0 +1 @@
module.exports = require('./config/webpack.dev.js');

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;;;;;;;;;;;;AAEH,sCAA8D;AAC9D,uDAA+C;AAE/C,0FAAsF;AACtF,sFAAmF;AACnF,oFAAiF;AACjF,oGAAgG;AAChG,gGAA4F;AAC5F,gEAA8D;AAkB9D,8DAAyD;AACzD,sEAAiE;AACjE,iEAA4D;AAC5D,mDAA8C;AAC9C,+DAA0D;AAC1D,oEAA+D;AAElD,QAAA,iBAAiB,GAAU;IACpC,iDAAsB;IACtB,oDAAuB;IACvB,+CAAqB;IACrB,8DAA4B;IAC5B,0DAA0B;CAC7B,CAAC;AAEW,QAAA,gBAAgB,GAAU;IACnC,8BAAa;CAChB,CAAC;AAgBF,IAAa,YAAY;IAAzB;IASA,CAAC;IARU,oBAAO,GAAd;QACI,MAAM,CAAC;YACH,QAAQ,EAAE,cAAY;YACtB,SAAS,EACF,wBAAgB,QACtB;SACJ,CAAC;IACN,CAAC;IACL,mBAAC;AAAD,CAAC,AATD,IASC;AATY,YAAY;IAdxB,eAAQ,CAAC;QACN,OAAO,EAAE;YACL,8BAAU;SACb;QACD,YAAY,EACL,yBAAiB,QACvB;QACD,SAAS,EACF,wBAAgB,QACtB;QACD,OAAO,EACA,yBAAiB,QACvB;KACJ,CAAC;GACW,YAAY,CASxB;AATY,oCAAY"}

View File

@@ -16,6 +16,7 @@
*/
import { NgModule, ModuleWithProviders } from '@angular/core';
import { MdIconModule, MdProgressSpinnerModule, MdButtonModule } from '@angular/material';
import { CoreModule } from 'ng2-alfresco-core';
import { UploadDragAreaComponent } from './src/components/upload-drag-area.component';
@@ -25,22 +26,6 @@ import { FileUploadingDialogComponent } from './src/components/file-uploading-di
import { FileUploadingListComponent } from './src/components/file-uploading-list.component';
import { UploadService } from './src/services/upload.service';
/**
* ng2-alfresco-upload, provide components to upload files to alfresco repository.
*
* Components provided:
* - A button to upload files
* <alfresco-upload-button [showDialogUpload]="boolean"
* [showNotificationBar]="boolean"
* [uploadFolders]="boolean"
* [multipleFiles]="boolean"
* [acceptedFilesType]="string">
* </alfresco-upload-button>
*
* - Drag and drop area to upload files:
* <alfresco-upload-drag-area [showDialogUpload]="boolean" ></alfresco-upload-drag-area>
*/
export * from './src/components/upload-button.component';
export * from './src/components/file-uploading-dialog.component';
export * from './src/components/upload-drag-area.component';
@@ -49,6 +34,7 @@ export * from './src/directives/file-draggable.directive';
export * from './src/components/file-uploading-list.component';
export * from './src/models/file.model';
export * from './src/models/permissions.model';
export * from './src/events/file.event';
export const UPLOAD_DIRECTIVES: any[] = [
FileDraggableDirective,
@@ -64,7 +50,10 @@ export const UPLOAD_PROVIDERS: any[] = [
@NgModule({
imports: [
CoreModule
CoreModule,
MdIconModule,
MdProgressSpinnerModule,
MdButtonModule
],
declarations: [
...UPLOAD_DIRECTIVES

View File

@@ -8,17 +8,13 @@ module.exports = function (config) {
files: [
'./node_modules/hammerjs/hammer.js',
{pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css', included: true, watched: false},
//diagrams
'./node_modules/chart.js/dist/Chart.js',
'./node_modules/alfresco-js-api/dist/alfresco-js-api.js',
'./node_modules/raphael/raphael.js',
'./node_modules/moment/min/moment.min.js',
'./node_modules/md-date-time-picker/dist/js/mdDateTimePicker.js',
{pattern: './node_modules/ng2-translate/**/*.js', included: false, watched: false},
{pattern: './node_modules/ng2-charts/**/*.js', included: false, served: true, watched: false},
{pattern: './node_modules/md-date-time-picker/**/*.js', included: false, served: true, watched: false},
{pattern: './node_modules/moment/**/*.js', included: false, served: true, watched: false},
{pattern: 'karma-test-shim.js', watched: false},
@@ -27,7 +23,7 @@ module.exports = function (config) {
{pattern: './src/**/*.ts', included: false, served: true, watched: false}
],
webpack: webpackConfig,
webpack: (config.mode === 'coverage') ? require('./webpack.coverage') : require('./webpack.test'),
webpackMiddleware: {
stats: 'errors-only'

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,17 @@
{
"name": "ng2-alfresco-upload",
"description": "Alfresco Angular2 Upload Component",
"version": "1.5.0",
"version": "1.6.0",
"author": "Alfresco Software, Ltd.",
"scripts": {
"clean": "rimraf dist node_modules typings bundles coverage .npmrc",
"clean-lock": "rimraf package-lock.json",
"rimraf": "rimraf",
"build": "webpack --config webpack.build.js --progress --profile --bail",
"test": "karma start karma.conf.js --reporters mocha,coverage --single-run --component",
"test": "karma start karma.conf.js --reporters mocha,coverage --single-run --mode coverage",
"test-browser": "karma start karma.conf.js --reporters kjhtml --component",
"coverage": "npm run test && wsrv -o -p 9875 ./coverage/report",
"prepublish" : "npm run build"
"prepublishOnly": "npm run build"
},
"main": "bundles/ng2-alfresco-upload.js",
"repository": {
@@ -51,13 +52,12 @@
"@angular/platform-browser": "~4.0.0",
"@angular/platform-browser-dynamic": "~4.0.0",
"@angular/router": "~4.0.0",
"@angular/material": "2.0.0-beta.1",
"alfresco-js-api": "~1.5.0",
"@angular/material": "2.0.0-beta.6",
"alfresco-js-api": "~1.6.0",
"core-js": "2.4.1",
"hammerjs": "2.0.8",
"ng2-alfresco-core": "1.5.0",
"ng2-translate": "5.0.0",
"ng2-alfresco-core": "1.6.0",
"@ngx-translate/core": "^7.0.0",
"reflect-metadata": "0.1.10",
"rxjs": "5.1.0",
"systemjs": "0.19.27",
@@ -75,6 +75,8 @@
"cssnano": "^3.8.1",
"extract-text-webpack-plugin": "^2.0.0-rc.3",
"file-loader": "0.11.1",
"fork-ts-checker-webpack-plugin": "^0.2.3",
"happypack": "3.0.0",
"html-loader": "^0.4.4",
"html-webpack-plugin": "^2.28.0",
"istanbul-instrumenter-loader": "0.2.0",
@@ -93,12 +95,14 @@
"karma-webpack": "^2.0.2",
"loader-utils": "^1.1.0",
"merge-stream": "^1.0.1",
"node-sass": "^4.5.3",
"null-loader": "^0.1.1",
"package-json-merge": "0.0.1",
"raw-loader": "^0.5.1",
"remap-istanbul": "^0.6.3",
"rimraf": "^2.5.4",
"rimraf": "^2.6.1",
"run-sequence": "^1.2.2",
"sass-loader": "^6.0.5",
"script-loader": "0.7.0",
"source-map-loader": "^0.1.6",
"style-loader": "^0.13.1",
@@ -109,7 +113,7 @@
"ts-node": "^1.7.0",
"tslint": "^4.4.2",
"tslint-loader": "^3.3.0",
"typescript": "^2.1.6",
"typescript": "^2.3.4",
"webpack": "^2.2.1",
"webpack-dev-server": "^2.3.0",
"webpack-merge": "2.6.1",

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();
}
}
}

View File

@@ -35,7 +35,9 @@
"ng2-alfresco-viewer": ["../ng2-alfresco-viewer/"],
"ng2-alfresco-webscript": ["../ng2-alfresco-webscript/"],
"ng2-alfresco-userinfo": ["../ng2-alfresco-userinfo"],
"alfresco-js-api": ["../node_modules/alfresco-js-api/"]
"alfresco-js-api": ["./node_modules/alfresco-js-api/"],
"@angular/*": ["./node_modules/@angular/*"],
"rxjs/*": ["./node_modules/rxjs/*"]
},
"lib": [
"es2015",

View File

@@ -1,17 +1 @@
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const commonConfig = require('./config/webpack.common.js');
module.exports = webpackMerge(commonConfig, {
output: {
filename: './bundles/[name].js',
library: '[name]',
libraryTarget: 'umd',
chunkFilename: '[id].chunk.js'
},
entry: {
"ng2-alfresco-upload": "./index.ts"
}
});
module.exports = require('./config/webpack.build.js');

View File

@@ -0,0 +1 @@
module.exports = require('./config/webpack.coverage.js');

View File

@@ -1,8 +1 @@
const webpack = require('webpack');
const webpackMerge = require('webpack-merge');
const testConfig = require('./config/webpack.test.js');
module.exports = webpackMerge(testConfig, {
});
module.exports = require('./config/webpack.test.js');