remove FM submodule

This commit is contained in:
Alexander 2019-12-16 18:48:23 +02:00
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
View 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*

View 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 cant go back!**
If you arent 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 youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt 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

File diff suppressed because it is too large Load diff

View 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"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View 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>

View 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"
}

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

View file

@ -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">&times;</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">&#8593;</span> {window.GLOBAL.App.Constants.FM_MoveUp}</li>
<li><span className="shortcut">&#8595;</span> {window.GLOBAL.App.Constants.FM_MoveDown}</li>
<li><span className="shortcut">&#8592;</span> {window.GLOBAL.App.Constants.FM_MoveLeft}</li>
<li><span className="shortcut">&#8594;</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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}

View 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);

View 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;
}
}

View 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;

View 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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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">&quot;{props.fName}&quot;</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;

View 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">&quot;{props.fName}&quot;</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;

View file

@ -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">&quot;{props.fName}&quot;</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;

View file

@ -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">&quot;{props.fName}&quot;</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;

View file

@ -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;

View 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;
}
}
}
}
}
}

View 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">&quot;{props.fName}&quot;</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;

View file

@ -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;

View file

@ -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">&quot;{fName}&quot;</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;

View file

@ -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">&quot;{props.fName}&quot;</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;

View file

@ -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">&quot;{item.name}&quot; </span>
)}
</div> :
<div><h3>This file already exists</h3>
<span className="quot">&quot;{props.files[0].name}&quot;</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;

View file

@ -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">&#8595;</span>
</button>
);
} else {
return (
<button type="button" className="btn btn-secondary" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{sort(sorting)}
<span>&#8593;</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">&#8595;</span></span>
<span className={props.sorting === "Type" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Type", "ascending", props)}><span>&#8593;</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">&#8595;</span></span>
<span className={props.sorting === "Size" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Size", "ascending", props)}><span>&#8593;</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">&#8595;</span></span>
<span className={props.sorting === "Date" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Date", "ascending", props)}><span>&#8593;</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">&#8595;</span></span>
<span className={props.sorting === "Name" && props.order === "ascending" ? "dropdown-item active" : "dropdown-item"} onClick={() => changeSorting("Name", "ascending", props)}><span>&#8593;</span></span>
</li>
</ul>
</div>
</div>
);
}
export default Dropdown;

View file

@ -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;
}

View 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)}>&nbsp;/&nbsp;{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;

View 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;
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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}>&times;</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;

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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}>&times;</span>
<video className="video" autoPlay loop controls>
<source src="" type="video/mp4" />
</video>
</div>
);
}
export default Video;

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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;

View file

@ -0,0 +1,7 @@
.spinner-wrapper {
position: absolute;
bottom: 5px;
width: 15%;
left: 42%;
height: 10px;
}

View 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;

View 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;
}

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

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

View file

@ -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);

View 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;
}

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