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})
|
||||
};
|
||||
|
||||
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 removeFile = {
|
||||
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})
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {Label} from 'reactstrap';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Treebeard, decorators} from 'react-treebeard';
|
||||
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) => ({
|
||||
tree: {
|
||||
|
@ -77,67 +77,73 @@ const style = (theme) => ({
|
|||
}
|
||||
}
|
||||
});
|
||||
class Header extends React.Component {
|
||||
resolveIcon() {
|
||||
let icon;
|
||||
let {node} = this.props;
|
||||
|
||||
|
||||
const Header = ({style, node}) => {
|
||||
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 fa fa-file-text';
|
||||
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:
|
||||
icon = 'fa fa-folder';
|
||||
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 fa fa-file-text';
|
||||
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:
|
||||
icon = 'fa fa-folder';
|
||||
}
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-1" style={style.base}>
|
||||
<div style={style.title}>
|
||||
<i className={classNames('mr-1', icon)} />
|
||||
{node.name}
|
||||
render() {
|
||||
let {node, style} = this.props;
|
||||
return (
|
||||
<div className="mb-1 d-inline-block"
|
||||
style={style.base}>
|
||||
<div style={style.title}>
|
||||
<i className={classNames('mr-1', this.resolveIcon())} />
|
||||
{node.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
Header.propTypes = {
|
||||
|
@ -146,6 +152,7 @@ Header.propTypes = {
|
|||
};
|
||||
|
||||
decorators.Header = Header;
|
||||
decorators.Container = FileExplorerRowContainer;
|
||||
|
||||
class FileExplorer extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
|
@ -4,6 +4,9 @@ import {Button, Nav, NavLink} from 'reactstrap';
|
|||
import classnames from 'classnames';
|
||||
import FontAwesomeIcon from 'react-fontawesome';
|
||||
|
||||
import AddFileModal from '../components/AddFileModal';
|
||||
import AddFolderModal from '../components/AddFolderModal';
|
||||
|
||||
export const TextEditorToolbarTabs = {
|
||||
Interact: { label: 'Interact', icon: 'bolt' },
|
||||
Details: { label: 'Details', icon: 'info-circle' },
|
||||
|
@ -13,6 +16,11 @@ export const TextEditorToolbarTabs = {
|
|||
};
|
||||
|
||||
class TextEditorToolbar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.addFileModal = React.createRef();
|
||||
this.addFolderModal = React.createRef();
|
||||
}
|
||||
|
||||
isActiveTab(tab) {
|
||||
return this.props.activeTab === tab;
|
||||
|
@ -38,6 +46,16 @@ class TextEditorToolbar extends Component {
|
|||
return (
|
||||
<ol className="breadcrumb mb-0">
|
||||
<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}>
|
||||
<FontAwesomeIcon className="mr-2" name="save"/>
|
||||
Save
|
||||
|
@ -61,7 +79,11 @@ class TextEditorToolbar extends Component {
|
|||
|
||||
TextEditorToolbar.propTypes = {
|
||||
isContract: PropTypes.bool,
|
||||
theme: PropTypes.string,
|
||||
save: PropTypes.func,
|
||||
saveFile: PropTypes.func,
|
||||
rootDirname: PropTypes.string,
|
||||
saveFolder: PropTypes.func,
|
||||
remove: PropTypes.func,
|
||||
toggleShowHiddenFiles: PropTypes.func,
|
||||
openAsideTab: PropTypes.func,
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, {Component} from 'react';
|
|||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {files as filesAction, file as fileAction} from "../actions";
|
||||
|
||||
import FileExplorer from '../components/FileExplorer';
|
||||
import DataWrapper from "../components/DataWrapper";
|
||||
import {getFiles, getTheme} from "../reducers/selectors";
|
||||
|
@ -14,13 +13,13 @@ class FileExplorerContainer extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile, showHiddenFiles, toggleShowHiddenFiles, theme}) => (
|
||||
<FileExplorer files={files}
|
||||
fetchFile={fetchFile}
|
||||
showHiddenFiles={showHiddenFiles}
|
||||
toggleShowHiddenFiles={toggleShowHiddenFiles}
|
||||
theme={theme} />
|
||||
)} />
|
||||
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile, showHiddenFiles, toggleShowHiddenFiles, theme}) => (
|
||||
<FileExplorer files={files}
|
||||
fetchFile={fetchFile}
|
||||
showHiddenFiles={showHiddenFiles}
|
||||
toggleShowHiddenFiles={toggleShowHiddenFiles}
|
||||
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 {
|
||||
saveFile as saveFileAction,
|
||||
removeFile as removeFileAction
|
||||
removeFile as removeFileAction,
|
||||
saveFolder as saveFolderAction
|
||||
} from '../actions';
|
||||
import { getRootDirname, getTheme } from '../reducers/selectors';
|
||||
|
||||
class TextEditorToolbarContainer extends Component {
|
||||
save() {
|
||||
|
@ -22,6 +24,10 @@ class TextEditorToolbarContainer extends Component {
|
|||
toggleShowHiddenFiles={this.props.toggleShowHiddenFiles}
|
||||
openAsideTab={this.props.openAsideTab}
|
||||
save={() => this.save()}
|
||||
saveFile={this.props.saveFile}
|
||||
theme={this.props.theme}
|
||||
saveFolder={this.props.saveFolder}
|
||||
rootDirname={this.props.rootDirname}
|
||||
remove={() => this.remove()}
|
||||
activeTab={this.props.activeTab} />;
|
||||
}
|
||||
|
@ -29,18 +35,29 @@ class TextEditorToolbarContainer extends Component {
|
|||
|
||||
TextEditorToolbarContainer.propTypes = {
|
||||
currentFile: PropTypes.object,
|
||||
theme: PropTypes.string,
|
||||
isContract: PropTypes.bool,
|
||||
saveFile: PropTypes.func,
|
||||
saveFolder: PropTypes.func,
|
||||
removeFile: PropTypes.func,
|
||||
rootDirname: PropTypes.string,
|
||||
toggleShowHiddenFiles: PropTypes.func,
|
||||
openAsideTab: PropTypes.func,
|
||||
activeTab: PropTypes.object
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
rootDirname: getRootDirname(state),
|
||||
theme: getTheme(state)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapStateToProps,
|
||||
{
|
||||
saveFile: saveFileAction.request,
|
||||
saveFolder: saveFolderAction.request,
|
||||
removeFile: removeFileAction.request
|
||||
},
|
||||
)(TextEditorToolbarContainer);
|
||||
|
|
|
@ -81,3 +81,11 @@
|
|||
.bg-black {
|
||||
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;
|
||||
}
|
||||
|
||||
export function getRootDirname(state) {
|
||||
return state.entities.files[0] && state.entities.files[0].dirname;
|
||||
}
|
||||
|
||||
export function getCurrentFile(state) {
|
||||
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 fetchFile = doRequest.bind(null, actions.file, api.fetchFile);
|
||||
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 fetchEthGas = doRequest.bind(null, actions.gasOracle, api.getEthGasAPI);
|
||||
export const startDebug = doRequest.bind(null, actions.startDebug, api.startDebug);
|
||||
|
@ -209,9 +210,18 @@ export function *watchPostFile() {
|
|||
}
|
||||
|
||||
export function *watchPostFileSuccess() {
|
||||
yield takeEvery(actions.SAVE_FILE[actions.SUCCESS], fetchFiles);
|
||||
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() {
|
||||
yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile);
|
||||
}
|
||||
|
@ -511,6 +521,7 @@ export default function *root() {
|
|||
fork(watchFetchFiles),
|
||||
fork(watchFetchFile),
|
||||
fork(watchPostFile),
|
||||
fork(watchPostFolder),
|
||||
fork(watchDeleteFile),
|
||||
fork(watchDeleteFileSuccess),
|
||||
fork(watchFetchFileSuccess),
|
||||
|
@ -543,6 +554,7 @@ export default function *root() {
|
|||
fork(watchRemoveEditorTabs),
|
||||
fork(watchAddEditorTabsSuccess),
|
||||
fork(watchRemoveEditorTabsSuccess),
|
||||
fork(watchPostFileSuccess)
|
||||
fork(watchPostFileSuccess),
|
||||
fork(watchPostFolderSuccess)
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ function request(type, path, params = {}) {
|
|||
'X-Embark-Cnonce': cnonce
|
||||
},
|
||||
...(type === 'post' ? { data: params } : {}),
|
||||
...(type === 'get' ? { params: params.params } : {})
|
||||
...(['get', 'delete'].includes(type) ? { params: params.params } : {})
|
||||
}
|
||||
|
||||
return axios(req)
|
||||
|
@ -169,6 +169,10 @@ export function postFile() {
|
|||
return post('/files', ...arguments);
|
||||
}
|
||||
|
||||
export function postFolder() {
|
||||
return post('/folders', ...arguments);
|
||||
}
|
||||
|
||||
export function deleteFile(payload) {
|
||||
return destroy('/file', {params: payload, credentials: payload.credentials});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import Convert from 'ansi-to-html';
|
||||
import qs from 'qs';
|
||||
|
||||
import {DARK_THEME} from '../constants';
|
||||
|
||||
export function last(array) {
|
||||
return array && array.length ? array[array.length - 1] : undefined;
|
||||
}
|
||||
|
@ -37,3 +39,5 @@ export function stripQueryToken(location) {
|
|||
);
|
||||
return _location;
|
||||
}
|
||||
|
||||
export const isDarkTheme = (theme) => theme === DARK_THEME;
|
||||
|
|
|
@ -29,9 +29,12 @@ class Pipeline {
|
|||
'get',
|
||||
'/embark-api/file',
|
||||
(req, res) => {
|
||||
if (!fs.existsSync(req.query.path) || !req.query.path.startsWith(fs.dappPath())) {
|
||||
return res.send({error: 'Path is invalid'});
|
||||
try {
|
||||
this.apiGuardBadFile(req.query.path);
|
||||
} catch (error) {
|
||||
return res.send({error: error.message});
|
||||
}
|
||||
|
||||
const name = path.basename(req.query.path);
|
||||
const content = fs.readFileSync(req.query.path, 'utf8');
|
||||
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(
|
||||
'post',
|
||||
'/embark-api/files',
|
||||
|
@ -60,7 +79,7 @@ class Pipeline {
|
|||
'/embark-api/file',
|
||||
(req, res) => {
|
||||
try {
|
||||
this.apiGuardBadFile(req.query.path);
|
||||
this.apiGuardBadFile(req.query.path, {ensureExists: true});
|
||||
} catch (error) {
|
||||
return res.send({error: error.message});
|
||||
}
|
||||
|
@ -100,10 +119,14 @@ class Pipeline {
|
|||
);
|
||||
}
|
||||
|
||||
apiGuardBadFile(pathToCheck) {
|
||||
apiGuardBadFile(pathToCheck, options = {ensureExists: false}) {
|
||||
const dir = path.dirname(pathToCheck);
|
||||
if (!fs.existsSync(pathToCheck) || !dir.startsWith(fs.dappPath())) {
|
||||
throw new Error('Path is invalid');
|
||||
const error = 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