mirror of
https://github.com/serghey-rodin/vesta.git
synced 2025-08-22 14:24:07 -07:00
remove FM submodule
This commit is contained in:
parent
e4539baab8
commit
103129545b
53 changed files with 18525 additions and 1 deletions
|
@ -1 +0,0 @@
|
||||||
Subproject commit 68058c136e9c533164cda88ded29d7961fb341c8
|
|
23
src/react/FileManagerComponent/.gitignore
vendored
Executable file
23
src/react/FileManagerComponent/.gitignore
vendored
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
68
src/react/FileManagerComponent/README.md
Executable file
68
src/react/FileManagerComponent/README.md
Executable file
|
@ -0,0 +1,68 @@
|
||||||
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.<br>
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.<br>
|
||||||
|
You will also see any lint errors in the console.
|
||||||
|
|
||||||
|
### `npm test`
|
||||||
|
|
||||||
|
Launches the test runner in the interactive watch mode.<br>
|
||||||
|
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `build` folder.<br>
|
||||||
|
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.<br>
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||||
|
|
||||||
|
### `npm run eject`
|
||||||
|
|
||||||
|
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||||
|
|
||||||
|
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||||
|
|
||||||
|
Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||||
|
|
||||||
|
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||||
|
|
||||||
|
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||||
|
|
||||||
|
### Code Splitting
|
||||||
|
|
||||||
|
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
|
||||||
|
|
||||||
|
### Analyzing the Bundle Size
|
||||||
|
|
||||||
|
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
|
||||||
|
|
||||||
|
### Making a Progressive Web App
|
||||||
|
|
||||||
|
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
|
||||||
|
|
||||||
|
### `npm run build` fails to minify
|
||||||
|
|
||||||
|
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
|
14950
src/react/FileManagerComponent/package-lock.json
generated
Normal file
14950
src/react/FileManagerComponent/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
44
src/react/FileManagerComponent/package.json
Normal file
44
src/react/FileManagerComponent/package.json
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"name": "file_manager",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^5.10.1",
|
||||||
|
"@fortawesome/fontawesome-svg-core": "^1.2.21",
|
||||||
|
"@fortawesome/free-brands-svg-icons": "^5.10.1",
|
||||||
|
"@fortawesome/free-solid-svg-icons": "^5.10.1",
|
||||||
|
"@fortawesome/react-fontawesome": "^0.1.4",
|
||||||
|
"axios": "^0.18.1",
|
||||||
|
"bootstrap": "^4.3.1",
|
||||||
|
"classname": "0.0.0",
|
||||||
|
"jquery": "^3.4.1",
|
||||||
|
"node-sass": "^4.12.0",
|
||||||
|
"popper.js": "^1.15.0",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react": "^16.10.2",
|
||||||
|
"react-codemirror": "^1.0.0",
|
||||||
|
"react-dom": "^16.10.2",
|
||||||
|
"react-router-dom": "^5.1.2",
|
||||||
|
"react-scripts": "^3.2.0",
|
||||||
|
"react-toastify": "^5.3.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build && npm run delete-remote-js && npm run delete-remote-css && npm run move-local-js && npm run move-local-css",
|
||||||
|
"delete-remote-js": "ssh root@r5.vestacp.com 'rm -rf /usr/local/vesta/web/static/js' ",
|
||||||
|
"delete-remote-css": "ssh root@r5.vestacp.com 'rm -rf /usr/local/vesta/web/static/css'",
|
||||||
|
"move-local-js": "scp -r build/static/js root@r5.vestacp.com:/usr/local/vesta/web/static",
|
||||||
|
"move-local-css": "scp -r build/static/css root@r5.vestacp.com:/usr/local/vesta/web/static",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not ie <= 11",
|
||||||
|
"not op_mini all"
|
||||||
|
]
|
||||||
|
}
|
BIN
src/react/FileManagerComponent/public/favicon.ico
Executable file
BIN
src/react/FileManagerComponent/public/favicon.ico
Executable file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
26
src/react/FileManagerComponent/public/index.html
Executable file
26
src/react/FileManagerComponent/public/index.html
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<link rel="stylesheet" href="/node_modules/bootstrap/dist/css/bootstrap.css">
|
||||||
|
<link rel="stylesheet" href="/node_modules/bootstrap/dist/js/bootstrap.min.js">
|
||||||
|
<title>File Manager</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
|
||||||
|
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
|
||||||
|
integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
15
src/react/FileManagerComponent/public/manifest.json
Executable file
15
src/react/FileManagerComponent/public/manifest.json
Executable file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
119
src/react/FileManagerComponent/src/FileManagerHelper.js
Normal file
119
src/react/FileManagerComponent/src/FileManagerHelper.js
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import axios from "axios";
|
||||||
|
const server = window.location.origin + "/file_manager/fm_api.php?";
|
||||||
|
|
||||||
|
export function validateAction(url) {
|
||||||
|
return axios.get(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cacheData(currentUser, history) {
|
||||||
|
if (localStorage.getItem("lastUser") === null || currentUser !== localStorage.getItem("lastUser")) {
|
||||||
|
localStorage.setItem("lastUser", currentUser);
|
||||||
|
localStorage.setItem("activeWindow", "left");
|
||||||
|
localStorage.setItem("leftListPath", window.GLOBAL.ROOT_DIR);
|
||||||
|
localStorage.setItem("rightListPath", window.GLOBAL.ROOT_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (localStorage.getItem("activeWindow") === null || localStorage.getItem("leftListPath") === null || localStorage.getItem("rightListPath") === null) {
|
||||||
|
let path = history.location.search.substring(6).split('/');
|
||||||
|
localStorage.setItem("activeWindow", "left");
|
||||||
|
localStorage.setItem("leftListPath", path);
|
||||||
|
localStorage.setItem("rightListPath", window.GLOBAL.ROOT_DIR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeDirectoryOnLoading(server, list) {
|
||||||
|
return axios.get(`${server}dir=${encodePath(localStorage.getItem(list))}&action=cd`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function changeDirectory(server, path) {
|
||||||
|
return axios.get(`${server}dir=${encodePath(path)}&action=cd`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getData(path) {
|
||||||
|
return axios.get(`${server}dir=%2F${path}&action=cd`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkExistingFileName(selectedFiles, activeWindow, leftListData, rightListData) {
|
||||||
|
let selectedFileNames = [];
|
||||||
|
let existingFileNames = [];
|
||||||
|
let newFiles = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < selectedFiles.length; i++) {
|
||||||
|
selectedFileNames.push(selectedFiles[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeWindow === "left") {
|
||||||
|
for (let i = 0; i < selectedFileNames.length; i++) {
|
||||||
|
if (leftListData.map((item) => { return item.name }).includes(selectedFileNames[i].name)) {
|
||||||
|
existingFileNames.push(selectedFileNames[i]);
|
||||||
|
} else {
|
||||||
|
newFiles.push(selectedFileNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < selectedFileNames.length; i++) {
|
||||||
|
if (rightListData.map((item) => { return item.name }).includes(selectedFileNames[i].name)) {
|
||||||
|
existingFileNames.push(selectedFileNames[i]);
|
||||||
|
} else {
|
||||||
|
newFiles.push(selectedFileNames[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { existingFileNames, newFiles };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encodePath(path) {
|
||||||
|
let splitPath = path.split('/');
|
||||||
|
let encodedPath = splitPath.join('%2F');
|
||||||
|
return encodedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function activeWindowPath() {
|
||||||
|
if (localStorage.getItem("activeWindow") === "left") {
|
||||||
|
let currentPath = localStorage.getItem("leftListPath");
|
||||||
|
return currentPath;
|
||||||
|
} else if (localStorage.getItem("activeWindow") === "right") {
|
||||||
|
let currentPath = localStorage.getItem("rightListPath");
|
||||||
|
return currentPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteItems(url, path, selection) {
|
||||||
|
if (!selection.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promisesArray = selection.map(item =>
|
||||||
|
validateAction(`${url}item=${path}%2F${item}&dir=${path}&action=delete_files`)
|
||||||
|
.then(() => { })
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.all(promisesArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveItems(url, path, targetPath, selection) {
|
||||||
|
if (!selection.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promisesArray = selection.map(item =>
|
||||||
|
validateAction(`${url}item=${path}%2F${item}&target_name=${targetPath}&action=move_file`)
|
||||||
|
.then(() => { })
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.all(promisesArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyItems(url, path, targetPath, selection) {
|
||||||
|
if (!selection.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const promisesArray = selection.map(item =>
|
||||||
|
validateAction(`${url}item=${path}%2F${item}&filename=${item}&dir=${path}&dir_target=${targetPath}&action=copy_file`)
|
||||||
|
.then(() => { })
|
||||||
|
);
|
||||||
|
|
||||||
|
return Promise.all(promisesArray);
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './Hotkeys.scss'
|
||||||
|
|
||||||
|
function style(style) {
|
||||||
|
if (style === "inactive") {
|
||||||
|
return "none";
|
||||||
|
} else {
|
||||||
|
return "block";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Hotkeys = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="panel panel-default" style={{ display: style(props.style) }}>
|
||||||
|
<div className="panel-heading">
|
||||||
|
<h2>Shortcuts</h2>
|
||||||
|
<button type="button" className="close" onClick={props.close} >
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="panel-body">
|
||||||
|
<ul>
|
||||||
|
<li><span className="shortcut">u</span> {window.GLOBAL.App.Constants.FM_Upload}</li>
|
||||||
|
<li><span className="shortcut">n</span> {window.GLOBAL.App.Constants.FM_NewFile}</li>
|
||||||
|
<li><span className="shortcut">F7</span> {window.GLOBAL.App.Constants.FM_NewFolder}</li>
|
||||||
|
<li><span className="shortcut">d</span> {window.GLOBAL.App.Constants.FM_Download}</li>
|
||||||
|
<li><span className="shortcut">F2 / Shift + F6</span> {window.GLOBAL.App.Constants.FM_Rename}</li>
|
||||||
|
<li><span className="shortcut">m</span> {window.GLOBAL.App.Constants.FM_Move}</li>
|
||||||
|
<li><span className="shortcut">F5</span> {window.GLOBAL.App.Constants.FM_Copy}</li>
|
||||||
|
<li><span className="shortcut">F8 / Del</span> {window.GLOBAL.App.Constants.FM_Delete}</li>
|
||||||
|
<li><span className="shortcut">F2</span> {window.GLOBAL.App.Constants.FM_SaveFile}</li>
|
||||||
|
<li><span className="shortcut">h</span> {window.GLOBAL.App.Constants.FM_DisplayClose}</li>
|
||||||
|
<li><span className="shortcut">Esc</span> {window.GLOBAL.App.Constants.FM_Close}</li>
|
||||||
|
<li><span className="shortcut">F10</span> Close Preview / Editor</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><span className="shortcut">↑</span> {window.GLOBAL.App.Constants.FM_MoveUp}</li>
|
||||||
|
<li><span className="shortcut">↓</span> {window.GLOBAL.App.Constants.FM_MoveDown}</li>
|
||||||
|
<li><span className="shortcut">←</span> {window.GLOBAL.App.Constants.FM_MoveLeft}</li>
|
||||||
|
<li><span className="shortcut">→</span> {window.GLOBAL.App.Constants.FM_MoveRight}</li>
|
||||||
|
<li><span className="shortcut">a</span> {window.GLOBAL.App.Constants.FM_Archive}</li>
|
||||||
|
<li><span className="shortcut">Tab</span> {window.GLOBAL.App.Constants.FM_Switch}</li>
|
||||||
|
<li><span className="shortcut">Enter</span> {window.GLOBAL.App.Constants.FM_Open}</li>
|
||||||
|
<li><span className="shortcut">F4</span> Edit file permissions</li>
|
||||||
|
<li><span className="shortcut">Backspace</span> {window.GLOBAL.App.Constants.FM_GoBack}</li>
|
||||||
|
<li><span className="shortcut">Ctr + Click</span> {window.GLOBAL.App.Constants.FM_SelectBunch}</li>
|
||||||
|
<li><span className="shortcut">Shift + Cursor up/down</span> {window.GLOBAL.App.Constants.FM_AddToSelection}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Hotkeys;
|
|
@ -0,0 +1,72 @@
|
||||||
|
.panel {
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 30%;
|
||||||
|
bottom: 0;
|
||||||
|
width: 40%;
|
||||||
|
font-size: 15px;
|
||||||
|
background: #38383891;
|
||||||
|
|
||||||
|
.panel-heading {
|
||||||
|
background: #474646b0;
|
||||||
|
text-align: center;
|
||||||
|
color: #ffcc00;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
right: 10px;
|
||||||
|
top: 10px;
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&:hover,&:focus{
|
||||||
|
color: #333;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: rgba(56,56,56,0.4);
|
||||||
|
|
||||||
|
ul {
|
||||||
|
width: 50%;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.shortcut {
|
||||||
|
color: #48F4EF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotkeys-button {
|
||||||
|
color: white;
|
||||||
|
background: rgb(170, 166, 166);
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
width: 40px;
|
||||||
|
height: 38px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #aacc0d;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import './Hotkeys.scss'
|
||||||
|
|
||||||
|
const HotkeysButton = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="hotkeys-button" onClick={props.open}>
|
||||||
|
<FontAwesomeIcon icon="ellipsis-h" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HotkeysButton;
|
|
@ -0,0 +1,303 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Spinner from '../../Spinner/Spinner';
|
||||||
|
import Path from '../../Path/Path';
|
||||||
|
import Row from '../Row/Row';
|
||||||
|
import '../List.scss';
|
||||||
|
|
||||||
|
class DirectoryList extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
changePathAfterToggle: PropTypes.func,
|
||||||
|
openCertainDirectory: PropTypes.func,
|
||||||
|
openDirectory: PropTypes.func,
|
||||||
|
passSelection: PropTypes.func,
|
||||||
|
modalVisible: PropTypes.bool,
|
||||||
|
changePath: PropTypes.func,
|
||||||
|
addToPath: PropTypes.func,
|
||||||
|
history: PropTypes.object,
|
||||||
|
isActive: PropTypes.bool,
|
||||||
|
cursor: PropTypes.number,
|
||||||
|
passData: PropTypes.func,
|
||||||
|
download: PropTypes.func,
|
||||||
|
moveBack: PropTypes.func,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
loading: PropTypes.bool,
|
||||||
|
path: PropTypes.string,
|
||||||
|
list: PropTypes.string,
|
||||||
|
data: PropTypes.array
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
orderType: "descending",
|
||||||
|
sortingType: "Type",
|
||||||
|
itemsSelected: [],
|
||||||
|
cursor: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillMount = () => {
|
||||||
|
if (localStorage.getItem(`${this.props.list}Sorting`) && localStorage.getItem(`${this.props.list}Order`)) {
|
||||||
|
this.setState({ sortingType: localStorage.getItem(`${this.props.list}Sorting`), orderType: localStorage.getItem(`${this.props.list}Order`) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
document.addEventListener("keydown", this.handleLiSelection);
|
||||||
|
document.addEventListener("keydown", this.moveBackOnButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
document.removeEventListener("keydown", this.handleLiSelection);
|
||||||
|
document.removeEventListener("keydown", this.moveBackOnButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheSorting = () => {
|
||||||
|
localStorage.setItem(`${this.props.list}Sorting`, this.state.sortingType);
|
||||||
|
localStorage.setItem(`${this.props.list}Order`, this.state.orderType);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveBackOnButton = (e) => {
|
||||||
|
if (e.keyCode === 8 && !this.props.modalVisible && this.props.isActive) {
|
||||||
|
this.moveBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moveBack = () => {
|
||||||
|
if (this.isHomeDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.moveBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
isHomeDirectory = () => {
|
||||||
|
return this.props.path === window.GLOBAL.ROOT_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleActiveList = () => {
|
||||||
|
const { history, path, list, onClick, changePathAfterToggle, isActive } = this.props;
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
onClick(list);
|
||||||
|
changePathAfterToggle(path);
|
||||||
|
history.push({
|
||||||
|
pathname: '/list/directory/',
|
||||||
|
search: `?path=${path}`
|
||||||
|
});
|
||||||
|
this.cacheActiveWindowAndPaths();
|
||||||
|
this.passData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheActiveWindowAndPaths = () => {
|
||||||
|
localStorage.setItem("activeWindow", this.props.list);
|
||||||
|
localStorage.setItem(`${this.props.list}ListPath`, this.props.path);
|
||||||
|
localStorage.setItem(`${this.props.list}ListPath`, this.props.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSelected = (i) => {
|
||||||
|
return this.state.itemsSelected.indexOf(i) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
addToSelection(i) {
|
||||||
|
const { itemsSelected } = this.state;
|
||||||
|
const result = [...itemsSelected];
|
||||||
|
const duplicate = itemsSelected.indexOf(i);
|
||||||
|
if (duplicate !== -1) {
|
||||||
|
result.splice(duplicate, 1);
|
||||||
|
} else {
|
||||||
|
if (i === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ itemsSelected: result });
|
||||||
|
this.props.passSelection(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLiSelection = (e) => {
|
||||||
|
const { data, isActive, modalVisible, changePath, path } = this.props;
|
||||||
|
const { cursor } = this.state;
|
||||||
|
|
||||||
|
if (!isActive || modalVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 40) {
|
||||||
|
if (cursor === data.listing.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
let name = data.listing[cursor].name;
|
||||||
|
this.addToSelection(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ cursor: cursor + 1 });
|
||||||
|
this.passData();
|
||||||
|
changePath(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 38) {
|
||||||
|
if (cursor === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.shiftKey) {
|
||||||
|
let name = data.listing[cursor].name;
|
||||||
|
this.addToSelection(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ cursor: cursor - 1 });
|
||||||
|
this.passData();
|
||||||
|
changePath(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetData = () => {
|
||||||
|
this.setState({ cursor: 0, itemsSelected: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
passData = () => {
|
||||||
|
const { data, passData } = this.props;
|
||||||
|
const { name, permissions, type } = data.listing[this.state.cursor];
|
||||||
|
passData(this.state.cursor, name, permissions, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
openDirectory = (name) => {
|
||||||
|
const { history, path, addToPath, openDirectory } = this.props;
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
pathname: '/list/directory/',
|
||||||
|
search: `?path=${path}/${name}`
|
||||||
|
});
|
||||||
|
addToPath(name);
|
||||||
|
openDirectory();
|
||||||
|
this.setState({ cursor: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
openCertainDirectory = (path) => {
|
||||||
|
const { history, openCertainDirectory, changePath } = this.props;
|
||||||
|
|
||||||
|
if (this.isHomeDirectory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push({
|
||||||
|
pathname: '/list/directory/',
|
||||||
|
search: `?path=${path}`
|
||||||
|
});
|
||||||
|
changePath(path);
|
||||||
|
openCertainDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSorting = (sortingType, orderType) => {
|
||||||
|
this.setState({ sortingType, orderType }, () => this.cacheSorting());
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByType = (a, b) => {
|
||||||
|
if (this.state.orderType === "descending" && a.name !== "") {
|
||||||
|
return a.type.localeCompare(b.type);
|
||||||
|
} else if (this.state.orderType === "ascending" && b.name !== "") {
|
||||||
|
return b.type.localeCompare(a.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortBySize = (a, b) => {
|
||||||
|
if (this.state.orderType === "descending" && a.name !== "") {
|
||||||
|
return a.size - b.size;
|
||||||
|
} else if (this.state.orderType === "ascending" && b.name !== "") {
|
||||||
|
return b.size - a.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByDate = (a, b) => {
|
||||||
|
if (this.state.orderType === "descending" && a.name !== "") {
|
||||||
|
return new Date(a.date) - new Date(b.date);
|
||||||
|
} else if (this.state.orderType === "ascending" && a.name !== "") {
|
||||||
|
return new Date(b.date) - new Date(a.date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByName = (a, b) => {
|
||||||
|
if (this.state.orderType === "descending" && a.name !== "") {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
} else if (this.state.orderType === "ascending" && b.name !== "") {
|
||||||
|
return b.name.localeCompare(a.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortData = (a, b) => {
|
||||||
|
switch (this.state.sortingType) {
|
||||||
|
case "Type": return this.sortByType(a, b);
|
||||||
|
case "Size": if (a.type !== "d" && b.type !== "d") { return this.sortBySize(a, b) }; break;
|
||||||
|
case "Date": return this.sortByDate(a, b);
|
||||||
|
case "Name": return this.sortByName(a, b);
|
||||||
|
default: return this.sortByType(a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rows = () => {
|
||||||
|
const { isActive, modalVisible, path, download } = this.props;
|
||||||
|
const { cursor } = this.state;
|
||||||
|
const data = { ...this.props.data };
|
||||||
|
|
||||||
|
if (data.listing.length !== 0) {
|
||||||
|
let sortedData = data.listing.sort((a, b) => this.sortData(a, b));
|
||||||
|
return (
|
||||||
|
sortedData.map((item, key) =>
|
||||||
|
(item.name !== "" && sortedData.length !== 0) ?
|
||||||
|
(<Row key={key}
|
||||||
|
selectOnClick={(cursor, name, permissions, type) => {
|
||||||
|
this.setState({ cursor });
|
||||||
|
this.props.passData(cursor, name, permissions, type);
|
||||||
|
}}
|
||||||
|
selectMultiple={() => this.addToSelection(item.name)}
|
||||||
|
selected={this.isSelected(item.name)}
|
||||||
|
openDirectory={this.openDirectory}
|
||||||
|
modalVisible={modalVisible}
|
||||||
|
activeRow={key === cursor}
|
||||||
|
isActiveList={isActive}
|
||||||
|
download={download}
|
||||||
|
cursor={key}
|
||||||
|
data={item}
|
||||||
|
path={path} />) :
|
||||||
|
(<Row key={key}
|
||||||
|
selectOnClick={(cursor, name, permissions, type) => {
|
||||||
|
this.setState({ cursor });
|
||||||
|
this.props.passData(cursor, name, permissions, type);
|
||||||
|
}}
|
||||||
|
openDirectory={this.moveBack}
|
||||||
|
modalVisible={modalVisible}
|
||||||
|
activeRow={key === cursor}
|
||||||
|
isActiveList={isActive}
|
||||||
|
cursor={key}
|
||||||
|
data={item}
|
||||||
|
path={path} />))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isActive, path, loading } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={isActive ? "list active" : "list"} onClick={this.toggleActiveList}>
|
||||||
|
<Path class={isActive ? "active-path" : "path"}
|
||||||
|
openDirectory={this.openCertainDirectory}
|
||||||
|
changeSorting={this.changeSorting}
|
||||||
|
sorting={this.state.sortingType}
|
||||||
|
order={this.state.orderType}
|
||||||
|
isActive={isActive}
|
||||||
|
path={path} />
|
||||||
|
<div className="list-container">
|
||||||
|
<ul>
|
||||||
|
{loading && isActive ? <Spinner /> : this.rows()}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DirectoryList;
|
|
@ -0,0 +1,56 @@
|
||||||
|
.list {
|
||||||
|
width: 50%;
|
||||||
|
height: 100vh;
|
||||||
|
flex: 1 1 50%;
|
||||||
|
|
||||||
|
.list-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 0;
|
||||||
|
color: #3a3a3a;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: block;
|
||||||
|
height: 34px;
|
||||||
|
line-height: 30px;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-top: 2px;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover{
|
||||||
|
background: #DEDEDE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active {
|
||||||
|
background: #FFDC5A;
|
||||||
|
|
||||||
|
.marker {
|
||||||
|
float: left;
|
||||||
|
width: 4px;
|
||||||
|
height: 34px;
|
||||||
|
margin-right: 5px;
|
||||||
|
background: #79641a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.selected {
|
||||||
|
background: #fdfdbb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px){
|
||||||
|
.permissions, .owner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
191
src/react/FileManagerComponent/src/components/Lists/Row/Row.jsx
Normal file
191
src/react/FileManagerComponent/src/components/Lists/Row/Row.jsx
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faJs, faCss3, faPhp, faHtml5, faSass } from '@fortawesome/free-brands-svg-icons';
|
||||||
|
import './Row.scss';
|
||||||
|
|
||||||
|
class Row extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
selectMultiple: PropTypes.func,
|
||||||
|
selectOnClick: PropTypes.func,
|
||||||
|
openDirectory: PropTypes.func,
|
||||||
|
modalVisible: PropTypes.bool,
|
||||||
|
isActiveList: PropTypes.bool,
|
||||||
|
activeRow: PropTypes.bool,
|
||||||
|
selected: PropTypes.func,
|
||||||
|
download: PropTypes.func,
|
||||||
|
cursor: PropTypes.number,
|
||||||
|
path: PropTypes.string,
|
||||||
|
key: PropTypes.number,
|
||||||
|
data: PropTypes.array
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
document.addEventListener("keydown", this.openOnEnter);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
document.removeEventListener("keydown", this.openOnEnter);
|
||||||
|
}
|
||||||
|
|
||||||
|
openOnEnter = (e) => {
|
||||||
|
const { activeRow, data: { name, type }, isActiveList, modalVisible, openDirectory, cursor, download, path } = this.props;
|
||||||
|
|
||||||
|
if (modalVisible || !activeRow || !isActiveList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
if (this.isArchive(name) || type === "l") {
|
||||||
|
download();
|
||||||
|
} else if (this.isFile(type) && cursor !== 0) {
|
||||||
|
this.changePath(path, name);
|
||||||
|
} else {
|
||||||
|
openDirectory(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openItem = () => {
|
||||||
|
const { data: { type, name }, openDirectory, download, path, isActiveList } = this.props;
|
||||||
|
|
||||||
|
if (!isActiveList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isArchive(name) || type === "l" || name.match('.mp4')) {
|
||||||
|
return download();
|
||||||
|
} else if (this.isFile(type)) {
|
||||||
|
return this.changePath(path, name);
|
||||||
|
} else if (type === 'd') {
|
||||||
|
return openDirectory(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changePath = (path, name) => {
|
||||||
|
this.props.history.push({
|
||||||
|
pathname: '/list/directory/preview/',
|
||||||
|
search: `?path=${path}/${name}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectRow = (e) => {
|
||||||
|
const { data: { name, permissions, type }, selectMultiple, selectOnClick, cursor, activeRow } = this.props;
|
||||||
|
|
||||||
|
if (e.ctrlKey && cursor !== 0) {
|
||||||
|
selectMultiple();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeRow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectOnClick(cursor, name, permissions, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
className = () => {
|
||||||
|
const { activeRow, selected, isActiveList } = this.props;
|
||||||
|
|
||||||
|
if (isActiveList) {
|
||||||
|
let isActive = activeRow ? 'active' : '';
|
||||||
|
let isSelected = selected ? 'selected' : '';
|
||||||
|
return isActive.length ? isActive : isSelected;
|
||||||
|
} else {
|
||||||
|
let isActive = activeRow ? 'inactive' : '';
|
||||||
|
let isSelected = selected ? 'inactive-selected' : '';
|
||||||
|
return isActive.length ? isActive : isSelected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeFormatter = (bytes, decimals) => {
|
||||||
|
if (bytes === undefined || this.props.data.type === "d") {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (bytes === "0") {
|
||||||
|
return <span className="value">0 <span className="unit">b</span></span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let k = 1024,
|
||||||
|
dm = decimals <= 0 ? 0 : decimals || 2,
|
||||||
|
sizes = ['b', 'kb', 'Mb', 'GB'],
|
||||||
|
i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return (<span className="value">{parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} <span className="unit">{sizes[i]}</span></span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
dateFormatter = (fDate) => {
|
||||||
|
if (fDate === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let date = new Date(fDate),
|
||||||
|
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||||
|
appMonths = window.GLOBAL.App.Constants.FM_TRANSLATED_DATES,
|
||||||
|
getDay = date.getDate(),
|
||||||
|
getMonth = appMonths[months[date.getMonth()]];
|
||||||
|
return (<span className="date">{getMonth} {getDay}</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
glyph = () => {
|
||||||
|
const { data: { type, name } } = this.props;
|
||||||
|
|
||||||
|
if (type === 'd') {
|
||||||
|
return <FontAwesomeIcon icon="folder-open" className="folder-open" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isFile(type)) {
|
||||||
|
if (this.isArchive(name)) {
|
||||||
|
return <FontAwesomeIcon icon="book" className="archive" />;
|
||||||
|
} else if (name.match(/png|jpg|jpeg|gif/g)) {
|
||||||
|
return <FontAwesomeIcon icon="image" className="image" />;
|
||||||
|
} else if (name.match('.mp4') !== null) {
|
||||||
|
return <FontAwesomeIcon icon="download" className="download" />;
|
||||||
|
} else if (name.match('.txt')) {
|
||||||
|
return <FontAwesomeIcon icon="file-alt" className="file-alt" />;
|
||||||
|
} else if (name.match('.js')) {
|
||||||
|
return <FontAwesomeIcon icon={faJs} className="js" />;
|
||||||
|
} else if (name.match('.html')) {
|
||||||
|
return <FontAwesomeIcon icon={faHtml5} className="html5" />;
|
||||||
|
} else if (name.match('.php')) {
|
||||||
|
return <FontAwesomeIcon icon={faPhp} className="php" />;
|
||||||
|
} else if (name.match(/.scss/i)) {
|
||||||
|
return <FontAwesomeIcon icon={faSass} className="sass" />;
|
||||||
|
} else if (name.match(/.css/i)) {
|
||||||
|
return <FontAwesomeIcon icon={faCss3} className="css3" />;
|
||||||
|
} else {
|
||||||
|
return <FontAwesomeIcon icon="file" className="file" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === "l") {
|
||||||
|
return <FontAwesomeIcon icon="download" className="download" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isArchive(name) {
|
||||||
|
return name.match(/zip|tgz|tar.gz|gzip|tbz|tar.bz|gz|zip|tar|rar/g);
|
||||||
|
}
|
||||||
|
|
||||||
|
isFile(type) {
|
||||||
|
return type === 'f';
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data: { name, owner, permissions, size, date, time } } = this.props;
|
||||||
|
return (
|
||||||
|
<li className={this.className()} onClick={this.selectRow} >
|
||||||
|
<span className="marker"></span>
|
||||||
|
{this.glyph()}
|
||||||
|
<span className="fName"><span className="name" onClick={(e) => this.openItem(e)}>{this.props.cursor === 0 ? ".." : name}</span></span>
|
||||||
|
<span className="fPermissions">{permissions}</span>
|
||||||
|
<span className="fOwner">{owner}</span>
|
||||||
|
<span className="fSize">{this.sizeFormatter(size)}</span>
|
||||||
|
<span className="fDate">{this.dateFormatter(date)}</span>
|
||||||
|
<span className="fTime">{time}</span>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(Row);
|
165
src/react/FileManagerComponent/src/components/Lists/Row/Row.scss
Normal file
165
src/react/FileManagerComponent/src/components/Lists/Row/Row.scss
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
.list .list-container ul li svg {
|
||||||
|
width: 25px;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-top: 9px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.html5 {
|
||||||
|
color: #f16529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-alt {
|
||||||
|
color: #20d2d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-container ul li {
|
||||||
|
.marker {
|
||||||
|
float: left;
|
||||||
|
width: 4px;
|
||||||
|
height: 34px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-container ul li .js {
|
||||||
|
width: 16px;
|
||||||
|
color: #f7df1c;
|
||||||
|
background: black;
|
||||||
|
margin: 9px 4px 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.php {
|
||||||
|
color: #777bb3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.css3 {
|
||||||
|
color: #0079cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sass {
|
||||||
|
color: #cd6699;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
color: #36afae;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder-open {
|
||||||
|
color: #e29741;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
color: #11b1b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archive {
|
||||||
|
color: rgb(209, 206, 43);
|
||||||
|
}
|
||||||
|
|
||||||
|
.download {
|
||||||
|
color: #929ca3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fOwner,
|
||||||
|
.fPermissions,
|
||||||
|
.fSize,
|
||||||
|
.fDate,
|
||||||
|
.fTime {
|
||||||
|
font-size: 14px;
|
||||||
|
float: right;
|
||||||
|
width: 60px;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-container ul li .fName {
|
||||||
|
margin-left: 5px;
|
||||||
|
width: 260px;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: clip;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
padding: 0 5px;
|
||||||
|
float: left;
|
||||||
|
margin-left: 5px;
|
||||||
|
line-height: 34px;
|
||||||
|
font-size: 15px;
|
||||||
|
white-space: nowrap;
|
||||||
|
transition: all ease-out .3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transition: all ease-out .2s;
|
||||||
|
background: rgb(201, 199, 199);
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-container ul li.active .fName .name:hover {
|
||||||
|
background: #F0B607;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-container ul li .fPermissions {
|
||||||
|
margin-left: 35px;
|
||||||
|
width: 50px;
|
||||||
|
color: #727272;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-container ul li .fDate .date {
|
||||||
|
color: #727272;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.list .list-container ul li .fTime {
|
||||||
|
color: #727272;
|
||||||
|
font-size: 11px;
|
||||||
|
width: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fSize {
|
||||||
|
padding-right: 15px;
|
||||||
|
text-align: right;
|
||||||
|
width: 95px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-container ul li .fOwner {
|
||||||
|
color: #81A64F;
|
||||||
|
font-style: italic;
|
||||||
|
width: 50px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: #44a8b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
font-weight: bolder;
|
||||||
|
color: #727272;
|
||||||
|
}
|
||||||
|
|
||||||
|
li.inactive {
|
||||||
|
.marker {
|
||||||
|
float: left;
|
||||||
|
width: 4px;
|
||||||
|
height: 34px;
|
||||||
|
margin-right: 5px;
|
||||||
|
background: rgb(163, 163, 163);
|
||||||
|
}
|
||||||
|
|
||||||
|
background: rgb(201, 199, 199);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive-selected {
|
||||||
|
background: rgb(220, 220, 220);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px){
|
||||||
|
.fPermissions, .fOwner {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
181
src/react/FileManagerComponent/src/components/Menu/Menu.jsx
Normal file
181
src/react/FileManagerComponent/src/components/Menu/Menu.jsx
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import './Menu.scss';
|
||||||
|
|
||||||
|
class Menu extends Component {
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
document.addEventListener("keydown", this.hotKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
document.removeEventListener("keydown", this.hotKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
newFile = () => {
|
||||||
|
this.props.openModal("Add file");
|
||||||
|
}
|
||||||
|
|
||||||
|
newDirectory = () => {
|
||||||
|
this.props.openModal("Add directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = () => {
|
||||||
|
const { selection, openModal, cursor } = this.props;
|
||||||
|
if (selection.length === 0) {
|
||||||
|
if (cursor === 0) {
|
||||||
|
openModal("Nothing selected");
|
||||||
|
} else {
|
||||||
|
openModal("Delete");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openModal("Delete", selection.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rename = () => {
|
||||||
|
if (this.props.cursor === 0) {
|
||||||
|
this.props.openModal("Nothing selected");
|
||||||
|
} else {
|
||||||
|
this.props.openModal("Rename");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
permissions = () => {
|
||||||
|
if (this.props.cursor === 0) {
|
||||||
|
this.props.openModal("Nothing selected");
|
||||||
|
} else {
|
||||||
|
this.props.openModal("Permissions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
move = () => {
|
||||||
|
const { selection, openModal, cursor } = this.props;
|
||||||
|
if (selection.length === 0) {
|
||||||
|
if (cursor === 0) {
|
||||||
|
openModal("Nothing selected");
|
||||||
|
} else {
|
||||||
|
openModal("Move");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openModal("Move", selection.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
archive = () => {
|
||||||
|
const { selection, openModal, cursor } = this.props;
|
||||||
|
|
||||||
|
if (selection.length === 0) {
|
||||||
|
if (cursor === 0) {
|
||||||
|
openModal("Nothing selected");
|
||||||
|
} else {
|
||||||
|
openModal("Archive");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openModal("Archive", selection.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extract = () => {
|
||||||
|
if (this.props.cursor === 0) {
|
||||||
|
this.props.openModal("Nothing selected");
|
||||||
|
} else {
|
||||||
|
this.props.openModal("Extract");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copy = () => {
|
||||||
|
const { selection, openModal, cursor } = this.props;
|
||||||
|
if (selection.length === 0) {
|
||||||
|
if (cursor === 0) {
|
||||||
|
openModal("Nothing selected");
|
||||||
|
} else {
|
||||||
|
openModal("Copy");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
openModal("Copy", selection.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
upload = (e) => {
|
||||||
|
if (e.target.files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.upload(e.target.files);
|
||||||
|
}
|
||||||
|
|
||||||
|
download = () => {
|
||||||
|
if (this.props.cursor === 0) {
|
||||||
|
this.props.openModal("Nothing selected");
|
||||||
|
} else if (this.props.itemType === "d") {
|
||||||
|
this.props.openModal("Nothing selected", null, true);
|
||||||
|
} else {
|
||||||
|
this.props.download();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKeys = (e) => {
|
||||||
|
if (this.props.modalVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.shiftKey && e.keyCode === 117) {
|
||||||
|
this.rename();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (e.keyCode) {
|
||||||
|
case 46: return this.delete();
|
||||||
|
case 65: return this.archive();
|
||||||
|
case 68: return this.download();
|
||||||
|
case 77: return this.move();
|
||||||
|
case 78: return this.newFile();
|
||||||
|
case 85: return this.inputFile.click();
|
||||||
|
case 113: return this.rename();
|
||||||
|
case 115: return this.permissions();
|
||||||
|
case 116: return this.copy();
|
||||||
|
case 118: return this.newDirectory();
|
||||||
|
case 119: return this.delete();
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let matchArchive = this.props.name.match(/.zip|.tgz|.tar.gz|.gzip|.tbz|.tar.bz|.gz|.zip|.tar|.rar/g);
|
||||||
|
return (
|
||||||
|
<div className="menu">
|
||||||
|
<div className="logo">
|
||||||
|
<a href={window.location.origin}>
|
||||||
|
<img src="../../images/logo.png" alt="Logo" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="btn-group" role="group" aria-label="First group">
|
||||||
|
<input type="file" className="upload" multiple onChange={this.upload} ref={inputFile => this.inputFile = inputFile} />
|
||||||
|
<button type="button" className="btn btn-light" id="upload" onClick={() => this.inputFile.click()}>{window.GLOBAL.App.Constants.FM_UPLOAD}</button>
|
||||||
|
<button type="button" className="btn btn-light big" onClick={this.newFile}>{window.GLOBAL.App.Constants.FM_NEW_FILE}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.newFile} title={window.GLOBAL.App.Constants.FM_NEW_FILE}><FontAwesomeIcon icon="file" className="icon file" /></button>
|
||||||
|
<button type="button" className="btn btn-light big" onClick={this.newDirectory}>{window.GLOBAL.App.Constants.FM_NEW_DIR}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.newDirectory} title={window.GLOBAL.App.Constants.FM_NEW_DIR}><FontAwesomeIcon icon="folder" className="icon folder-close" /></button>
|
||||||
|
<button type="button" className="btn btn-light big" onClick={this.download}>{window.GLOBAL.App.Constants.FM_DOWNLOAD}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.download} title={window.GLOBAL.App.Constants.FM_DOWNLOAD}><FontAwesomeIcon icon="download" className="icon download" /></button>
|
||||||
|
<button type="button" className="btn btn-light big" onClick={this.rename}>{window.GLOBAL.App.Constants.FM_RENAME_BUTTON}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.rename} title={window.GLOBAL.App.Constants.FM_RENAME_BUTTON}><FontAwesomeIcon icon="italic" className="icon italic" /></button>
|
||||||
|
<button type="button" className="btn btn-light big" onClick={this.permissions}>{window.GLOBAL.App.Constants.FM_RIGHTS}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.permissions} title={window.GLOBAL.App.Constants.FM_RIGHTS}><FontAwesomeIcon icon="user" className="icon user" /></button>
|
||||||
|
<button type="button" className="btn btn-light big" onClick={this.copy}>{window.GLOBAL.App.Constants.FM_COPY_BUTTON}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.copy} title={window.GLOBAL.App.Constants.FM_COPY_BUTTON}><FontAwesomeIcon icon="copy" className="icon copy" /></button>
|
||||||
|
<button type="button" className="btn btn-light big" onClick={this.move}>{window.GLOBAL.App.Constants.FM_MOVE_BUTTON}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.move} title={window.GLOBAL.App.Constants.FM_MOVE_BUTTON}><FontAwesomeIcon icon="paste" className="icon paste" /></button>
|
||||||
|
{matchArchive ? null : <button type="button" className="btn btn-light big" onClick={this.archive}>{window.GLOBAL.App.Constants.FM_ARCHIVE}</button>}
|
||||||
|
{matchArchive ? null : <button type="button" className="btn btn-light small" onClick={this.archive} title={window.GLOBAL.App.Constants.FM_ARCHIVE}><FontAwesomeIcon icon="book" className="icon book" /></button>}
|
||||||
|
{matchArchive ? <button type="button" className="btn btn-light big" onClick={this.extract}>{window.GLOBAL.App.Constants.FM_EXTRACT}</button> : null}
|
||||||
|
{matchArchive ? <button type="button" className="btn btn-light small" onClick={this.extract} title={window.GLOBAL.App.Constants.FM_EXTRACT}><FontAwesomeIcon icon="box-open" className="icon open" /></button> : null}
|
||||||
|
<button type="button" className="btn btn-light big delete" onClick={this.delete} >{window.GLOBAL.App.Constants.FM_DELETE_BUTTON}</button>
|
||||||
|
<button type="button" className="btn btn-light small" onClick={this.delete} title={window.GLOBAL.App.Constants.FM_DELETE_BUTTON}><FontAwesomeIcon icon="trash" className="icon trash" /></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Menu;
|
113
src/react/FileManagerComponent/src/components/Menu/Menu.scss
Normal file
113
src/react/FileManagerComponent/src/components/Menu/Menu.scss
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
.menu {
|
||||||
|
display: flex;
|
||||||
|
background: #f8f9fa;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
padding-left: 5px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 17px;
|
||||||
|
border-radius: 0;
|
||||||
|
background: #F8F9FA;
|
||||||
|
|
||||||
|
.small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#upload {
|
||||||
|
line-height: 0;
|
||||||
|
margin-top: 7px;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 7px;
|
||||||
|
margin-left: 2px;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #AACC0D;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
background: rgb(184, 221, 17);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 2px;
|
||||||
|
padding-top: 11px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: 25px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgb(1, 148, 148);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete:hover{
|
||||||
|
color: rgb(221, 57, 57);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px){
|
||||||
|
.menu .btn-group {
|
||||||
|
.small {
|
||||||
|
font-size: 18px;
|
||||||
|
padding-top: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
&:hover {
|
||||||
|
.file, .folder-close, .download, .copy {
|
||||||
|
color: #aacc0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.italic, .user, .paste {
|
||||||
|
color: #44a8b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trash {
|
||||||
|
color: rgb(221, 57, 57);
|
||||||
|
}
|
||||||
|
|
||||||
|
.book {
|
||||||
|
color: rgb(216, 186, 77);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.big {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const AddDirectory = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3 className="modal-title directory" >{window.GLOBAL.App.Constants.FM_CREATE_DIRECTORY}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<input type="text" ref={props.reference} autoFocus></input>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save}>{window.GLOBAL.App.Constants.FM_CREATE}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddDirectory;
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const AddFile = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3 className="modal-title" >{window.GLOBAL.App.Constants.FM_CREATE_FILE}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<input type="text" ref={props.reference}></input>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save}>{window.GLOBAL.App.Constants.FM_CREATE}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddFile;
|
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Archive = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
{props.items > 0 ?
|
||||||
|
<h3 className="modal-title">{window.GLOBAL.App.Constants.FM_PACK} <span className="quot">({props.items})</span>?</h3> :
|
||||||
|
<h3 className="modal-title rename">{window.GLOBAL.App.Constants.FM_PACK} <span className="quot">"{props.fName}"</span>?</h3>}
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<input type="text" autoFocus defaultValue={`${props.path}/${props.fName}.tar.gz`} onBlur={props.onChange} ref={props.reference}></input>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save}>{window.GLOBAL.App.Constants.FM_PACK_BUTTON}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Archive;
|
22
src/react/FileManagerComponent/src/components/Modal/Copy.jsx
Normal file
22
src/react/FileManagerComponent/src/components/Modal/Copy.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Copy = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
{props.items > 0 ?
|
||||||
|
<h3 className="modal-title">{window.GLOBAL.App.Constants.FM_COPY_BULK} <span className="quot">({props.items})</span> {window.GLOBAL.App.Constants.FM_INTO_KEYWORD}:</h3> :
|
||||||
|
<h3 className="modal-title rename">{window.GLOBAL.App.Constants.FM_COPY} <span className="quot">"{props.fName}"</span> {window.GLOBAL.App.Constants.FM_INTO_KEYWORD}:</h3>}
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<input type="text" autoFocus defaultValue={props.path} onChange={props.onChange} ref={props.reference}></input>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save}>{window.GLOBAL.App.Constants.FM_COPY}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Copy;
|
|
@ -0,0 +1,19 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Delete = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content delete">
|
||||||
|
<div className="modal-header">
|
||||||
|
{props.items > 0 ?
|
||||||
|
<h3>{window.GLOBAL.App.Constants.FM_CONFIRM_DELETE_BULK} <span className="quot">({props.items})</span> ?</h3> :
|
||||||
|
<h3>{window.GLOBAL.App.Constants.FM_CONFIRM_DELETE} <span className="quot">"{props.fName}"</span>?</h3>}
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer lower">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save} autoFocus>{window.GLOBAL.App.Constants.FM_DELETE}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Delete;
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Extract = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3 className="modal-title rename">{window.GLOBAL.App.Constants.FM_EXTRACT} <span className="quot">"{props.fName}"</span>{window.GLOBAL.App.Constants.FM_INTO_KEYWORD}</h3>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<input type="text" autoFocus defaultValue={props.path} onBlur={props.onChange} ref={props.reference}></input>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save}>{window.GLOBAL.App.Constants.FM_EXTRACT}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Extract;
|
|
@ -0,0 +1,94 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import AddFile from './AddFile';
|
||||||
|
import AddDirectory from './AddDirectory';
|
||||||
|
import Rename from './Rename';
|
||||||
|
import Delete from './Delete';
|
||||||
|
import NothingSelected from './NothingSelected';
|
||||||
|
import Permissions from './Permissions';
|
||||||
|
import Move from './Move';
|
||||||
|
import Archive from './Archive';
|
||||||
|
import Extract from './Extract';
|
||||||
|
import Copy from './Copy';
|
||||||
|
import './Modal.scss';
|
||||||
|
import Replace from './Replace';
|
||||||
|
|
||||||
|
class Modal extends Component {
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
window.addEventListener("click", this.closeOutside);
|
||||||
|
document.addEventListener("keydown", this.hotkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
window.removeEventListener("click", this.closeOutside);
|
||||||
|
document.removeEventListener("keydown", this.hotkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
hotkeys = (e) => {
|
||||||
|
if (e.keyCode === 27) {
|
||||||
|
this.closeModal();
|
||||||
|
} else if (e.keyCode === 13) {
|
||||||
|
this.saveAndClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAndClose = () => {
|
||||||
|
this.props.onClick();
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
changePermissions = (permissions) => {
|
||||||
|
this.props.onChangePermissions(permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
replace = (file) => {
|
||||||
|
this.props.onClick(file);
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (e) => {
|
||||||
|
this.props.onChangeValue(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal = () => {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
closeOutside = (e) => {
|
||||||
|
let modal = document.getElementById("modal");
|
||||||
|
if (e.target === modal) {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content = () => {
|
||||||
|
const { type, reference, fName, permissions, items, path, files, notAvailable } = this.props;
|
||||||
|
switch (type) {
|
||||||
|
case 'Copy': return <Copy close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} items={items} path={path} />;
|
||||||
|
case 'Move': return <Move close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} items={items} path={path} />;
|
||||||
|
case 'Permissions': return <Permissions close={this.closeModal} save={this.saveAndClose} changePermissions={this.changePermissions} fName={fName} permissions={permissions} />;
|
||||||
|
case 'Extract': return <Extract close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} path={path} />;
|
||||||
|
case 'Archive': return <Archive close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} items={items} name={type} fName={fName} path={path} />;
|
||||||
|
case 'Rename': return <Rename close={this.closeModal} save={this.saveAndClose} reference={reference} onChange={this.onChange} name={type} fName={fName} />;
|
||||||
|
case 'Add directory': return <AddDirectory close={this.closeModal} save={this.saveAndClose} reference={reference} />;
|
||||||
|
case 'Delete': return <Delete close={this.closeModal} save={this.saveAndClose} fName={fName} items={items} />;
|
||||||
|
case 'Add file': return <AddFile close={this.closeModal} save={this.saveAndClose} reference={reference} />;
|
||||||
|
case 'Replace': return <Replace close={this.closeModal} replace={(files) => this.replace(files)} files={files} />
|
||||||
|
case 'Nothing selected': return <NothingSelected close={this.closeModal} notAvailable={notAvailable} />;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="modal" id="modal">
|
||||||
|
{this.content()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Modal;
|
184
src/react/FileManagerComponent/src/components/Modal/Modal.scss
Normal file
184
src/react/FileManagerComponent/src/components/Modal/Modal.scss
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
$black: #000;
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
padding-top: 200px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: $black;
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
box-shadow: 0 2px 11px 0 rgba(0, 0, 0, 0.5);
|
||||||
|
background: #333;
|
||||||
|
margin: auto;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 25%;
|
||||||
|
height: auto;
|
||||||
|
color: white;
|
||||||
|
margin-top: 100px;
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: #333;
|
||||||
|
border: 1px solid #111;
|
||||||
|
color: white;
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 85%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 0;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #EBE697;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replace {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quot {
|
||||||
|
color: rgb(182, 120, 28);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
border-top: 1px solid #555;
|
||||||
|
|
||||||
|
button {
|
||||||
|
color: white;
|
||||||
|
border-color: none;
|
||||||
|
background: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.btn-danger {
|
||||||
|
border-color: transparent;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button.btn-danger:hover {
|
||||||
|
background: #00cccb;
|
||||||
|
border-color: #00cccb;
|
||||||
|
}
|
||||||
|
|
||||||
|
button + button {
|
||||||
|
padding: 6px 30px;
|
||||||
|
background: #CACE33;
|
||||||
|
border-color: #CACE33;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #00cccb;
|
||||||
|
border-color: #00cccb;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header .quot {
|
||||||
|
color: rgb(182, 120, 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nothing-selected {
|
||||||
|
h3 {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: #d9534f;
|
||||||
|
border-color: #d9534f;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete .modal-header {
|
||||||
|
border-bottom: none;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions {
|
||||||
|
height: auto;
|
||||||
|
width: 30%;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
.error, &:focus {
|
||||||
|
border: 1px solid red;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
size: 40px;
|
||||||
|
width: 60px;
|
||||||
|
margin: auto auto 10px auto;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h3 {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
margin-left: 40px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/react/FileManagerComponent/src/components/Modal/Move.jsx
Normal file
22
src/react/FileManagerComponent/src/components/Modal/Move.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Move = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content">
|
||||||
|
<div className="modal-header">
|
||||||
|
{props.items > 0 ?
|
||||||
|
<h3 className="modal-title">{window.GLOBAL.App.Constants.FM_MOVE_BULK} <span className="quot">({props.items})</span> {window.GLOBAL.App.Constants.FM_INTO_KEYWORD}:</h3> :
|
||||||
|
<h3 className="modal-title rename">{window.GLOBAL.App.Constants.FM_MOVE} <span className="quot">"{props.fName}"</span> {window.GLOBAL.App.Constants.FM_INTO_KEYWORD}:</h3>}
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<input type="text" autoFocus defaultValue={props.path} onChange={props.onChange} ref={props.reference}></input>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save}>{window.GLOBAL.App.Constants.FM_MOVE}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Move;
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const NothingSelected = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content nothing-selected">
|
||||||
|
<div className="header">
|
||||||
|
{props.notAvailable ? <h3>{window.GLOBAL.App.Constants.FM_DirDownloadNotAvailable}</h3> : <h3>{window.GLOBAL.App.Constants.FM_NO_FILE_SELECTED}</h3>}
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CLOSE}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NothingSelected;
|
|
@ -0,0 +1,143 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import classNames from 'classname';
|
||||||
|
|
||||||
|
const defaultPermissions = {
|
||||||
|
owner: {
|
||||||
|
read: 0,
|
||||||
|
write: 0,
|
||||||
|
execute: 0,
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
read: 0,
|
||||||
|
write: 0,
|
||||||
|
execute: 0,
|
||||||
|
},
|
||||||
|
others: {
|
||||||
|
read: 0,
|
||||||
|
write: 0,
|
||||||
|
execute: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
class Permissions extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
permissions: this.decode(this.props.permissions) || defaultPermissions,
|
||||||
|
inputInvalid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inArray(number, array) {
|
||||||
|
return !!~array.indexOf(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
decodeSingleNumber = (string) => {
|
||||||
|
const number = parseInt(string, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
read: this.inArray(number, [4, 5, 6, 7]) ? 4 : 0,
|
||||||
|
write: this.inArray(number, [2, 3, 6, 7]) ? 2 : 0,
|
||||||
|
execute: this.inArray(number, [1, 3, 5, 7]) ? 1 : 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid(numbers = '') {
|
||||||
|
if (numbers.length !== 3 || numbers === '000' || numbers.match(/[A-Za-z]/)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return numbers.split('').find((number) => parseInt(number, 0) < 0 || parseInt(number, 0) > 7) === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
decode(numbers) {
|
||||||
|
if (!this.isValid(numbers)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numbersArray = numbers.split('');
|
||||||
|
const result = numbersArray.map(this.decodeSingleNumber);
|
||||||
|
return { owner: result[0], group: result[1], others: result[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
encode() {
|
||||||
|
function sumPermissions(permissionObject) {
|
||||||
|
return Object.values(permissionObject).map((number) => parseInt(number, 0)).reduce((acc, n) => acc + n, 0);
|
||||||
|
}
|
||||||
|
return ['owner', 'group', 'others'].reduce((acc, role) => {
|
||||||
|
const roleObject = this.state.permissions[role];
|
||||||
|
return acc + sumPermissions(roleObject);
|
||||||
|
}, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeForm = (event) => {
|
||||||
|
const checkbox = event.target;
|
||||||
|
const [role, permissionName] = checkbox.name.split('_');
|
||||||
|
this.setState({
|
||||||
|
permissions: {
|
||||||
|
...this.state.permissions,
|
||||||
|
[role]: {
|
||||||
|
...this.state.permissions[role],
|
||||||
|
[permissionName]: checkbox.checked ? checkbox.value : 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, (state) => {
|
||||||
|
this.inputRef.value = this.encode();
|
||||||
|
this.props.changePermissions(this.inputRef.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange = (event) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
if (!this.isValid(value)) {
|
||||||
|
return this.setState({ inputInvalid: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
permissions: this.decode(value),
|
||||||
|
inputInvalid: false,
|
||||||
|
});
|
||||||
|
this.props.changePermissions(this.inputRef.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { inputInvalid } = this.state;
|
||||||
|
const { close, save, fName } = this.props;
|
||||||
|
const inputClasses = classNames({
|
||||||
|
'form-control total': true,
|
||||||
|
'error': inputInvalid,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-content permissions">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3 className="modal-title perms">{window.GLOBAL.App.Constants.FM_CHMOD} <span className="quot">"{fName}"</span></h3>
|
||||||
|
</div>
|
||||||
|
<form name="form" onChange={this.onChangeForm}>
|
||||||
|
<div>
|
||||||
|
<label><input type="checkbox" name={'owner_read'} value="4" checked={!!this.state.permissions["owner"].read} id="read" />{window.GLOBAL.App.Constants.FM_READ_BY_OWNER}</label>
|
||||||
|
<label><input type="checkbox" name={'owner_write'} value="2" checked={!!this.state.permissions["owner"].write} />{window.GLOBAL.App.Constants.FM_WRITE_BY_OWNER}</label>
|
||||||
|
<label><input type="checkbox" name={'owner_execute'} value="1" checked={!!this.state.permissions["owner"].execute} />{window.GLOBAL.App.Constants.FM_EXECUTE_BY_OWNER}</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label><input type="checkbox" name={'group_read'} value="4" checked={!!this.state.permissions["group"].read} id="read" />{window.GLOBAL.App.Constants.FM_READ_BY_GROUP}</label>
|
||||||
|
<label><input type="checkbox" name={'group_write'} value="2" checked={!!this.state.permissions["group"].write} />{window.GLOBAL.App.Constants.FM_WRITE_BY_GROUP}</label>
|
||||||
|
<label><input type="checkbox" name={'group_execute'} value="1" checked={!!this.state.permissions["group"].execute} />{window.GLOBAL.App.Constants.FM_EXECUTE_BY_GROUP}</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label><input type="checkbox" name={'others_read'} value="4" checked={!!this.state.permissions["others"].read} id="read" />{window.GLOBAL.App.Constants.FM_READ_BY_OTHERS}</label>
|
||||||
|
<label><input type="checkbox" name={'others_write'} value="2" checked={!!this.state.permissions["others"].write} />{window.GLOBAL.App.Constants.FM_WRITE_BY_OTHERS}</label>
|
||||||
|
<label><input type="checkbox" name={'others_execute'} value="1" checked={!!this.state.permissions["others"].execute} />{window.GLOBAL.App.Constants.FM_EXECUTE_BY_OTHERS}</label>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<input type="text" className={inputClasses} defaultValue={this.encode()} ref={(ref) => this.inputRef = ref} onChange={this.handleInputChange} maxLength="3" />
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={save} disabled={inputInvalid}>{window.GLOBAL.App.Constants.FM_OK}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Permissions;
|
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Rename = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content rename">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h3 className="modal-title rename">{window.GLOBAL.App.Constants.FM_RENAME} <span className="quot">"{props.fName}"</span></h3>
|
||||||
|
</div>
|
||||||
|
<div className="modal-body">
|
||||||
|
<input type="text" autoFocus defaultValue={props.fName} onChange={props.onChange} ref={props.reference}></input>
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-danger mr-auto" onClick={props.close}>{window.GLOBAL.App.Constants.FM_CANCEL}</button>
|
||||||
|
<button type="button" className="btn btn-primary" onClick={props.save}>{window.GLOBAL.App.Constants.FM_RENAME}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Rename;
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const Replace = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="modal-content replace">
|
||||||
|
<div className="modal-header">
|
||||||
|
{props.files.length > 1 ?
|
||||||
|
<div><h3>These files already exist</h3>
|
||||||
|
{props.files.map(item =>
|
||||||
|
<span className="quot">"{item.name}" </span>
|
||||||
|
)}
|
||||||
|
</div> :
|
||||||
|
<div><h3>This file already exists</h3>
|
||||||
|
<span className="quot">"{props.files[0].name}"</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button type="button" className="btn btn-primary mr-auto" onClick={props.close}>Cancel</button>
|
||||||
|
<button type="button" className="btn btn-danger" onClick={() => props.replace(props.files)}>Overwrite</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Replace;
|
|
@ -0,0 +1,70 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './Dropdown.scss';
|
||||||
|
|
||||||
|
function changeSorting(field, order, props) {
|
||||||
|
if (!props.isActive) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
props.changeSorting(field, order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort(sorting) {
|
||||||
|
if (sorting === "Type") {
|
||||||
|
return window.GLOBAL.App.Constants.FM_type;
|
||||||
|
} else if (sorting === "Size") {
|
||||||
|
return window.GLOBAL.App.Constants.FM_size;
|
||||||
|
} else if (sorting === "Date") {
|
||||||
|
return window.GLOBAL.App.Constants.FM_date;
|
||||||
|
} else if (sorting === "Name") {
|
||||||
|
return window.GLOBAL.App.Constants.FM_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function button(sorting, order) {
|
||||||
|
if (order === "descending") {
|
||||||
|
return (
|
||||||
|
<button type="button" className="btn btn-secondary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
{sort(sorting)}
|
||||||
|
<span className="arrow-down">↓</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<button type="button" className="btn btn-secondary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
{sort(sorting)}
|
||||||
|
<span>↑</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dropdown = (props) => {
|
||||||
|
return (
|
||||||
|
<div class="btn-group">
|
||||||
|
{button(props.sorting, props.order)}
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<ul className="dropdown-list">
|
||||||
|
<li>
|
||||||
|
<span className={props.sorting === "Type" && props.order === "descending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Type", "descending", props)}>{window.GLOBAL.App.Constants.FM_type}<span className="arrow-down">↓</span></span>
|
||||||
|
<span className={props.sorting === "Type" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Type", "ascending", props)}><span>↑</span></span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={props.sorting === "Size" && props.order === "descending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Size", "descending", props)}>{window.GLOBAL.App.Constants.FM_size}<span className="arrow-down">↓</span></span>
|
||||||
|
<span className={props.sorting === "Size" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Size", "ascending", props)}><span>↑</span></span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={props.sorting === "Date" && props.order === "descending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Date", "descending", props)}>{window.GLOBAL.App.Constants.FM_date}<span className="arrow-down">↓</span></span>
|
||||||
|
<span className={props.sorting === "Date" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Date", "ascending", props)}><span>↑</span></span>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span className={props.sorting === "Name" && props.order === "descending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Name", "descending", props)}>{window.GLOBAL.App.Constants.FM_name}<span className="arrow-down">↓</span></span>
|
||||||
|
<span className={props.sorting === "Name" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Name", "ascending", props)}><span>↑</span></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dropdown;
|
|
@ -0,0 +1,52 @@
|
||||||
|
.btn-group button {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: inline-block;
|
||||||
|
list-style: none;
|
||||||
|
height: 150px;
|
||||||
|
width: 100%;
|
||||||
|
color: #333;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 37.5px;
|
||||||
|
border-top: 1px solid #dbdbdb;
|
||||||
|
|
||||||
|
.arrow-down {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
&:hover {
|
||||||
|
background: #bdbdbd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding: 0 5px 0 10px;
|
||||||
|
line-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.active{
|
||||||
|
background: #ffc900;
|
||||||
|
}
|
||||||
|
|
||||||
|
span + span {
|
||||||
|
text-align: center;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu.show{
|
||||||
|
transform: translate3d(-57%, 39px, 0px)!important;
|
||||||
|
}
|
45
src/react/FileManagerComponent/src/components/Path/Path.jsx
Normal file
45
src/react/FileManagerComponent/src/components/Path/Path.jsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './Path.scss';
|
||||||
|
import Dropdown from './Dropdown/Dropdown';
|
||||||
|
|
||||||
|
function clickablePath(props) {
|
||||||
|
let path = props.path;
|
||||||
|
let splitPath = props.path.split('/');
|
||||||
|
splitPath.splice(0, 3);
|
||||||
|
|
||||||
|
if (path !== window.GLOBAL.ROOT_DIR) {
|
||||||
|
return (
|
||||||
|
splitPath.map((item, index) => <span className="clickable" key={index} onClick={() => openDirectory(props, index)}> / {item}</span>)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDirectory(props, index) {
|
||||||
|
let pathArray = props.path.split('/');
|
||||||
|
|
||||||
|
if (!props.isActive) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (index !== undefined) {
|
||||||
|
let newPathArray = pathArray.splice(0, index + 4);
|
||||||
|
let newPath = newPathArray.join('/');
|
||||||
|
props.openDirectory(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Path = (props) => {
|
||||||
|
return (
|
||||||
|
<div className={props.class}>
|
||||||
|
<div className="clickable-wrapper">
|
||||||
|
<span className="clickable-path">
|
||||||
|
<span className="clickable" onClick={() => props.openDirectory(window.GLOBAL.ROOT_DIR)}>{window.GLOBAL.ROOT_DIR}</span>
|
||||||
|
{clickablePath(props)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Dropdown changeSorting={props.changeSorting} sorting={props.sorting} order={props.order} isActive={props.isActive} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Path;
|
151
src/react/FileManagerComponent/src/components/Path/Path.scss
Normal file
151
src/react/FileManagerComponent/src/components/Path/Path.scss
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
$white: #fff;
|
||||||
|
|
||||||
|
.path {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 1px 0 0 5px;
|
||||||
|
height: 40px;
|
||||||
|
background: #7b7b7b;
|
||||||
|
color: $white;
|
||||||
|
box-shadow: 0 2px 10px -4px gray;
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
width: 75px;
|
||||||
|
margin-left: 15px;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
background: #7b7b7b;
|
||||||
|
|
||||||
|
.btn-secondary:not(:disabled):not(.disabled).active, .btn-secondary:not(:disabled):not(.disabled):active, .show>.btn-secondary.dropdown-toggle {
|
||||||
|
border-color: #7b7b7b;
|
||||||
|
background-color: #7b7b7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 5px;
|
||||||
|
color: white;
|
||||||
|
background: #7b7b7b;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 0 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active, &:focus {
|
||||||
|
background-color: #7b7b7b;
|
||||||
|
border-color: #7b7b7b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: #7b7b7b;
|
||||||
|
border-color: #7b7b7b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
&:hover {
|
||||||
|
color: rgb(1, 148, 148);
|
||||||
|
background: #7b7b7b;
|
||||||
|
border-color: #7b7b7b;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: rgb(1, 148, 148);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
padding-top: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: goldenrod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-path {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 1px 0 0 5px;
|
||||||
|
height: 40px;
|
||||||
|
background: #333;
|
||||||
|
color: $white;
|
||||||
|
box-shadow: 0 2px 6px -2px gray;
|
||||||
|
|
||||||
|
.clickable-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-group {
|
||||||
|
width: 75px;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
background: #333;
|
||||||
|
|
||||||
|
.btn-secondary:not(:disabled):not(.disabled).active, .btn-secondary:not(:disabled):not(.disabled):active, .show>.btn-secondary.dropdown-toggle {
|
||||||
|
border-color: #333;
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 5px;
|
||||||
|
color: white;
|
||||||
|
background: #333;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #fff;
|
||||||
|
padding: 0 0 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active, &:focus {
|
||||||
|
background-color: #333;
|
||||||
|
border-color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.active.focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn:active:focus, .btn:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: #333;
|
||||||
|
border-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
&:hover {
|
||||||
|
color: rgb(1, 148, 148);
|
||||||
|
background: #333;
|
||||||
|
border-color: #333;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: rgb(1, 148, 148);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable {
|
||||||
|
padding-top: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: goldenrod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickable-path {
|
||||||
|
display: flex;
|
||||||
|
float: 0 0 auto;
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import CodeMirror from 'react-codemirror';
|
||||||
|
import './Editor.scss';
|
||||||
|
import 'codemirror/lib/codemirror.css';
|
||||||
|
import 'codemirror/mode/javascript/javascript';
|
||||||
|
import 'codemirror/mode/xml/xml';
|
||||||
|
import 'codemirror/mode/markdown/markdown';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import axios from 'axios';
|
||||||
|
import Spinner from '../../Spinner/Spinner';
|
||||||
|
import { toast, ToastContainer } from 'react-toastify';
|
||||||
|
|
||||||
|
class Editor extends Component {
|
||||||
|
state = {
|
||||||
|
code: '',
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
|
||||||
|
encodePath = (path) => {
|
||||||
|
let splitPath = path.split('/');
|
||||||
|
let encodedPath = splitPath.join('%2F');
|
||||||
|
return encodedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount = () => {
|
||||||
|
document.addEventListener("keydown", this.hotKey);
|
||||||
|
|
||||||
|
const { history } = this.props;
|
||||||
|
let path = history.location.search.substring(6, history.location.search.lastIndexOf('/'));
|
||||||
|
this.setState({ loading: true }, () => {
|
||||||
|
axios.get(`${window.location.origin}/file_manager/fm_api.php?dir=${this.encodePath(path)}&item=${this.props.name}&action=open_fs_file`)
|
||||||
|
.then(result => {
|
||||||
|
if (result.data.content) {
|
||||||
|
this.setState({ code: result.data.content, loading: false });
|
||||||
|
} else {
|
||||||
|
toast.warning('This file is empty!', {
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 3000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
document.removeEventListener("keydown", this.hotKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
hotKey = (e) => {
|
||||||
|
if (e.keyCode === 113) {
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
save = () => {
|
||||||
|
let formData = new FormData();
|
||||||
|
let path = this.props.history.location.search.substring(6, this.props.history.location.search.lastIndexOf('/'));
|
||||||
|
|
||||||
|
formData.append('save', 'Save');
|
||||||
|
formData.append('contents', this.state.code);
|
||||||
|
|
||||||
|
this.setState({ loading: true }, () => {
|
||||||
|
axios.post(`${window.location.origin}/edit/file/?path=${path}%2F${this.props.name}`, formData)
|
||||||
|
.then(toast.success('Saved successfully!', {
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 3000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true
|
||||||
|
}), this.setState({ loading: false })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCode = (newCode) => {
|
||||||
|
this.setState({
|
||||||
|
code: newCode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let options = {
|
||||||
|
mode: 'javascript',
|
||||||
|
lineNumbers: true
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="editor">
|
||||||
|
<ToastContainer />
|
||||||
|
<div className="panel-editor">
|
||||||
|
<button type="button" className="btn btn-primary" onClick={this.save}>Save</button>
|
||||||
|
<button type="button" className="btn btn-danger" onClick={this.props.close}>Close</button>
|
||||||
|
</div>
|
||||||
|
{this.state.loading ? <Spinner /> : <CodeMirror value={this.state.code} onChange={this.updateCode} options={options} autoFocus />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(Editor);
|
|
@ -0,0 +1,64 @@
|
||||||
|
.editor {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.panel-editor {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
position: absolute;
|
||||||
|
padding: 3px;
|
||||||
|
margin: 0;
|
||||||
|
background: #000000bf;
|
||||||
|
z-index: 4;
|
||||||
|
transition: 0.5s all ease-in-out;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-size: 17px;
|
||||||
|
height: 98%;
|
||||||
|
margin: 0 0 0 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
z-index: 4;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
font-size: 40px;
|
||||||
|
opacity: 1;
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: rgb(139, 139, 139);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
height: 100vh;
|
||||||
|
padding-top: 4vh;
|
||||||
|
|
||||||
|
.CodeMirror-selected {
|
||||||
|
background:#ACCEF7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.save {
|
||||||
|
font-size: 20px;
|
||||||
|
left: 95%;
|
||||||
|
bottom: 10px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import classNames from 'classname';
|
||||||
|
import * as FM from '../../../FileManagerHelper';
|
||||||
|
import './Photo.scss';
|
||||||
|
import Spinner from '../../Spinner/Spinner';
|
||||||
|
|
||||||
|
class Photo extends Component {
|
||||||
|
state = {
|
||||||
|
activeSlide: 0,
|
||||||
|
photoGallery: [],
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
|
||||||
|
imgClass = (item) => {
|
||||||
|
if (item.match(/.gif/i)) {
|
||||||
|
return "gif";
|
||||||
|
} else {
|
||||||
|
return "img";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
encodePath = (path) => {
|
||||||
|
let splitPath = path.split('/');
|
||||||
|
splitPath.splice(splitPath.length - 1, 1);
|
||||||
|
splitPath.splice(0, 1);
|
||||||
|
return splitPath.join('%2F');
|
||||||
|
}
|
||||||
|
|
||||||
|
formatPath = (path) => {
|
||||||
|
let splitPath = path.split('/');
|
||||||
|
splitPath.splice(splitPath.length - 1, 1);
|
||||||
|
return splitPath.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
carouselIndicators = () => {
|
||||||
|
const gallery = this.state.photoGallery;
|
||||||
|
return gallery.map((item, i) => {
|
||||||
|
const imageClasses = classNames({ 'control-photo': true, 'active': i === this.state.activeSlide });
|
||||||
|
const result = (<div data-target="#photoGallery" data-slide-to={i} key={i} className="indicator">
|
||||||
|
<img src={`${window.location.origin}/view/file/${this.formatPath(this.props.path)}/${item}&raw=true`} alt={i} className={imageClasses} />
|
||||||
|
</div>);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
carouselPhotos = () => {
|
||||||
|
const gallery = this.state.photoGallery || [];
|
||||||
|
return gallery.map((item, i) => (
|
||||||
|
<div className={i === this.state.activeSlide ? 'carousel-item active' : 'carousel-item'} key={i}>
|
||||||
|
<div className="d-flex align-items-center justify-content-center min-vh-100">
|
||||||
|
<img className={this.imgClass(item)} src={`${window.location.origin}/view/file/${this.formatPath(this.props.path)}/${item}&raw=true`} alt={i} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
setStateAsync = updater => new Promise(resolve => this.setState(updater, resolve));
|
||||||
|
|
||||||
|
setPhotoGallery = async () => {
|
||||||
|
await this.setStateAsync({ loading: true });
|
||||||
|
const result = await FM.getData(this.encodePath(this.props.path));
|
||||||
|
let photoGallery = [...this.state.photoGallery];
|
||||||
|
result.data.listing.filter(item => item.name.match(/.png|.jpg|.jpeg|.gif/g) && !item.name.match(/.zip|.tgz|.tar.gz|.gzip|.tbz|.tar.bz|.gz|.zip|.tar|.rar/g) ? photoGallery.push(item.name) : null)
|
||||||
|
await this.setStateAsync({ photoGallery, loading: false })
|
||||||
|
this.setActiveImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveImage = () => {
|
||||||
|
let activeImage = this.props.activeImage;
|
||||||
|
let activeImageIndex = this.state.photoGallery.indexOf(activeImage);
|
||||||
|
this.setState({ activeSlide: activeImageIndex });
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setPhotoGallery();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.state.loading ? <Spinner /> :
|
||||||
|
<div id="photoGallery" className="carousel slide" data-ride="carousel">
|
||||||
|
<span className="close" onClick={this.props.close}>×</span>
|
||||||
|
<div className="carousel-inner">
|
||||||
|
{this.carouselPhotos()}
|
||||||
|
</div>
|
||||||
|
<div className="carousel-indicators">
|
||||||
|
{this.carouselIndicators()}
|
||||||
|
</div>
|
||||||
|
<a className="carousel-control-prev" href="#photoGallery" role="button" data-slide="prev">
|
||||||
|
<span className="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||||
|
<span className="sr-only">Previous</span>
|
||||||
|
</a>
|
||||||
|
<a className="carousel-control-next" href="#photoGallery" role="button" data-slide="next">
|
||||||
|
<span className="carousel-control-next-icon" aria-hidden="true"></span>
|
||||||
|
<span className="sr-only">Next</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Photo;
|
|
@ -0,0 +1,66 @@
|
||||||
|
.carousel {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
a {
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-inner {
|
||||||
|
.carousel-item {
|
||||||
|
background: #000;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
.gif {
|
||||||
|
height: 50%;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
height: 90vh;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-indicators {
|
||||||
|
.indicator {
|
||||||
|
margin: 0 15px;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
|
||||||
|
.control-photo {
|
||||||
|
width: 100px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active > img {
|
||||||
|
border: 3px solid #3cc3f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border: 3px solid #3cc3f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.close {
|
||||||
|
z-index: 10;
|
||||||
|
font-size: 30px;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 10px;
|
||||||
|
opacity: 1;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import Editor from './Editor/Editor';
|
||||||
|
import Photo from './Photo/Photo';
|
||||||
|
import Video from './Video/Video';
|
||||||
|
|
||||||
|
class Preview extends Component {
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
document.addEventListener("keydown", this.hotkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
document.removeEventListener("keydown", this.hotkeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
hotkeys = (e) => {
|
||||||
|
if (e.keyCode === 121) {
|
||||||
|
this.props.onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content = () => {
|
||||||
|
const { location, onClose } = this.props;
|
||||||
|
let split = location.search.split('/');
|
||||||
|
let name = split[split.length - 1];
|
||||||
|
|
||||||
|
if (location.pathname !== '/list/directory/preview/') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.match('.mp4')) {
|
||||||
|
return <Video closeModal={onClose} />;
|
||||||
|
} else if (name.match(/png|jpg|jpeg|gif/g)) {
|
||||||
|
return <Photo closeModal={onClose} close={onClose} path={location.search} activeImage={name} />;
|
||||||
|
} else {
|
||||||
|
return <Editor close={onClose} name={name} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{this.content()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(Preview);
|
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
// import video from '../../../2.mp4';
|
||||||
|
import './Video.scss';
|
||||||
|
|
||||||
|
const Video = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="video-preview">
|
||||||
|
<span className="close" onClick={props.closeModal}>×</span>
|
||||||
|
<video className="video" autoPlay loop controls>
|
||||||
|
<source src="" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Video;
|
|
@ -0,0 +1,28 @@
|
||||||
|
.video-preview {
|
||||||
|
padding-bottom: 17px;
|
||||||
|
width: 100%;
|
||||||
|
background: #000;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 30px;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 2px;
|
||||||
|
opacity: 1;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './ProgressBar.scss';
|
||||||
|
|
||||||
|
const ProgressBar = (props) => {
|
||||||
|
return (
|
||||||
|
<div class="progress">
|
||||||
|
<div class="progress-bar" role="progressbar" style={{ width: `${props.progress}%` }} aria-valuenow={props.progress} aria-valuemin="0" aria-valuemax="100"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProgressBar;
|
|
@ -0,0 +1,13 @@
|
||||||
|
.spinner-wrapper .progress {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
z-index: 5;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
height: 6px;
|
||||||
|
display: inline-table;
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 10px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
import './Spinner.scss';
|
||||||
|
|
||||||
|
const Spinner = () => {
|
||||||
|
return (
|
||||||
|
<div className="spinner-wrapper">
|
||||||
|
<div className="progress">
|
||||||
|
<div className="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style={{ width: "100%" }}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Spinner;
|
|
@ -0,0 +1,7 @@
|
||||||
|
.spinner-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5px;
|
||||||
|
width: 15%;
|
||||||
|
left: 42%;
|
||||||
|
height: 10px;
|
||||||
|
}
|
35
src/react/FileManagerComponent/src/containers/App/App.js
Executable file
35
src/react/FileManagerComponent/src/containers/App/App.js
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import FileManager from '../FileManager/FileManager';
|
||||||
|
import { BrowserRouter as Router, Route } from "react-router-dom";
|
||||||
|
import Preview from '../../components/Preview/Preview';
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
|
import { faBook, faFile, faDownload, faFileAlt, faImage, faFolderOpen, faEllipsisH, faFolder, faItalic, faUser, faCopy, faPaste, faTrash, faBoxOpen, faArrowDown, faArrowUp, faBell, faPlus, faAngleRight } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css';
|
||||||
|
import 'bootstrap/dist/js/bootstrap.min';
|
||||||
|
import './App.scss';
|
||||||
|
|
||||||
|
library.add(faBook, faDownload, faFile, faFileAlt, faFolderOpen, faImage, faEllipsisH, faFolder, faItalic, faUser, faCopy, faPaste, faTrash, faBoxOpen, faArrowDown, faArrowUp, faBell, faPlus, faAngleRight);
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
|
||||||
|
onClose = (history) => {
|
||||||
|
let lastOpenedDirectory = history.location.search.substring(6, history.location.search.lastIndexOf('/'));
|
||||||
|
history.push({
|
||||||
|
pathname: '/list/directory',
|
||||||
|
search: `?path=${lastOpenedDirectory}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<Router>
|
||||||
|
<Route path="/list/directory/preview" component={(props) => <Preview onClose={() => this.onClose(props.history)} />} />
|
||||||
|
<Route path="/list/directory" exact component={FileManager} />
|
||||||
|
</Router>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
35
src/react/FileManagerComponent/src/containers/App/App.scss
Normal file
35
src/react/FileManagerComponent/src/containers/App/App.scss
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
.App {
|
||||||
|
font-size: 25px;
|
||||||
|
font-family: Arial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
background: #ececec;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.lists-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
height: 10px;
|
||||||
|
transition: all ease-out 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Toastify__toast-body {
|
||||||
|
font-size: 20px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.Toastify__toast-container {
|
||||||
|
top: 15em;
|
||||||
|
}
|
9
src/react/FileManagerComponent/src/containers/App/App.test.js
Executable file
9
src/react/FileManagerComponent/src/containers/App/App.test.js
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
ReactDOM.render(<App />, div);
|
||||||
|
ReactDOM.unmountComponentAtNode(div);
|
||||||
|
});
|
135
src/react/FileManagerComponent/src/containers/App/serviceWorker.js
Executable file
135
src/react/FileManagerComponent/src/containers/App/serviceWorker.js
Executable file
|
@ -0,0 +1,135 @@
|
||||||
|
// This optional code is used to register a service worker.
|
||||||
|
// register() is not called by default.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on subsequent visits to a page, after all the
|
||||||
|
// existing tabs open on the page have been closed, since previously cached
|
||||||
|
// resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
|
// opt-in, read http://bit.ly/CRA-PWA
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export function register(config) {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (isLocalhost) {
|
||||||
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl, config);
|
||||||
|
|
||||||
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
|
// service worker/PWA documentation.
|
||||||
|
navigator.serviceWorker.ready.then(() => {
|
||||||
|
console.log(
|
||||||
|
'This web app is being served cache-first by a service ' +
|
||||||
|
'worker. To learn more, visit http://bit.ly/CRA-PWA'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is not localhost. Just register service worker
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl, config) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
if (installingWorker == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the updated precached content has been fetched,
|
||||||
|
// but the previous service worker will still serve the older
|
||||||
|
// content until all client tabs are closed.
|
||||||
|
console.log(
|
||||||
|
'New content is available and will be used when all ' +
|
||||||
|
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onUpdate) {
|
||||||
|
config.onUpdate(registration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onSuccess) {
|
||||||
|
config.onSuccess(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl, config) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl)
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,467 @@
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import DirectoryList from '../../components/Lists/DirectoryList/DirectoryList';
|
||||||
|
import ProgressBar from '../../components/ProgressBar/ProgressBar';
|
||||||
|
import HotkeysButton from '../../components/Hotkeys/HotkeysButton';
|
||||||
|
import { toast, ToastContainer } from 'react-toastify';
|
||||||
|
import Hotkeys from '../../components/Hotkeys/Hotkeys';
|
||||||
|
import Modal from '../../components/Modal/Modal';
|
||||||
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import Menu from '../../components/Menu/Menu';
|
||||||
|
import * as FM from '../../FileManagerHelper';
|
||||||
|
import '../App/App.scss';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const server = window.location.origin + "/file_manager/fm_api.php?";
|
||||||
|
|
||||||
|
class FileManager extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
leftList: {
|
||||||
|
path: window.GLOBAL.ROOT_DIR,
|
||||||
|
files: { listing: [] },
|
||||||
|
},
|
||||||
|
rightList: {
|
||||||
|
path: window.GLOBAL.ROOT_DIR,
|
||||||
|
files: { listing: [] },
|
||||||
|
},
|
||||||
|
currentPath: window.GLOBAL.ROOT_DIR,
|
||||||
|
currentUser: window.GLOBAL.ROOT_DIR,
|
||||||
|
activeWindow: "left",
|
||||||
|
modalWindow: null,
|
||||||
|
modalVisible: false,
|
||||||
|
cursor: 0,
|
||||||
|
itemName: "",
|
||||||
|
itemPermissions: "",
|
||||||
|
itemType: "",
|
||||||
|
itemsSelected: [],
|
||||||
|
modalInputValue: "",
|
||||||
|
uploadPercent: "0",
|
||||||
|
hotkeysPanel: "inactive",
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount = () => {
|
||||||
|
FM.cacheData(this.state.currentUser, this.props.history);
|
||||||
|
let currentPath = FM.activeWindowPath();
|
||||||
|
this.setState({ currentPath });
|
||||||
|
this.changeDirectoryOnLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount = () => {
|
||||||
|
window.addEventListener("keydown", this.switchActiveList);
|
||||||
|
window.addEventListener("keydown", this.toggleActiveListOnTab);
|
||||||
|
document.addEventListener("keydown", this.hotkeysListener);
|
||||||
|
|
||||||
|
if (localStorage.getItem('activeWindow')) {
|
||||||
|
this.setState({ activeWindow: localStorage.getItem('activeWindow') });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount = () => {
|
||||||
|
window.removeEventListener("keydown", this.switchActiveList);
|
||||||
|
window.removeEventListener("keydown", this.toggleActiveListOnTab);
|
||||||
|
document.removeEventListener("keydown", this.hotkeysListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
cachePaths = () => {
|
||||||
|
localStorage.setItem('activeWindow', this.state.activeWindow);
|
||||||
|
localStorage.setItem('leftListPath', this.state.leftList.path);
|
||||||
|
localStorage.setItem('rightListPath', this.state.rightList.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
setStateAsync = updater => new Promise(resolve => this.setState(updater, resolve));
|
||||||
|
|
||||||
|
changeDirectoryOnLoading = async () => {
|
||||||
|
['leftList', 'rightList'].map(async (side) => {
|
||||||
|
const result = await FM.changeDirectoryOnLoading(server, `${side}Path`);
|
||||||
|
let path = localStorage.getItem(`${side}Path`);
|
||||||
|
let listing = result.data.listing;
|
||||||
|
await this.setStateAsync({ [side]: { files: { listing }, path } });
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.setStateAsync({ loading: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
changeDirectory = () => {
|
||||||
|
const { activeWindow, currentPath } = this.state;
|
||||||
|
FM.changeDirectory(server, currentPath)
|
||||||
|
.then(result => {
|
||||||
|
let listing = result.data.listing;
|
||||||
|
|
||||||
|
if (this.state.leftList.path === this.state.rightList.path) {
|
||||||
|
this.setState({ leftList: { files: { listing }, path: currentPath }, rightList: { files: { listing }, path: currentPath }, loading: false });
|
||||||
|
this.leftList.resetData();
|
||||||
|
this.rightList.resetData();
|
||||||
|
} else if (activeWindow === "left") {
|
||||||
|
this.setState({ leftList: { files: { listing }, path: currentPath }, loading: false });
|
||||||
|
this.leftList.resetData();
|
||||||
|
} else {
|
||||||
|
this.setState({ rightList: { files: { listing }, path: currentPath }, loading: false });
|
||||||
|
this.rightList.resetData();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleActiveListOnTab = (e) => {
|
||||||
|
const { activeWindow, rightList, leftList, currentPath } = this.state;
|
||||||
|
|
||||||
|
if (this.state.modalVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 9) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (activeWindow === "left") {
|
||||||
|
this.setState({ activeWindow: "right", currentPath: rightList.path });
|
||||||
|
this.rightList.passData();
|
||||||
|
} else {
|
||||||
|
this.setState({ activeWindow: "left", currentPath: leftList.path });
|
||||||
|
this.leftList.passData();
|
||||||
|
}
|
||||||
|
this.changeQuery(currentPath);
|
||||||
|
this.cachePaths();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passSelection = (itemsSelected) => {
|
||||||
|
this.setState({ itemsSelected });
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleActiveList = (list) => {
|
||||||
|
this.setState({ activeWindow: list });
|
||||||
|
}
|
||||||
|
|
||||||
|
switchActiveList = (e) => {
|
||||||
|
if (this.state.modalVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 39) {
|
||||||
|
this.setState({ activeWindow: "right", currentPath: this.state.rightList.path });
|
||||||
|
this.changeQuery(this.state.currentPath);
|
||||||
|
this.rightList.passData();
|
||||||
|
this.cachePaths();
|
||||||
|
} else if (e.keyCode === 37) {
|
||||||
|
this.setState({ activeWindow: "left", currentPath: this.state.leftList.path });
|
||||||
|
this.changeQuery(this.state.currentPath);
|
||||||
|
this.leftList.passData();
|
||||||
|
this.cachePaths();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateAction = async (url) => {
|
||||||
|
await this.setStateAsync({ loading: true });
|
||||||
|
let response = await FM.validateAction(url);
|
||||||
|
if (response.data.result) {
|
||||||
|
this.changeDirectory();
|
||||||
|
} else {
|
||||||
|
this.showError(response.data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showError = (error) => {
|
||||||
|
toast.error(error, {
|
||||||
|
position: "top-center",
|
||||||
|
autoClose: 3000,
|
||||||
|
hideProgressBar: false,
|
||||||
|
closeOnClick: true,
|
||||||
|
pauseOnHover: true,
|
||||||
|
draggable: true
|
||||||
|
});
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
download = () => {
|
||||||
|
const { cursor, currentPath, itemName } = this.state;
|
||||||
|
|
||||||
|
if (cursor !== 0) {
|
||||||
|
window.open('/download/file/?path=' + currentPath + '/' + itemName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkExistingFileName = (selectedFiles) => {
|
||||||
|
const { activeWindow, leftList, rightList } = this.state;
|
||||||
|
const { existingFileNames, newFiles } = FM.checkExistingFileName(selectedFiles, activeWindow, leftList.files.listing, rightList.files.listing);
|
||||||
|
|
||||||
|
if (existingFileNames.length !== 0) {
|
||||||
|
this.modal("Replace", existingFileNames);
|
||||||
|
this.upload(newFiles);
|
||||||
|
} else {
|
||||||
|
this.upload(selectedFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceFiles = (selectedFiles) => {
|
||||||
|
for (let i = 0; i < selectedFiles.length; i++) {
|
||||||
|
this.validateAction(`${server}item=${FM.encodePath(this.state.currentPath)}%2F${selectedFiles[i].name}&dir=${FM.encodePath(this.state.currentPath)}&action=delete_files`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.upload(selectedFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
upload = (selectedFiles) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
if (selectedFiles.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < selectedFiles.length; i++) {
|
||||||
|
formData.append('files[]', selectedFiles[i], selectedFiles[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ loading: true }, () => {
|
||||||
|
axios.post(`${window.location.origin}/upload/?dir=${this.state.currentPath}`, formData, {
|
||||||
|
onUploadProgress: progressEvent => {
|
||||||
|
let uploadPercent = Math.round(progressEvent.loaded / progressEvent.total * 100);
|
||||||
|
this.setState({ uploadPercent });
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
this.setState({ uploadPercent: "0" });
|
||||||
|
this.changeDirectory();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDelete = async () => {
|
||||||
|
const { itemsSelected, itemName, currentPath } = this.state;
|
||||||
|
if (itemsSelected.length > 0) {
|
||||||
|
await this.setStateAsync({ loading: true });
|
||||||
|
await FM.deleteItems(server, FM.encodePath(currentPath), itemsSelected);
|
||||||
|
this.changeDirectory();
|
||||||
|
} else {
|
||||||
|
this.validateAction(`${server}item=${FM.encodePath(currentPath)}%2F${itemName}&dir=${FM.encodePath(currentPath)}&action=delete_files`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newFile = () => {
|
||||||
|
let name = this.inputElement.value;
|
||||||
|
this.validateAction(`${server}filename=${name}&dir=${FM.encodePath(this.state.currentPath)}&action=create_file`);
|
||||||
|
}
|
||||||
|
|
||||||
|
newDir = () => {
|
||||||
|
let name = this.inputElement.value;
|
||||||
|
this.validateAction(`${server}dirname=${name}&dir=${FM.encodePath(this.state.currentPath)}&action=create_dir`);
|
||||||
|
}
|
||||||
|
|
||||||
|
onRename = () => {
|
||||||
|
const { modalInputValue, itemType, itemName, currentPath } = this.state;
|
||||||
|
let name = modalInputValue;
|
||||||
|
if (itemType === "f") {
|
||||||
|
this.validateAction(`${server}item=${itemName}&target_name=${name}&dir=${FM.encodePath(currentPath)}&action=rename_file`);
|
||||||
|
} else if (itemType === "d") {
|
||||||
|
this.validateAction(`${server}item=${itemName}&target_name=${name}&dir=${FM.encodePath(currentPath)}%2F&action=rename_directory`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangePermissions = () => {
|
||||||
|
let permissions = this.state.modalInputValue;
|
||||||
|
this.validateAction(`${server}dir=${FM.encodePath(this.state.currentPath)}%2F&item=${this.state.itemName}&permissions=${permissions}&action=chmod_item`);
|
||||||
|
this.setState({ itemPermissions: permissions });
|
||||||
|
}
|
||||||
|
|
||||||
|
archiveItem = () => {
|
||||||
|
let name = this.inputElement.value;
|
||||||
|
|
||||||
|
if (this.state.itemsSelected.length > 0) {
|
||||||
|
this.setState({ loading: true }, () => {
|
||||||
|
let items = [];
|
||||||
|
for (let i = 0; i < this.state.itemsSelected.length; i++) {
|
||||||
|
let path = `${this.state.currentPath}/`;
|
||||||
|
items.push(path += this.state.itemsSelected[i]);
|
||||||
|
}
|
||||||
|
this.validateAction(`${server}items=${items}&dst_item=${FM.encodePath(name)}&action=pack_item`);
|
||||||
|
this.setState({ itemsSelected: [] });
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.validateAction(`${server}items=${FM.encodePath(this.state.currentPath)}%2F${this.state.itemName}&dst_item=${FM.encodePath(name)}&action=pack_item`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extractItem = () => {
|
||||||
|
let name = this.inputElement.value;
|
||||||
|
this.validateAction(`${server}item=${FM.encodePath(this.state.currentPath)}%2F${this.state.itemName}&filename=${this.state.itemName}&dir=${FM.encodePath(this.state.currentPath)}&dir_target=${name}&action=unpack_item`);
|
||||||
|
}
|
||||||
|
|
||||||
|
moveItem = async () => {
|
||||||
|
const { currentPath, itemsSelected, itemName } = this.state;
|
||||||
|
let targetDir = this.inputElement.value;
|
||||||
|
|
||||||
|
if (itemsSelected.length > 0) {
|
||||||
|
await this.setStateAsync({ loading: true });
|
||||||
|
await FM.moveItems(server, FM.encodePath(currentPath), targetDir, itemsSelected);
|
||||||
|
await this.setStateAsync({ itemsSelected: [] });
|
||||||
|
this.changeDirectory();
|
||||||
|
} else {
|
||||||
|
this.validateAction(`${server}item=${currentPath}%2F${itemName}&target_name=${targetDir}&action=move_file`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copyItem = async () => {
|
||||||
|
const { currentPath, itemsSelected, itemName } = this.state;
|
||||||
|
let targetDir = this.inputElement.value;
|
||||||
|
|
||||||
|
if (itemsSelected.length > 0) {
|
||||||
|
await this.setStateAsync({ loading: true });
|
||||||
|
await FM.copyItems(server, FM.encodePath(currentPath), targetDir, itemsSelected);
|
||||||
|
await this.setStateAsync({ itemsSelected: [] });
|
||||||
|
this.changeDirectory();
|
||||||
|
} else {
|
||||||
|
this.validateAction(`${server}item=${currentPath}%2F${itemName}&filename=${itemName}&dir=${currentPath}&dir_target=${targetDir}&action=copy_file`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeQuery = (path) => {
|
||||||
|
this.props.history.push({
|
||||||
|
pathname: '/list/directory/',
|
||||||
|
search: `?path=${path}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openDirectory = () => {
|
||||||
|
this.setState({ loading: true }, () => {
|
||||||
|
this.changeDirectory();
|
||||||
|
this.cachePaths();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openCertainDirectory = () => {
|
||||||
|
this.setState({ loading: true }, () => {
|
||||||
|
this.changeDirectory();
|
||||||
|
this.cachePaths();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
moveBack = () => {
|
||||||
|
const { activeWindow } = this.state;
|
||||||
|
|
||||||
|
let list = { ...this.state[`${activeWindow}List`] };
|
||||||
|
list.path = list.path.substring(0, list.path.lastIndexOf('/'));
|
||||||
|
this.setState({ [`${activeWindow}List`]: list, currentPath: list.path });
|
||||||
|
this.props.history.push({ search: `?path=${list.path}` })
|
||||||
|
this.openDirectory();
|
||||||
|
}
|
||||||
|
|
||||||
|
addToPath = (name) => {
|
||||||
|
const { activeWindow } = this.state;
|
||||||
|
|
||||||
|
let activeList = { ...this.state[`${activeWindow}List`] };
|
||||||
|
let oldPath = activeList.path;
|
||||||
|
activeList.path = `${oldPath}/${name}`;
|
||||||
|
this.setState({ [`${activeWindow}List`]: activeList, currentPath: activeList.path });
|
||||||
|
}
|
||||||
|
|
||||||
|
changeInputValue = (modalInputValue) => {
|
||||||
|
this.setState({ modalInputValue });
|
||||||
|
}
|
||||||
|
|
||||||
|
changePathAfterToggle = (currentPath) => {
|
||||||
|
this.setState({ currentPath });
|
||||||
|
}
|
||||||
|
|
||||||
|
changePath = (currentPath) => {
|
||||||
|
if (this.state.activeWindow === "left") {
|
||||||
|
this.setState({ leftList: { files: { ...this.state.leftList.files }, path: currentPath }, currentPath });
|
||||||
|
} else {
|
||||||
|
this.setState({ rightList: { files: { ...this.state.rightList.files }, path: currentPath }, currentPath });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passData = (cursor, itemName, itemPermissions, itemType) => {
|
||||||
|
this.setState({ cursor, itemName, itemPermissions, itemType });
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal = () => {
|
||||||
|
this.setState({ modalVisible: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
hotkeysListener = (e) => {
|
||||||
|
if (this.state.modalVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.keyCode === 72) {
|
||||||
|
this.hotkeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hotkeys = () => {
|
||||||
|
if (this.state.hotkeysPanel === "inactive") {
|
||||||
|
this.setState({ hotkeysPanel: "active" });
|
||||||
|
} else {
|
||||||
|
this.setState({ hotkeysPanel: "inactive" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modal = (type, items, available) => {
|
||||||
|
const { modalVisible, itemName, itemPermissions, currentPath } = this.state;
|
||||||
|
switch (type) {
|
||||||
|
case 'Copy': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} fName={itemName} path={currentPath} onClick={this.copyItem} items={items} onClose={this.closeModal} onChangeValue={this.changeInputValue} reference={(inp) => this.inputElement = inp} />, modalVisible: true });
|
||||||
|
case 'Move': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} fName={itemName} path={currentPath} onClick={this.moveItem} items={items} onClose={this.closeModal} onChangeValue={this.changeInputValue} reference={(inp) => this.inputElement = inp} />, modalVisible: true });
|
||||||
|
case 'Extract': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} fName={itemName} onClick={this.extractItem} onClose={this.closeModal} onChangeValue={this.changeInputValue} path={currentPath} reference={(inp) => this.inputElement = inp} />, modalVisible: true });
|
||||||
|
case 'Archive': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} fName={itemName} onClick={this.archiveItem} items={items} onClose={this.closeModal} onChangeValue={this.changeInputValue} path={currentPath} reference={(inp) => this.inputElement = inp} />, modalVisible: true });
|
||||||
|
case 'Permissions': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} fName={itemName} onClick={this.onChangePermissions} onClose={this.closeModal} onChangePermissions={this.changeInputValue} permissions={itemPermissions} />, modalVisible: true });
|
||||||
|
case 'Rename': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} fName={itemName} onChangeValue={this.changeInputValue} onClick={this.onRename} onClose={this.closeModal} reference={(inp) => this.inputElement = inp} />, modalVisible: true });
|
||||||
|
case 'Add directory': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} onClick={this.newDir} onClose={this.closeModal} reference={(inp) => this.inputElement = inp} />, modalVisible: true });
|
||||||
|
case 'Add file': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} onClick={this.newFile} onClose={this.closeModal} reference={(inp) => this.inputElement = inp} />, modalVisible: true });
|
||||||
|
case 'Delete': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} fName={itemName} onClick={this.onDelete} onClose={this.closeModal} items={items} />, modalVisible: true });
|
||||||
|
case 'Nothing selected': return this.setState({ modalWindow: <Modal modalVisible={modalVisible} notAvailable={available} type={type} onClose={this.closeModal} onClick={this.closeModal} />, modalVisible: true });
|
||||||
|
case "Replace": return this.setState({ modalWindow: <Modal modalVisible={modalVisible} type={type} files={items} onClick={(files) => this.replaceFiles(files)} onClose={this.closeModal} />, modalVisible: true });
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { activeWindow, modalWindow, modalVisible, itemsSelected, itemName, loading, uploadPercent, hotkeysPanel, itemType } = this.state;
|
||||||
|
const DirectoryLists = ['left', 'right'].map((side) =>
|
||||||
|
<DirectoryList
|
||||||
|
changePathAfterToggle={this.changePathAfterToggle}
|
||||||
|
openCertainDirectory={this.openCertainDirectory}
|
||||||
|
isActive={activeWindow === side}
|
||||||
|
openDirectory={this.openDirectory}
|
||||||
|
passSelection={this.passSelection}
|
||||||
|
data={this.state[`${side}List`].files}
|
||||||
|
onClick={this.toggleActiveList}
|
||||||
|
changePath={this.changePath}
|
||||||
|
modalVisible={modalVisible}
|
||||||
|
addToPath={this.addToPath}
|
||||||
|
cursor={this.state.cursor}
|
||||||
|
passData={this.passData}
|
||||||
|
ref={el => this[`${side}List`] = el}
|
||||||
|
download={this.download}
|
||||||
|
moveBack={this.moveBack}
|
||||||
|
path={this.state[`${side}List`].path}
|
||||||
|
history={this.props.history}
|
||||||
|
loading={loading}
|
||||||
|
list={side} />
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
<div className="window">
|
||||||
|
{uploadPercent !== "0" ? <ProgressBar progress={uploadPercent} /> : null}
|
||||||
|
<ToastContainer />
|
||||||
|
<Menu
|
||||||
|
onDelete={this.onDeleteFileHandler}
|
||||||
|
modalVisible={modalVisible}
|
||||||
|
download={this.download}
|
||||||
|
openModal={this.modal}
|
||||||
|
selection={itemsSelected}
|
||||||
|
itemType={itemType}
|
||||||
|
upload={this.checkExistingFileName}
|
||||||
|
cursor={this.state.cursor}
|
||||||
|
name={itemName} />
|
||||||
|
<div className="lists-container">
|
||||||
|
{DirectoryLists}
|
||||||
|
<Hotkeys style={hotkeysPanel} close={this.hotkeys} />
|
||||||
|
<HotkeysButton open={this.hotkeys} />
|
||||||
|
</div>
|
||||||
|
{modalVisible && modalWindow}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(FileManager);
|
14
src/react/FileManagerComponent/src/index.css
Executable file
14
src/react/FileManagerComponent/src/index.css
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||||
|
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||||
|
monospace;
|
||||||
|
}
|
12
src/react/FileManagerComponent/src/index.js
Executable file
12
src/react/FileManagerComponent/src/index.js
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import './index.css';
|
||||||
|
import App from './containers/App/App';
|
||||||
|
import * as serviceWorker from './containers/App/serviceWorker';
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'));
|
||||||
|
|
||||||
|
// If you want your app to work offline and load faster, you can change
|
||||||
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
|
// Learn more about service workers: http://bit.ly/CRA-PWA
|
||||||
|
serviceWorker.unregister();
|
Loading…
Add table
Add a link
Reference in a new issue