├── public
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── Components
│ ├── FileList
│ │ ├── FileListSublist
│ │ │ ├── FileListSublist.css
│ │ │ └── FileListSublist.jsx
│ │ ├── FileListEmptyMessage.css
│ │ ├── FileList.css
│ │ ├── FileListEmptyMessage.jsx
│ │ └── FileList.jsx
│ ├── Breadcrumb
│ │ ├── BreadcrumbText.css
│ │ ├── Breadcrumb.css
│ │ ├── Breadcrumb.jsx
│ │ └── BreadcrumbText.jsx
│ ├── File
│ │ ├── File.css
│ │ ├── FileSublist
│ │ │ └── FileSublist.jsx
│ │ └── File.jsx
│ ├── ContextMenu
│ │ ├── ContextMenu.css
│ │ ├── ContextMenuActions
│ │ │ ├── RemoveAction.jsx
│ │ │ ├── RenameAction.jsx
│ │ │ ├── DownloadAction.jsx
│ │ │ ├── EditAction.jsx
│ │ │ ├── CopyAction.jsx
│ │ │ ├── MoveAction.jsx
│ │ │ ├── UploadFileAction.jsx
│ │ │ ├── CreateFolderAction.jsx
│ │ │ └── OpenAction.jsx
│ │ └── ContextMenu.jsx
│ ├── Loader
│ │ └── Loader.jsx
│ ├── Dialogs
│ │ ├── Dialogs.jsx
│ │ ├── Content
│ │ │ └── Content.jsx
│ │ ├── CreateFolder
│ │ │ └── CreateFolder.jsx
│ │ ├── Rename
│ │ │ └── Rename.jsx
│ │ ├── UploadFile
│ │ │ └── UploadFile.jsx
│ │ ├── Edit
│ │ │ └── Edit.jsx
│ │ ├── Copy
│ │ │ └── Copy.jsx
│ │ └── Move
│ │ │ └── Move.jsx
│ ├── FileUploader
│ │ ├── UploadFileList.jsx
│ │ └── FileUploader.jsx
│ ├── Navbar
│ │ ├── ThreeDotsMenu.jsx
│ │ └── Navbar.jsx
│ └── Notification
│ │ ├── DynamicSnackbar.jsx
│ │ └── NotificationBar.jsx
├── App.test.js
├── index.js
├── index.css
├── config.js
├── App.js
├── Api
│ ├── Api.js
│ └── ApiHandler.js
├── serviceWorker.js
├── Reducers
│ └── MainReducer.js
└── Actions
│ └── Actions.js
├── .gitignore
├── README.md
└── package.json
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joni2back/react-filemanager/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/Components/FileList/FileListSublist/FileListSublist.css:
--------------------------------------------------------------------------------
1 | .FileListSublist {
2 | overflow: auto;
3 | max-height: 20em;
4 | }
--------------------------------------------------------------------------------
/src/Components/Breadcrumb/BreadcrumbText.css:
--------------------------------------------------------------------------------
1 | .BreadcrumbText {
2 | }
3 |
4 | .BreadcrumbText span {
5 | cursor: pointer;
6 | text-overflow: ellipsis;
7 | }
8 |
9 | .BreadcrumbText span:hover {
10 | color: #efefef;
11 | }
--------------------------------------------------------------------------------
/src/Components/FileList/FileListEmptyMessage.css:
--------------------------------------------------------------------------------
1 | .FileListEmptyMessage {
2 | margin: 5px 10px;
3 | padding: 20px;
4 | display: block;
5 | border-radius: 5px;
6 | background: #efefef;
7 | color: #333;
8 | font-size: 15px;
9 | }
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/Components/Breadcrumb/Breadcrumb.css:
--------------------------------------------------------------------------------
1 | .Breadcrumb {
2 | background: gray;
3 | font-size: 13px;
4 | color: #fff;
5 | padding: 10px 25px;
6 | }
7 |
8 | .Breadcrumb > span {
9 | font-weight: bold;
10 | min-width: 20px;
11 | display: inline-block;
12 | text-align: center;
13 | cursor: pointer;
14 | }
15 |
16 | .Breadcrumb > span:hover {
17 | color: #eee;
18 | }
--------------------------------------------------------------------------------
/src/Components/File/File.css:
--------------------------------------------------------------------------------
1 | .File {
2 | cursor: pointer;
3 | float: left;
4 | display: block;
5 | width: 100%;
6 | user-select: none;
7 | }
8 |
9 | .File:hover {
10 | cursor: pointer;
11 | background: #fafafa;
12 | }
13 |
14 | .File[data-selected=true] {
15 | background-color: #e8f0fe;
16 |
17 | }
18 |
19 | .File[data-selected=true],
20 | .File[data-selected=true] > li > div > span {
21 | color: #1967d2;
22 | }
--------------------------------------------------------------------------------
/src/Components/FileList/FileList.css:
--------------------------------------------------------------------------------
1 | .FileList {
2 | overflow: auto
3 | }
4 |
5 | .FileList .File .filename > span {
6 | text-overflow: ellipsis;
7 | overflow: hidden;
8 | white-space: nowrap;
9 | }
10 |
11 | @media (min-width : 600px) {
12 | .FileList .File {
13 | float: left;
14 | width: 33.3%;
15 | }
16 | }
17 | @media (min-width : 1024px) {
18 | .FileList .File {
19 | float: left;
20 | width: 25%;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenu.css:
--------------------------------------------------------------------------------
1 | .XXXXXXXXXXXContextMenu {
2 | max-width: 200px;
3 | position: absolute;
4 | display: block;
5 | background: green;
6 | border: 1px solid black;
7 | padding: 10px;
8 | text-align: left;
9 | }
10 |
11 | .ContextMenu ul {
12 | margin: 0;
13 | padding: 0;
14 | list-style: none;
15 | }
16 |
17 | .ContextMenu ul li {
18 | padding: 10px 20px;
19 | border-bottom: 1px solid #000;
20 | }
21 |
22 | .ContextMenu ul li:last-child {
23 | border: none;
24 | }
--------------------------------------------------------------------------------
/src/Components/Breadcrumb/Breadcrumb.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import './Breadcrumb.css';
4 | import BreadcrumbText from './BreadcrumbText.jsx';
5 |
6 | class Breadcrumb extends Component {
7 | render() {
8 | return
9 |
10 |
11 | }
12 | }
13 |
14 | const mapDispatchToProps = (dispatch) => {
15 | return {
16 | };
17 | };
18 |
19 | const mapStateToProps = (state) => {
20 | return {
21 | };
22 | };
23 | export default connect(mapStateToProps, mapDispatchToProps)(Breadcrumb);
24 |
--------------------------------------------------------------------------------
/src/Components/FileList/FileListEmptyMessage.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import './FileListEmptyMessage.css';
4 |
5 | class FileListEmptyMessage extends Component {
6 | render() {
7 | return (
8 |
9 | No files in this folder
10 |
11 | );
12 | }
13 | }
14 |
15 | const mapStateToProps = (state) => {
16 | return {
17 | };
18 | };
19 |
20 |
21 | const mapDispatchToProps = (dispatch) => {
22 | return {
23 | };
24 | };
25 |
26 | export default connect(mapStateToProps, mapDispatchToProps)(FileListEmptyMessage);
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/Components/Loader/Loader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import CircularProgress from '@material-ui/core/CircularProgress';
5 | import Grid from '@material-ui/core/Grid';
6 |
7 | const styles = theme => ({
8 | progress: {
9 | margin: theme.spacing.unit * 10,
10 | },
11 | });
12 |
13 | function Loader(props) {
14 | const { classes } = props;
15 | return (
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | Loader.propTypes = {
23 | classes: PropTypes.object.isRequired,
24 | };
25 |
26 | export default withStyles(styles)(Loader);
27 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/Dialogs.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DialogContent from './Content/Content.jsx';
3 | import DialogEdit from './Edit/Edit.jsx';
4 | import DialogCreateFolder from './CreateFolder/CreateFolder.jsx';
5 | import DialogRename from './Rename/Rename.jsx';
6 | import DialogMove from './Move/Move.jsx';
7 | import DialogCopy from './Copy/Copy.jsx';
8 | import DialogUploadFile from './UploadFile/UploadFile.jsx';
9 |
10 | function Dialogs(props) {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | export default Dialogs;
25 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import thunk from 'redux-thunk';
4 | import { Provider } from 'react-redux';
5 | import { createStore, applyMiddleware } from 'redux'
6 | import MainReducer from './Reducers/MainReducer'
7 | import * as serviceWorker from './serviceWorker';
8 | import App from './App';
9 | import './index.css';
10 |
11 | const store = createStore(MainReducer, applyMiddleware(thunk));
12 | ReactDOM.render(
13 |
14 |
15 | ,
16 | document.getElementById('root')
17 | );
18 |
19 | // If you want your app to work offline and load faster, you can change
20 | // unregister() to register() below. Note this comes with some pitfalls.
21 | // Learn more about service workers: http://bit.ly/CRA-PWA
22 | serviceWorker.register();
23 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
5 | -webkit-font-smoothing: antialiased;
6 | -moz-osx-font-smoothing: grayscale;
7 | }
8 |
9 | code {
10 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
11 | }
12 |
13 | *::-webkit-scrollbar-track {
14 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
15 | border-radius: 0;
16 | background-color: #F5F5F5;
17 | cursor: default;
18 | }
19 |
20 | *::-webkit-scrollbar {
21 | width: 8px;
22 | background-color: #F5F5F5;
23 | cursor: default;
24 | }
25 |
26 | *::-webkit-scrollbar-thumb {
27 | border-radius: 0px;
28 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
29 | background-color: #555;
30 | cursor: default;
31 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Components/FileUploader/UploadFileList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import List from '@material-ui/core/List';
4 | import ListItem from '@material-ui/core/ListItem';
5 | import ListItemIcon from '@material-ui/core/ListItemIcon';
6 | import ListItemText from '@material-ui/core/ListItemText';
7 | import FileIcon from '@material-ui/icons/InsertDriveFile';
8 | import { getHumanFileSize } from '../../Api/ApiHandler';
9 |
10 | function UploadFileList(props) {
11 | const { files } = props;
12 | const list = files.map((f, i) =>
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
20 |
21 | return (
22 |
23 |
24 | {list}
25 |
26 |
27 | );
28 | }
29 |
30 | UploadFileList.propTypes = {
31 | files: PropTypes.array.isRequired
32 | };
33 |
34 | export default UploadFileList;
35 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/RemoveAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import { removeItems } from '../../../Actions/Actions.js';
5 | import ListItemIcon from '@material-ui/core/ListItemIcon';
6 | import Typography from '@material-ui/core/Typography';
7 | import DeleteIcon from '@material-ui/icons/Delete';
8 |
9 | function RemoveAction(props) {
10 | const {handleClick, selectedFiles} = props;
11 | return (
12 |
20 | );
21 | }
22 |
23 | const mapStateToProps = (state) => {
24 | return {
25 | selectedFiles: state.selectedFiles
26 | };
27 | };
28 |
29 | const mapDispatchToProps = (dispatch, ownProps) => {
30 | return {
31 | handleClick: (event, selectedFiles) => {
32 | dispatch(removeItems(selectedFiles));
33 | }
34 | };
35 | };
36 |
37 | export default connect(mapStateToProps, mapDispatchToProps)(RemoveAction);
38 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/RenameAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import ListItemIcon from '@material-ui/core/ListItemIcon';
5 | import Typography from '@material-ui/core/Typography';
6 | import WrapTextIcon from '@material-ui/icons/WrapText';
7 | import { setVisibleDialogRename } from '../../../Actions/Actions.js';
8 |
9 | function MoveAction(props) {
10 | const {handleClick, selectedFiles} = props;
11 |
12 | return (
13 |
21 | );
22 | }
23 |
24 | const mapStateToProps = (state) => {
25 | return {
26 | selectedFiles: state.selectedFiles
27 | };
28 | };
29 |
30 | const mapDispatchToProps = (dispatch, ownProps) => {
31 | return {
32 | handleClick: (event, selectedFiles) => {
33 | dispatch(setVisibleDialogRename(true));
34 | }
35 | };
36 | };
37 |
38 | export default connect(mapStateToProps, mapDispatchToProps)(MoveAction);
39 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/DownloadAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import { downloadFile } from '../../../Actions/Actions.js';
5 | import ListItemIcon from '@material-ui/core/ListItemIcon';
6 | import Typography from '@material-ui/core/Typography';
7 | import CloudDownloadIcon from '@material-ui/icons/CloudDownload';
8 |
9 | function DownloadAction(props) {
10 | const {handleClick, selectedFiles} = props;
11 | return (
12 |
20 | );
21 | }
22 |
23 | const mapStateToProps = (state) => {
24 | return {
25 | selectedFiles: state.selectedFiles
26 | };
27 | };
28 |
29 | const mapDispatchToProps = (dispatch, ownProps) => {
30 | return {
31 | handleClick: (event, selectedFiles) => {
32 | dispatch(downloadFile(selectedFiles[0].name));
33 | }
34 | };
35 | };
36 |
37 | export default connect(mapStateToProps, mapDispatchToProps)(DownloadAction);
38 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/EditAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import { getFileContentForEdit } from '../../../Actions/Actions.js';
5 | import ListItemIcon from '@material-ui/core/ListItemIcon';
6 | import Typography from '@material-ui/core/Typography';
7 | import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
8 |
9 | function OpenAction(props) {
10 | const {handleClick, selectedFiles} = props;
11 | return (
12 |
20 | );
21 | }
22 |
23 | const mapStateToProps = (state) => {
24 | return {
25 | selectedFiles: state.selectedFiles
26 | };
27 | };
28 |
29 | const mapDispatchToProps = (dispatch, ownProps) => {
30 | return {
31 | handleClick: (event, selectedFiles) => {
32 | dispatch(getFileContentForEdit(selectedFiles[0].name));
33 | }
34 | };
35 | };
36 |
37 | export default connect(mapStateToProps, mapDispatchToProps)(OpenAction);
38 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/CopyAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import ListItemIcon from '@material-ui/core/ListItemIcon';
5 | import Typography from '@material-ui/core/Typography';
6 | import FileCopyIcon from '@material-ui/icons/FileCopy';
7 | import { initSubList, setVisibleDialogCopy } from '../../../Actions/Actions.js';
8 |
9 | function CopyAction(props) {
10 | const {handleClick, selectedFiles} = props;
11 |
12 | return (
13 |
21 | );
22 | }
23 |
24 | const mapStateToProps = (state) => {
25 | return {
26 | selectedFiles: state.selectedFiles
27 | };
28 | };
29 |
30 | const mapDispatchToProps = (dispatch, ownProps) => {
31 | return {
32 | handleClick: (event, selectedFiles) => {
33 | dispatch(initSubList());
34 | dispatch(setVisibleDialogCopy(true));
35 | }
36 | };
37 | };
38 |
39 | export default connect(mapStateToProps, mapDispatchToProps)(CopyAction);
40 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/MoveAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import ListItemIcon from '@material-ui/core/ListItemIcon';
5 | import Typography from '@material-ui/core/Typography';
6 | import HowToVoteIcon from '@material-ui/icons/HowToVote';
7 | import { initSubList, setVisibleDialogMove } from '../../../Actions/Actions.js';
8 |
9 | function MoveAction(props) {
10 | const {handleClick, selectedFiles} = props;
11 |
12 | return (
13 |
21 | );
22 | }
23 |
24 | const mapStateToProps = (state) => {
25 | return {
26 | selectedFiles: state.selectedFiles
27 | };
28 | };
29 |
30 | const mapDispatchToProps = (dispatch, ownProps) => {
31 | return {
32 | handleClick: (event, selectedFiles) => {
33 | dispatch(initSubList());
34 | dispatch(setVisibleDialogMove(true));
35 | }
36 | };
37 | };
38 |
39 | export default connect(mapStateToProps, mapDispatchToProps)(MoveAction);
40 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/UploadFileAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import ListItemIcon from '@material-ui/core/ListItemIcon';
5 | import Typography from '@material-ui/core/Typography';
6 | import CloudUploadIcon from '@material-ui/icons/CloudUpload';
7 | import { setVisibleDialogUploadFile } from '../../../Actions/Actions.js';
8 |
9 | function UploadFileAction(props) {
10 | const {handleClick, handleClose} = props;
11 |
12 | const handleCloseAfter = (callback) => (event) => {
13 | callback();
14 | handleClose();
15 | };
16 |
17 | return (
18 |
26 | );
27 | }
28 |
29 | const mapStateToProps = (state) => {
30 | return {
31 | };
32 | };
33 |
34 | const mapDispatchToProps = (dispatch, ownProps) => {
35 | return {
36 | handleClick: (event) => {
37 | dispatch(setVisibleDialogUploadFile(true));
38 | }
39 | };
40 | };
41 |
42 | export default connect(mapStateToProps, mapDispatchToProps)(UploadFileAction);
43 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/CreateFolderAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import ListItemIcon from '@material-ui/core/ListItemIcon';
5 | import Typography from '@material-ui/core/Typography';
6 | import CreateNewFolderIcon from '@material-ui/icons/CreateNewFolder';
7 | import { setVisibleDialogCreateFolder } from '../../../Actions/Actions.js';
8 |
9 | function CreateFolderAction(props) {
10 | const {handleClick, handleClose} = props;
11 |
12 | const handleCloseAfter = (callback) => (event) => {
13 | callback();
14 | handleClose();
15 | };
16 |
17 | return (
18 |
26 | );
27 | }
28 |
29 | const mapStateToProps = (state) => {
30 | return {
31 | };
32 | };
33 |
34 | const mapDispatchToProps = (dispatch, ownProps) => {
35 | return {
36 | handleClick: (event) => {
37 | dispatch(setVisibleDialogCreateFolder(true));
38 | }
39 | };
40 | };
41 |
42 | export default connect(mapStateToProps, mapDispatchToProps)(CreateFolderAction);
43 |
--------------------------------------------------------------------------------
/src/Components/FileList/FileList.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import File from '../File/File.jsx';
4 | import FileListEmptyMessage from './FileListEmptyMessage';
5 | import Loader from '../Loader/Loader.jsx';
6 | import './FileList.css';
7 |
8 | class FileList extends Component {
9 | render() {
10 | const { fileList, loading } = this.props;
11 |
12 | const fileListComponent = fileList.map((file, key) => {
13 | return
14 | });
15 |
16 | return
17 | { loading ?
18 | :
19 | fileListComponent.length ? fileListComponent :
20 | }
21 |
22 | }
23 | }
24 |
25 |
26 | const mapStateToProps = (state) => {
27 | const filteredList = state.fileList.filter(
28 | file => state.fileListFilter ? file.name.toLocaleLowerCase().match(state.fileListFilter.toLocaleLowerCase()) : true
29 | );
30 | return {
31 | fileList: filteredList,
32 | loading: state.loading
33 | };
34 | };
35 |
36 |
37 | const mapDispatchToProps = (dispatch) => {
38 | return {
39 | handleClick: (event) => {
40 | }
41 | };
42 | };
43 |
44 | export default connect(mapStateToProps, mapDispatchToProps)(FileList);
45 |
46 |
47 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenuActions/OpenAction.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MenuItem from '@material-ui/core/MenuItem';
3 | import { connect } from 'react-redux';
4 | import { getFileContent, enterToDirectory } from '../../../Actions/Actions.js';
5 | import ListItemIcon from '@material-ui/core/ListItemIcon';
6 | import Typography from '@material-ui/core/Typography';
7 | import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser';
8 |
9 | function OpenAction(props) {
10 | const {handleClick, selectedFiles} = props;
11 | return (
12 |
20 | );
21 | }
22 |
23 | const mapStateToProps = (state) => {
24 | return {
25 | selectedFiles: state.selectedFiles
26 | };
27 | };
28 |
29 | const mapDispatchToProps = (dispatch, ownProps) => {
30 | return {
31 | handleClick: (event, selectedFiles) => {
32 | if (selectedFiles[0].type === 'dir') {
33 | dispatch(enterToDirectory(selectedFiles[0].name));
34 | return;
35 | }
36 | dispatch(getFileContent(selectedFiles[0].name));
37 | }
38 | };
39 | };
40 |
41 | export default connect(mapStateToProps, mapDispatchToProps)(OpenAction);
42 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | const host = 'http://localhost:8000';
2 |
3 | export default {
4 | url_list: `${host}/filemanager/list`,
5 | url_create_folder: `${host}/filemanager/dir/create`,
6 | url_get_content: `${host}/filemanager/file/content`,
7 | url_download: `${host}/filemanager/file/content`,
8 | url_upload: `${host}/filemanager/items/upload`,
9 | url_remove: `${host}/filemanager/items/remove`,
10 | url_rename: `${host}/filemanager/item/move`,
11 | url_move: `${host}/filemanager/items/move`,
12 | url_copy: `${host}/filemanager/items/copy`,
13 | url_edit: `${host}/filemanager/file/edit`,
14 | url_compress: `${host}/filemanager/items/compress`,
15 | url_extract: `${host}/filemanager/file/extract`,
16 |
17 | isEditableFilePattern: /\.(txt|diff?|patch|svg|asc|cnf|cfg|conf|html?|cfm|cgi|aspx?|ini|pl|py|md|css|cs|jsx?|jsp|log|htaccess|htpasswd|gitignore|gitattributes|env|json|atom|eml|rss|markdown|sql|xml|xslt?|sh|rb|as|bat|cmd|cob|for|ftn|frm|frx|inc|lisp|scm|coffee|php[3-6]?|java|c|cbl|go|h|scala|vb|tmpl|lock|go|yml|yaml|tsv|lst)$/i,
18 | isImageFilePattern: /\.(jpe?g|gif|bmp|png|svg|tiff?)$/i,
19 | isExtractableFilePattern: /\.(gz|tar|rar|g?zip)$/i,
20 |
21 | actions: {
22 | create_folder: true,
23 | move: true,
24 | copy: true,
25 | copy_folder: true,
26 | compress: true,
27 | extract: true,
28 | edit: true,
29 | remove: true,
30 | upload: true,
31 | upload_by_chunks: true,
32 | preview_images: true,
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/src/Components/FileList/FileListSublist/FileListSublist.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import FileSublist from '../../File//FileSublist/FileSublist.jsx';
4 | import Loader from '../../Loader/Loader.jsx';
5 | import FileListEmptyMessage from '../FileListEmptyMessage';
6 | import './FileListSublist.css';
7 |
8 | class FileListSublist extends Component {
9 | render() {
10 | const { fileList, loadingSublist } = this.props;
11 |
12 | const fileListComponent = fileList.map((file, key) => {
13 | return
14 | });
15 |
16 | return
17 | { loadingSublist ?
18 | :
19 | fileListComponent.length ? fileListComponent :
20 | }
21 |
22 | }
23 | }
24 |
25 | const mapStateToProps = (state) => {
26 | const filteredList = state.fileListSublist
27 | .filter(file => file.type === 'dir')
28 | .filter(file => state.path.join('').trim() === state.pathSublist.join('').trim() ?
29 | !state.selectedFiles.find(f => f.name === file.name) : true
30 | );
31 | return {
32 | fileList: filteredList,
33 | loadingSublist: state.loadingSublist,
34 | };
35 | };
36 |
37 | const mapDispatchToProps = (dispatch) => {
38 | return {
39 | };
40 | };
41 |
42 | export default connect(mapStateToProps, mapDispatchToProps)(FileListSublist);
--------------------------------------------------------------------------------
/src/Components/FileUploader/FileUploader.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import Button from '@material-ui/core/Button';
4 | import UploadFileList from './UploadFileList';
5 |
6 | class FileUploader extends Component {
7 |
8 | handleReset(event) {
9 | this.refs.inputfile.value = '';
10 | this.props.handleReset(event);
11 | }
12 |
13 | render() {
14 | const { fileUploadList, handleSelectedFiles } = this.props;
15 | const styles = {
16 | inputfile: {
17 | display: 'none'
18 | }, inputreset: {
19 | display: fileUploadList.length ? 'inline-flex' : 'none'
20 | }
21 | }
22 |
23 | return (
24 |
25 |
31 |
32 |
35 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | FileUploader.propTypes = {
43 | fileUploadList: PropTypes.array.isRequired,
44 | handleReset: PropTypes.func.isRequired,
45 | handleSelectedFiles: PropTypes.func.isRequired,
46 | };
47 |
48 | export default FileUploader;
49 |
--------------------------------------------------------------------------------
/src/Components/Navbar/ThreeDotsMenu.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Menu from '@material-ui/core/Menu';
3 | import IconButton from '@material-ui/core/IconButton';
4 | import MoreVertIcon from '@material-ui/icons/MoreVert';
5 | import { connect } from 'react-redux';
6 | import CreateFolderAction from '../ContextMenu/ContextMenuActions/CreateFolderAction.jsx';
7 | import UploadFileAction from '../ContextMenu/ContextMenuActions/UploadFileAction.jsx';
8 |
9 | class ThreeDotsMenu extends React.Component {
10 | state = {
11 | anchorEl: null,
12 | };
13 |
14 | handleClick = event => {
15 | this.setState({ anchorEl: event.currentTarget });
16 | };
17 |
18 | handleClose = () => {
19 | this.setState({ anchorEl: null });
20 | };
21 |
22 | render() {
23 | const { anchorEl } = this.state;
24 | return (
25 |
26 |
31 |
32 |
33 |
34 |
38 |
39 | );
40 | }
41 | }
42 |
43 |
44 | const mapStateToProps = (state) => {
45 | return {
46 | };
47 | };
48 |
49 | const mapDispatchToProps = (dispatch, ownProps) => {
50 | return {
51 | };
52 | };
53 |
54 | export default connect(mapStateToProps, mapDispatchToProps)(ThreeDotsMenu);
55 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import FileList from './Components/FileList/FileList.jsx';
3 | import Navbar from './Components/Navbar/Navbar.jsx';
4 | import ContextMenu from './Components/ContextMenu/ContextMenu.jsx';
5 | import Dialogs from './Components/Dialogs/Dialogs.jsx';
6 |
7 | import { MuiThemeProvider as MaterialUI, createMuiTheme } from '@material-ui/core/styles';
8 | import blue from '@material-ui/core/colors/blue';
9 | import { connect } from 'react-redux';
10 | import { setContextMenuVisible, refreshFileList } from './Actions/Actions.js';
11 | import DynamicSnackbar from './Components/Notification/DynamicSnackbar.jsx';
12 |
13 | const theme = createMuiTheme({
14 | palette: {
15 | primary: blue,
16 | },
17 | typography: {
18 | useNextVariants: true,
19 | }
20 | });
21 |
22 | class App extends Component {
23 |
24 | componentDidMount() {
25 | this.props.init();
26 | };
27 |
28 | render() {
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 | const mapStateToProps = (state) => {
44 | return {
45 | };
46 | };
47 |
48 | const mapDispatchToProps = (dispatch) => {
49 | return {
50 | init: () => {
51 | dispatch(refreshFileList());
52 | },
53 |
54 | handleHideContextMenu: (event) => {
55 | if (! (event.target.tagName === 'INPUT' || /label/i.test(event.target.className))) {
56 | event.preventDefault();
57 | }
58 | dispatch(setContextMenuVisible(false));
59 | }
60 | };
61 | };
62 |
63 | export default connect(mapStateToProps, mapDispatchToProps)(App);
64 |
--------------------------------------------------------------------------------
/src/Components/Notification/DynamicSnackbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import Snackbar from '@material-ui/core/Snackbar';
5 | import IconButton from '@material-ui/core/IconButton';
6 | import CloseIcon from '@material-ui/icons/Close';
7 | import { connect } from 'react-redux';
8 |
9 | const styles = theme => ({
10 | close: {
11 | padding: theme.spacing.unit / 2,
12 | },
13 | });
14 |
15 | class DynamicSnackbar extends React.Component {
16 | render() {
17 | const { classes, errorMsg, handleClose, open, notificationDuration } = this.props;
18 | return (
19 |
20 | {errorMsg}}
32 | action={[
33 |
34 |
35 | ,
36 | ]}
37 | />
38 |
39 | );
40 | }
41 | }
42 |
43 | DynamicSnackbar.propTypes = {
44 | classes: PropTypes.object.isRequired,
45 | };
46 |
47 |
48 | const mapStateToProps = (state, ownProps) => {
49 | return {
50 | open: !!state.errorMsg,
51 | errorMsg: state.errorMsg,
52 | notificationDuration: state.notificationDuration || 60000
53 | };
54 | };
55 |
56 | const mapDispatchToProps = (dispatch, ownProps) => {
57 | return {
58 | handleClose: (event) => {
59 | dispatch({
60 | type: 'SET_ERROR_MSG',
61 | value: null
62 | });
63 | }
64 | };
65 | };
66 |
67 | export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(DynamicSnackbar));
68 |
69 |
--------------------------------------------------------------------------------
/src/Components/Breadcrumb/BreadcrumbText.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import { withStyles } from '@material-ui/core/styles';
4 | import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft';
5 | import Button from '@material-ui/core/Button';
6 | import './BreadcrumbText.css';
7 |
8 | const styles = theme => ({
9 | lastPath: {
10 | display: 'block',
11 | [theme.breakpoints.up('sm')]: {
12 | display: 'none'
13 | }
14 | },
15 | paths: {
16 | display: 'none',
17 | [theme.breakpoints.up('sm')]: {
18 | display: 'block',
19 | }
20 | }
21 | });
22 |
23 | class BreadcrumbText extends Component {
24 |
25 | render() {
26 | const { classes, handleClickPath, path, rootTitle, handleGoBack, canGoBack } = this.props;
27 |
28 | const separator = >;
29 | const rootPath = handleClickPath(e, -1, path)} data-index={0}>
30 | { rootTitle } { path.length ? separator : '' }
31 | ;
32 | const lastPath = [...path].pop() || rootTitle;
33 |
34 | const directories = path.map((dir, index) => {
35 | return handleClickPath(e, index, path)}>
36 | {dir} { path.length -1 !== index ? separator : '' }
37 |
38 | });
39 |
40 | return (
41 |
42 |
43 |
46 | {lastPath}
47 |
48 |
{rootPath} {directories}
49 |
50 | );
51 | }
52 | }
53 |
54 |
55 | const mapDispatchToProps = (dispatch) => {
56 | return {
57 | };
58 | };
59 |
60 | const mapStateToProps = (state) => {
61 | return {
62 | };
63 | };
64 |
65 | export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(BreadcrumbText));
66 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/Content/Content.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import Dialog from '@material-ui/core/Dialog';
4 | import DialogActions from '@material-ui/core/DialogActions';
5 | import DialogContent from '@material-ui/core/DialogContent';
6 | import DialogTitle from '@material-ui/core/DialogTitle';
7 | import { connect } from 'react-redux';
8 | import { setVisibleDialogContent } from '../../../Actions/Actions.js';
9 |
10 | class FormDialog extends Component {
11 |
12 | state = {
13 | lastBlobUrl: null,
14 | content: '...',
15 | loading: false
16 | };
17 |
18 | componentDidUpdate() {
19 | if (this.props.blobUrl !== this.state.lastBlobUrl) {
20 | this.setState({
21 | lastBlobUrl: this.props.blobUrl
22 | });
23 | this.setState({
24 | loading: true
25 | });
26 | }
27 | }
28 |
29 | render() {
30 | const { handleClose, open } = this.props;
31 | return (
32 |
33 |
44 |
45 | );
46 | }
47 | }
48 |
49 | const mapStateToProps = (state) => {
50 | return {
51 | open: state.visibleDialogContent,
52 | blobUrl: state.fileContentBlobUrl
53 | };
54 | };
55 |
56 | const mapDispatchToProps = (dispatch, ownProps) => {
57 | return {
58 | handleClose: (event) => {
59 | dispatch(setVisibleDialogContent(false));
60 | },
61 | handleOpen: (event) => {
62 | dispatch(setVisibleDialogContent(true));
63 | },
64 | };
65 | };
66 |
67 | export default connect(mapStateToProps, mapDispatchToProps)(FormDialog);
68 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/CreateFolder/CreateFolder.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import TextField from '@material-ui/core/TextField';
4 | import Dialog from '@material-ui/core/Dialog';
5 | import DialogActions from '@material-ui/core/DialogActions';
6 | import DialogContent from '@material-ui/core/DialogContent';
7 | import DialogTitle from '@material-ui/core/DialogTitle';
8 | import { connect } from 'react-redux';
9 | import { createNewFolder, setVisibleDialogCreateFolder } from '../../../Actions/Actions.js';
10 |
11 | class FormDialog extends Component {
12 |
13 | render() {
14 | const { handleClose, handleSave, value, open } = this.props;
15 |
16 | return (
17 |
33 | );
34 | }
35 | }
36 |
37 | const mapStateToProps = (state) => {
38 | return {
39 | createFolderName: state.createFolderName,
40 | open: state.visibleDialogCreateFolder
41 | };
42 | };
43 |
44 | const mapDispatchToProps = (dispatch, ownProps) => {
45 | return {
46 | handleClose: event => {
47 | dispatch(setVisibleDialogCreateFolder(false));
48 | },
49 | handleSave: event => {
50 | event.preventDefault();
51 | const folderName = event.currentTarget.form.querySelector('input').value;
52 | dispatch(createNewFolder(folderName));
53 | }
54 | };
55 | };
56 |
57 | export default connect(mapStateToProps, mapDispatchToProps)(FormDialog);
58 |
--------------------------------------------------------------------------------
/src/Components/File/FileSublist/FileSublist.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import {
4 | setSelectedFolderSublist, enterToDirectorySublist
5 | } from '../../../Actions/Actions.js';
6 |
7 | import { withStyles } from '@material-ui/core/styles';
8 | import ListItem from '@material-ui/core/ListItem';
9 | import ListItemAvatar from '@material-ui/core/ListItemAvatar';
10 | import ListItemText from '@material-ui/core/ListItemText';
11 | import Avatar from '@material-ui/core/Avatar';
12 | import FolderIcon from '@material-ui/icons/Folder';
13 | import FileIcon from '@material-ui/icons/InsertDriveFile';
14 | import blue from '@material-ui/core/colors/blue';
15 | import '../File.css';
16 |
17 | const styles = theme => ({
18 | });
19 |
20 |
21 | class FileSublist extends Component {
22 | render() {
23 | const { type, name, handleClick, isSelected, handleDoubleClick } = this.props;
24 | const avatarStyle = {
25 | backgroundColor: isSelected ? blue['A200'] : null
26 | };
27 | return (
28 |
29 |
30 |
31 |
32 | { type === 'dir' ? : }
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 |
43 | const mapStateToProps = (state, ownProps) => {
44 | return {
45 | filePath: [...state.path, ownProps.name],
46 | isSelected: state.selectedFolderSublist && (state.selectedFolderSublist.name === ownProps.name)
47 | };
48 | };
49 |
50 | const mapDispatchToProps = (dispatch, ownProps) => {
51 | return {
52 | /**
53 | * @param {Object} event
54 | * @returns {undefined}
55 | */
56 | handleDoubleClick: (event) => {
57 | dispatch(enterToDirectorySublist(ownProps.name));
58 | dispatch(setSelectedFolderSublist(null));
59 | },
60 |
61 | /**
62 | * @param {Object} event
63 | * @returns {undefined}
64 | */
65 | handleClick: (event) => {
66 | event.stopPropagation();
67 | dispatch(setSelectedFolderSublist(ownProps));
68 | }
69 | };
70 | };
71 |
72 | export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(FileSublist));
73 |
74 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/Rename/Rename.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import TextField from '@material-ui/core/TextField';
4 | import Dialog from '@material-ui/core/Dialog';
5 | import DialogActions from '@material-ui/core/DialogActions';
6 | import DialogContent from '@material-ui/core/DialogContent';
7 | import DialogTitle from '@material-ui/core/DialogTitle';
8 | import { connect } from 'react-redux';
9 | import { renameItem, setVisibleDialogRename } from '../../../Actions/Actions.js';
10 |
11 | class FormDialog extends Component {
12 |
13 | state = {
14 | value: ''
15 | };
16 |
17 | componentWillReceiveProps (props) {
18 | this.setState({value: props.realName});
19 | }
20 |
21 | handleChange (event) {
22 | this.setState({value: event.currentTarget.form.querySelector('input').value});
23 | }
24 |
25 | handleSave (event) {
26 | this.props.handleSave(event)(this.props.realName, this.state.value);
27 | }
28 |
29 | render() {
30 | const { value } = this.state;
31 | const { handleClose, open } = this.props;
32 |
33 | return (
34 |
50 | );
51 | }
52 | }
53 |
54 | const mapStateToProps = (state) => {
55 | return {
56 | open: state.visibleDialogRename,
57 | realName: state.selectedFiles.length ? state.selectedFiles[0].name : ''
58 | };
59 | };
60 |
61 | const mapDispatchToProps = (dispatch, ownProps) => {
62 | return {
63 | handleClose: event => {
64 | dispatch(setVisibleDialogRename(false));
65 | },
66 | handleSave: event => (realName, newName) => {
67 | event.preventDefault();
68 | dispatch(renameItem(realName, newName));
69 | }
70 | };
71 | };
72 |
73 | export default connect(mapStateToProps, mapDispatchToProps)(FormDialog);
74 |
--------------------------------------------------------------------------------
/src/Components/ContextMenu/ContextMenu.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 | import './ContextMenu.css';
4 | import Menu from '@material-ui/core/Menu';
5 | import { getActionsByMultipleFiles } from '../../Api/ApiHandler.js';
6 | import OpenAction from './ContextMenuActions/OpenAction.jsx';
7 | import RemoveAction from './ContextMenuActions/RemoveAction.jsx';
8 | import MoveAction from './ContextMenuActions/MoveAction.jsx';
9 | import CopyAction from './ContextMenuActions/CopyAction.jsx';
10 | import EditAction from './ContextMenuActions/EditAction.jsx';
11 | import RenameAction from './ContextMenuActions/RenameAction.jsx';
12 | import DownloadAction from './ContextMenuActions/DownloadAction.jsx';
13 |
14 | class ContextMenu extends Component {
15 |
16 | render() {
17 | const { acts, visible, x, y } = this.props;
18 | const actionsComp = acts.map((act, key) => {
19 | let component;
20 | if (act === 'open') {
21 | component = ;
22 | }
23 | if (act === 'edit') {
24 | component = ;
25 | }
26 | if (act === 'copy') {
27 | component = ;
28 | }
29 | if (act === 'move') {
30 | component = ;
31 | }
32 | if (act === 'rename') {
33 | component = ;
34 | }
35 | if (act === 'download') {
36 | component = ;
37 | }
38 | if (act === 'remove') {
39 | component = ;
40 | }
41 | return component;
42 | });
43 |
44 | return (
45 |
46 |
58 |
59 | );
60 | }
61 | }
62 |
63 | const mapStateToProps = (state) => {
64 | return {
65 | x: state.contextMenuPosition[0] || 0,
66 | y: state.contextMenuPosition[1] || 0,
67 | visible: !!state.contextMenuVisible,
68 | acts: getActionsByMultipleFiles(state.selectedFiles),
69 | };
70 | };
71 |
72 | const mapDispatchToProps = (dispatch) => {
73 | return {
74 | };
75 | };
76 |
77 | export default connect(mapStateToProps, mapDispatchToProps)(ContextMenu);
78 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/UploadFile/UploadFile.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import Dialog from '@material-ui/core/Dialog';
4 | import DialogActions from '@material-ui/core/DialogActions';
5 | import DialogContent from '@material-ui/core/DialogContent';
6 | import DialogTitle from '@material-ui/core/DialogTitle';
7 | import LinearProgress from '@material-ui/core/LinearProgress';
8 | import { connect } from 'react-redux';
9 | import { resetFileUploader, uploadFiles, setFileUploadList } from '../../../Actions/Actions.js';
10 | import FileUploader from '../../FileUploader/FileUploader.jsx';
11 |
12 | class FormDialog extends Component {
13 |
14 | render() {
15 | const { handleClose, handleReset, handleUpload, open, canUpload, fileUploadProgress, fileUploadList, handleSelectedFiles } = this.props;
16 |
17 | return (
18 |
37 | );
38 | }
39 | }
40 |
41 | const mapStateToProps = (state) => {
42 | return {
43 | open: state.visibleDialogUploadFile,
44 | canUpload: state.fileUploadList.length,
45 | fileUploadList: state.fileUploadList,
46 | fileUploadProgress: state.fileUploadProgress
47 | };
48 | };
49 |
50 | const mapDispatchToProps = (dispatch, ownProps) => {
51 | return {
52 | handleClose: (event) => {
53 | dispatch(resetFileUploader());
54 | },
55 | handleUpload: (event) => {
56 | event.preventDefault();
57 | const files = event.currentTarget.form.querySelector('input[type=file]').files;
58 | dispatch(uploadFiles(files));
59 | },
60 | handleSelectedFiles: (event) => {
61 | dispatch(setFileUploadList(
62 | [...event.target.files].map(f => ({name: f.name, size: f.size}))
63 | ));
64 | },
65 | handleReset: (event) => {
66 | dispatch(setFileUploadList([]));
67 | }
68 | };
69 | };
70 |
71 | export default connect(mapStateToProps, mapDispatchToProps)(FormDialog);
72 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/Edit/Edit.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import Dialog from '@material-ui/core/Dialog';
4 | import DialogActions from '@material-ui/core/DialogActions';
5 | import DialogContent from '@material-ui/core/DialogContent';
6 | import DialogContentText from '@material-ui/core/DialogContentText';
7 | import DialogTitle from '@material-ui/core/DialogTitle';
8 | import { connect } from 'react-redux';
9 | import { setVisibleDialogEdit } from '../../../Actions/Actions.js';
10 |
11 | class FormDialog extends Component {
12 |
13 | state = {
14 | lastBlobUrl: null,
15 | content: null,
16 | loading: false
17 | };
18 |
19 | componentDidUpdate() {
20 | if (this.props.blobUrl !== this.state.lastBlobUrl) {
21 | this.setState({
22 | lastBlobUrl: this.props.blobUrl
23 | });
24 | this.setState({
25 | loading: true
26 | });
27 |
28 | this.props.blobUrl && fetch(this.props.blobUrl).then(r => {
29 | return r.text();
30 | }).then(t => {
31 | this.setState({
32 | content: t
33 | });
34 | this.setState({
35 | loading: false
36 | });
37 | });
38 | }
39 | }
40 |
41 | render() {
42 | const { handleClose, handleSave, open } = this.props;
43 | const textAreaStyle = {
44 | width: '100%',
45 | minHeight: '300px'
46 | };
47 | const textArea = ;
48 |
49 | return (
50 |
51 |
67 |
68 | );
69 | }
70 | }
71 |
72 | const mapStateToProps = (state) => {
73 | return {
74 | open: state.visibleDialogEdit,
75 | blobUrl: state.fileContentBlobUrl
76 | };
77 | };
78 |
79 | const mapDispatchToProps = (dispatch, ownProps) => {
80 | return {
81 | handleClose: (event) => {
82 | dispatch(setVisibleDialogEdit(false));
83 | },
84 | handleOpen: (event) => {
85 | dispatch(setVisibleDialogEdit(true));
86 | },
87 | handleSave: (event) => {
88 | }
89 | };
90 | };
91 |
92 | export default connect(mapStateToProps, mapDispatchToProps)(FormDialog);
93 |
--------------------------------------------------------------------------------
/src/Components/Notification/NotificationBar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import classNames from 'classnames';
4 | import CheckCircleIcon from '@material-ui/icons/CheckCircle';
5 | import ErrorIcon from '@material-ui/icons/Error';
6 | import InfoIcon from '@material-ui/icons/Info';
7 | import green from '@material-ui/core/colors/green';
8 | import amber from '@material-ui/core/colors/amber';
9 | import SnackbarContent from '@material-ui/core/SnackbarContent';
10 | import WarningIcon from '@material-ui/icons/Warning';
11 | import { withStyles } from '@material-ui/core/styles';
12 | import { connect } from 'react-redux';
13 |
14 | const variantIcon = {
15 | success: CheckCircleIcon,
16 | warning: WarningIcon,
17 | error: ErrorIcon,
18 | info: InfoIcon,
19 | };
20 |
21 | const styles1 = theme => ({
22 | success: {
23 | backgroundColor: green[600],
24 | },
25 | error: {
26 | backgroundColor: theme.palette.error.dark,
27 | },
28 | info: {
29 | backgroundColor: theme.palette.primary.dark,
30 | },
31 | warning: {
32 | backgroundColor: amber[700],
33 | },
34 | icon: {
35 | fontSize: 20,
36 | },
37 | iconVariant: {
38 | opacity: 0.9,
39 | marginRight: theme.spacing.unit,
40 | },
41 | message: {
42 | display: 'flex',
43 | alignItems: 'center',
44 | },
45 | });
46 |
47 | function MySnackbarContent(props) {
48 | const { open, classes, className, message, onClose, variant, ...other } = props;
49 | const Icon = variantIcon[variant];
50 |
51 | return open ? (
52 |
57 |
58 | {message}
59 |
60 | }
61 | {...other}
62 | />
63 | ) : '';
64 | }
65 |
66 | MySnackbarContent.propTypes = {
67 | classes: PropTypes.object.isRequired,
68 | className: PropTypes.string,
69 | message: PropTypes.node,
70 | onClose: PropTypes.func,
71 | variant: PropTypes.oneOf(['success', 'warning', 'error', 'info']).isRequired,
72 | };
73 |
74 | const MySnackbarContentWrapper = withStyles(styles1)(MySnackbarContent);
75 |
76 | const styles2 = theme => ({
77 | margin: {
78 | margin: theme.spacing.unit,
79 | },
80 | });
81 |
82 | class CustomizedSnackbars extends React.Component {
83 |
84 | render() {
85 | const { classes, open, errorMsg } = this.props;
86 | return (
87 |
93 | );
94 | }
95 | }
96 |
97 | CustomizedSnackbars.propTypes = {
98 | classes: PropTypes.object.isRequired,
99 | };
100 |
101 | const mapStateToProps = (state) => {
102 | return {
103 | open: !!state.errorMsg,
104 | errorMsg: state.errorMsg,
105 | };
106 | };
107 |
108 |
109 | const mapDispatchToProps = (dispatch) => {
110 | return {
111 | };
112 | };
113 |
114 | export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles2)(CustomizedSnackbars));
115 |
116 |
117 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/Copy/Copy.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import Dialog from '@material-ui/core/Dialog';
4 | import DialogActions from '@material-ui/core/DialogActions';
5 | import DialogContent from '@material-ui/core/DialogContent';
6 | import DialogTitle from '@material-ui/core/DialogTitle';
7 | import { connect } from 'react-redux';
8 | import { setVisibleDialogCopy, setSelectedFolderSublist, enterToPreviousDirectorySublist, copyItems } from '../../../Actions/Actions.js';
9 | import FileListSublist from '../../FileList/FileListSublist/FileListSublist.jsx';
10 | import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft';
11 |
12 | class FormDialog extends Component {
13 |
14 | render() {
15 | const {
16 | selectedPath, handleClose, handleSave, open,
17 | canGoBack, canCopy, selectedFiles, handleGoBack
18 | } = this.props;
19 |
20 | return (
21 |
43 | );
44 | }
45 | }
46 |
47 | const mapStateToProps = (state) => {
48 | // prevent copying to same folder
49 | const canCopy = state.path.join('') !== state.pathSublist.join('') + (state.selectedFolderSublist ? state.selectedFolderSublist.name : '');
50 |
51 | return {
52 | open: state.visibleDialogCopy,
53 | selectedFolderSublist: state.selectedFolderSublist,
54 | selectedPath: state.selectedFolderSublist ? [...state.pathSublist, state.selectedFolderSublist.name] : [],
55 | canGoBack: state.pathSublist.length,
56 | canCopy: state.selectedFolderSublist && canCopy,
57 | selectedFiles: state.selectedFiles
58 | };
59 | };
60 |
61 | const mapDispatchToProps = (dispatch, ownProps) => {
62 | return {
63 | handleClose: (event) => {
64 | dispatch(setSelectedFolderSublist(null));
65 | dispatch(setVisibleDialogCopy(false));
66 | },
67 | handleSave: (event, selectedFiles) => {
68 | dispatch(copyItems(selectedFiles));
69 | },
70 | handleGoBack: (event) => {
71 | dispatch(setSelectedFolderSublist(null));
72 | dispatch(enterToPreviousDirectorySublist());
73 | }
74 | };
75 | };
76 |
77 | export default connect(mapStateToProps, mapDispatchToProps)(FormDialog);
78 |
--------------------------------------------------------------------------------
/src/Components/Dialogs/Move/Move.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import Dialog from '@material-ui/core/Dialog';
4 | import DialogActions from '@material-ui/core/DialogActions';
5 | import DialogContent from '@material-ui/core/DialogContent';
6 | import DialogTitle from '@material-ui/core/DialogTitle';
7 | import { connect } from 'react-redux';
8 | import { setVisibleDialogMove, setSelectedFolderSublist, enterToPreviousDirectorySublist, moveItems } from '../../../Actions/Actions.js';
9 | import FileListSublist from '../../FileList/FileListSublist/FileListSublist.jsx';
10 | import KeyboardArrowLeftIcon from '@material-ui/icons/KeyboardArrowLeft';
11 |
12 | class FormDialog extends Component {
13 |
14 | render() {
15 | const {
16 | selectedPath, handleClose, handleSave, open,
17 | selectedFiles, canGoBack, canMove, handleGoBack
18 | } = this.props;
19 |
20 | return (
21 |
43 | );
44 | }
45 | }
46 |
47 | const mapStateToProps = (state) => {
48 | // prevent moving to same folder
49 | const canMove = state.path.join('') !== state.pathSublist.join('') + (state.selectedFolderSublist ? state.selectedFolderSublist.name : '');
50 |
51 | return {
52 | open: state.visibleDialogMove,
53 | selectedFolderSublist: state.selectedFolderSublist,
54 | selectedPath: state.selectedFolderSublist ? [...state.pathSublist, state.selectedFolderSublist.name] : [],
55 | selectedFiles: state.selectedFiles,
56 | pathSublist: state.pathSublist,
57 | canGoBack: state.pathSublist.length,
58 | canMove: state.selectedFolderSublist && canMove
59 | };
60 | };
61 |
62 | const mapDispatchToProps = (dispatch, ownProps) => {
63 | return {
64 | handleClose: (event) => {
65 | dispatch(setSelectedFolderSublist(null));
66 | dispatch(setVisibleDialogMove(false));
67 | },
68 | handleSave: (event, selectedFiles) => {
69 | dispatch(moveItems(selectedFiles));
70 | },
71 | handleGoBack: (event) => {
72 | dispatch(setSelectedFolderSublist(null));
73 | dispatch(enterToPreviousDirectorySublist());
74 | }
75 | };
76 | };
77 |
78 | export default connect(mapStateToProps, mapDispatchToProps)(FormDialog);
79 |
--------------------------------------------------------------------------------
/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/Components/File/File.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { connect } from 'react-redux';
4 | import {
5 | enterToDirectory, setContextMenuVisible, toggleSelectedFile, setContextMenuPosition,
6 | setSelectedFileFromLastTo, getFileContent, getFileContentForEdit,
7 | rightClickOnFile, setSelectedFiles
8 | } from '../../Actions/Actions.js';
9 | import './File.css';
10 |
11 | import ListItem from '@material-ui/core/ListItem';
12 | import ListItemAvatar from '@material-ui/core/ListItemAvatar';
13 | import ListItemText from '@material-ui/core/ListItemText';
14 | import Avatar from '@material-ui/core/Avatar';
15 | import FolderIcon from '@material-ui/icons/Folder';
16 | import FileIcon from '@material-ui/icons/InsertDriveFile';
17 | import blue from '@material-ui/core/colors/blue';
18 | import config from '../../config.js';
19 | import { getHumanFileSize } from '../../Api/ApiHandler';
20 |
21 | class File extends Component {
22 | render() {
23 | const { isSelected, type, name, size, handleClick, handleDoubleClick, handleContextMenu } = this.props;
24 | const avatarStyle = {
25 | backgroundColor: isSelected ? blue['A200'] : null
26 | };
27 | const realSize = typeof size !== 'undefined' && type !== 'dir' ? getHumanFileSize(size) : null;
28 | return (
29 |
30 |
31 |
32 |
33 | { type === 'dir' ? : }
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 |
44 | const mapStateToProps = (state, ownProps) => {
45 | return {
46 | filePath: [...state.path, ownProps.name],
47 | isSelected: !!state.selectedFiles.find(f => f.name === ownProps.name)
48 | };
49 | };
50 |
51 | const mapDispatchToProps = (dispatch, ownProps) => {
52 | return {
53 | /**
54 | * @param {Object} event
55 | * @returns {undefined}
56 | */
57 | handleDoubleClick: (event) => {
58 | if (ownProps.type === 'file') {
59 | if (config.isEditableFilePattern.test(ownProps.name) || ownProps.editable) {
60 | dispatch(getFileContentForEdit(ownProps.name));
61 | } else if (config.isImageFilePattern.test(ownProps.name)) {
62 | dispatch(getFileContent(ownProps.name));
63 | }
64 | return;
65 | }
66 |
67 | dispatch(enterToDirectory(ownProps.name));
68 | },
69 |
70 | /**
71 | * @param {Object} event
72 | * @returns {undefined}
73 | */
74 | handleContextMenu: (event) => {
75 | event.preventDefault();
76 | event.stopPropagation();
77 |
78 | const x = event.clientX || (event.touches && event.touches[0].pageX);
79 | const y = event.clientY || (event.touches && event.touches[0].pageY);
80 |
81 | if (event.shiftKey) {
82 | dispatch(setSelectedFileFromLastTo(ownProps));
83 | } else {
84 | dispatch(rightClickOnFile(ownProps));
85 | }
86 |
87 | dispatch(setContextMenuVisible(true));
88 | dispatch(setContextMenuPosition(x, y));
89 | },
90 |
91 | /**
92 | * @param {Object} event
93 | * @returns {undefined}
94 | */
95 | handleClick: (event) => {
96 | event.stopPropagation();
97 |
98 | if (event.ctrlKey) {
99 | dispatch(toggleSelectedFile(ownProps));
100 | } else if (event.shiftKey) {
101 | dispatch(setSelectedFileFromLastTo(ownProps));
102 | } else {
103 | dispatch(setSelectedFiles([ownProps]));
104 | }
105 | }
106 | };
107 | };
108 |
109 | File.propTypes = {
110 | name: PropTypes.string.isRequired,
111 | type: PropTypes.string.isRequired,
112 | size: PropTypes.oneOfType([
113 | PropTypes.string,
114 | PropTypes.number
115 | ]),
116 | editable: PropTypes.oneOfType([
117 | PropTypes.bool, PropTypes.number
118 | ])
119 | };
120 |
121 | export default connect(mapStateToProps, mapDispatchToProps)(File);
122 |
123 |
--------------------------------------------------------------------------------
/src/Components/Navbar/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import AppBar from '@material-ui/core/AppBar';
4 | import Toolbar from '@material-ui/core/Toolbar';
5 | import Typography from '@material-ui/core/Typography';
6 | import InputBase from '@material-ui/core/InputBase';
7 | import { fade } from '@material-ui/core/styles/colorManipulator';
8 | import { withStyles } from '@material-ui/core/styles';
9 | import SearchIcon from '@material-ui/icons/Search';
10 | import { connect } from 'react-redux';
11 | import { setFileListFilter, enterToPreviousDirectoryByIndex } from '../../Actions/Actions.js';
12 | import ThreeDotsMenu from './ThreeDotsMenu.jsx';
13 | import BreadcrumbText from '../Breadcrumb/BreadcrumbText.jsx';
14 | import { enterToPreviousDirectory } from '../../Actions/Actions.js';
15 |
16 | const styles = theme => ({
17 | root: {
18 | width: '100%',
19 | marginBottom: '4.3em'
20 | },
21 | grow: {
22 | flexGrow: 1,
23 | },
24 | menuButton: {
25 | marginLeft: -12,
26 | marginRight: 20,
27 | },
28 | title: {
29 | display: 'block', // was none
30 | [theme.breakpoints.up('sm')]: {
31 | display: 'block',
32 | },
33 | },
34 | search: {
35 | position: 'relative',
36 | borderRadius: theme.shape.borderRadius,
37 | backgroundColor: fade(theme.palette.common.white, 0.15),
38 | '&:hover': {
39 | backgroundColor: fade(theme.palette.common.white, 0.25),
40 | },
41 | marginLeft: 0,
42 | width: '100%',
43 | display: 'none',
44 | [theme.breakpoints.up('sm')]: {
45 | marginLeft: theme.spacing.unit,
46 | width: 'auto',
47 | display: 'block'
48 | },
49 | },
50 | searchIcon: {
51 | width: theme.spacing.unit * 9,
52 | height: '100%',
53 | position: 'absolute',
54 | pointerEvents: 'none',
55 | display: 'flex',
56 | alignItems: 'center',
57 | justifyContent: 'center',
58 | },
59 | inputRoot: {
60 | color: 'inherit',
61 | width: '100%',
62 | },
63 | inputInput: {
64 | paddingTop: theme.spacing.unit,
65 | paddingRight: theme.spacing.unit,
66 | paddingBottom: theme.spacing.unit,
67 | paddingLeft: theme.spacing.unit * 10,
68 | transition: theme.transitions.create('width'),
69 | width: '100%',
70 | [theme.breakpoints.up('sm')]: {
71 | width: 100,
72 | '&:focus': {
73 | width: 200,
74 | },
75 | },
76 | },
77 | });
78 |
79 | function SearchAppBar(props) {
80 | const { classes, path, handleClickPath, handleGoBack, canGoBack } = props;
81 | return (
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
109 |
110 |
111 |
112 |
113 |
114 | );
115 | }
116 |
117 | SearchAppBar.propTypes = {
118 | classes: PropTypes.object.isRequired,
119 | };
120 |
121 |
122 | const mapStateToProps = (state) => {
123 | return {
124 | value: state.fileListFilter || '',
125 | path: state.path,
126 | canGoBack: state.path.length
127 | };
128 | };
129 |
130 | const mapDispatchToProps = (dispatch) => {
131 | return {
132 | handleChange: (event) => {
133 | dispatch(setFileListFilter(event.currentTarget.value));
134 | },
135 | handleGoBack: (event) => {
136 | dispatch(enterToPreviousDirectory());
137 | },
138 | /**
139 | * @param {Object} event
140 | * @param {Number} index
141 | * @param {Array} path
142 | * @returns {undefined}
143 | */
144 | handleClickPath: (event, index) => {
145 | dispatch(enterToPreviousDirectoryByIndex(index));
146 | event.preventDefault();
147 | }
148 | };
149 | };
150 |
151 |
152 | export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(SearchAppBar));
153 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/Reducers/MainReducer.js:
--------------------------------------------------------------------------------
1 | export const defaultState = {
2 | path: [],
3 | pathSublist: [],
4 | fileList: [],
5 | fileListSublist: [],
6 | fileListFilter: null,
7 | contextMenuVisible: null,
8 | contextMenuPosition: [],
9 | contextMenuPositionElement: null,
10 | selectedFiles: [],
11 | selectedFolderSublist: null,
12 | loading: false,
13 | loadingSublist: false,
14 | errorMsg: null,
15 | visibleDialogCreateFolder: false,
16 | visibleDialogUploadFile: false,
17 | visibleDialogContent: false,
18 | visibleDialogEdit: false,
19 | visibleDialogMove: false,
20 | visibleDialogCopy: false,
21 | visibleDialogRename: false,
22 | fileContentBlobUrl: null,
23 | fileUploadProgress: 0,
24 | fileUploadList: []
25 | };
26 |
27 | /**
28 | * Main content reducer
29 | * @param {Object} state
30 | * @param {Object} action
31 | * @returns {Object}
32 | */
33 | const MainReducer = (state = defaultState, action) => {
34 | switch (action.type) {
35 | case 'SET_PATH':
36 | return Object.assign({}, state, {
37 | path: action.value
38 | });
39 | case 'SET_PATH_SUB_LIST':
40 | return Object.assign({}, state, {
41 | pathSublist: action.value
42 | });
43 | case 'ENTER_TO_DIRECTORY':
44 | return Object.assign({}, state, {
45 | path: [...state.path, action.value]
46 | });
47 | case 'ENTER_TO_DIRECTORY_SUB_LIST':
48 | return Object.assign({}, state, {
49 | pathSublist: [...state.pathSublist, action.value]
50 | });
51 | case 'SET_FILE_LIST':
52 | return Object.assign({}, state, {
53 | fileList: action.value.sort((a, b) => a.type < b.type ? -1 : a.name.toLowerCase() > b.name.toLowerCase())
54 | });
55 | case 'SET_FILE_LIST_SUB_LIST':
56 | return Object.assign({}, state, {
57 | fileListSublist: action.value.sort((a, b) => a.type < b.type ? -1 : a.name.toLowerCase() > b.name.toLowerCase())
58 | });
59 | case 'SET_FILE_LIST_FILTER':
60 | return Object.assign({}, state, {
61 | fileListFilter: action.value
62 | });
63 | case 'SET_CONTEXT_MENU_VISIBLE':
64 | return Object.assign({}, state, {
65 | contextMenuVisible: action.value
66 | });
67 | case 'SET_CONTEXT_MENU_POSITION':
68 | return Object.assign({}, state, {
69 | contextMenuPosition: action.value
70 | });
71 | case 'SET_CONTEXT_MENU_POSITION_ELEMENT':
72 | return Object.assign({}, state, {
73 | contextMenuPositionElement: action.value
74 | });
75 | case 'SET_SELECTED_FILES':
76 | return Object.assign({}, state, {
77 | selectedFiles: (action.value).filter((f, i, self) => self.map(ff => ff.name).indexOf(f.name) === i)
78 | });
79 | case 'SET_SELECTED_FOLDER_SUB_LIST':
80 | return Object.assign({}, state, {
81 | selectedFolderSublist: action.value
82 | });
83 |
84 | case 'TOGGLE_SELECTED_FILE':
85 | return Object.assign({}, state, {
86 | selectedFiles: state.selectedFiles.find(f => f.name === action.value.name) ?
87 | state.selectedFiles.filter(f => f.name !== action.value.name) :
88 | [...state.selectedFiles, action.value]
89 | });
90 | case 'SET_FILE_UPLOAD_PROGRESS':
91 | return Object.assign({}, state, {
92 | fileUploadProgress: parseInt(action.value || 0)
93 | });
94 | case 'SET_LOADING':
95 | return Object.assign({}, state, {
96 | loading: action.value
97 | });
98 | case 'SET_LOADING_SUB_LIST':
99 | return Object.assign({}, state, {
100 | loadingSublist: action.value
101 | });
102 | case 'SET_ERROR_MSG':
103 | return Object.assign({}, state, {
104 | errorMsg: action.value
105 | });
106 |
107 | case 'SET_VISIBLE_DIALOG_CREATE_FOLDER':
108 | return Object.assign({}, state, {
109 | visibleDialogCreateFolder: !!action.value
110 | });
111 |
112 | case 'SET_VISIBLE_DIALOG_UPLOAD_FILE':
113 | return Object.assign({}, state, {
114 | visibleDialogUploadFile: !!action.value
115 | });
116 |
117 | case 'SET_VISIBLE_DIALOG_CONTENT':
118 | return Object.assign({}, state, {
119 | visibleDialogContent: !!action.value
120 | });
121 |
122 | case 'SET_VISIBLE_DIALOG_EDIT':
123 | return Object.assign({}, state, {
124 | visibleDialogEdit: !!action.value
125 | });
126 |
127 | case 'SET_VISIBLE_DIALOG_MOVE':
128 | return Object.assign({}, state, {
129 | visibleDialogMove: !!action.value
130 | });
131 | case 'SET_VISIBLE_DIALOG_COPY':
132 | return Object.assign({}, state, {
133 | visibleDialogCopy: !!action.value
134 | });
135 | case 'SET_VISIBLE_DIALOG_RENAME':
136 | return Object.assign({}, state, {
137 | visibleDialogRename: !!action.value
138 | });
139 | case 'SET_FILE_UPLOAD_LIST':
140 | return Object.assign({}, state, {
141 | fileUploadList: action.value
142 | });
143 |
144 | case 'SET_FILE_CONTENT':
145 | /**
146 | * Removing old blob url
147 | */
148 | state.fileContentBlobUrl && URL.revokeObjectURL(state.fileContentBlobUrl);
149 | return Object.assign({}, state, {
150 | fileContentBlobUrl: action.value ? URL.createObjectURL(action.value) : null
151 | });
152 |
153 | default:
154 | return state;
155 | }
156 | };
157 |
158 | export default MainReducer;
159 |
--------------------------------------------------------------------------------
/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