mirror of https://github.com/embarklabs/embark.git
feat: add CRUD to file explorer
This commit is contained in:
parent
c8d6f18648
commit
f82d3de1b0
|
@ -293,10 +293,17 @@ export const saveFile = {
|
||||||
failure: (error) => action(SAVE_FILE[FAILURE], {error})
|
failure: (error) => action(SAVE_FILE[FAILURE], {error})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SAVE_FOLDER = createRequestTypes('SAVE_FOLDER');
|
||||||
|
export const saveFolder = {
|
||||||
|
request: ({path}) => action(SAVE_FOLDER[REQUEST], {path}),
|
||||||
|
success: () => action(SAVE_FOLDER[SUCCESS]),
|
||||||
|
failure: (error) => action(SAVE_FOLDER[FAILURE], {error})
|
||||||
|
};
|
||||||
|
|
||||||
export const REMOVE_FILE = createRequestTypes('REMOVE_FILE');
|
export const REMOVE_FILE = createRequestTypes('REMOVE_FILE');
|
||||||
export const removeFile = {
|
export const removeFile = {
|
||||||
request: ({name, path, content}) => action(REMOVE_FILE[REQUEST], {name, path, content}),
|
request: ({name, path, content}) => action(REMOVE_FILE[REQUEST], {name, path, content}),
|
||||||
success: () => action(REMOVE_FILE[SUCCESS]),
|
success: (_, file) => action(REMOVE_FILE[SUCCESS], {file}),
|
||||||
failure: (error) => action(REMOVE_FILE[FAILURE], {error})
|
failure: (error) => action(REMOVE_FILE[FAILURE], {error})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Button, Modal, ModalHeader, ModalBody, ModalFooter, Input} from 'reactstrap';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import {isDarkTheme} from '../utils/utils';
|
||||||
|
|
||||||
|
class AddFileModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {modal: false, filename: ''};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.setState({modal: !this.state.modal});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(event) {
|
||||||
|
this.setState({filename: event.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
addFile() {
|
||||||
|
this.props.saveFile({path: `${this.props.node.path}/${this.state.filename}`, content: ''});
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal contentClassName={classNames({'dark-theme': isDarkTheme(this.props.theme)})}
|
||||||
|
isOpen={this.state.modal}
|
||||||
|
toggle={() => this.toggle()}>
|
||||||
|
<ModalHeader toggle={() => this.toggle()}>Please give the file a name</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Input autofocus="true" value={this.state.filename} onChange={e => this.handleChange(e)} />
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={() => this.addFile()}>Add File</Button>{' '}
|
||||||
|
<Button color="secondary" onClick={() => this.toggle()}>Cancel</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFileModal.propTypes = {
|
||||||
|
saveFile: PropTypes.func,
|
||||||
|
node: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFileModal;
|
|
@ -0,0 +1,51 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {Button, Modal, ModalHeader, ModalBody, ModalFooter, Input} from 'reactstrap';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import {isDarkTheme} from '../utils/utils';
|
||||||
|
|
||||||
|
class AddFolderModal extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {modal: false, folder: ''};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
this.setState({modal: !this.state.modal});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange(event) {
|
||||||
|
this.setState({folder: event.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
addFolder() {
|
||||||
|
this.props.saveFolder({path: `${this.props.node.path}/${this.state.folder}`});
|
||||||
|
this.toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<Modal contentClassName={classNames({'dark-theme': isDarkTheme(this.props.theme)})}
|
||||||
|
isOpen={this.state.modal}
|
||||||
|
toggle={() => this.toggle()}>
|
||||||
|
<ModalHeader toggle={() => this.toggle()}>Please give the folder a name</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
<Input autofocus="true" value={this.state.filename} onChange={e => this.handleChange(e)} />
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={() => this.addFolder()}>Add Folder</Button>{' '}
|
||||||
|
<Button color="secondary" onClick={() => this.toggle()}>Cancel</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddFolderModal.propTypes = {
|
||||||
|
saveFolder: PropTypes.func,
|
||||||
|
node: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddFolderModal;
|
|
@ -1,12 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
import {AppSwitch} from '@coreui/react';
|
import {AppSwitch} from '@coreui/react';
|
||||||
import {Label} from 'reactstrap';
|
import {Label} from 'reactstrap';
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {Treebeard, decorators} from 'react-treebeard';
|
import {Treebeard, decorators} from 'react-treebeard';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {DARK_THEME} from '../constants';
|
|
||||||
|
|
||||||
const isDarkTheme= (theme) => theme === DARK_THEME;
|
import FileExplorerRowContainer from '../containers/FileExplorerRowContainer';
|
||||||
|
import {isDarkTheme} from '../utils/utils';
|
||||||
|
|
||||||
const style = (theme) => ({
|
const style = (theme) => ({
|
||||||
tree: {
|
tree: {
|
||||||
|
@ -77,67 +77,73 @@ const style = (theme) => ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
class Header extends React.Component {
|
||||||
|
resolveIcon() {
|
||||||
|
let icon;
|
||||||
|
let {node} = this.props;
|
||||||
|
|
||||||
|
if (!node.children) {
|
||||||
const Header = ({style, node}) => {
|
const extension = node.path.split('.').pop();
|
||||||
let icon;
|
switch(extension) {
|
||||||
|
case 'html':
|
||||||
if (!node.children) {
|
icon = 'text-danger fa fa-html5';
|
||||||
const extension = node.path.split('.').pop();
|
break;
|
||||||
switch(extension) {
|
case 'css':
|
||||||
case 'html':
|
icon = 'text-warning fa fa-css3';
|
||||||
icon = 'text-danger fa fa-html5';
|
break;
|
||||||
break;
|
case 'js':
|
||||||
case 'css':
|
case 'jsx':
|
||||||
icon = 'text-warning fa fa-css3';
|
icon = 'text-primary icon js-icon';
|
||||||
break;
|
break;
|
||||||
case 'js':
|
case 'json':
|
||||||
case 'jsx':
|
icon = 'text-success icon hjson-icon';
|
||||||
icon = 'text-primary icon js-icon';
|
break;
|
||||||
break;
|
case 'sol':
|
||||||
case 'json':
|
icon = 'text-warning icon solidity-icon';
|
||||||
icon = 'text-success icon hjson-icon';
|
break;
|
||||||
break;
|
default:
|
||||||
case 'sol':
|
icon = 'fa fa-file-o';
|
||||||
icon = 'text-warning icon solidity-icon';
|
}
|
||||||
break;
|
} else {
|
||||||
default:
|
switch(node.name) {
|
||||||
icon = 'fa fa-file-o';
|
case 'dist':
|
||||||
}
|
icon = 'text-danger icon easybuild-icon';
|
||||||
} else {
|
break;
|
||||||
switch(node.name) {
|
case 'config':
|
||||||
case 'dist':
|
icon = 'text-warning fa fa-cogs';
|
||||||
icon = 'text-danger icon easybuild-icon';
|
break;
|
||||||
break;
|
case 'contracts':
|
||||||
case 'config':
|
icon = 'text-success fa fa-file-text';
|
||||||
icon = 'text-warning fa fa-cogs';
|
break;
|
||||||
break;
|
case 'app':
|
||||||
case 'contracts':
|
icon = 'text-primary fa fa-code';
|
||||||
icon = 'text-success fa fa-file-text';
|
break;
|
||||||
break;
|
case 'test':
|
||||||
case 'app':
|
icon = 'icon test-dir-icon';
|
||||||
icon = 'text-primary fa fa-code';
|
break;
|
||||||
break;
|
case 'node_modules':
|
||||||
case 'test':
|
icon = 'fa fa-folder-o';
|
||||||
icon = 'icon test-dir-icon';
|
break;
|
||||||
break;
|
default:
|
||||||
case 'node_modules':
|
icon = 'fa fa-folder';
|
||||||
icon = 'fa fa-folder-o';
|
}
|
||||||
break;
|
|
||||||
default:
|
|
||||||
icon = 'fa fa-folder';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
render() {
|
||||||
<div className="mb-1" style={style.base}>
|
let {node, style} = this.props;
|
||||||
<div style={style.title}>
|
return (
|
||||||
<i className={classNames('mr-1', icon)} />
|
<div className="mb-1 d-inline-block"
|
||||||
{node.name}
|
style={style.base}>
|
||||||
|
<div style={style.title}>
|
||||||
|
<i className={classNames('mr-1', this.resolveIcon())} />
|
||||||
|
{node.name}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Header.propTypes = {
|
Header.propTypes = {
|
||||||
|
@ -146,6 +152,7 @@ Header.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
decorators.Header = Header;
|
decorators.Header = Header;
|
||||||
|
decorators.Container = FileExplorerRowContainer;
|
||||||
|
|
||||||
class FileExplorer extends React.Component {
|
class FileExplorer extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|
|
@ -4,6 +4,9 @@ import {Button, Nav, NavLink} from 'reactstrap';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import FontAwesomeIcon from 'react-fontawesome';
|
import FontAwesomeIcon from 'react-fontawesome';
|
||||||
|
|
||||||
|
import AddFileModal from '../components/AddFileModal';
|
||||||
|
import AddFolderModal from '../components/AddFolderModal';
|
||||||
|
|
||||||
export const TextEditorToolbarTabs = {
|
export const TextEditorToolbarTabs = {
|
||||||
Interact: { label: 'Interact', icon: 'bolt' },
|
Interact: { label: 'Interact', icon: 'bolt' },
|
||||||
Details: { label: 'Details', icon: 'info-circle' },
|
Details: { label: 'Details', icon: 'info-circle' },
|
||||||
|
@ -13,6 +16,11 @@ export const TextEditorToolbarTabs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class TextEditorToolbar extends Component {
|
class TextEditorToolbar extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.addFileModal = React.createRef();
|
||||||
|
this.addFolderModal = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
isActiveTab(tab) {
|
isActiveTab(tab) {
|
||||||
return this.props.activeTab === tab;
|
return this.props.activeTab === tab;
|
||||||
|
@ -38,6 +46,16 @@ class TextEditorToolbar extends Component {
|
||||||
return (
|
return (
|
||||||
<ol className="breadcrumb mb-0">
|
<ol className="breadcrumb mb-0">
|
||||||
<li className="breadcrumb-item">
|
<li className="breadcrumb-item">
|
||||||
|
<Button color="success" size="sm" className="mr-1" onClick={() => this.addFileModal.current.toggle()}>
|
||||||
|
<FontAwesomeIcon className="mr-2" name="plus"/>
|
||||||
|
Add File
|
||||||
|
</Button>
|
||||||
|
<AddFileModal theme={this.props.theme} node={{path: this.props.rootDirname}} saveFile={this.props.saveFile} ref={this.addFileModal} />
|
||||||
|
<Button color="success" size="sm" className="mr-1" onClick={() => this.addFolderModal.current.toggle()}>
|
||||||
|
<FontAwesomeIcon className="mr-2" name="folder-open"/>
|
||||||
|
Add Folder
|
||||||
|
</Button>
|
||||||
|
<AddFolderModal theme={this.props.theme} node={{path: this.props.rootDirname}} saveFolder={this.props.saveFolder} ref={this.addFolderModal} />
|
||||||
<Button color="success" size="sm" className="mr-1" onClick={this.props.save}>
|
<Button color="success" size="sm" className="mr-1" onClick={this.props.save}>
|
||||||
<FontAwesomeIcon className="mr-2" name="save"/>
|
<FontAwesomeIcon className="mr-2" name="save"/>
|
||||||
Save
|
Save
|
||||||
|
@ -61,7 +79,11 @@ class TextEditorToolbar extends Component {
|
||||||
|
|
||||||
TextEditorToolbar.propTypes = {
|
TextEditorToolbar.propTypes = {
|
||||||
isContract: PropTypes.bool,
|
isContract: PropTypes.bool,
|
||||||
|
theme: PropTypes.string,
|
||||||
save: PropTypes.func,
|
save: PropTypes.func,
|
||||||
|
saveFile: PropTypes.func,
|
||||||
|
rootDirname: PropTypes.string,
|
||||||
|
saveFolder: PropTypes.func,
|
||||||
remove: PropTypes.func,
|
remove: PropTypes.func,
|
||||||
toggleShowHiddenFiles: PropTypes.func,
|
toggleShowHiddenFiles: PropTypes.func,
|
||||||
openAsideTab: PropTypes.func,
|
openAsideTab: PropTypes.func,
|
||||||
|
|
|
@ -2,7 +2,6 @@ import React, {Component} from 'react';
|
||||||
import {connect} from 'react-redux';
|
import {connect} from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {files as filesAction, file as fileAction} from "../actions";
|
import {files as filesAction, file as fileAction} from "../actions";
|
||||||
|
|
||||||
import FileExplorer from '../components/FileExplorer';
|
import FileExplorer from '../components/FileExplorer';
|
||||||
import DataWrapper from "../components/DataWrapper";
|
import DataWrapper from "../components/DataWrapper";
|
||||||
import {getFiles, getTheme} from "../reducers/selectors";
|
import {getFiles, getTheme} from "../reducers/selectors";
|
||||||
|
@ -14,13 +13,13 @@ class FileExplorerContainer extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile, showHiddenFiles, toggleShowHiddenFiles, theme}) => (
|
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile, showHiddenFiles, toggleShowHiddenFiles, theme}) => (
|
||||||
<FileExplorer files={files}
|
<FileExplorer files={files}
|
||||||
fetchFile={fetchFile}
|
fetchFile={fetchFile}
|
||||||
showHiddenFiles={showHiddenFiles}
|
showHiddenFiles={showHiddenFiles}
|
||||||
toggleShowHiddenFiles={toggleShowHiddenFiles}
|
toggleShowHiddenFiles={toggleShowHiddenFiles}
|
||||||
theme={theme} />
|
theme={theme} />
|
||||||
)} />
|
)} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
import React from 'react';
|
||||||
|
import {UncontrolledTooltip} from 'reactstrap';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import FontAwesome from 'react-fontawesome';
|
||||||
|
|
||||||
|
import {removeFile as removeFileAction, saveFile as saveFileAction, saveFolder as saveFolderAction} from '../actions';
|
||||||
|
import AddFileModal from '../components/AddFileModal';
|
||||||
|
import AddFolderModal from '../components/AddFolderModal';
|
||||||
|
import { getTheme } from '../reducers/selectors';
|
||||||
|
|
||||||
|
class FileExplorerRowContainer extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {active: false};
|
||||||
|
this.addFileModal = React.createRef();
|
||||||
|
this.addFolderModal = React.createRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
activateNode() {
|
||||||
|
this.setState({active : true});
|
||||||
|
}
|
||||||
|
|
||||||
|
deactivateNode() {
|
||||||
|
this.setState({active : false});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAction() {
|
||||||
|
return (
|
||||||
|
<span className="float-right mr-2">
|
||||||
|
{this.props.node.children &&
|
||||||
|
<React.Fragment>
|
||||||
|
<span id="add-file"
|
||||||
|
className="pointer"
|
||||||
|
onClick={() => this.addFileModal.current.toggle()}>
|
||||||
|
<FontAwesome name="plus" className="text-success mr-2" />
|
||||||
|
</span>
|
||||||
|
<span id="add-folder"
|
||||||
|
className="pointer"
|
||||||
|
onClick={() => this.addFolderModal.current.toggle()}>
|
||||||
|
<FontAwesome name="folder-open" className="text-success mr-2" />
|
||||||
|
</span>
|
||||||
|
<UncontrolledTooltip placement="bottom" target="add-file">
|
||||||
|
Add File
|
||||||
|
</UncontrolledTooltip>
|
||||||
|
<UncontrolledTooltip placement="bottom" target="add-folder">
|
||||||
|
Add Folder
|
||||||
|
</UncontrolledTooltip>
|
||||||
|
<AddFileModal theme={this.props.theme} node={this.props.node} saveFile={this.props.saveFile} ref={this.addFileModal} />
|
||||||
|
<AddFolderModal theme={this.props.theme} node={this.props.node} saveFolder={this.props.saveFolder} ref={this.addFolderModal} />
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
<span id="delete"
|
||||||
|
style={{cursor: "pointer"}}
|
||||||
|
onClick={() => this.props.removeFile(this.props.node)}>
|
||||||
|
<FontAwesome name="trash" className="text-danger" />
|
||||||
|
</span>
|
||||||
|
<UncontrolledTooltip placement="bottom" target="delete">
|
||||||
|
Delete
|
||||||
|
</UncontrolledTooltip>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div style={this.props.style.container}
|
||||||
|
onMouseEnter={() => this.activateNode()}
|
||||||
|
onMouseLeave={() => this.deactivateNode()}>
|
||||||
|
<span onClick={this.props.onClick}>
|
||||||
|
<this.props.decorators.Toggle style={this.props.style.toggle}/>
|
||||||
|
<this.props.decorators.Header node={this.props.node} style={this.props.style.header}/>
|
||||||
|
</span>
|
||||||
|
{this.state.active && this.renderAction()}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileExplorerRowContainer.propTypes = {
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
removeFile: PropTypes.func,
|
||||||
|
saveFile: PropTypes.func,
|
||||||
|
saveFolder: PropTypes.func,
|
||||||
|
style: PropTypes.object,
|
||||||
|
node: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
theme: getTheme(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
{
|
||||||
|
removeFile: removeFileAction.request,
|
||||||
|
saveFile: saveFileAction.request,
|
||||||
|
saveFolder: saveFolderAction.request
|
||||||
|
}
|
||||||
|
)(FileExplorerRowContainer);
|
|
@ -5,8 +5,10 @@ import TextEditorToolbar from '../components/TextEditorToolbar';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
saveFile as saveFileAction,
|
saveFile as saveFileAction,
|
||||||
removeFile as removeFileAction
|
removeFile as removeFileAction,
|
||||||
|
saveFolder as saveFolderAction
|
||||||
} from '../actions';
|
} from '../actions';
|
||||||
|
import { getRootDirname, getTheme } from '../reducers/selectors';
|
||||||
|
|
||||||
class TextEditorToolbarContainer extends Component {
|
class TextEditorToolbarContainer extends Component {
|
||||||
save() {
|
save() {
|
||||||
|
@ -22,6 +24,10 @@ class TextEditorToolbarContainer extends Component {
|
||||||
toggleShowHiddenFiles={this.props.toggleShowHiddenFiles}
|
toggleShowHiddenFiles={this.props.toggleShowHiddenFiles}
|
||||||
openAsideTab={this.props.openAsideTab}
|
openAsideTab={this.props.openAsideTab}
|
||||||
save={() => this.save()}
|
save={() => this.save()}
|
||||||
|
saveFile={this.props.saveFile}
|
||||||
|
theme={this.props.theme}
|
||||||
|
saveFolder={this.props.saveFolder}
|
||||||
|
rootDirname={this.props.rootDirname}
|
||||||
remove={() => this.remove()}
|
remove={() => this.remove()}
|
||||||
activeTab={this.props.activeTab} />;
|
activeTab={this.props.activeTab} />;
|
||||||
}
|
}
|
||||||
|
@ -29,18 +35,29 @@ class TextEditorToolbarContainer extends Component {
|
||||||
|
|
||||||
TextEditorToolbarContainer.propTypes = {
|
TextEditorToolbarContainer.propTypes = {
|
||||||
currentFile: PropTypes.object,
|
currentFile: PropTypes.object,
|
||||||
|
theme: PropTypes.string,
|
||||||
isContract: PropTypes.bool,
|
isContract: PropTypes.bool,
|
||||||
saveFile: PropTypes.func,
|
saveFile: PropTypes.func,
|
||||||
|
saveFolder: PropTypes.func,
|
||||||
removeFile: PropTypes.func,
|
removeFile: PropTypes.func,
|
||||||
|
rootDirname: PropTypes.string,
|
||||||
toggleShowHiddenFiles: PropTypes.func,
|
toggleShowHiddenFiles: PropTypes.func,
|
||||||
openAsideTab: PropTypes.func,
|
openAsideTab: PropTypes.func,
|
||||||
activeTab: PropTypes.object
|
activeTab: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
rootDirname: getRootDirname(state),
|
||||||
|
theme: getTheme(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default connect(
|
export default connect(
|
||||||
null,
|
mapStateToProps,
|
||||||
{
|
{
|
||||||
saveFile: saveFileAction.request,
|
saveFile: saveFileAction.request,
|
||||||
|
saveFolder: saveFolderAction.request,
|
||||||
removeFile: removeFileAction.request
|
removeFile: removeFileAction.request
|
||||||
},
|
},
|
||||||
)(TextEditorToolbarContainer);
|
)(TextEditorToolbarContainer);
|
||||||
|
|
|
@ -81,3 +81,11 @@
|
||||||
.bg-black {
|
.bg-black {
|
||||||
background-color: #1C1C1C;
|
background-color: #1C1C1C;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark-theme .modal-header span {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
|
@ -169,6 +169,10 @@ export function getFiles(state) {
|
||||||
return state.entities.files;
|
return state.entities.files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getRootDirname(state) {
|
||||||
|
return state.entities.files[0] && state.entities.files[0].dirname;
|
||||||
|
}
|
||||||
|
|
||||||
export function getCurrentFile(state) {
|
export function getCurrentFile(state) {
|
||||||
return state.editorTabs.find(file => file.active) || {};
|
return state.editorTabs.find(file => file.active) || {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ export const postEnsRecord = doRequest.bind(null, actions.ensRecords, api.postEn
|
||||||
export const fetchFiles = doRequest.bind(null, actions.files, api.fetchFiles);
|
export const fetchFiles = doRequest.bind(null, actions.files, api.fetchFiles);
|
||||||
export const fetchFile = doRequest.bind(null, actions.file, api.fetchFile);
|
export const fetchFile = doRequest.bind(null, actions.file, api.fetchFile);
|
||||||
export const postFile = doRequest.bind(null, actions.saveFile, api.postFile);
|
export const postFile = doRequest.bind(null, actions.saveFile, api.postFile);
|
||||||
|
export const postFolder = doRequest.bind(null, actions.saveFolder, api.postFolder);
|
||||||
export const deleteFile = doRequest.bind(null, actions.removeFile, api.deleteFile);
|
export const deleteFile = doRequest.bind(null, actions.removeFile, api.deleteFile);
|
||||||
export const fetchEthGas = doRequest.bind(null, actions.gasOracle, api.getEthGasAPI);
|
export const fetchEthGas = doRequest.bind(null, actions.gasOracle, api.getEthGasAPI);
|
||||||
export const startDebug = doRequest.bind(null, actions.startDebug, api.startDebug);
|
export const startDebug = doRequest.bind(null, actions.startDebug, api.startDebug);
|
||||||
|
@ -209,9 +210,18 @@ export function *watchPostFile() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function *watchPostFileSuccess() {
|
export function *watchPostFileSuccess() {
|
||||||
|
yield takeEvery(actions.SAVE_FILE[actions.SUCCESS], fetchFiles);
|
||||||
yield takeEvery(actions.SAVE_FILE[actions.SUCCESS], addEditorTabs);
|
yield takeEvery(actions.SAVE_FILE[actions.SUCCESS], addEditorTabs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function *watchPostFolder() {
|
||||||
|
yield takeEvery(actions.SAVE_FOLDER[actions.REQUEST], postFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function *watchPostFolderSuccess() {
|
||||||
|
yield takeEvery(actions.SAVE_FOLDER[actions.SUCCESS], fetchFiles);
|
||||||
|
}
|
||||||
|
|
||||||
export function *watchDeleteFile() {
|
export function *watchDeleteFile() {
|
||||||
yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile);
|
yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile);
|
||||||
}
|
}
|
||||||
|
@ -511,6 +521,7 @@ export default function *root() {
|
||||||
fork(watchFetchFiles),
|
fork(watchFetchFiles),
|
||||||
fork(watchFetchFile),
|
fork(watchFetchFile),
|
||||||
fork(watchPostFile),
|
fork(watchPostFile),
|
||||||
|
fork(watchPostFolder),
|
||||||
fork(watchDeleteFile),
|
fork(watchDeleteFile),
|
||||||
fork(watchDeleteFileSuccess),
|
fork(watchDeleteFileSuccess),
|
||||||
fork(watchFetchFileSuccess),
|
fork(watchFetchFileSuccess),
|
||||||
|
@ -543,6 +554,7 @@ export default function *root() {
|
||||||
fork(watchRemoveEditorTabs),
|
fork(watchRemoveEditorTabs),
|
||||||
fork(watchAddEditorTabsSuccess),
|
fork(watchAddEditorTabsSuccess),
|
||||||
fork(watchRemoveEditorTabsSuccess),
|
fork(watchRemoveEditorTabsSuccess),
|
||||||
fork(watchPostFileSuccess)
|
fork(watchPostFileSuccess),
|
||||||
|
fork(watchPostFolderSuccess)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ function request(type, path, params = {}) {
|
||||||
'X-Embark-Cnonce': cnonce
|
'X-Embark-Cnonce': cnonce
|
||||||
},
|
},
|
||||||
...(type === 'post' ? { data: params } : {}),
|
...(type === 'post' ? { data: params } : {}),
|
||||||
...(type === 'get' ? { params: params.params } : {})
|
...(['get', 'delete'].includes(type) ? { params: params.params } : {})
|
||||||
}
|
}
|
||||||
|
|
||||||
return axios(req)
|
return axios(req)
|
||||||
|
@ -169,6 +169,10 @@ export function postFile() {
|
||||||
return post('/files', ...arguments);
|
return post('/files', ...arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function postFolder() {
|
||||||
|
return post('/folders', ...arguments);
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteFile(payload) {
|
export function deleteFile(payload) {
|
||||||
return destroy('/file', {params: payload, credentials: payload.credentials});
|
return destroy('/file', {params: payload, credentials: payload.credentials});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import Convert from 'ansi-to-html';
|
import Convert from 'ansi-to-html';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
|
|
||||||
|
import {DARK_THEME} from '../constants';
|
||||||
|
|
||||||
export function last(array) {
|
export function last(array) {
|
||||||
return array && array.length ? array[array.length - 1] : undefined;
|
return array && array.length ? array[array.length - 1] : undefined;
|
||||||
}
|
}
|
||||||
|
@ -37,3 +39,5 @@ export function stripQueryToken(location) {
|
||||||
);
|
);
|
||||||
return _location;
|
return _location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isDarkTheme = (theme) => theme === DARK_THEME;
|
||||||
|
|
|
@ -29,9 +29,12 @@ class Pipeline {
|
||||||
'get',
|
'get',
|
||||||
'/embark-api/file',
|
'/embark-api/file',
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
if (!fs.existsSync(req.query.path) || !req.query.path.startsWith(fs.dappPath())) {
|
try {
|
||||||
return res.send({error: 'Path is invalid'});
|
this.apiGuardBadFile(req.query.path);
|
||||||
|
} catch (error) {
|
||||||
|
return res.send({error: error.message});
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = path.basename(req.query.path);
|
const name = path.basename(req.query.path);
|
||||||
const content = fs.readFileSync(req.query.path, 'utf8');
|
const content = fs.readFileSync(req.query.path, 'utf8');
|
||||||
res.send({name, content, path: req.query.path});
|
res.send({name, content, path: req.query.path});
|
||||||
|
@ -39,6 +42,22 @@ class Pipeline {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
plugin.registerAPICall(
|
||||||
|
'post',
|
||||||
|
'/embark-api/folders',
|
||||||
|
(req, res) => {
|
||||||
|
try {
|
||||||
|
this.apiGuardBadFile(req.body.path);
|
||||||
|
} catch (error) {
|
||||||
|
return res.send({error: error.message});
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirpSync(req.body.path);
|
||||||
|
const name = path.basename(req.body.path);
|
||||||
|
res.send({name, path: req.body.path});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
plugin.registerAPICall(
|
plugin.registerAPICall(
|
||||||
'post',
|
'post',
|
||||||
'/embark-api/files',
|
'/embark-api/files',
|
||||||
|
@ -60,7 +79,7 @@ class Pipeline {
|
||||||
'/embark-api/file',
|
'/embark-api/file',
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
try {
|
try {
|
||||||
this.apiGuardBadFile(req.query.path);
|
this.apiGuardBadFile(req.query.path, {ensureExists: true});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.send({error: error.message});
|
return res.send({error: error.message});
|
||||||
}
|
}
|
||||||
|
@ -100,10 +119,14 @@ class Pipeline {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
apiGuardBadFile(pathToCheck) {
|
apiGuardBadFile(pathToCheck, options = {ensureExists: false}) {
|
||||||
const dir = path.dirname(pathToCheck);
|
const dir = path.dirname(pathToCheck);
|
||||||
if (!fs.existsSync(pathToCheck) || !dir.startsWith(fs.dappPath())) {
|
const error = new Error('Path is invalid');
|
||||||
throw new Error('Path is invalid');
|
if (options.ensureExists && !fs.existsSync(pathToCheck)) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (!dir.startsWith(fs.dappPath())) {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue