├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── Actions
└── Actions.js
├── Api
├── Api.js
└── ApiHandler.js
├── App.js
├── App.test.js
├── Components
├── Breadcrumb
│ ├── Breadcrumb.css
│ ├── Breadcrumb.jsx
│ ├── BreadcrumbText.css
│ └── BreadcrumbText.jsx
├── ContextMenu
│ ├── ContextMenu.css
│ ├── ContextMenu.jsx
│ └── ContextMenuActions
│ │ ├── CopyAction.jsx
│ │ ├── CreateFolderAction.jsx
│ │ ├── DownloadAction.jsx
│ │ ├── EditAction.jsx
│ │ ├── MoveAction.jsx
│ │ ├── OpenAction.jsx
│ │ ├── RemoveAction.jsx
│ │ ├── RenameAction.jsx
│ │ └── UploadFileAction.jsx
├── Dialogs
│ ├── Content
│ │ └── Content.jsx
│ ├── Copy
│ │ └── Copy.jsx
│ ├── CreateFolder
│ │ └── CreateFolder.jsx
│ ├── Dialogs.jsx
│ ├── Edit
│ │ └── Edit.jsx
│ ├── Move
│ │ └── Move.jsx
│ ├── Rename
│ │ └── Rename.jsx
│ └── UploadFile
│ │ └── UploadFile.jsx
├── File
│ ├── File.css
│ ├── File.jsx
│ └── FileSublist
│ │ └── FileSublist.jsx
├── FileList
│ ├── FileList.css
│ ├── FileList.jsx
│ ├── FileListEmptyMessage.css
│ ├── FileListEmptyMessage.jsx
│ └── FileListSublist
│ │ ├── FileListSublist.css
│ │ └── FileListSublist.jsx
├── FileUploader
│ ├── FileUploader.jsx
│ └── UploadFileList.jsx
├── Loader
│ └── Loader.jsx
├── Navbar
│ ├── Navbar.jsx
│ └── ThreeDotsMenu.jsx
└── Notification
│ ├── DynamicSnackbar.jsx
│ └── NotificationBar.jsx
├── Reducers
└── MainReducer.js
├── config.js
├── index.css
├── index.js
└── serviceWorker.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Filemanager
2 |
3 | Hello ex [angular-filemanager](https://github.com/joni2back/angular-filemanager/) user, this is the new version in React.
4 |
5 | I will try to make it clean and retro-compatible with the previous bridges/connectors
6 |
7 | It's very important for me your collaboration on my development tasks and time.
8 | Please help me to move forward with a donation by paypal :) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XRB7EW72PS982)
9 |
10 | ---
11 |
12 | ### Environment configuration
13 | **1) Install deps using NPM with**
14 | ```npm install```
15 |
16 | **2) Start development environment**
17 | ```npm start```
18 |
19 | **3) Run tests**
20 | ```npm run test```
21 |
22 | **4) Compile for production**
23 | ```npm run build```
24 |
25 | ---
26 |
27 | ## Connectors
28 | I am also developing a local file connector API in NodeJS in [filemanager-connector-node](https://github.com/joni2back/filemanager-connector-node)
29 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-filemanager",
3 | "version": "0.0.14",
4 | "private": true,
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/joni2back/react-filemanager.git"
8 | },
9 | "keywords": [
10 | "filemanager"
11 | ],
12 | "author": "Jonas Sciangula Street",
13 | "license": "MIT",
14 | "bugs": {
15 | "url": "https://github.com/joni2back/react-filemanager/issues"
16 | },
17 | "homepage": "https://joni2back.github.io/react-filemanager",
18 | "dependencies": {
19 | "@material-ui/core": "^4.11.0",
20 | "@material-ui/icons": "^4.9.1",
21 | "react": "^16.6.3",
22 | "react-dom": "^16.6.3",
23 | "react-redux": "~5.1.1",
24 | "react-scripts": "^4.0.0",
25 | "redux": "~4.0.1",
26 | "redux-thunk": "~2.3.0"
27 | },
28 | "scripts": {
29 | "start": "react-scripts start",
30 | "build": "react-scripts build",
31 | "test": "react-scripts test",
32 | "eject": "react-scripts eject"
33 | },
34 | "eslintConfig": {
35 | "extends": "react-app"
36 | },
37 | "browserslist": [
38 | ">0.2%",
39 | "not dead",
40 | "not ie <= 11",
41 | "not op_mini all"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joni2back/react-filemanager/07d8f22325db7291033075bf4583142b9cb52354/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
24 |
25 | React Filemanager
26 |
45 |
46 |
47 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React Filemanager",
3 | "name": "React Filemanager",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#fff",
14 | "background_color": "#2196f3"
15 | }
16 |
--------------------------------------------------------------------------------
/src/Actions/Actions.js:
--------------------------------------------------------------------------------
1 | import * as APIHandler from '../Api/ApiHandler.js';
2 |
3 | /**
4 | * Request API to get file list for the selected path then refresh UI
5 | * @returns {Function}
6 | */
7 | export const uploadFiles = (fileList) => (dispatch, getState) => {
8 | const { path } = getState();
9 | dispatch(setLoading(true));
10 | dispatch(setSelectedFiles([]));
11 | dispatch(setFileUploadProgress(50));
12 |
13 | APIHandler.uploadFiles(path.join('/'), fileList).then(r => {
14 | dispatch(setFileUploadProgress(100));
15 | setTimeout(f => {
16 | dispatch(resetFileUploader());
17 | }, 300);
18 | dispatch(refreshFileList());
19 | }).catch(r => {
20 | dispatch({
21 | type: 'SET_ERROR_MSG',
22 | value: r.toString()
23 | });
24 | dispatch(setLoading(false));
25 | });
26 | };
27 |
28 | /**
29 | * Request API to get file list for the selected path then refresh UI
30 | * @returns {Function}
31 | */
32 | export const refreshFileList = () => (dispatch, getState) => {
33 | const { path } = getState();
34 | dispatch(setLoading(true));
35 | dispatch(setSelectedFiles([]));
36 |
37 | APIHandler.getFileList(path.join('/')).then(r => {
38 | dispatch(setLoading(false));
39 | dispatch(setFileList(r));
40 | }).catch(r => {
41 | dispatch(setFileList([]));
42 | dispatch({
43 | type: 'SET_ERROR_MSG',
44 | value: r.toString()
45 | });
46 | dispatch(setLoading(false));
47 | });
48 | };
49 |
50 |
51 | /**
52 | * Request API to get file list for the selected path then refresh UI
53 | * @returns {Function}
54 | */
55 | export const refreshFileListSublist = () => (dispatch, getState) => {
56 | const { pathSublist } = getState();
57 | dispatch(setLoadingSublist(true));
58 | dispatch(setSelectedFolderSublist(null));
59 |
60 | APIHandler.getFileList(pathSublist.join('/')).then(r => {
61 | dispatch(setLoadingSublist(false));
62 | dispatch(setFileListSublist(r));
63 | }).catch(r => {
64 | dispatch(setFileListSublist([]));
65 | dispatch({
66 | type: 'SET_ERROR_MSG',
67 | value: r.toString()
68 | });
69 | dispatch(setLoadingSublist(false));
70 | });
71 | };
72 |
73 |
74 | /**
75 | * Request API to get file content then dispatch defined events
76 | * @param {String} fileName
77 | * @returns {Function}
78 | */
79 | export const getFileContent = (fileName) => (dispatch, getState) => {
80 | const { path } = getState();
81 |
82 | dispatch(setLoading(true));
83 | dispatch(setFileContent(null));
84 | dispatch(setVisibleDialogContent(true));
85 | APIHandler.getFileBody(path.join('/'), fileName).then(blob => {
86 | dispatch(setFileContent(blob));
87 | dispatch(setLoading(false));
88 | }).catch(r => {
89 | dispatch({
90 | type: 'SET_ERROR_MSG',
91 | value: r.toString()
92 | });
93 | dispatch(setLoading(false));
94 | });
95 | };
96 |
97 | /**
98 | * Request API to rename file then dispatch defined events
99 | * @param {String} fileName
100 | * @returns {Function}
101 | */
102 | export const renameItem = (fileName, newFileName) => (dispatch, getState) => {
103 | const { path } = getState();
104 | dispatch(setLoading(true));
105 | APIHandler.renameItem(path.join('/'), fileName, newFileName).then(blob => {
106 | dispatch(setVisibleDialogRename(false));
107 | dispatch(setLoading(false));
108 | dispatch(refreshFileList());
109 | }).catch(r => {
110 | dispatch({
111 | type: 'SET_ERROR_MSG',
112 | value: r.toString()
113 | });
114 | dispatch(setLoading(false));
115 | });
116 | };
117 |
118 | /**
119 | * Request API to get download file then dispatch defined events
120 | * @param {String} fileName
121 | * @returns {Function}
122 | */
123 | export const downloadFile = (fileName) => (dispatch, getState) => {
124 | const { path } = getState();
125 | dispatch(setLoading(true));
126 | APIHandler.getFileBody(path.join('/'), fileName).then(blob => {
127 | // TODO workaround large files disables ui for long time
128 | const blobUrl = window.URL.createObjectURL(blob);
129 | let tempLink = window.document.createElement('a');
130 | tempLink.href = blobUrl;
131 | tempLink.setAttribute('download', fileName);
132 | tempLink.click();
133 | window.URL.revokeObjectURL(blobUrl);
134 | dispatch(setLoading(false));
135 | }).catch(r => {
136 | dispatch({
137 | type: 'SET_ERROR_MSG',
138 | value: r.toString()
139 | });
140 | dispatch(setLoading(false));
141 | });
142 | };
143 |
144 | /**
145 | * Request API to get file content then dispatch defined events
146 | * @param {String} fileName
147 | * @returns {Function}
148 | */
149 | export const getFileContentForEdit = (fileName) => (dispatch, getState) => {
150 | const { path } = getState();
151 | dispatch(setLoading(true));
152 | dispatch(setFileContent(null));
153 | dispatch(setVisibleDialogEdit(true));
154 | APIHandler.getFileBody(path.join('/'), fileName).then(blob => {
155 | dispatch(setFileContent(blob));
156 | dispatch(setLoading(false));
157 | }).catch(r => {
158 | dispatch({
159 | type: 'SET_ERROR_MSG',
160 | value: r.toString()
161 | });
162 | dispatch(setLoading(false));
163 | });
164 | };
165 |
166 |
167 | /**
168 | * Request API to create a folder then dispatch defined events
169 | * @param {String} createFolderName
170 | * @returns {Function}
171 | */
172 | export const createNewFolder = (createFolderName) => (dispatch, getState) => {
173 | const { path } = getState();
174 | dispatch(setLoading(true));
175 |
176 | APIHandler.createFolder(path.join('/'), createFolderName).then(r => {
177 | dispatch(setVisibleDialogCreateFolder(false));
178 | dispatch(setLoading(false));
179 | dispatch(refreshFileList());
180 | }).catch(r => {
181 | dispatch({
182 | type: 'SET_ERROR_MSG',
183 | value: r.toString()
184 | });
185 | dispatch(setLoading(false));
186 | });
187 | };
188 |
189 |
190 | /**
191 | * Request API to remove an item then dispatch defined events
192 | * @param {Array} filenames
193 | * @returns {Function}
194 | */
195 | export const removeItems = (files) => (dispatch, getState) => {
196 | const { path } = getState();
197 | const filenames = files.map(f => f.name);
198 |
199 | dispatch(setLoading(true));
200 | APIHandler.removeItems(path.join('/'), filenames).then(r => {
201 | dispatch(setLoading(false));
202 | dispatch(refreshFileList());
203 | }).catch(r => {
204 | dispatch({
205 | type: 'SET_ERROR_MSG',
206 | value: r.toString()
207 | });
208 | dispatch(setLoading(false));
209 | });
210 | };
211 |
212 |
213 | /**
214 | * Request API to move an item then dispatch defined events
215 | * @param {Array} filenames
216 | * @returns {Function}
217 | */
218 | export const moveItems = (files) => (dispatch, getState) => {
219 | const { path, pathSublist, selectedFolderSublist } = getState();
220 | const destination = pathSublist.join('/') + '/' + selectedFolderSublist.name;
221 | const filenames = files.map(f => f.name);
222 |
223 | dispatch(setLoading(true));
224 | APIHandler.moveItems(path.join('/'), destination, filenames).then(r => {
225 | dispatch(setLoading(false));
226 | dispatch(setVisibleDialogMove(false));
227 | dispatch(refreshFileList());
228 | }).catch(r => {
229 | dispatch({
230 | type: 'SET_ERROR_MSG',
231 | value: r.toString()
232 | });
233 | dispatch(setLoading(false));
234 | });
235 | };
236 |
237 |
238 | /**
239 | * Request API to copy an item then dispatch defined events
240 | * @param {Array} filenames
241 | * @returns {Function}
242 | */
243 | export const copyItems = (files) => (dispatch, getState) => {
244 | const { path, pathSublist, selectedFolderSublist } = getState();
245 | const destination = pathSublist.join('/') + '/' + selectedFolderSublist.name;
246 | const filenames = files.map(f => f.name);
247 |
248 | dispatch(setLoading(true));
249 | APIHandler.copyItems(path.join('/'), destination, filenames).then(r => {
250 | dispatch(setLoading(false));
251 | dispatch(setVisibleDialogCopy(false));
252 | dispatch(refreshFileList());
253 | }).catch(r => {
254 | dispatch({
255 | type: 'SET_ERROR_MSG',
256 | value: r.toString()
257 | });
258 | dispatch(setLoading(false));
259 | });
260 | };
261 |
262 | /**
263 | * This handles multiple selection by using shift key
264 | * @param {Object} lastFile
265 | * @returns {Function}
266 | */
267 | export const setSelectedFileFromLastTo = (lastFile) => (dispatch, getState) => {
268 | const { fileList, selectedFiles } = getState();
269 |
270 | const lastPreviouslySelected = [...selectedFiles].pop();
271 | const lastPreviouslySelectedIndex = fileList.indexOf(fileList.find(f => f.name === lastPreviouslySelected.name))
272 | const lastSelectedIndex = fileList.indexOf(fileList.find(f => f.name === lastFile.name))
273 |
274 | let toAdd = [];
275 | if (lastSelectedIndex > lastPreviouslySelectedIndex) {
276 | toAdd = fileList.filter((index, element) => {
277 | return fileList.indexOf(index) <= lastSelectedIndex && fileList.indexOf(index) >= lastPreviouslySelectedIndex
278 | });
279 | } else {
280 | toAdd = fileList.filter((index, element) => {
281 | return fileList.indexOf(index) >= lastSelectedIndex && fileList.indexOf(index) <= lastPreviouslySelectedIndex
282 | });
283 | }
284 | dispatch(setSelectedFiles([...selectedFiles, ...toAdd]));
285 | };
286 |
287 |
288 | /**
289 | * @returns {Function}
290 | */
291 | export const initSubList = () => (dispatch, getState) => {
292 | const { path } = getState();
293 | dispatch(setSelectedFolderSublist(null));
294 | dispatch(setFileListSublist([]));
295 | dispatch(setPathSublist([...path]));
296 | dispatch(refreshFileListSublist());
297 | };
298 |
299 | export const resetFileUploader = () => (dispatch, getState) => {
300 | dispatch(setFileUploadProgress(0));
301 | dispatch(setVisibleDialogUploadFile(false));
302 | dispatch(setFileUploadList([]));
303 | };
304 |
305 | export const enterToPreviousDirectory = () => (dispatch, getState) => {
306 | const { path } = getState();
307 | dispatch(setPath(path.slice(0, -1)));
308 | dispatch(setFileListFilter(null));
309 | dispatch(refreshFileList());
310 | };
311 |
312 | export const enterToPreviousDirectoryByIndex = (index) => (dispatch, getState) => {
313 | const { path } = getState();
314 | const newPath = [...path].slice(0, ++index);
315 | dispatch(setPath(newPath));
316 | dispatch(refreshFileList());
317 | dispatch(setFileListFilter(null));
318 | };
319 |
320 | export const enterToPreviousDirectorySublist = () => (dispatch, getState) => {
321 | const { pathSublist } = getState();
322 | dispatch(setPathSublist(pathSublist.slice(0, -1)));
323 | dispatch(refreshFileListSublist());
324 | };
325 |
326 | export const setPath = (path) => {
327 | return {
328 | type: 'SET_PATH',
329 | value: path
330 | };
331 | };
332 |
333 | export const setPathSublist = (path) => {
334 | return {
335 | type: 'SET_PATH_SUB_LIST',
336 | value: path
337 | };
338 | };
339 |
340 | export const enterToDirectory = (directory) => (dispatch, getState) => {
341 | dispatch({
342 | type: 'ENTER_TO_DIRECTORY',
343 | value: directory
344 | });
345 | dispatch(setFileListFilter(null));
346 | dispatch(refreshFileList());
347 | };
348 |
349 | export const enterToDirectorySublist = (directory) => (dispatch, getState) => {
350 | dispatch({
351 | type: 'ENTER_TO_DIRECTORY_SUB_LIST',
352 | value: directory
353 | });
354 | dispatch(refreshFileListSublist());
355 | };
356 |
357 | export const setFileList = (fileList) => {
358 | return {
359 | type: 'SET_FILE_LIST',
360 | value: fileList
361 | };
362 | };
363 |
364 | export const setFileListSublist = (fileList) => {
365 | return {
366 | type: 'SET_FILE_LIST_SUB_LIST',
367 | value: fileList
368 | };
369 | };
370 |
371 | export const setSelectedFiles = (files) => {
372 | return {
373 | type: 'SET_SELECTED_FILES',
374 | value: files
375 | };
376 | };
377 |
378 | export const setSelectedFolderSublist = (file) => {
379 | return {
380 | type: 'SET_SELECTED_FOLDER_SUB_LIST',
381 | value: file
382 | };
383 | };
384 |
385 | export const setFileListFilter = (search) => {
386 | return {
387 | type: 'SET_FILE_LIST_FILTER',
388 | value: search
389 | };
390 | };
391 |
392 | export const setContextMenuVisible = (visible) => {
393 | return {
394 | type: 'SET_CONTEXT_MENU_VISIBLE',
395 | value: !!visible
396 | };
397 | };
398 |
399 | export const setContextMenuPosition = (x, y) => {
400 | return {
401 | type: 'SET_CONTEXT_MENU_POSITION',
402 | value: [x, y]
403 | };
404 | };
405 |
406 | export const setContextMenuPositionElement = (element) => {
407 | return {
408 | type: 'SET_CONTEXT_MENU_POSITION_ELEMENT',
409 | value: element
410 | };
411 | };
412 |
413 | export const toggleSelectedFile = (file) => {
414 | return {
415 | type: 'TOGGLE_SELECTED_FILE',
416 | value: file
417 | };
418 | };
419 |
420 | export const rightClickOnFile = (file) => (dispatch, getState) => {
421 | const { selectedFiles } = getState();
422 | const isSelected = selectedFiles.indexOf(selectedFiles.find(f => f.name === file.name)) !== -1;
423 |
424 | !isSelected && dispatch(setSelectedFiles([file]));
425 | };
426 |
427 | export const setLoading = (value) => {
428 | return {
429 | type: 'SET_LOADING',
430 | value: value
431 | };
432 | };
433 |
434 | export const setLoadingSublist = (value) => {
435 | return {
436 | type: 'SET_LOADING_SUB_LIST',
437 | value: value
438 | };
439 | };
440 |
441 | export const setVisibleDialogCreateFolder = (visible) => {
442 | return {
443 | type: 'SET_VISIBLE_DIALOG_CREATE_FOLDER',
444 | value: !!visible
445 | };
446 | };
447 |
448 | export const setVisibleDialogUploadFile = (visible) => {
449 | return {
450 | type: 'SET_VISIBLE_DIALOG_UPLOAD_FILE',
451 | value: !!visible
452 | };
453 | };
454 |
455 | export const setVisibleDialogRename = (visible) => {
456 | return {
457 | type: 'SET_VISIBLE_DIALOG_RENAME',
458 | value: !!visible
459 | };
460 | };
461 |
462 | export const setVisibleDialogMove = (visible) => {
463 | return {
464 | type: 'SET_VISIBLE_DIALOG_MOVE',
465 | value: !!visible
466 | };
467 | };
468 |
469 | export const setVisibleDialogCopy = (visible) => {
470 | return {
471 | type: 'SET_VISIBLE_DIALOG_COPY',
472 | value: !!visible
473 | };
474 | };
475 |
476 | export const setVisibleDialogContent = (visible) => {
477 | return {
478 | type: 'SET_VISIBLE_DIALOG_CONTENT',
479 | value: !!visible
480 | };
481 | };
482 |
483 | export const setVisibleDialogEdit = (visible) => {
484 | return {
485 | type: 'SET_VISIBLE_DIALOG_EDIT',
486 | value: !!visible
487 | };
488 | };
489 |
490 | export const setFileContent = (blob) => {
491 | return {
492 | type: 'SET_FILE_CONTENT',
493 | value: blob
494 | };
495 | };
496 |
497 | export const setFileUploadProgress = (percentage) => {
498 | return {
499 | type: 'SET_FILE_UPLOAD_PROGRESS',
500 | value: percentage
501 | };
502 | };
503 |
504 | export const setFileUploadList = (files) => {
505 | return {
506 | type: 'SET_FILE_UPLOAD_LIST',
507 | value: files
508 | };
509 | };
--------------------------------------------------------------------------------
/src/Api/Api.js:
--------------------------------------------------------------------------------
1 | import config from './../config.js';
2 |
3 | /**
4 | * Fetch API to list files from directory
5 | * @param {String} path
6 | * @returns {Object}
7 | */
8 | export function list(path) {
9 | return fetch(config.url_list + '?path=' + (encodeURIComponent(path) || '/'));
10 | };
11 |
12 |
13 | /**
14 | * Fetch API to create a directory
15 | * @param {String} path
16 | * @param {String} directory
17 | * @returns {Object}
18 | */
19 | export function createDirectory(path, directory) {
20 | return fetch(config.url_create_folder, {
21 | method: 'POST',
22 | headers: {
23 | 'Content-Type': 'application/json'
24 | },
25 | body: JSON.stringify({
26 | path, directory
27 | })
28 | });
29 | };
30 |
31 |
32 | /**
33 | * Fetch API to get file body
34 | * @param {String} path
35 | * @returns {Object}
36 | */
37 | export function getFileContent(path) {
38 | return fetch(config.url_get_content + '?path=' + (encodeURIComponent(path) || '/'));
39 | };
40 |
41 |
42 | /**
43 | * Fetch API to remove a file or folder
44 | * @param {String} path
45 | * @param {Array} filenames
46 | * @param {Boolean} recursive
47 | * @returns {Object}
48 | */
49 | export function remove(path, filenames, recursive = true) {
50 | return fetch(config.url_remove, {
51 | method: 'POST',
52 | headers: {
53 | 'Content-Type': 'application/json'
54 | },
55 | body: JSON.stringify({
56 | path, filenames, recursive
57 | })
58 | });
59 | };
60 |
61 | /**
62 | * Fetch API to move files
63 | * @param {String} path
64 | * @param {Array} filenames
65 | * @param {Boolean} recursive
66 | * @returns {Object}
67 | */
68 | export function move(path, destination, filenames) {
69 | return fetch(config.url_move, {
70 | method: 'POST',
71 | headers: {
72 | 'Content-Type': 'application/json'
73 | },
74 | body: JSON.stringify({
75 | path, destination, filenames
76 | })
77 | });
78 | };
79 |
80 | /**
81 | * Fetch API to move files
82 | * @param {String} path
83 | * @param {Array} filenames
84 | * @param {Boolean} recursive
85 | * @returns {Object}
86 | */
87 | export function rename(path, destination) {
88 | return fetch(config.url_rename, {
89 | method: 'POST',
90 | headers: {
91 | 'Content-Type': 'application/json'
92 | },
93 | body: JSON.stringify({
94 | path, destination
95 | })
96 | });
97 | };
98 |
99 | /**
100 | * Fetch API to copy files
101 | * @param {String} path
102 | * @param {Array} filenames
103 | * @param {Boolean} recursive
104 | * @returns {Object}
105 | */
106 | export function copy(path, destination, filenames) {
107 | return fetch(config.url_copy, {
108 | method: 'POST',
109 | headers: {
110 | 'Content-Type': 'application/json'
111 | },
112 | body: JSON.stringify({
113 | path, destination, filenames
114 | })
115 | });
116 | };
117 |
118 | /**
119 | * Fetch API to copy files
120 | * @param {String} path
121 | * @param {Object} fileList
122 | * @returns {Object}
123 | */
124 | export function upload(path, fileList, formData = new FormData()) {
125 | [...fileList].forEach(f => {
126 | formData.append('file[]', f);
127 | });
128 | formData.append('path', path);
129 |
130 | return fetch(config.url_upload, {
131 | method: 'POST',
132 | body: formData,
133 | headers: {
134 | // a workaround for node connector, passing the path by header
135 | path: path
136 | }
137 | });
138 | };
139 |
--------------------------------------------------------------------------------
/src/Api/ApiHandler.js:
--------------------------------------------------------------------------------
1 | import * as API from './Api.js';
2 | import config from './../config.js';
3 |
4 | const messageTranslation = {
5 | 'unknown_response': 'Unknown error response from connector',
6 | 'TypeError: Failed to fetch': 'Cannot get a response from connector.',
7 | };
8 |
9 | /**
10 | * Response handler for fetch responses
11 | * @param {Function} resolve
12 | * @param {Function} reject
13 | * @returns {Object}
14 | */
15 | const handleFetch = (resolve, reject) => {
16 | return {
17 | xthen: (response) => {
18 | const contentType = response.headers.get('content-type');
19 | const contentDisp = response.headers.get('content-disposition');
20 | const isJson = /(application|text)\/json/.test(contentType);
21 | const isAttachment = /attachment/.test(contentDisp);
22 |
23 | if (! response.ok) {
24 | if (isJson) {
25 | throw response.json();
26 | }
27 | throw Error(messageTranslation['unknown_response']);
28 | }
29 |
30 | if (isAttachment) {
31 | response.blob().then(blob => {
32 | resolve(blob);
33 | });
34 | return;
35 | }
36 |
37 | if (isJson) {
38 | response.json().then(json => {
39 | if (! json.success) {
40 | throw new Error();
41 | }
42 | resolve(json.data);
43 | });
44 | return;
45 | }
46 | },
47 | xcatch: (errorResponse) => {
48 | // is thrown json
49 | if (errorResponse && errorResponse.then) {
50 | errorResponse.then(errJson => {
51 | return reject(errJson.errorMsg || JSON.stringify(errJson));
52 | });
53 | } else {
54 | return reject(messageTranslation[errorResponse] || errorResponse);
55 | }
56 | }
57 | }
58 | }
59 |
60 | /**
61 | * Clean path string removing double slashes and prepending a slash
62 | * @param {String} path
63 | * @returns {String}
64 | */
65 | const fixPath = (path) => {
66 | return ('/' + path).replace(/\/\//g, '/');
67 | };
68 |
69 | /**
70 | * Wrap API response for retrive file liest
71 | * @param {String} path
72 | * @returns {Object}
73 | */
74 | export const getFileList = (path) => {
75 | path = fixPath(path);
76 | return new Promise((resolve, reject) => {
77 | return API.list(path)
78 | .then(handleFetch(resolve, reject).xthen)
79 | .catch(handleFetch(resolve, reject).xcatch)
80 | })
81 | };
82 |
83 | /**
84 | * Wrap API response for retrive file content
85 | * @param {String} path
86 | * @returns {Object}
87 | */
88 | export const getFileBody = (path, filename) => {
89 | path = fixPath(path + '/' + filename);
90 | return new Promise((resolve, reject) => {
91 | return API.getFileContent(path)
92 | .then(handleFetch(resolve, reject).xthen)
93 | .catch(handleFetch(resolve, reject).xcatch)
94 | })
95 | };
96 |
97 |
98 | /**
99 | * Wrap API response for retrive file content
100 | * @param {String} path
101 | * @returns {Object}
102 | */
103 | export const renameItem = (path, filename, newFileName) => {
104 | const oldPath = fixPath(path + '/' + filename);
105 | const newPath = fixPath(path + '/' + newFileName);
106 |
107 | return new Promise((resolve, reject) => {
108 | return API.rename(oldPath, newPath)
109 | .then(handleFetch(resolve, reject).xthen)
110 | .catch(handleFetch(resolve, reject).xcatch)
111 | })
112 | };
113 |
114 | /**
115 | * Wrap API response for create folder
116 | * @param {String} path
117 | * @param {String} folder
118 | * @returns {Object}
119 | */
120 | export const createFolder = (path, folder) => {
121 | path = fixPath(path);
122 | return new Promise((resolve, reject) => {
123 | if (! (folder || '').trim()) {
124 | return reject('Invalid folder name');
125 | }
126 | return API.createDirectory(path, folder)
127 | .then(handleFetch(resolve, reject).xthen)
128 | .catch(handleFetch(resolve, reject).xcatch)
129 | })
130 | };
131 |
132 | /**
133 | * Wrap API response for remove file or folder
134 | * @param {String} path
135 | * @param {Array} filenames
136 | * @param {Boolean} recursive
137 | * @returns {Object}
138 | */
139 | export const removeItems = (path, filenames, recursive = true) => {
140 | path = fixPath(path);
141 | return new Promise((resolve, reject) => {
142 | if (! filenames.length) {
143 | return reject('No files to remove');
144 | }
145 | return API.remove(path, filenames, recursive)
146 | .then(handleFetch(resolve, reject).xthen)
147 | .catch(handleFetch(resolve, reject).xcatch)
148 | })
149 | };
150 |
151 | /**
152 | * Wrap API response for move file or folder
153 | * @param {String} path
154 | * @param {Array} filenames
155 | * @param {Boolean} recursive
156 | * @returns {Object}
157 | */
158 | export const moveItems = (path, destination, filenames) => {
159 | path = fixPath(path);
160 | destination = fixPath(destination);
161 | return new Promise((resolve, reject) => {
162 | if (! filenames.length) {
163 | return reject('No files to move');
164 | }
165 | return API.move(path, destination, filenames)
166 | .then(handleFetch(resolve, reject).xthen)
167 | .catch(handleFetch(resolve, reject).xcatch)
168 | })
169 | };
170 |
171 | /**
172 | * Wrap API response for copy file or folder
173 | * @param {String} path
174 | * @param {Array} filenames
175 | * @param {Boolean} recursive
176 | * @returns {Object}
177 | */
178 | export const copyItems = (path, destination, filenames) => {
179 | path = fixPath(path);
180 | destination = fixPath(destination);
181 | return new Promise((resolve, reject) => {
182 | if (! filenames.length) {
183 | return reject('No files to copy');
184 | }
185 | return API.copy(path, destination, filenames)
186 | .then(handleFetch(resolve, reject).xthen)
187 | .catch(handleFetch(resolve, reject).xcatch)
188 | })
189 | };
190 |
191 | /**
192 | * Wrap API response for upload files
193 | * @param {String} path
194 | * @param {Object} fileList
195 | * @returns {Object}
196 | */
197 | export const uploadFiles = (path, fileList) => {
198 | path = fixPath(path);
199 |
200 | return new Promise((resolve, reject) => {
201 | if (! fileList.length) {
202 | return reject('No files to upload');
203 | }
204 | return API.upload(path, fileList)
205 | .then(handleFetch(resolve, reject).xthen)
206 | .catch(handleFetch(resolve, reject).xcatch)
207 | })
208 | };
209 |
210 | /**
211 | * Calculate available actions for a file
212 | * @param {Object} file
213 | * @returns {Array}
214 | */
215 | export const getActionsByFile = (file, acts = []) => {
216 | if (file.type === 'dir') {
217 | acts.push('open');
218 |
219 | typeof file.compressible !== 'undefined' ?
220 | file.compressible && acts.push('compress'):
221 | acts.push('compress');
222 | }
223 |
224 | if (file.type === 'file') {
225 | acts.push('download');
226 | config.isImageFilePattern.test(file.name) && acts.push('open');
227 |
228 | typeof file.editable !== 'undefined' ?
229 | file.editable && acts.push('edit'):
230 | config.isEditableFilePattern.test(file.name) && acts.push('edit');
231 |
232 | typeof file.extractable !== 'undefined' ?
233 | file.extractable && acts.push('extract'):
234 | config.isExtractableFilePattern.test(file.name) && acts.push('extract');
235 |
236 | acts.push('copy');
237 | }
238 |
239 | acts.push('move');
240 | acts.push('rename');
241 | acts.push('perms');
242 | acts.push('remove');
243 |
244 | return acts;
245 | }
246 |
247 | /**
248 | * Calculate available actions for selected files, excluding non coincidences
249 | * @param {Array