2018-10-24 17:57:57 +00:00
|
|
|
import {AppSwitch} from '@coreui/react';
|
|
|
|
import {Label} from 'reactstrap';
|
2018-08-30 12:13:37 +00:00
|
|
|
import React from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
import {Treebeard, decorators} from 'react-treebeard';
|
2018-10-25 07:06:37 +00:00
|
|
|
import classNames from 'classnames';
|
2018-10-25 13:51:54 +00:00
|
|
|
import {DARK_THEME} from '../constants';
|
2018-08-30 12:13:37 +00:00
|
|
|
|
2018-10-25 11:02:13 +00:00
|
|
|
const isDarkTheme= (theme) => theme === DARK_THEME;
|
|
|
|
|
|
|
|
const style = (theme) => ({
|
2018-10-11 11:08:47 +00:00
|
|
|
tree: {
|
|
|
|
base: {
|
|
|
|
listStyle: 'none',
|
2018-10-25 11:02:13 +00:00
|
|
|
backgroundColor: isDarkTheme(theme) ? '#1C1C1C' : '#FFFFFF',
|
|
|
|
color: isDarkTheme(theme) ? '#FFFFFF' : '#000000',
|
2018-10-25 07:06:37 +00:00
|
|
|
padding: '10px 0 0 10px',
|
2018-10-11 11:08:47 +00:00
|
|
|
margin: 0,
|
2018-10-25 13:51:54 +00:00
|
|
|
overflow: 'auto',
|
|
|
|
position: 'absolute',
|
|
|
|
top: 0,
|
|
|
|
bottom: '40px',
|
|
|
|
left: 0,
|
|
|
|
right: 0
|
2018-10-11 11:08:47 +00:00
|
|
|
},
|
|
|
|
node: {
|
|
|
|
base: {
|
2018-10-25 07:06:37 +00:00
|
|
|
position: 'relative',
|
2018-10-25 13:51:54 +00:00
|
|
|
verticalAlign: 'middle'
|
2018-10-11 11:08:47 +00:00
|
|
|
},
|
|
|
|
link: {
|
|
|
|
cursor: 'pointer',
|
|
|
|
position: 'relative',
|
|
|
|
padding: '0px 5px',
|
|
|
|
display: 'block'
|
|
|
|
},
|
|
|
|
toggle: {
|
|
|
|
base: {
|
|
|
|
display: 'inline-block',
|
2018-10-25 07:06:37 +00:00
|
|
|
marginRight: '10px'
|
2018-10-11 11:08:47 +00:00
|
|
|
},
|
|
|
|
wrapper: {
|
2018-10-25 13:51:54 +00:00
|
|
|
margin: '-7px 0 0 0'
|
2018-10-11 11:08:47 +00:00
|
|
|
},
|
2018-10-25 07:06:37 +00:00
|
|
|
height: 7,
|
|
|
|
width: 7,
|
2018-10-11 11:08:47 +00:00
|
|
|
arrow: {
|
2018-10-25 11:02:13 +00:00
|
|
|
fill: isDarkTheme(theme) ? '#FFFFFF' : '#000000',
|
2018-10-11 11:08:47 +00:00
|
|
|
strokeWidth: 0
|
|
|
|
}
|
|
|
|
},
|
|
|
|
header: {
|
|
|
|
base: {
|
2018-10-25 13:51:54 +00:00
|
|
|
display: 'inline-block'
|
2018-10-11 11:08:47 +00:00
|
|
|
},
|
|
|
|
connector: {
|
|
|
|
width: '2px',
|
|
|
|
height: '12px',
|
|
|
|
borderLeft: 'solid 2px black',
|
|
|
|
borderBottom: 'solid 2px black',
|
|
|
|
position: 'absolute',
|
|
|
|
top: '0px',
|
|
|
|
left: '-21px'
|
|
|
|
},
|
|
|
|
title: {
|
2018-10-25 13:51:54 +00:00
|
|
|
lineHeight: '24px'
|
2018-10-11 11:08:47 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
subtree: {
|
|
|
|
listStyle: 'none',
|
2018-10-25 07:06:37 +00:00
|
|
|
paddingLeft: '22px'
|
2018-10-11 11:08:47 +00:00
|
|
|
},
|
|
|
|
loading: {
|
|
|
|
color: '#E2C089'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-25 11:02:13 +00:00
|
|
|
});
|
2018-10-11 11:08:47 +00:00
|
|
|
|
2018-10-25 07:06:37 +00:00
|
|
|
|
2018-08-30 12:13:37 +00:00
|
|
|
const Header = ({style, node}) => {
|
2018-10-25 07:06:37 +00:00
|
|
|
let icon;
|
|
|
|
|
|
|
|
if (!node.children) {
|
|
|
|
const extension = node.path.split('.').pop();
|
|
|
|
switch(extension) {
|
|
|
|
case 'html':
|
|
|
|
icon = 'text-danger fa fa-html5';
|
|
|
|
break;
|
|
|
|
case 'css':
|
|
|
|
icon = 'text-warning fa fa-css3';
|
|
|
|
break;
|
|
|
|
case 'js':
|
|
|
|
case 'jsx':
|
|
|
|
icon = 'text-primary icon js-icon';
|
|
|
|
break;
|
|
|
|
case 'json':
|
|
|
|
icon = 'text-success icon hjson-icon';
|
|
|
|
break;
|
|
|
|
case 'sol':
|
|
|
|
icon = 'text-warning icon solidity-icon';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
icon = 'fa fa-file-o';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch(node.name) {
|
|
|
|
case 'dist':
|
|
|
|
icon = 'text-danger icon easybuild-icon';
|
|
|
|
break;
|
|
|
|
case 'config':
|
|
|
|
icon = 'text-warning fa fa-cogs';
|
|
|
|
break;
|
|
|
|
case 'contracts':
|
|
|
|
icon = 'text-success icon appstore-icon';
|
|
|
|
break;
|
|
|
|
case 'app':
|
|
|
|
icon = 'text-primary fa fa-code';
|
|
|
|
break;
|
|
|
|
case 'test':
|
|
|
|
icon = 'icon test-dir-icon';
|
|
|
|
break;
|
|
|
|
case 'node_modules':
|
|
|
|
icon = 'fa fa-folder-o';
|
|
|
|
break;
|
|
|
|
default:
|
2018-10-25 13:51:54 +00:00
|
|
|
icon = 'fa fa-folder';
|
2018-10-25 07:06:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2018-08-30 12:13:37 +00:00
|
|
|
|
|
|
|
return (
|
2018-10-25 07:06:37 +00:00
|
|
|
<div className="mb-1" style={style.base}>
|
2018-10-09 06:01:48 +00:00
|
|
|
<div style={style.title}>
|
2018-10-25 07:06:37 +00:00
|
|
|
<i className={classNames('mr-1', icon)} />
|
2018-10-09 06:01:48 +00:00
|
|
|
{node.name}
|
2018-08-30 12:13:37 +00:00
|
|
|
</div>
|
2018-10-09 06:01:48 +00:00
|
|
|
</div>
|
2018-08-30 12:13:37 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
Header.propTypes = {
|
|
|
|
style: PropTypes.object,
|
|
|
|
node: PropTypes.object
|
|
|
|
};
|
|
|
|
|
|
|
|
decorators.Header = Header;
|
|
|
|
|
|
|
|
class FileExplorer extends React.Component {
|
2018-10-09 06:01:48 +00:00
|
|
|
constructor(props) {
|
2018-08-30 12:13:37 +00:00
|
|
|
super(props);
|
2018-10-23 11:18:02 +00:00
|
|
|
this.state = {cursor: null};
|
2018-08-30 12:13:37 +00:00
|
|
|
}
|
2018-08-30 12:13:37 +00:00
|
|
|
|
2018-10-09 06:01:48 +00:00
|
|
|
onToggle(node, toggled) {
|
2018-08-30 12:13:37 +00:00
|
|
|
node.active = true;
|
2018-10-09 06:01:48 +00:00
|
|
|
if (node.children) {
|
2018-08-30 12:13:37 +00:00
|
|
|
node.toggled = toggled;
|
2018-08-30 12:13:37 +00:00
|
|
|
} else {
|
|
|
|
this.props.fetchFile(node);
|
2018-08-30 12:13:37 +00:00
|
|
|
}
|
2018-10-09 06:01:48 +00:00
|
|
|
this.setState({cursor: node});
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeEquals(a, b) {
|
|
|
|
return a && b && a.path && b.path && a.name && b.name &&
|
|
|
|
a.path === b.path &&
|
|
|
|
a.name === b.name;
|
2018-08-30 12:13:37 +00:00
|
|
|
}
|
2018-08-30 12:13:37 +00:00
|
|
|
|
2018-10-09 06:01:48 +00:00
|
|
|
isNodeInPath(a, b) {
|
|
|
|
return a && b && a.path && b.path &&
|
|
|
|
a.path !== b.path &&
|
|
|
|
b.path.indexOf(a.path) > -1;
|
|
|
|
}
|
|
|
|
|
2018-10-23 11:18:02 +00:00
|
|
|
data(nodes) {
|
2018-10-09 06:01:48 +00:00
|
|
|
let filtered = [];
|
|
|
|
if (!Array.isArray(nodes)) return filtered;
|
|
|
|
|
2018-10-23 11:18:02 +00:00
|
|
|
const cursor = this.state.cursor;
|
|
|
|
const showHidden = this.props.showHiddenFiles;
|
2018-10-09 06:01:48 +00:00
|
|
|
// we need a foreach to build an array instead of a
|
2018-10-24 17:57:57 +00:00
|
|
|
// filter to prevent mutating the original object (in props)
|
2018-10-09 06:01:48 +00:00
|
|
|
nodes.forEach(node => {
|
|
|
|
if (!showHidden && node.isHidden) return;
|
|
|
|
let updatedNode = {...node};
|
|
|
|
|
|
|
|
// if it's a folder, filter the children
|
|
|
|
if (node.children) {
|
2018-10-23 11:18:02 +00:00
|
|
|
const children = this.data(node.children);
|
2018-10-09 06:01:48 +00:00
|
|
|
if (children.length) {
|
|
|
|
updatedNode.children = children;
|
|
|
|
}
|
|
|
|
}
|
2018-10-24 17:57:57 +00:00
|
|
|
|
2018-10-09 06:01:48 +00:00
|
|
|
// if this is the selected node, set it as active
|
|
|
|
if (this.nodeEquals(node, cursor)) {
|
|
|
|
updatedNode.active = cursor.active;
|
2018-10-24 17:57:57 +00:00
|
|
|
// if this node is the selected node and is a folder, set
|
2018-10-09 21:13:29 +00:00
|
|
|
// it as toggled (expanded) according to the selected node
|
2018-10-09 06:01:48 +00:00
|
|
|
if (node.children) updatedNode.toggled = cursor.toggled;
|
|
|
|
}
|
2018-10-09 21:13:29 +00:00
|
|
|
// if this node is a folder, and it's a parent of the selected
|
|
|
|
// folder, force toggle it
|
2018-10-09 06:01:48 +00:00
|
|
|
if (node.children && this.isNodeInPath(node, cursor)) {
|
|
|
|
updatedNode.toggled = true;
|
|
|
|
}
|
|
|
|
filtered.push(updatedNode);
|
2018-10-08 05:43:51 +00:00
|
|
|
});
|
2018-10-09 06:01:48 +00:00
|
|
|
|
|
|
|
return filtered;
|
2018-10-08 05:43:51 +00:00
|
|
|
}
|
|
|
|
|
2018-10-09 06:01:48 +00:00
|
|
|
render() {
|
2018-08-30 12:13:37 +00:00
|
|
|
return (
|
2018-10-25 13:51:54 +00:00
|
|
|
<div className="d-flex flex-column">
|
2018-10-08 05:43:51 +00:00
|
|
|
<Treebeard
|
2018-10-23 11:18:02 +00:00
|
|
|
data={this.data(this.props.files)}
|
2018-10-08 05:43:51 +00:00
|
|
|
decorators={decorators}
|
|
|
|
onToggle={this.onToggle.bind(this)}
|
2018-10-25 11:02:13 +00:00
|
|
|
style={style(this.props.theme)}
|
2018-10-08 05:43:51 +00:00
|
|
|
/>
|
2018-10-24 17:57:57 +00:00
|
|
|
|
2018-10-25 13:51:54 +00:00
|
|
|
<Label className="hidden-toogle mb-0 pt-2 pr-2 pb-1 border-top text-right">
|
2018-10-24 19:20:31 +00:00
|
|
|
<span className="mr-2 align-top">Show hidden files</span>
|
|
|
|
<AppSwitch color="success" variant="pill" size="sm" onChange={this.props.toggleShowHiddenFiles}/>
|
2018-10-24 17:57:57 +00:00
|
|
|
</Label>
|
|
|
|
</div>
|
2018-08-30 12:13:37 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
FileExplorer.propTypes = {
|
2018-08-30 12:13:37 +00:00
|
|
|
files: PropTypes.array,
|
2018-10-23 11:18:02 +00:00
|
|
|
fetchFile: PropTypes.func,
|
|
|
|
showHiddenFiles: PropTypes.bool,
|
2018-10-25 11:02:13 +00:00
|
|
|
toggleShowHiddenFiles: PropTypes.func,
|
|
|
|
theme: PropTypes.string
|
2018-08-30 12:13:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export default FileExplorer;
|