diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..86659e2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,25 @@
+sudo: required
+dist: trusty
+language: node_js
+node_js: "6.11.0"
+
+cache:
+ directories:
+ - node_modules
+
+apt:
+ sources:
+ - google-chrome
+ packages:
+ - google-chrome-stable
+ - google-chrome-beta
+
+install:
+ - npm i -g @angular/cli
+ - npm i
+ - npm test
+
+before_install:
+ - export CHROME_BIN=chromium-browser
+ - export DISPLAY=:99.0
+ - sh -e /etc/init.d/xvfb start
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d4d931c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2017 The angular-translate team and Pascal Precht
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/README.md b/README.md
index c07eed6..cdcbb3a 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,14 @@ This project is a very simple __Angular2 file manager__.
## Features
+### v1.0.0
+* update angular2-tree to verison 2.x.x
+* update angular to version 4.x.x
+* use ngrx/store
+* prepare events for all actions
+* update configuration: allowed file types filter for upload files, allow limit for uploaded file
+* create examples: with backend in node, without backend on local storage
+
### v0.5.4
* fix problem with open "choose file window"
@@ -59,37 +67,110 @@ Install npm package
In your project put this line
- Loading...
-
-## Override API
-
-To override endpoints to manage files and directories provide special provider in you module
-
+ Loading...
+
+### Provide configuration
+In your module add following lines with configuration
+
+ import {FileManagerModule, IFileManagerConfiguration} from '../../../main';
+ ...
+ const fileManagerConfiguration: IFileManagerConfiguration = {
+ urls: {
+ foldersUrl: '/api/folder',
+ filesUrl: /api/files,
+ folderMoveUrl: '/api/folder/move'
+ },
+ isMultiSelection: true,
+ mimeTypes: ['image/jpg', 'image/jpeg', 'image/png'],
+ maxFileSize: 50 * 1024
+ }
+ ...
+
+You can create a simple configuration object, it should contains a subset of below options
+
+* __urls__
+ * _foldersUrl_ - url for create (POST), delete (DELETE), edit (PUT) and get (GET) folders
+ * _filesUrl_ - url for upload (POST), edit (PUT), delete (DELETE) and get (GET) files
+ * _folderMoveUrl_ -
+* __isMultiselection__ - allow/disallow multiselection
+* __mimeTypes__ - list of file type mimes which are allowed to upload
+* __maxFileSize__ - limit of the single file size
+
+Then you have to provide this constant as a configuration service
+
@NgModule({
- ...
- providers: [
- ...
- {
- provide: 'fileManagerUrls',
- useValue: {foldersUrl: '/api/filemanager/folder', filesUrl: '/api/filemanager/file'}
- }
- ]
- ...
+ ...
+ imports: [
+ ...,
+ FileManagerModule
+ ],
+ providers: [
+ {provide: 'fileManagerConfiguration', useValue: fileManagerConfiguration}
+ ],
+ bootstrap: [AppComponent]
})
+ export class AppModule {
+ }
+
+### Create API service
+
+Now you should create your own API service to communicate with backend or use existing one _FileManagerBackendApiService_.
+If you create your own API service it should have implemented _IFileManagerApi_ interface
+* _add(node: IOuterNode, parentNodeId: string): Observable;_ - create new node of the tree
+* _load(nodeId: string): Observable;_ - load tree branch (if nodeId is empty string it loads root level)
+* _move(srcNode: IOuterNode, targetNode: IOuterNode | null): Observable;_ - move one node (with all its sub nodes) to another parent
+* _update(node: IOuterNode): Observable;_ - update node name
+* _remove(nodeId: string): Observable;_ - remove node
+* _cropFile(file: IOuterFile, bounds: ICropBounds): Observable;_ - crop file to provided bounds
+* _loadFiles(nodeId: string): Observable;_ - load files from given node
+* _removeFile(file: IOuterFile): Observable;_ - remove single file
+* _uploadFile(file: IOuterFile): Observable;_ - do actions with uploaded file (real upload is done in ng2-upload-file)
+
+All those actions should manipulate on two protected properties:
+* _nodes: IOuterNode[]_ - list of all loaded nodes
+* _files: IFileDataProperties[]_ - list of files form current node
+
+You can see two examples of that service:
+* [_FileManagerApiService_](src/store/fileManagerApi.service.ts) - works on local storage
+* [_FileManagerBackendApiService_](src/store/fileManagerBackendApi.service.ts) - works on backend (written in node)
+
+### Attach to any Effects
+
+Because of using _store_, _actions_ and _effects_ you can attach to any actions by creating your own effects service.
+You are able to connect to actions for doing something special (but this is not obligatory, this is only possibility):
+* _FileManagerActionsService.FILEMANAGER_CROP_FILE_
+* _FileManagerActionsService.FILEMANAGER_CROP_FILE_SUCCESS_
+* _FileManagerActionsService.FILEMANAGER_CROP_FILE_ERROR_
+* _FileManagerActionsService.FILEMANAGER_DELETE_FILE_
+* _FileManagerActionsService.FILEMANAGER_DELETE_FILE_SUCCESS_
+* _FileManagerActionsService.FILEMANAGER_DELETE_FILE_SELECTION_
+* _FileManagerActionsService.FILEMANAGER_DELETE_FILE_SELECTION_SUCCESS_
+* _FileManagerActionsService.FILEMANAGER_INVERSE_FILE_SELECTION_
+* _FileManagerActionsService.FILEMANAGER_LOAD_FILES_
+* _FileManagerActionsService.FILEMANAGER_LOAD_FILES_SUCCESS_
+* _FileManagerActionsService.FILEMANAGER_SELECT_ALL_
+* _FileManagerActionsService.FILEMANAGER_SELECT_FILE_
+* _FileManagerActionsService.FILEMANAGER_UNSELECT_FILE_
+* _FileManagerActionsService.FILEMANAGER_UNSELECT_ALL_
+* _FileManagerActionsService.FILEMANAGER_UPLOAD_FILE_
+* _FileManagerActionsService.FILEMANAGER_UPLOAD_FILE_ERROR_
+* _FileManagerActionsService.FILEMANAGER_UPLOAD_FILE_SUCCESS_
## Demo
-To run demo you have to serve frontend and backend. To do this run:
+To run local demo you have to serve frontend and backend. To do this run:
* frontend:
-
+ * using local storage
+
npm start
+
+ * or using real backend
+
+ npm run startWithBackend
* backend
npm run backend
-## TODO
-
-* files upload progress
-* multi selection events (delete, select)
+Or you can see online [demo](https://qjon.github.io/angular2-filemanager/) with _local storage_
diff --git a/angular-cli.json b/angular-cli.json
index 2e5935e..f9cf3ba 100644
--- a/angular-cli.json
+++ b/angular-cli.json
@@ -1,11 +1,12 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
- "version": "1.0.0-beta.32.3",
+ "version": "1.2.0",
"name": "angular2-filemanager"
},
"apps": [
{
+ "name": "withoutBackend",
"root": "demo/src",
"outDir": "dist",
"assets": [
@@ -16,8 +17,37 @@
"index": "index.html",
"main": "main.ts",
"polyfills": "polyfills.ts",
- "test": "test.ts",
+ "test": "../../src/test.ts",
"tsconfig": "tsconfig.json",
+ "testTsconfig": "../../src/tsconfig.spec.json",
+ "prefix": "app",
+ "mobile": false,
+ "styles": [
+ "../../node_modules/bootstrap/dist/css/bootstrap.min.css",
+ "../../node_modules/font-awesome/css/font-awesome.css"
+ ],
+ "scripts": [],
+ "environmentSource": "environments/environment.ts",
+ "environments": {
+ "dev": "environments/environment.ts",
+ "prod": "environments/environment.prod.ts"
+ }
+ },
+ {
+ "name": "withBackend",
+ "root": "demo/src",
+ "outDir": "dist",
+ "assets": [
+ "assets",
+ "icons",
+ "favicon.ico"
+ ],
+ "index": "index.html",
+ "main": "mainWithBackend.ts",
+ "polyfills": "polyfills.ts",
+ "test": "../../src/test.ts",
+ "tsconfig": "tsconfig.json",
+ "testTsconfig": "../../src/tsconfig.spec.json",
"prefix": "app",
"mobile": false,
"styles": [
diff --git a/demo/backend/index.js b/demo/backend/index.js
index 6f6bf53..0947435 100644
--- a/demo/backend/index.js
+++ b/demo/backend/index.js
@@ -37,15 +37,16 @@ app.use(express.static(basePath));
app.get('/folders', function (req, res) {
var paths = [];
- var subdir = req.query.nodeId || '';
- var items = fs.readdirSync(basePath + subdir);
+ var subNode = req.query.nodeId || '';
+ var items = fs.readdirSync(basePath + subNode);
for (var i = 0; i < items.length; i++) {
var name = items[i];
- var stat = fs.statSync(basePath + subdir + '/' + name);
+ var stat = fs.statSync(basePath + subNode + '/' + name);
if (stat && stat.isDirectory()) {
var dir = {
- id: subdir + '/' + name,
+ id: subNode + '/' + name,
+ parentId: subNode || null,
name: name,
children: []
};
@@ -59,79 +60,105 @@ app.get('/folders', function (req, res) {
});
app.put('/folders', function (req, res) {
- var folder = req.body;
+ var node = req.body;
- if (isDirectory(folder.id)) {
- var subdirs = folder.id.split('/');
- subdirs[subdirs.length - 1] = folder.name;
- var newDirName = subdirs.join('/');
+ if (isDirectory(node.id)) {
+ var subNodes = node.id.split('/');
+ subNodes[subNodes.length - 1] = node.name;
+ var newNodeName = subNodes.join('/');
- if (isDirectory(newDirName)) {
+ if (isDirectory(newNodeName)) {
res.sendStatus(403);
res.json({msg: 'Directory already exists'});
}
else {
- fs.renameSync(basePath + folder.id, basePath + newDirName);
+ fs.renameSync(basePath + node.id, basePath + newNodeName);
- if (isDirectory(newDirName)) {
- folder.id = newDirName;
- res.json(folder);
+ if (isDirectory(newNodeName)) {
+ node.id = newNodeName;
+ res.json(node);
} else {
res.sendStatus(403);
- res.json({msg: 'Could not change directory name'});
+ res.json({msg: 'Could not change node name'});
}
}
} else {
res.sendStatus(403);
- res.json({msg: 'Directory does not exist'});
+ res.json({msg: 'Node does not exist'});
}
-
});
+app.put('/folders/move', function (req, res) {
+ var data = req.body;
+ console.log(data);
+
+ if (data.target === null) {
+ data.target = '';
+ }
+
+ if (isDirectory(data.source) && isDirectory(data.target)) {
+ var subNodes = data.source.split('/');
+ var dirName = subNodes[subNodes.length - 1];
+ var newNodeName = data.target + '/' + dirName;
+
+ fs.renameSync(basePath + data.source, basePath + newNodeName);
+ var dir = {
+ id: newNodeName,
+ name: dirName,
+ parentId: data.target,
+ children: []
+ };
+
+ res.json(dir);
+ } else {
+ res.sendStatus(403);
+ res.json({msg: 'Node does not exist'});
+ }
+
+});
app.post('/folders', function (req, res) {
var data = req.body;
- var folder = data.node;
+ var node = data.node;
var parentFolderId = data.parentNodeId || '';
- var newDirId = parentFolderId + '/' + folder.name;
+ var newNodeId = parentFolderId + '/' + node.name;
- if (!isDirectory(newDirId)) {
- fs.mkdirSync(basePath + newDirId);
+ if (!isDirectory(newNodeId)) {
+ fs.mkdirSync(basePath + newNodeId);
- if (isDirectory(newDirId)) {
+ if (isDirectory(newNodeId)) {
res.json({
- id: newDirId,
- name: folder.name,
+ id: newNodeId,
+ name: node.name,
+ parentId: parentFolderId || null,
children: []
});
} else {
res.sendStatus(403);
- res.json({msg: 'Directory has not been added'});
+ res.json({msg: 'Node has not been added'});
}
} else {
res.sendStatus(403);
- res.json({msg: 'Directory exists'});
+ res.json({msg: 'Node exists'});
}
-
});
app.delete('/folders', function (req, res) {
var data = req.body;
- var folderId = data.nodeId || null;
+ var nodeId = data.nodeId || null;
- if (isDirectory(folderId)) {
- fs.rmdirSync(basePath + folderId);
+ if (isDirectory(nodeId)) {
+ fs.rmdirSync(basePath + nodeId);
res.json({
- success: !isDirectory(folderId)
+ success: !isDirectory(nodeId)
});
} else {
res.sendStatus(403);
res.json({msg: 'Directory exists'});
}
-
});
@@ -155,7 +182,7 @@ function prepareFile(filePath) {
name: name,
thumbnailUrl: src,
url: src,
- mime: mimeType,
+ type: mimeType,
width: isImage ? dimensions.width : 0,
height: isImage ? dimensions.height : 0
};
@@ -182,6 +209,7 @@ app.get('/files', function (req, res) {
app.post('/files', function (req, res) {
var fileExist = false;
+ var newPath;
var form = new formidable.IncomingForm();
form.multiples = true;
@@ -190,7 +218,6 @@ app.post('/files', function (req, res) {
form.on('file', function (field, file) {
var folder = req.header('folderId');
- var newPath;
file.name = file.name.replace(/[^A-Za-z0-9\-\._]/g, '');
@@ -217,7 +244,7 @@ app.post('/files', function (req, res) {
res.statusCode = 409;
res.end('error');
} else {
- res.end('success');
+ res.json(prepareFile(newPath));
}
});
diff --git a/demo/src/app/app.component.html b/demo/src/app/app.component.html
index 94ce9cb..e32d140 100644
--- a/demo/src/app/app.component.html
+++ b/demo/src/app/app.component.html
@@ -2,9 +2,9 @@
Filemanager
- Loading...
+ Loading...
diff --git a/demo/src/app/app.component.spec.ts b/demo/src/app/app.component.spec.ts
deleted file mode 100644
index be6cd8a..0000000
--- a/demo/src/app/app.component.spec.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/* tslint:disable:no-unused-variable */
-
-import {TestBed, async} from '@angular/core/testing';
-import {AppComponent} from './app.component';
-
-describe('AppComponent', () => {
- beforeEach(() => {
- TestBed.configureTestingModule({
- declarations: [
- AppComponent
- ],
- });
- TestBed.compileComponents();
- });
-
- it('should create the app', async(() => {
- let fixture = TestBed.createComponent(AppComponent);
- let app = fixture.debugElement.componentInstance;
- expect(app).toBeTruthy();
- }));
-
- it(`should have as title 'app works!'`, async(() => {
- let fixture = TestBed.createComponent(AppComponent);
- let app = fixture.debugElement.componentInstance;
- expect(app.title).toEqual('app works!');
- }));
-
- it('should render title in a h1 tag', async(() => {
- let fixture = TestBed.createComponent(AppComponent);
- fixture.detectChanges();
- let compiled = fixture.debugElement.nativeElement;
- expect(compiled.querySelector('h1').textContent).toContain('app works!');
- }));
-});
diff --git a/demo/src/app/app.component.ts b/demo/src/app/app.component.ts
index 0f84a3a..f812cc0 100644
--- a/demo/src/app/app.component.ts
+++ b/demo/src/app/app.component.ts
@@ -1,21 +1,22 @@
import {Component} from '@angular/core';
-import {ISelectFile} from "../../../main";
+import {FileManagerConfiguration, FileManagerDispatcherService} from '../../../main';
+
@Component({
- selector: 'app-root',
- templateUrl: './app.component.html',
- styleUrls: ['./app.component.less']
+ selector: 'app-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.less']
})
export class AppComponent {
- title = 'app works!';
-
- public isMultiSelection = false;
+ public constructor(public fileManagerConfiguration: FileManagerConfiguration,
+ private fileManagerDispatcher: FileManagerDispatcherService) {
+ }
- public toggleMultiSelection() {
- this.isMultiSelection = !this.isMultiSelection;
- }
+ public toggleMultiSelection() {
+ this.fileManagerConfiguration.isMultiSelection = !this.fileManagerConfiguration.isMultiSelection;
- public selectFile(data: ISelectFile) {
- console.log(data);
+ if (!this.fileManagerConfiguration.isMultiSelection) {
+ this.fileManagerDispatcher.unSelectAllFiles();
}
+ }
}
diff --git a/demo/src/app/app.module.ts b/demo/src/app/app.module.ts
index e4305a5..b8bcf8b 100644
--- a/demo/src/app/app.module.ts
+++ b/demo/src/app/app.module.ts
@@ -4,8 +4,18 @@ import {FormsModule} from '@angular/forms';
import {HttpModule} from '@angular/http';
import {AppComponent} from './app.component';
-import {FileManagerModule} from "../../../src/filemanager.module";
+import {FileManagerModule, IFileManagerConfiguration} from '../../../main';
+const fileManagerConfiguration: IFileManagerConfiguration = {
+ urls: {
+ foldersUrl: '/api/folder',
+ filesUrl: null,
+ folderMoveUrl: '/api/folder/move'
+ },
+ isMultiSelection: true,
+ mimeTypes: ['image/jpg', 'image/jpeg', 'image/png'],
+ maxFileSize: 50 * 1024
+};
@NgModule({
declarations: [
@@ -18,7 +28,7 @@ import {FileManagerModule} from "../../../src/filemanager.module";
HttpModule
],
providers: [
- {provide: 'fileManagerUrls', useValue: {foldersUrl: '/api/folder', filesUrl: '/api/files'}}
+ {provide: 'fileManagerConfiguration', useValue: fileManagerConfiguration}
],
bootstrap: [AppComponent]
})
diff --git a/demo/src/appWithBackend/appWithBackend.component.html b/demo/src/appWithBackend/appWithBackend.component.html
new file mode 100644
index 0000000..3150356
--- /dev/null
+++ b/demo/src/appWithBackend/appWithBackend.component.html
@@ -0,0 +1,10 @@
+