mirror of https://github.com/embarklabs/embark.git
File Editor
This commit is contained in:
parent
5b16ce0691
commit
a08690ef43
File diff suppressed because it is too large
Load Diff
|
@ -125,6 +125,13 @@ export const contractDeploy = {
|
|||
failure: (error) => action(CONTRACT_DEPLOY[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const CONTRACT_COMPILE = createRequestTypes('CONTRACT_COMPILE');
|
||||
export const contractCompile = {
|
||||
post: (code, name) => action(CONTRACT_COMPILE[REQUEST], {code, name}),
|
||||
success: (result, payload) => action(CONTRACT_COMPILE[SUCCESS], {contractCompiles: [{...result, ...payload}]}),
|
||||
failure: (error) => action(CONTRACT_COMPILE[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const VERSIONS = createRequestTypes('VERSIONS');
|
||||
export const versions = {
|
||||
request: () => action(VERSIONS[REQUEST]),
|
||||
|
@ -168,31 +175,6 @@ export const ensRecords = {
|
|||
failure: (error) => action(ENS_RECORDS[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const FIDDLE = createRequestTypes('FIDDLE');
|
||||
export const fiddle = {
|
||||
post: (codeToCompile, timestamp) => action(FIDDLE[REQUEST], {codeToCompile, timestamp}),
|
||||
success: (fiddle, payload) => {
|
||||
return action(FIDDLE[SUCCESS], {fiddles: [{...fiddle, ...payload}]});
|
||||
},
|
||||
failure: (error) => action(FIDDLE[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const FIDDLE_DEPLOY = createRequestTypes('FIDDLE_DEPLOY');
|
||||
export const fiddleDeploy = {
|
||||
post: (compiledCode) => action(FIDDLE_DEPLOY[REQUEST], {compiledCode}),
|
||||
success: (response) => {
|
||||
return action(FIDDLE_DEPLOY[SUCCESS], {fiddleDeploys: response.result});
|
||||
},
|
||||
failure: (error) => action(FIDDLE_DEPLOY[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const FIDDLE_FILE = createRequestTypes('FIDDLE_FILE');
|
||||
export const fiddleFile = {
|
||||
request: () => action(FIDDLE_FILE[REQUEST]),
|
||||
success: (codeToCompile) => action(FIDDLE_FILE[SUCCESS], {codeToCompile}),
|
||||
failure: (error) => action(FIDDLE_FILE[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const FILES = createRequestTypes('FILES');
|
||||
export const files = {
|
||||
request: () => action(FILES[REQUEST]),
|
||||
|
@ -200,6 +182,43 @@ export const files = {
|
|||
failure: (error) => action(FILES[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const FILE = createRequestTypes('FILE');
|
||||
export const file = {
|
||||
request: (file) => action(FILE[REQUEST], file),
|
||||
success: (file) => action(FILE[SUCCESS], file),
|
||||
failure: (error) => action(FILE[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const SAVE_FILE = createRequestTypes('SAVE_FILE');
|
||||
export const saveFile = {
|
||||
request: ({name, path, content}) => {
|
||||
return action(SAVE_FILE[REQUEST], {name, path, content});
|
||||
},
|
||||
success: () => action(SAVE_FILE[SUCCESS]),
|
||||
failure: (error) => action(SAVE_FILE[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]),
|
||||
failure: (error) => action(REMOVE_FILE[FAILURE], {error})
|
||||
};
|
||||
|
||||
export const CURRENT_FILE = createRequestTypes('CURRENT_FILE');
|
||||
export const currentFile = {
|
||||
request: () => action(CURRENT_FILE[REQUEST]),
|
||||
success: (file) => action(CURRENT_FILE[SUCCESS], {currentFiles: [file]}),
|
||||
failure: () => action(CURRENT_FILE[FAILURE])
|
||||
};
|
||||
|
||||
export const SAVE_CURRENT_FILE = createRequestTypes('SAVE_CURRENT_FILE');
|
||||
export const saveCurrentFile = {
|
||||
request: (file) => action(SAVE_CURRENT_FILE[REQUEST], file),
|
||||
success: (file) => action(SAVE_CURRENT_FILE[SUCCESS], {currentFiles: [file]}),
|
||||
failure: () => action(SAVE_CURRENT_FILE[FAILURE])
|
||||
};
|
||||
|
||||
export const GAS_ORACLE = createRequestTypes('GAS_ORACLE');
|
||||
export const gasOracle = {
|
||||
request: () => action(GAS_ORACLE[REQUEST]),
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Badge} from 'tabler-react';
|
||||
|
||||
const CompilerError = ({ index, onClick, errorType, row, errorMessage}) => (
|
||||
<a
|
||||
href="#editor"
|
||||
className="list-group-item list-group-item-action"
|
||||
onClick={onClick}
|
||||
key={index}
|
||||
>
|
||||
<Badge color={errorType === "error" ? "danger" : errorType} className="mr-1" key={index}>
|
||||
Line {row}
|
||||
</Badge>
|
||||
{errorMessage}
|
||||
</a>
|
||||
);
|
||||
|
||||
CompilerError.propTypes = {
|
||||
index: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
errorType: PropTypes.string,
|
||||
row: PropTypes.string,
|
||||
errorMessage: PropTypes.string
|
||||
};
|
||||
|
||||
export default CompilerError;
|
|
@ -1,53 +0,0 @@
|
|||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import 'brace/mode/javascript';
|
||||
import 'brace/theme/tomorrow_night_blue';
|
||||
import 'ace-mode-solidity/build/remix-ide/mode-solidity';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class Fiddle extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.ace = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {onCodeChange, value, errors, warnings} = this.props;
|
||||
const annotations = errors && errors.map((error) => { return error.annotation; }).concat(warnings.map(warning => { return warning.annotation; }));
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
<AceEditor
|
||||
mode="solidity"
|
||||
theme="tomorrow_night_blue"
|
||||
name="fiddle"
|
||||
height="60em"
|
||||
width="100%"
|
||||
onChange={onCodeChange}
|
||||
value={value}
|
||||
showGutter={true}
|
||||
annotations={annotations}
|
||||
ref={(ace) => { this.ace = ace; }}
|
||||
setOptions={{
|
||||
useWorker: false
|
||||
}}
|
||||
editorProps={{
|
||||
$blockScrolling: Infinity,
|
||||
enableLiveAutocompletion:true,
|
||||
highlightSelectedWord: true
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Fiddle.propTypes = {
|
||||
onCodeChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
errors: PropTypes.array,
|
||||
warnings: PropTypes.array
|
||||
};
|
||||
|
||||
export default Fiddle;
|
|
@ -1,19 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Button} from 'tabler-react';
|
||||
|
||||
const FiddleDeployButton = ({ onDeployClick }) => (
|
||||
<Button
|
||||
color="dark"
|
||||
size="sm"
|
||||
icon="upload-cloud"
|
||||
onClick={onDeployClick}>
|
||||
Deploy
|
||||
</Button>
|
||||
);
|
||||
|
||||
FiddleDeployButton.propTypes = {
|
||||
onDeployClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default FiddleDeployButton;
|
|
@ -1,25 +1,26 @@
|
|||
import React from 'react';
|
||||
import {Route, Switch} from 'react-router-dom';
|
||||
import {
|
||||
Page,
|
||||
Grid
|
||||
} from "tabler-react";
|
||||
|
||||
import FiddleContainer from '../containers/FiddleContainer';
|
||||
import TextEditorContainer from '../containers/TextEditorContainer';
|
||||
import FileExplorerContainer from '../containers/FileExplorerContainer';
|
||||
|
||||
const ExplorerLayout = () => (
|
||||
<Grid.Row>
|
||||
class FiddleLayout extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Grid.Row className="my-5">
|
||||
<Grid.Col md={3}>
|
||||
<Page.Title className="my-5">Fiddle</Page.Title>
|
||||
<Page.Title>Fiddle</Page.Title>
|
||||
<FileExplorerContainer />
|
||||
</Grid.Col>
|
||||
<Grid.Col md={9}>
|
||||
<Switch>
|
||||
<Route exact path="/embark/fiddle/" component={FiddleContainer} />
|
||||
</Switch>
|
||||
<TextEditorContainer />
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ExplorerLayout;
|
||||
export default FiddleLayout;
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const FiddleResults = ({warningsCard, errorsCard, fatalFiddleCard, fatalFiddleDeployCard, deployedContractsCard, fatalErrorCard, forwardedRef}) => (
|
||||
<div ref={forwardedRef}>
|
||||
{fatalErrorCard}
|
||||
{fatalFiddleCard}
|
||||
{fatalFiddleDeployCard}
|
||||
{deployedContractsCard}
|
||||
{errorsCard}
|
||||
{warningsCard}
|
||||
</div>
|
||||
);
|
||||
|
||||
FiddleResults.propTypes = {
|
||||
errorsCard: PropTypes.node,
|
||||
warningsCard: PropTypes.node,
|
||||
fatalFiddleCard: PropTypes.node,
|
||||
fatalFiddleDeployCard: PropTypes.node,
|
||||
deployedContractsCard: PropTypes.node,
|
||||
fatalErrorCard: PropTypes.node,
|
||||
forwardedRef: PropTypes.any
|
||||
};
|
||||
|
||||
export default FiddleResults;
|
|
@ -1,69 +0,0 @@
|
|||
import React, {Component} from 'react';
|
||||
import {Badge, Icon, Loader} from 'tabler-react';
|
||||
import PropTypes from 'prop-types';
|
||||
import FiddleDeployButton from './FiddleDeployButton';
|
||||
import classNames from 'classnames';
|
||||
|
||||
class FiddleResultsSummary extends Component {
|
||||
|
||||
_renderFatal(fatalType, title) {
|
||||
return <a className="badge-link" href={`#fatal-${fatalType}`} onClick={(e) => this.props.onFatalClick(e)}><Badge color="danger"><Icon name="slash" className="mr-1" />{title}</Badge></a>;
|
||||
}
|
||||
|
||||
_renderError(errorType, numErrors) {
|
||||
const color = errorType === 'error' ? 'danger' : 'warning';
|
||||
const clickAction = errorType === 'error' ? this.props.onWarningsClick : this.props.onErrorsClick;
|
||||
return <a className="badge-link" href={`#${errorType}`} onClick={(e) => clickAction(e)}><Badge color={color}>{numErrors} {errorType}{numErrors > 1 ? "s" : ""}</Badge></a>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {numWarnings, numErrors, isLoading, loadingMessage, isVisible, showDeploy, showFatalFiddle, showFatalFiddleDeploy, showFatalError} = this.props;
|
||||
const classes = classNames("compilation-summary", {
|
||||
'visible': isVisible
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
{isLoading &&
|
||||
<Loader className="mr-1">
|
||||
<span className="loader-text">{loadingMessage}</span>
|
||||
</Loader>}
|
||||
|
||||
{showFatalError && this._renderFatal("error", "Error")}
|
||||
|
||||
{showFatalFiddle && this._renderFatal("compile", "Compilation")}
|
||||
|
||||
{showFatalFiddleDeploy && this._renderFatal("deploy", "Deployment")}
|
||||
|
||||
{numErrors > 0 && this._renderError("error", numErrors)}
|
||||
|
||||
{numWarnings > 0 && this._renderError("warning", numWarnings)}
|
||||
|
||||
{showDeploy &&
|
||||
<React.Fragment key="success">
|
||||
<Badge className="badge-link" color="success">Compiled</Badge>
|
||||
<FiddleDeployButton onDeployClick={(e) => this.props.onDeployClick(e)} />
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FiddleResultsSummary.propTypes = {
|
||||
isLoading: PropTypes.bool,
|
||||
loadingMessage: PropTypes.string,
|
||||
isVisible: PropTypes.bool,
|
||||
showDeploy: PropTypes.bool,
|
||||
showFatalError: PropTypes.bool,
|
||||
showFatalFiddle: PropTypes.bool,
|
||||
showFatalFiddleDeploy: PropTypes.bool,
|
||||
onDeployClick: PropTypes.func,
|
||||
numErrors: PropTypes.number,
|
||||
numWarnings: PropTypes.number,
|
||||
onWarningsClick: PropTypes.func.isRequired,
|
||||
onErrorsClick: PropTypes.func.isRequired,
|
||||
onFatalClick: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default FiddleResultsSummary;
|
|
@ -29,26 +29,35 @@ class FileExplorer extends React.Component {
|
|||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
onToggle(node, toggled){
|
||||
let oldNode = this.state.cursor;
|
||||
if(oldNode) {
|
||||
oldNode.active = false;
|
||||
}
|
||||
node.active = true;
|
||||
if(node.children) {
|
||||
node.toggled = toggled;
|
||||
} else {
|
||||
this.props.fetchFile(node);
|
||||
}
|
||||
this.setState({ cursor: node });
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<Treebeard
|
||||
data={this.props.files}
|
||||
decorators={decorators}
|
||||
onToggle={(node, toggled) => this.onToggle(node, toggled)}
|
||||
onToggle={this.onToggle.bind(this)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FileExplorer.propTypes = {
|
||||
files: PropTypes.array
|
||||
files: PropTypes.array,
|
||||
fetchFile: PropTypes.func
|
||||
};
|
||||
|
||||
export default FileExplorer;
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
/* eslint {jsx-a11y/anchor-has-content:"off"} */
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Card, Icon, Dimmer} from 'tabler-react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
class LoadingCardWithIcon extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
errorsCollapsed: false,
|
||||
warningsCollapsed: false,
|
||||
errorsFullscreen: false,
|
||||
warningsFullscreen: false
|
||||
};
|
||||
}
|
||||
|
||||
_onToggle(e, type) {
|
||||
const className = e.currentTarget.parentElement.className.replace('card-options', '').replace(' ', '');
|
||||
const updatedState = {};
|
||||
updatedState[className + type] = !(this.state[className + type]);
|
||||
this.setState(updatedState);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
color,
|
||||
className,
|
||||
iconName,
|
||||
headerTitle,
|
||||
isLoading,
|
||||
body,
|
||||
showCardOptions = true,
|
||||
cardOptionsClassName} = this.props;
|
||||
|
||||
const isFullscreen = Boolean(this.state[cardOptionsClassName + 'Fullscreen']);
|
||||
const classes = classNames(className, {
|
||||
'card-fullscreen': showCardOptions && Boolean(this.state[cardOptionsClassName + 'Fullscreen']),
|
||||
'card-collapsed': showCardOptions && Boolean(this.state[cardOptionsClassName + 'Collapsed']) && !isFullscreen
|
||||
});
|
||||
|
||||
return (
|
||||
<Card
|
||||
statusColor={color}
|
||||
statusSide="true"
|
||||
className={classes}
|
||||
isCollapsible={showCardOptions}
|
||||
isFullscreenable={showCardOptions}>
|
||||
<Card.Header>
|
||||
<Card.Title color={color}>{iconName && <Icon name={iconName} className="mr-1" />}{headerTitle}</Card.Title>
|
||||
{showCardOptions &&
|
||||
<Card.Options className={cardOptionsClassName}>
|
||||
<Card.OptionsItem key="0" type="collapse" icon="chevron-up" onClick={(e) => this._onToggle(e, 'Collapsed')} />
|
||||
<Card.OptionsItem key="1" type="fullscreen" icon="maximize" onClick={(e) => this._onToggle(e, 'Fullscreen')} />
|
||||
</Card.Options>
|
||||
}
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Dimmer active={isLoading ? "active" : ""} loader>
|
||||
{body}
|
||||
</Dimmer>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
LoadingCardWithIcon.propTypes = {
|
||||
color: PropTypes.string.isRequired,
|
||||
className: PropTypes.string.isRequired,
|
||||
iconName: PropTypes.string,
|
||||
headerTitle: PropTypes.any,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
body: PropTypes.node,
|
||||
showCardOptions: PropTypes.bool,
|
||||
onOptionToggle: PropTypes.func,
|
||||
cardOptionsClassName: PropTypes.string
|
||||
};
|
||||
|
||||
export default LoadingCardWithIcon;
|
|
@ -0,0 +1,61 @@
|
|||
import React from 'react';
|
||||
import AceEditor from 'react-ace';
|
||||
import 'brace/mode/javascript';
|
||||
import 'brace/theme/tomorrow_night_blue';
|
||||
import 'ace-mode-solidity/build/remix-ide/mode-solidity';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class TextEditor extends React.Component {
|
||||
extractRowCol(errorMessage) {
|
||||
const errorSplit = errorMessage.split(':');
|
||||
if (errorSplit.length >= 3) {
|
||||
return {row: errorSplit[1], col: errorSplit[2]};
|
||||
}
|
||||
return {row: 0, col: 0};
|
||||
}
|
||||
|
||||
annotations() {
|
||||
const {errors, warnings} = this.props.contractCompile;
|
||||
return [].concat(errors).concat(warnings).filter((e) => e).map((e) => {
|
||||
const rowCol = this.extractRowCol(e.formattedMessage);
|
||||
return Object.assign({}, {
|
||||
row: rowCol.row - 1,
|
||||
column: rowCol.col - 1,
|
||||
text: e.formattedMessage,
|
||||
type: e.severity
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AceEditor
|
||||
mode="solidity"
|
||||
theme="tomorrow_night_blue"
|
||||
name="fiddle"
|
||||
height="60em"
|
||||
width="100%"
|
||||
onChange={this.props.onFileContentChange}
|
||||
value={this.props.value}
|
||||
showGutter={true}
|
||||
annotations={this.annotations()}
|
||||
setOptions={{
|
||||
useWorker: false
|
||||
}}
|
||||
editorProps={{
|
||||
$blockScrolling: Infinity,
|
||||
enableLiveAutocompletion:true,
|
||||
highlightSelectedWord: true
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TextEditor.propTypes = {
|
||||
onFileContentChange: PropTypes.func,
|
||||
value: PropTypes.string,
|
||||
contractCompile: PropTypes.object
|
||||
};
|
||||
|
||||
export default TextEditor;
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import {Card, Icon, Button} from 'tabler-react';
|
||||
|
||||
const TextEditorContractDeploy = (props) => (
|
||||
<Card statusColor="success"
|
||||
statusSide
|
||||
className="success-card">
|
||||
<Card.Header>
|
||||
<Card.Title color="success">
|
||||
<Icon name="check" className="mr-1" />
|
||||
Deploy Contract
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<Button to={`/embark/contracts/${Object.keys(props.result)[0]}/deployment`}
|
||||
RootComponent={NavLink}>
|
||||
Deploy my contract(s)
|
||||
</Button>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
|
||||
TextEditorContractDeploy.propTypes = {
|
||||
result: PropTypes.object
|
||||
};
|
||||
|
||||
export default TextEditorContractDeploy;
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Card, Icon, List} from 'tabler-react';
|
||||
|
||||
const TextEditorContractErrors = (props) => (
|
||||
<Card statusColor="danger"
|
||||
statusSide
|
||||
className="errors-card">
|
||||
<Card.Header>
|
||||
<Card.Title color="danger">
|
||||
<Icon name="alert-circle" className="mr-1" />
|
||||
Failed to compile
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<List.Group>
|
||||
{props.errors.map((error, index) => <List.GroupItem key={index}>{error.formattedMessage}</List.GroupItem>)}
|
||||
</List.Group>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
|
||||
TextEditorContractErrors.propTypes = {
|
||||
errors: PropTypes.array
|
||||
};
|
||||
|
||||
export default TextEditorContractErrors;
|
|
@ -0,0 +1,31 @@
|
|||
import React, {Component} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Grid, Badge, Icon} from 'tabler-react';
|
||||
import TextEditorToolbar from './TextEditorToolbar';
|
||||
|
||||
class TextEditorContractToolbar extends Component {
|
||||
render(){
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TextEditorToolbar {...this.props} />
|
||||
<Grid.Col md={6} className="text-right">
|
||||
{this.props.compilingContract &&
|
||||
<Badge color="warning"><Icon name="slash" className="mr-1" />compiling</Badge>}
|
||||
{!this.props.compilingContract && this.props.contractCompile.result &&
|
||||
<Badge color="success"><Icon name="check" className="mr-1" />compiled</Badge>}
|
||||
</Grid.Col>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TextEditorContractToolbar.propTypes = {
|
||||
currentFile: PropTypes.object,
|
||||
contractCompile: PropTypes.object,
|
||||
compilingContract: PropTypes.bool,
|
||||
deploy: PropTypes.func,
|
||||
save: PropTypes.func,
|
||||
remove: PropTypes.func
|
||||
};
|
||||
|
||||
export default TextEditorContractToolbar;
|
|
@ -0,0 +1,27 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Card, Icon, List} from 'tabler-react';
|
||||
|
||||
const TextEditorContractWarnings = (props) => (
|
||||
<Card statusColor="warning"
|
||||
statusSide
|
||||
className="warnings-card">
|
||||
<Card.Header>
|
||||
<Card.Title color="warning">
|
||||
<Icon name="alert-triangle" className="mr-1" />
|
||||
Warning during compilation
|
||||
</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Body>
|
||||
<List.Group>
|
||||
{props.warnings.map((warning, index) => <List.GroupItem key={index}>{warning.formattedMessage}</List.GroupItem>)}
|
||||
</List.Group>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
|
||||
TextEditorContractWarnings.propTypes = {
|
||||
warnings: PropTypes.array
|
||||
};
|
||||
|
||||
export default TextEditorContractWarnings;
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Grid, Button} from 'tabler-react';
|
||||
|
||||
const TextEditorToolbar = (props) => (
|
||||
<Grid.Col md={6}>
|
||||
<strong>{props.currentFile.name}</strong>
|
||||
<span className="mx-2">|</span>
|
||||
<Button color="green" size="sm" icon="save" onClick={props.save}>Save</Button>
|
||||
<span className="mx-2">|</span>
|
||||
<Button color="red" size="sm" icon="delete" onClick={props.remove}>Delete</Button>
|
||||
</Grid.Col>
|
||||
);
|
||||
|
||||
TextEditorToolbar.propTypes = {
|
||||
currentFile: PropTypes.object,
|
||||
save: PropTypes.func,
|
||||
remove: PropTypes.func
|
||||
};
|
||||
|
||||
export default TextEditorToolbar;
|
|
@ -4,21 +4,21 @@ import PropTypes from 'prop-types';
|
|||
import {withRouter} from 'react-router-dom';
|
||||
import {Page} from "tabler-react";
|
||||
|
||||
import {contractFile as contractFileAction} from '../actions';
|
||||
import {file as FileAction} from '../actions';
|
||||
import DataWrapper from "../components/DataWrapper";
|
||||
import Fiddle from "../components/Fiddle";
|
||||
import {getContract, getContractFile} from "../reducers/selectors";
|
||||
import TextEditor from "../components/TextEditor";
|
||||
import {getContract, getCurrentFile} from "../reducers/selectors";
|
||||
|
||||
class ContractSourceContainer extends Component {
|
||||
componentDidMount() {
|
||||
this.props.fetchContractFile(this.props.contract.filename);
|
||||
this.props.fetchFile({path: this.props.contract.path});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Page.Content title={`${this.props.contract.className} Source`}>
|
||||
<DataWrapper shouldRender={this.props.contractFile !== undefined } {...this.props} render={({contractFile}) => (
|
||||
<Fiddle value={contractFile.source} />
|
||||
<DataWrapper shouldRender={this.props.file !== undefined } {...this.props} render={({file}) => (
|
||||
<TextEditor value={file.content} contractCompile={{}} />
|
||||
)} />
|
||||
</Page.Content>
|
||||
);
|
||||
|
@ -27,11 +27,11 @@ class ContractSourceContainer extends Component {
|
|||
|
||||
function mapStateToProps(state, props) {
|
||||
const contract = getContract(state, props.match.params.contractName);
|
||||
const contractFile = getContractFile(state, contract.filename);
|
||||
const file = getCurrentFile(state);
|
||||
|
||||
return {
|
||||
contract,
|
||||
contractFile,
|
||||
file,
|
||||
error: state.errorMessage,
|
||||
loading: state.loading
|
||||
};
|
||||
|
@ -40,14 +40,14 @@ function mapStateToProps(state, props) {
|
|||
ContractSourceContainer.propTypes = {
|
||||
match: PropTypes.object,
|
||||
contract: PropTypes.object,
|
||||
contractFile: PropTypes.object,
|
||||
fetchContractFile: PropTypes.func,
|
||||
file: PropTypes.object,
|
||||
fetchFile: PropTypes.func,
|
||||
error: PropTypes.string
|
||||
};
|
||||
|
||||
export default withRouter(connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchContractFile: contractFileAction.request
|
||||
fetchFile: FileAction.request
|
||||
}
|
||||
)(ContractSourceContainer));
|
||||
|
|
|
@ -1,261 +0,0 @@
|
|||
/* eslint multiline-ternary: "off" */
|
||||
/* eslint operator-linebreak: "off" */
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
fiddle as fiddleAction,
|
||||
fiddleDeploy as fiddleDeployAction,
|
||||
fiddleFile as fiddleFileAction
|
||||
} from '../actions';
|
||||
import Fiddle from '../components/Fiddle';
|
||||
import FiddleResults from '../components/FiddleResults';
|
||||
import FiddleResultsSummary from '../components/FiddleResultsSummary';
|
||||
import scrollToComponent from 'react-scroll-to-component';
|
||||
import {getFiddle, getFiddleDeploy} from "../reducers/selectors";
|
||||
import CompilerError from "../components/CompilerError";
|
||||
import {List, Badge, Button} from 'tabler-react';
|
||||
import {NavLink} from 'react-router-dom';
|
||||
import LoadingCardWithIcon from '../components/LoadingCardWithIcon';
|
||||
import {hashCode} from '../utils/utils';
|
||||
|
||||
class FiddleContainer extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
value: undefined,
|
||||
loadingMessage: 'Loading...',
|
||||
readOnly: true
|
||||
};
|
||||
this.compileTimeout = null;
|
||||
this.ace = null;
|
||||
this.editor = null;
|
||||
this.warningsCardRef = null;
|
||||
this.errorsCardRef = null;
|
||||
this.fatalCardRef = null;
|
||||
this.deployedCardRef = null;
|
||||
this.fiddleResultsRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({loadingMessage: 'Loading saved state...'});
|
||||
this.props.fetchLastFiddle();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {lastFiddle} = this.props;
|
||||
if (this.state.value === '' && prevProps.lastFiddle === lastFiddle) return;
|
||||
if ((!this.state.value && lastFiddle && !lastFiddle.error) && this.state.value !== lastFiddle) {
|
||||
this._onCodeChange(lastFiddle, true);
|
||||
}
|
||||
}
|
||||
|
||||
_getRowCol(errorMessage) {
|
||||
const errorSplit = errorMessage.split(':');
|
||||
if (errorSplit.length >= 3) {
|
||||
return {row: errorSplit[1], col: errorSplit[2]};
|
||||
}
|
||||
return {row: 0, col: 0};
|
||||
}
|
||||
|
||||
_onCodeChange(newValue, immediate = false) {
|
||||
this.setState({readOnly: false, value: newValue});
|
||||
if (this.compileTimeout) clearTimeout(this.compileTimeout);
|
||||
this.compileTimeout = setTimeout(() => {
|
||||
this.setState({loadingMessage: 'Compiling...'});
|
||||
this.props.postFiddle(newValue, Date.now());
|
||||
}, immediate ? 0 : 1000);
|
||||
}
|
||||
|
||||
_onErrorClick(e, annotation) {
|
||||
e.preventDefault();
|
||||
this.editor.gotoLine(annotation.row + 1);
|
||||
scrollToComponent(this.ace);
|
||||
}
|
||||
|
||||
_onErrorSummaryClick(e, refName) {
|
||||
scrollToComponent(this[refName]);
|
||||
}
|
||||
|
||||
_onDeployClick(_e) {
|
||||
this.setState({loadingMessage: 'Deploying...'});
|
||||
this.props.postFiddleDeploy(this.props.fiddle.compilationResult);
|
||||
scrollToComponent(this.deployedCardRef || this.fiddleResultsRef.current); // deployedCardRef null on first Deploy click
|
||||
}
|
||||
|
||||
_renderErrors(errors, errorType) {
|
||||
return errors.reduce(
|
||||
(errors, error, index) => {
|
||||
if (error.severity === errorType) {
|
||||
const errorRowCol = this._getRowCol(error.formattedMessage);
|
||||
const annotation = Object.assign({}, {
|
||||
row: errorRowCol.row - 1, // must be 0 based
|
||||
column: errorRowCol.col - 1, // must be 0 based
|
||||
text: error.formattedMessage, // text to show in tooltip
|
||||
type: error.severity // "error"|"warning"|"info"
|
||||
});
|
||||
errors.push({
|
||||
solcError: error,
|
||||
node:
|
||||
<CompilerError
|
||||
onClick={(e) => { this._onErrorClick(e, annotation); }}
|
||||
key={`${errorType}_${index}`}
|
||||
index={index}
|
||||
errorType={errorType}
|
||||
row={errorRowCol.row}
|
||||
errorMessage={error.formattedMessage} />,
|
||||
annotation: annotation
|
||||
});
|
||||
}
|
||||
return errors;
|
||||
}, []);
|
||||
}
|
||||
|
||||
_renderErrorsCard(errors, errorType) {
|
||||
const color = (errorType === "error" ? "danger" : errorType);
|
||||
|
||||
return (Boolean(errors.length) && <LoadingCardWithIcon
|
||||
anchorId={errorType + "s"}
|
||||
color={color}
|
||||
className={errorType + "s-card "}
|
||||
key={errorType + "s-card"}
|
||||
showCardOptions={true}
|
||||
isLoading={this.props.loading}
|
||||
cardOptionsClassName={errorType + "s"}
|
||||
body={
|
||||
<List.Group>
|
||||
{errors.map(error => { return error.node; })}
|
||||
</List.Group>
|
||||
}
|
||||
headerTitle={
|
||||
<React.Fragment>
|
||||
<span className="mr-1">{errorType + "s"}</span><Badge color={color}>{errors.length}</Badge>
|
||||
</React.Fragment>
|
||||
}
|
||||
ref={cardRef => { this[errorType + "sCardRef"] = cardRef; }}
|
||||
/>);
|
||||
}
|
||||
|
||||
_renderSuccessCard(title, body) {
|
||||
return this._renderLoadingCard("success", "success-card", "check", title, body, (cardRef) => {
|
||||
this.deployedCardRef = cardRef;
|
||||
});
|
||||
}
|
||||
|
||||
_renderFatalCard(title, body) {
|
||||
return body && this._renderLoadingCard("danger", "fatal-card", "slash", title, body, (cardRef) => {
|
||||
this.fatalCardRef = cardRef;
|
||||
});
|
||||
}
|
||||
|
||||
_renderLoadingCard(color, className, iconName, headerTitle, body, refCb) {
|
||||
return (<LoadingCardWithIcon
|
||||
color={color}
|
||||
className={className}
|
||||
iconName={iconName}
|
||||
showCardOptions={false}
|
||||
isLoading={this.props.loading}
|
||||
body={body}
|
||||
headerTitle={headerTitle}
|
||||
key={hashCode([className, iconName, headerTitle].join(''))}
|
||||
ref={refCb}
|
||||
/>);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {fiddle, loading, fiddleError, fiddleDeployError, deployedContracts, fatalError} = this.props;
|
||||
const {loadingMessage, value, readOnly} = this.state;
|
||||
let warnings = [];
|
||||
let errors = [];
|
||||
if (fiddle && fiddle.errors) {
|
||||
warnings = this._renderErrors(fiddle.errors, "warning");
|
||||
errors = this._renderErrors(fiddle.errors, "error");
|
||||
}
|
||||
const hasResult = Boolean(fiddle);
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className="page-title">Fiddle</h1>
|
||||
<p>Play around with contract code and deploy against your running node.</p>
|
||||
<FiddleResultsSummary
|
||||
numErrors={errors.length}
|
||||
numWarnings={warnings.length}
|
||||
isLoading={loading}
|
||||
loadingMessage={loadingMessage}
|
||||
showFatalError={Boolean(fatalError)}
|
||||
showFatalFiddle={Boolean(fiddleError)}
|
||||
showFatalFiddleDeploy={Boolean(fiddleDeployError)}
|
||||
onDeployClick={(e) => this._onDeployClick(e)}
|
||||
isVisible={Boolean(fatalError || hasResult || loading)}
|
||||
showDeploy={hasResult && Boolean(fiddle.compilationResult)}
|
||||
onWarningsClick={(e) => this._onErrorSummaryClick(e, "errorsCardRef")}
|
||||
onErrorsClick={(e) => this._onErrorSummaryClick(e, "warningsCardRef")}
|
||||
onFatalClick={(e) => this._onErrorSummaryClick(e, "fatalCardRef")}
|
||||
/>
|
||||
<Fiddle
|
||||
value={value}
|
||||
readOnly={readOnly}
|
||||
onCodeChange={(n) => this._onCodeChange(n)}
|
||||
errors={errors}
|
||||
warnings={warnings}
|
||||
ref={(fiddle) => {
|
||||
if (fiddle) {
|
||||
this.editor = fiddle.ace.editor;
|
||||
this.ace = fiddle.ace;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FiddleResults
|
||||
key="results"
|
||||
errorsCard={this._renderErrorsCard(errors, "error")}
|
||||
warningsCard={this._renderErrorsCard(warnings, "warning")}
|
||||
fatalErrorCard={this._renderFatalCard("Fatal error", fatalError)}
|
||||
fatalFiddleCard={this._renderFatalCard("Failed to compile", fiddleError)}
|
||||
fatalFiddleDeployCard={this._renderFatalCard("Failed to deploy", fiddleDeployError)}
|
||||
deployedContractsCard={deployedContracts && this._renderSuccessCard("Contract(s) deployed!",
|
||||
<Button
|
||||
to={`/embark/contracts/${deployedContracts}/overview`}
|
||||
RootComponent={NavLink}
|
||||
>Play with my contract(s)</Button>
|
||||
)}
|
||||
forwardedRef={this.fiddleResultsRef}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
function mapStateToProps(state) {
|
||||
const fiddle = getFiddle(state);
|
||||
const deployedFiddle = getFiddleDeploy(state);
|
||||
return {
|
||||
fiddle: fiddle.data,
|
||||
deployedContracts: deployedFiddle.data,
|
||||
fiddleError: fiddle.error,
|
||||
fiddleDeployError: deployedFiddle.error,
|
||||
loading: state.loading,
|
||||
lastFiddle: fiddle.data ? fiddle.data.codeToCompile : undefined,
|
||||
fatalError: state.errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
FiddleContainer.propTypes = {
|
||||
fiddle: PropTypes.object,
|
||||
fiddleError: PropTypes.string,
|
||||
fiddleDeployError: PropTypes.string,
|
||||
loading: PropTypes.bool,
|
||||
postFiddle: PropTypes.func,
|
||||
postFiddleDeploy: PropTypes.func,
|
||||
deployedContracts: PropTypes.string,
|
||||
fetchLastFiddle: PropTypes.func,
|
||||
lastFiddle: PropTypes.any,
|
||||
fatalError: PropTypes.string
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
postFiddle: fiddleAction.post,
|
||||
postFiddleDeploy: fiddleDeployAction.post,
|
||||
fetchLastFiddle: fiddleFileAction.request
|
||||
},
|
||||
)(FiddleContainer);
|
|
@ -1,7 +1,7 @@
|
|||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {files as filesAction} from "../actions";
|
||||
import {files as filesAction, file as fileAction} from "../actions";
|
||||
|
||||
import FileExplorer from '../components/FileExplorer';
|
||||
import DataWrapper from "../components/DataWrapper";
|
||||
|
@ -14,8 +14,8 @@ class FileExplorerContainer extends Component {
|
|||
|
||||
render() {
|
||||
return (
|
||||
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files}) => (
|
||||
<FileExplorer files={files} />
|
||||
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile}) => (
|
||||
<FileExplorer files={files} fetchFile={fetchFile} />
|
||||
)} />
|
||||
);
|
||||
}
|
||||
|
@ -27,11 +27,13 @@ function mapStateToProps(state) {
|
|||
|
||||
FileExplorerContainer.propTypes = {
|
||||
files: PropTypes.array,
|
||||
fetchFiles: PropTypes.func
|
||||
fetchFiles: PropTypes.func,
|
||||
fetchFile: PropTypes.func
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,{
|
||||
fetchFiles: filesAction.request
|
||||
fetchFiles: filesAction.request,
|
||||
fetchFile: fileAction.request
|
||||
}
|
||||
)(FileExplorerContainer);
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
/* eslint multiline-ternary: "off" */
|
||||
/* eslint operator-linebreak: "off" */
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Grid} from 'tabler-react';
|
||||
import TextEditor from '../components/TextEditor';
|
||||
import TextEditorContractErrors from '../components/TextEditorContractErrors';
|
||||
import TextEditorContractWarnings from '../components/TextEditorContractWarnings';
|
||||
import TextEditorContractToolbar from '../components/TextEditorContractToolbar';
|
||||
import TextEditorContractDeploy from '../components/TextEditorContractDeploy';
|
||||
import TextEditorToolbar from '../components/TextEditorToolbar';
|
||||
import {
|
||||
currentFile as currentFileAction,
|
||||
saveCurrentFile as saveCurrentFileAction,
|
||||
saveFile as saveFileAction,
|
||||
removeFile as removeFileAction,
|
||||
contractCompile as contractCompileAction,
|
||||
contractDeploy as contractDeployAction
|
||||
} from '../actions';
|
||||
import {getCurrentFile, getContractCompile} from '../reducers/selectors';
|
||||
|
||||
const DEFAULT_FILE = {name: 'newContract.sol', content: ''};
|
||||
|
||||
class TextEditorContainer extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {currentFile: this.props.currentFile};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(this.props.currentFile.content === '') {
|
||||
this.props.fetchCurrentFile();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if(this.props.currentFile.path !== prevProps.currentFile.path) {
|
||||
this.setState({currentFile: this.props.currentFile});
|
||||
}
|
||||
}
|
||||
|
||||
isContract() {
|
||||
return this.state.currentFile.name.endsWith('.sol');
|
||||
}
|
||||
|
||||
onFileContentChange(newContent) {
|
||||
const newCurrentFile = this.state.currentFile;
|
||||
newCurrentFile.content = newContent;
|
||||
this.setState({currentFile: newCurrentFile});
|
||||
if (!this.isContract()) return;
|
||||
|
||||
this.compileTimeout = setTimeout(() => {
|
||||
this.props.compileContract(newContent, this.state.currentFile.name);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
save() {
|
||||
this.props.saveFile(this.state.currentFile);
|
||||
this.props.saveCurrentFile(this.state.currentFile);
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.props.removeFile(this.state.currentFile);
|
||||
this.setState({currentFile: DEFAULT_FILE});
|
||||
}
|
||||
|
||||
renderContractFooter() {
|
||||
if (!this.isContract()) return <React.Fragment />;
|
||||
let components = [];
|
||||
|
||||
const {errors, warnings, result} = this.props.contractCompile;
|
||||
if (errors && errors.length > 0) {
|
||||
components.push(<TextEditorContractErrors key={1} errors={errors} />);
|
||||
}
|
||||
|
||||
if (warnings && warnings.length > 0) {
|
||||
components.push(<TextEditorContractWarnings key={2} warnings={warnings} />);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
components.push(<TextEditorContractDeploy key={3} result={result}/>);
|
||||
}
|
||||
return <React.Fragment>{components}</React.Fragment>;
|
||||
}
|
||||
|
||||
renderToolbar(){
|
||||
if (this.isContract()) {
|
||||
return <TextEditorContractToolbar currentFile={this.state.currentFile}
|
||||
contractCompile={this.props.contractCompile}
|
||||
compilingContract={this.props.compilingContract}
|
||||
save={() => this.save()}
|
||||
remove={() => this.remove()} />;
|
||||
}
|
||||
return <TextEditorToolbar currentFile={this.state.currentFile}
|
||||
contractCompile={this.props.contractCompile}
|
||||
save={() => this.save()}
|
||||
remove={() => this.remove()} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid.Row className="my-2">
|
||||
{this.renderToolbar()}
|
||||
</Grid.Row>
|
||||
<TextEditor value={this.state.currentFile.content}
|
||||
contractCompile={this.props.contractCompile}
|
||||
onFileContentChange={(newContent) => this.onFileContentChange(newContent)} />
|
||||
{this.renderContractFooter()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const currentFile = getCurrentFile(state) || DEFAULT_FILE;
|
||||
const contractCompile = getContractCompile(state, currentFile) || {};
|
||||
return {
|
||||
currentFile,
|
||||
contractCompile,
|
||||
compilingContract: state.compilingContract,
|
||||
loading: state.loading,
|
||||
error: state.errorMessage
|
||||
};
|
||||
}
|
||||
|
||||
TextEditorContainer.propTypes = {
|
||||
currentFile: PropTypes.object,
|
||||
contractCompile: PropTypes.object,
|
||||
saveCurrentFile: PropTypes.func,
|
||||
fetchCurrentFile: PropTypes.func,
|
||||
saveFile: PropTypes.func,
|
||||
removeFile: PropTypes.func,
|
||||
deployContract: PropTypes.func,
|
||||
compileContract: PropTypes.func,
|
||||
compilingContract: PropTypes.bool,
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.string
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
{
|
||||
fetchCurrentFile: currentFileAction.request,
|
||||
saveCurrentFile: saveCurrentFileAction.request,
|
||||
saveFile: saveFileAction.request,
|
||||
removeFile: removeFileAction.request,
|
||||
deployContract: contractDeployAction.post,
|
||||
compileContract: contractCompileAction.post
|
||||
},
|
||||
)(TextEditorContainer);
|
|
@ -1,5 +1,5 @@
|
|||
import {combineReducers} from 'redux';
|
||||
import {REQUEST, SUCCESS} from "../actions";
|
||||
import {REQUEST, SUCCESS, FAILURE, CONTRACT_COMPILE, FILES} from "../actions";
|
||||
|
||||
const BN_FACTOR = 10000;
|
||||
const voidAddress = '0x0000000000000000000000000000000000000000';
|
||||
|
@ -12,20 +12,19 @@ const entitiesDefaultState = {
|
|||
processLogs: [],
|
||||
contracts: [],
|
||||
contractProfiles: [],
|
||||
contractFiles: [],
|
||||
contractFunctions: [],
|
||||
contractDeploys: [],
|
||||
contractCompiles: [],
|
||||
contractLogs: [],
|
||||
commands: [],
|
||||
messages: [],
|
||||
messageChannels: [],
|
||||
fiddles: [],
|
||||
fiddleDeploys: [],
|
||||
versions: [],
|
||||
plugins: [],
|
||||
ensRecords: [],
|
||||
files: [],
|
||||
gasOracleStats: []
|
||||
gasOracleStats: [],
|
||||
currentFiles: []
|
||||
};
|
||||
|
||||
const sorter = {
|
||||
|
@ -61,9 +60,6 @@ const filtrer = {
|
|||
contracts: function(contract, index, self) {
|
||||
return index === self.findIndex((t) => t.className === contract.className);
|
||||
},
|
||||
contractFiles: function(contractFile, index, self) {
|
||||
return index === self.findIndex((c) => c.filename === contractFile.filename);
|
||||
},
|
||||
accounts: function(account, index, self) {
|
||||
return index === self.findIndex((t) => t.address === account.address);
|
||||
},
|
||||
|
@ -91,6 +87,9 @@ const filtrer = {
|
|||
};
|
||||
|
||||
function entities(state = entitiesDefaultState, action) {
|
||||
if (action.type === FILES[SUCCESS]) {
|
||||
return {...state, files: action.files};
|
||||
}
|
||||
for (let name of Object.keys(state)) {
|
||||
let filter = filtrer[name] || (() => true);
|
||||
let sort = sorter[name] || (() => true);
|
||||
|
@ -137,9 +136,20 @@ function loading(_state = false, action) {
|
|||
return action.type.endsWith(REQUEST);
|
||||
}
|
||||
|
||||
function compilingContract(state = false, action) {
|
||||
if(action.type === CONTRACT_COMPILE[REQUEST]) {
|
||||
return true;
|
||||
} else if (action.type === CONTRACT_COMPILE[FAILURE] || action.type === CONTRACT_COMPILE[SUCCESS]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
entities,
|
||||
loading,
|
||||
compilingContract,
|
||||
errorMessage,
|
||||
errorEntities
|
||||
});
|
||||
|
|
|
@ -72,10 +72,6 @@ export function getContractProfile(state, contractName) {
|
|||
return state.entities.contractProfiles.find((contractProfile => contractProfile.name === contractName));
|
||||
}
|
||||
|
||||
export function getContractFile(state, filename) {
|
||||
return state.entities.contractFiles.find((contractFile => contractFile.filename === filename));
|
||||
}
|
||||
|
||||
export function getContractFunctions(state, contractName) {
|
||||
return state.entities.contractFunctions.filter((contractFunction => contractFunction.contractName === contractName));
|
||||
}
|
||||
|
@ -84,6 +80,17 @@ export function getContractDeploys(state, contractName) {
|
|||
return state.entities.contractDeploys.filter((contractDeploy => contractDeploy.contractName === contractName));
|
||||
}
|
||||
|
||||
export function getContractCompile(state, file) {
|
||||
let contractCompile = state.entities.contractCompiles.reverse().find((contractCompile => contractCompile.name === file.name));
|
||||
if (!contractCompile) return;
|
||||
if (contractCompile.errors) {
|
||||
contractCompile.warnings = contractCompile.errors.filter((error) => error.severity === 'warning');
|
||||
contractCompile.errors = contractCompile.errors.filter((error) => error.severity === 'error');
|
||||
}
|
||||
|
||||
return contractCompile;
|
||||
}
|
||||
|
||||
export function getVersions(state) {
|
||||
return state.entities.versions;
|
||||
}
|
||||
|
@ -119,15 +126,6 @@ export function getMessages(state) {
|
|||
return messages;
|
||||
}
|
||||
|
||||
export function getFiddle(state) {
|
||||
const fiddleCompilation = last(state.entities.fiddles.sort((a, b) => { return (a.timestamp || 0) - (b.timestamp || 0); }));
|
||||
const isNoTempFileError = Boolean(fiddleCompilation && fiddleCompilation.codeToCompile && fiddleCompilation.codeToCompile.error && fiddleCompilation.codeToCompile.error.indexOf('ENOENT') > -1);
|
||||
return {
|
||||
data: fiddleCompilation,
|
||||
error: isNoTempFileError ? undefined : state.errorEntities.fiddles
|
||||
};
|
||||
}
|
||||
|
||||
export function getFiddleDeploy(state) {
|
||||
return {
|
||||
data: last(state.entities.fiddleDeploys),
|
||||
|
@ -150,3 +148,7 @@ export function isEnsEnabled(state) {
|
|||
export function getFiles(state) {
|
||||
return state.entities.files;
|
||||
}
|
||||
|
||||
export function getCurrentFile(state) {
|
||||
return last(state.entities.currentFiles);
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
import * as actions from '../actions';
|
||||
import * as api from '../api';
|
||||
import * as api from '../services/api';
|
||||
import * as storage from '../services/storage';
|
||||
import {eventChannel} from 'redux-saga';
|
||||
import {all, call, fork, put, takeEvery, take} from 'redux-saga/effects';
|
||||
|
||||
const {account, accounts, block, blocks, transaction, transactions, processes, commands, processLogs,
|
||||
contracts, contract, contractProfile, messageSend, versions, plugins, messageListen, fiddle,
|
||||
fiddleDeploy, ensRecord, ensRecords, contractLogs, contractFile, contractFunction, contractDeploy,
|
||||
fiddleFile, files, gasOracle} = actions;
|
||||
|
||||
function *doRequest(entity, apiFn, payload) {
|
||||
const {response, error} = yield call(apiFn, payload);
|
||||
function *doRequest(entity, serviceFn, payload) {
|
||||
const {response, error} = yield call(serviceFn, payload);
|
||||
if(response) {
|
||||
yield put(entity.success(response.data, payload));
|
||||
} else if (error) {
|
||||
|
@ -17,32 +13,37 @@ function *doRequest(entity, apiFn, payload) {
|
|||
}
|
||||
}
|
||||
|
||||
export const fetchPlugins = doRequest.bind(null, plugins, api.fetchPlugins);
|
||||
export const fetchVersions = doRequest.bind(null, versions, api.fetchVersions);
|
||||
export const fetchAccount = doRequest.bind(null, account, api.fetchAccount);
|
||||
export const fetchBlock = doRequest.bind(null, block, api.fetchBlock);
|
||||
export const fetchTransaction = doRequest.bind(null, transaction, api.fetchTransaction);
|
||||
export const fetchAccounts = doRequest.bind(null, accounts, api.fetchAccounts);
|
||||
export const fetchBlocks = doRequest.bind(null, blocks, api.fetchBlocks);
|
||||
export const fetchTransactions = doRequest.bind(null, transactions, api.fetchTransactions);
|
||||
export const fetchProcesses = doRequest.bind(null, processes, api.fetchProcesses);
|
||||
export const postCommand = doRequest.bind(null, commands, api.postCommand);
|
||||
export const fetchProcessLogs = doRequest.bind(null, processLogs, api.fetchProcessLogs);
|
||||
export const fetchContractLogs = doRequest.bind(null, contractLogs, api.fetchContractLogs);
|
||||
export const fetchContracts = doRequest.bind(null, contracts, api.fetchContracts);
|
||||
export const fetchContract = doRequest.bind(null, contract, api.fetchContract);
|
||||
export const fetchContractProfile = doRequest.bind(null, contractProfile, api.fetchContractProfile);
|
||||
export const fetchContractFile = doRequest.bind(null, contractFile, api.fetchContractFile);
|
||||
export const fetchLastFiddle = doRequest.bind(null, fiddleFile, api.fetchLastFiddle);
|
||||
export const postContractFunction = doRequest.bind(null, contractFunction, api.postContractFunction);
|
||||
export const postContractDeploy = doRequest.bind(null, contractDeploy, api.postContractDeploy);
|
||||
export const postFiddle = doRequest.bind(null, fiddle, api.postFiddle);
|
||||
export const postFiddleDeploy = doRequest.bind(null, fiddleDeploy, api.postFiddleDeploy);
|
||||
export const sendMessage = doRequest.bind(null, messageSend, api.sendMessage);
|
||||
export const fetchEnsRecord = doRequest.bind(null, ensRecord, api.fetchEnsRecord);
|
||||
export const postEnsRecord = doRequest.bind(null, ensRecords, api.postEnsRecord);
|
||||
export const fetchFiles = doRequest.bind(null, files, api.fetchFiles);
|
||||
export const fetchEthGas = doRequest.bind(null, gasOracle, api.getEthGasAPI);
|
||||
export const fetchPlugins = doRequest.bind(null, actions.plugins, api.fetchPlugins);
|
||||
export const fetchVersions = doRequest.bind(null, actions.versions, api.fetchVersions);
|
||||
export const fetchAccount = doRequest.bind(null, actions.account, api.fetchAccount);
|
||||
export const fetchBlock = doRequest.bind(null, actions.block, api.fetchBlock);
|
||||
export const fetchTransaction = doRequest.bind(null, actions.transaction, api.fetchTransaction);
|
||||
export const fetchAccounts = doRequest.bind(null, actions.accounts, api.fetchAccounts);
|
||||
export const fetchBlocks = doRequest.bind(null, actions.blocks, api.fetchBlocks);
|
||||
export const fetchTransactions = doRequest.bind(null, actions.transactions, api.fetchTransactions);
|
||||
export const fetchProcesses = doRequest.bind(null, actions.processes, api.fetchProcesses);
|
||||
export const postCommand = doRequest.bind(null, actions.commands, api.postCommand);
|
||||
export const fetchProcessLogs = doRequest.bind(null, actions.processLogs, api.fetchProcessLogs);
|
||||
export const fetchContractLogs = doRequest.bind(null, actions.contractLogs, api.fetchContractLogs);
|
||||
export const fetchContracts = doRequest.bind(null, actions.contracts, api.fetchContracts);
|
||||
export const fetchContract = doRequest.bind(null, actions.contract, api.fetchContract);
|
||||
export const fetchContractProfile = doRequest.bind(null, actions.contractProfile, api.fetchContractProfile);
|
||||
export const postContractFunction = doRequest.bind(null, actions.contractFunction, api.postContractFunction);
|
||||
export const postContractDeploy = doRequest.bind(null, actions.contractDeploy, api.postContractDeploy);
|
||||
export const postContractCompile = doRequest.bind(null, actions.contractCompile, api.postContractCompile);
|
||||
export const sendMessage = doRequest.bind(null, actions.messageSend, api.sendMessage);
|
||||
export const fetchEnsRecord = doRequest.bind(null, actions.ensRecord, api.fetchEnsRecord);
|
||||
export const postEnsRecord = doRequest.bind(null, actions.ensRecords, api.postEnsRecord);
|
||||
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 deleteFile = doRequest.bind(null, actions.removeFile, api.deleteFile);
|
||||
export const fetchEthGas = doRequest.bind(null, actions.gasOracle, api.getEthGasAPI);
|
||||
|
||||
export const fetchCurrentFile = doRequest.bind(null, actions.currentFile, storage.fetchCurrentFile);
|
||||
export const postCurrentFile = doRequest.bind(null, actions.saveCurrentFile, storage.postCurrentFile);
|
||||
export const deleteCurrentFile = doRequest.bind(null, null, storage.deleteCurrentFile);
|
||||
|
||||
|
||||
export function *watchFetchTransaction() {
|
||||
yield takeEvery(actions.TRANSACTION[actions.REQUEST], fetchTransaction);
|
||||
|
@ -96,14 +97,6 @@ export function *watchFetchContractProfile() {
|
|||
yield takeEvery(actions.CONTRACT_PROFILE[actions.REQUEST], fetchContractProfile);
|
||||
}
|
||||
|
||||
export function *watchFetchContractFile() {
|
||||
yield takeEvery(actions.CONTRACT_FILE[actions.REQUEST], fetchContractFile);
|
||||
}
|
||||
|
||||
export function *watchFetchLastFiddle() {
|
||||
yield takeEvery(actions.FIDDLE_FILE[actions.REQUEST], fetchLastFiddle);
|
||||
}
|
||||
|
||||
export function *watchPostContractFunction() {
|
||||
yield takeEvery(actions.CONTRACT_FUNCTION[actions.REQUEST], postContractFunction);
|
||||
}
|
||||
|
@ -112,6 +105,10 @@ export function *watchPostContractDeploy() {
|
|||
yield takeEvery(actions.CONTRACT_DEPLOY[actions.REQUEST], postContractDeploy);
|
||||
}
|
||||
|
||||
export function *watchPostContractCompile() {
|
||||
yield takeEvery(actions.CONTRACT_COMPILE[actions.REQUEST], postContractCompile);
|
||||
}
|
||||
|
||||
export function *watchFetchVersions() {
|
||||
yield takeEvery(actions.VERSIONS[actions.REQUEST], fetchVersions);
|
||||
}
|
||||
|
@ -136,22 +133,39 @@ export function *watchListenToMessages() {
|
|||
yield takeEvery(actions.MESSAGE_LISTEN[actions.REQUEST], listenToMessages);
|
||||
}
|
||||
|
||||
export function *watchPostFiddle() {
|
||||
yield takeEvery(actions.FIDDLE[actions.REQUEST], postFiddle);
|
||||
}
|
||||
|
||||
export function *watchFetchLastFiddleSuccess() {
|
||||
yield takeEvery(actions.FIDDLE_FILE[actions.SUCCESS], postFiddle);
|
||||
}
|
||||
|
||||
export function *watchPostFiddleDeploy() {
|
||||
yield takeEvery(actions.FIDDLE_DEPLOY[actions.REQUEST], postFiddleDeploy);
|
||||
}
|
||||
|
||||
export function *watchFetchFiles() {
|
||||
yield takeEvery(actions.FILES[actions.REQUEST], fetchFiles);
|
||||
}
|
||||
|
||||
export function *watchFetchFile() {
|
||||
yield takeEvery(actions.FILE[actions.REQUEST], fetchFile);
|
||||
}
|
||||
|
||||
export function *watchPostFile() {
|
||||
yield takeEvery(actions.SAVE_FILE[actions.REQUEST], postFile);
|
||||
}
|
||||
|
||||
export function *watchDeleteFile() {
|
||||
yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile);
|
||||
}
|
||||
|
||||
export function *watchDeleteFileSuccess() {
|
||||
yield takeEvery(actions.REMOVE_FILE[actions.SUCCESS], fetchFiles);
|
||||
yield takeEvery(actions.REMOVE_FILE[actions.SUCCESS], deleteCurrentFile);
|
||||
}
|
||||
|
||||
export function *watchFetchFileSuccess() {
|
||||
yield takeEvery(actions.FILE[actions.SUCCESS], postCurrentFile);
|
||||
}
|
||||
|
||||
export function *watchFetchCurrentFile() {
|
||||
yield takeEvery(actions.CURRENT_FILE[actions.REQUEST], fetchCurrentFile);
|
||||
}
|
||||
|
||||
export function *watchPostCurrentFile() {
|
||||
yield takeEvery(actions.SAVE_CURRENT_FILE[actions.REQUEST], postCurrentFile);
|
||||
}
|
||||
|
||||
export function *watchFetchEthGas() {
|
||||
yield takeEvery(actions.GAS_ORACLE[actions.REQUEST], fetchEthGas);
|
||||
}
|
||||
|
@ -186,7 +200,7 @@ export function *listenToProcessLogs(action) {
|
|||
const channel = yield call(createChannel, socket);
|
||||
while (true) {
|
||||
const processLog = yield take(channel);
|
||||
yield put(processLogs.success([processLog]));
|
||||
yield put(actions.processLogs.success([processLog]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,7 +213,7 @@ export function *listenToContractLogs() {
|
|||
const channel = yield call(createChannel, socket);
|
||||
while (true) {
|
||||
const contractLog = yield take(channel);
|
||||
yield put(contractLogs.success([contractLog]));
|
||||
yield put(actions.contractLogs.success([contractLog]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,7 +226,7 @@ export function *listenGasOracle() {
|
|||
const channel = yield call(createChannel, socket);
|
||||
while (true) {
|
||||
const gasOracleStats = yield take(channel);
|
||||
yield put(gasOracle.success(gasOracleStats));
|
||||
yield put(actions.gasOracle.success(gasOracleStats));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +239,7 @@ export function *listenToMessages(action) {
|
|||
const channel = yield call(createChannel, socket);
|
||||
while (true) {
|
||||
const message = yield take(channel);
|
||||
yield put(messageListen.success([{channel: action.messageChannels[0], message: message.data, time: message.time}]));
|
||||
yield put(actions.messageListen.success([{channel: action.messageChannels[0], message: message.data, time: message.time}]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,20 +261,23 @@ export default function *root() {
|
|||
fork(watchFetchBlocks),
|
||||
fork(watchFetchContracts),
|
||||
fork(watchFetchContractProfile),
|
||||
fork(watchFetchContractFile),
|
||||
fork(watchPostContractFunction),
|
||||
fork(watchPostContractDeploy),
|
||||
fork(watchPostContractCompile),
|
||||
fork(watchListenToMessages),
|
||||
fork(watchSendMessage),
|
||||
fork(watchFetchContract),
|
||||
fork(watchFetchTransaction),
|
||||
fork(watchPostFiddle),
|
||||
fork(watchPostFiddleDeploy),
|
||||
fork(watchFetchLastFiddle),
|
||||
fork(watchFetchLastFiddleSuccess),
|
||||
fork(watchFetchEnsRecord),
|
||||
fork(watchPostEnsRecords),
|
||||
fork(watchFetchFiles),
|
||||
fork(watchFetchFile),
|
||||
fork(watchPostFile),
|
||||
fork(watchDeleteFile),
|
||||
fork(watchDeleteFileSuccess),
|
||||
fork(watchFetchFileSuccess),
|
||||
fork(watchFetchCurrentFile),
|
||||
fork(watchPostCurrentFile),
|
||||
fork(watchFetchEthGas),
|
||||
fork(watchListenGasOracle)
|
||||
]);
|
||||
|
|
|
@ -20,6 +20,16 @@ function post(path, params) {
|
|||
});
|
||||
}
|
||||
|
||||
function destroy(path, params) {
|
||||
return axios.delete(constants.httpEndpoint + path, params)
|
||||
.then((response) => {
|
||||
return {response, error: null};
|
||||
})
|
||||
.catch((error) => {
|
||||
return {response: null, error: error.message || 'Something bad happened'};
|
||||
});
|
||||
}
|
||||
|
||||
export function postCommand(payload) {
|
||||
return post('/command', payload);
|
||||
}
|
||||
|
@ -76,6 +86,10 @@ export function postContractDeploy(payload) {
|
|||
return post(`/contract/${payload.contractName}/deploy`, payload);
|
||||
}
|
||||
|
||||
export function postContractCompile(payload) {
|
||||
return post('/contract/compile', payload);
|
||||
}
|
||||
|
||||
export function fetchVersions() {
|
||||
return get('/versions');
|
||||
}
|
||||
|
@ -104,16 +118,24 @@ export function postEnsRecord(payload) {
|
|||
return post('/ens/register', payload);
|
||||
}
|
||||
|
||||
export function fetchContractFile(payload) {
|
||||
return get('/files/contracts', {params: payload});
|
||||
}
|
||||
|
||||
export function getEthGasAPI() {
|
||||
return get('/blockchain/gas/oracle', {});
|
||||
}
|
||||
|
||||
export function fetchLastFiddle() {
|
||||
return get('/files/lastfiddle', {params: 'temp'});
|
||||
export function fetchFiles() {
|
||||
return get('/files');
|
||||
}
|
||||
|
||||
export function fetchFile(payload) {
|
||||
return get('/file', {params: payload});
|
||||
}
|
||||
|
||||
export function postFile(payload) {
|
||||
return post('/files', payload);
|
||||
}
|
||||
|
||||
export function deleteFile(payload) {
|
||||
return destroy('/file', {params: payload});
|
||||
}
|
||||
|
||||
export function listenToChannel(channel) {
|
||||
|
@ -135,15 +157,3 @@ export function webSocketBlockHeader() {
|
|||
export function websocketGasOracle() {
|
||||
return new WebSocket(`${constants.wsEndpoint}/blockchain/gas/oracle`);
|
||||
}
|
||||
|
||||
export function postFiddle(payload) {
|
||||
return post('/contract/compile', payload);
|
||||
}
|
||||
|
||||
export function postFiddleDeploy(payload) {
|
||||
return post('/contract/deploy', {compiledContract: payload.compiledCode});
|
||||
}
|
||||
|
||||
export function fetchFiles() {
|
||||
return get('/files');
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
export function postCurrentFile(file) {
|
||||
return new Promise(function(resolve) {
|
||||
localStorage.setItem('currentFile', JSON.stringify(file));
|
||||
resolve({response: {data: file}});
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchCurrentFile() {
|
||||
return new Promise(function(resolve) {
|
||||
resolve({response: {data: JSON.parse(localStorage.getItem('currentFile'))}});
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteCurrentFile() {
|
||||
return new Promise(function(resolve) {
|
||||
localStorage.removeItem('currentFile');
|
||||
resolve({});
|
||||
});
|
||||
}
|
|
@ -2,7 +2,8 @@ let toposort = require('toposort');
|
|||
let async = require('async');
|
||||
const cloneDeep = require('clone-deep');
|
||||
|
||||
let utils = require('../../utils/utils.js');
|
||||
const utils = require('../../utils/utils.js');
|
||||
const fs = require('../../core/fs');
|
||||
|
||||
// TODO: create a contract object
|
||||
|
||||
|
@ -273,6 +274,7 @@ class ContractsManager {
|
|||
contract.abiDefinition = compiledContract.abiDefinition;
|
||||
contract.filename = compiledContract.filename;
|
||||
contract.originalFilename = compiledContract.originalFilename || ("contracts/" + contract.filename);
|
||||
contract.path = fs.dappPath(contract.originalFilename);
|
||||
|
||||
contract.gas = (contractConfig && contractConfig.gas) || self.contractsConfig.gas || 'auto';
|
||||
|
||||
|
|
|
@ -23,51 +23,62 @@ class Pipeline {
|
|||
this.events.setCommandHandler('pipeline:build', (options, callback) => this.build(options, callback));
|
||||
fs.removeSync(this.buildDir);
|
||||
|
||||
const self = this;
|
||||
self.events.setCommandHandler("files:contract", (filename, cb) => {
|
||||
// handle case where we have a fiddle file and not a file stored in the dapp
|
||||
if(filename.indexOf('.embark/fiddles') > -1){
|
||||
return fs.readFile(filename, 'utf8', (err, source) => {
|
||||
if (err) return cb({error: err});
|
||||
cb(source);
|
||||
});
|
||||
}
|
||||
let file = self.contractsFiles.find((file) => file.filename === filename);
|
||||
if (!file) {
|
||||
return cb({error: filename + " not found"});
|
||||
}
|
||||
file.content(cb);
|
||||
});
|
||||
|
||||
let plugin = this.plugins.createPlugin('deployment', {});
|
||||
plugin.registerAPICall(
|
||||
'get',
|
||||
'/embark-api/files/contracts/',
|
||||
'/embark-api/file',
|
||||
(req, res) => {
|
||||
self.events.request('files:contract', req.query.filename, res.send.bind(res));
|
||||
if (!fs.existsSync(req.query.path) || !req.query.path.startsWith(fs.dappPath())) {
|
||||
return res.send({error: 'Path is invalid'});
|
||||
}
|
||||
const name = path.basename(req.query.path);
|
||||
const content = fs.readFileSync(req.query.path, 'utf8');
|
||||
res.send({name, content, path: req.query.path});
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
plugin.registerAPICall(
|
||||
'post',
|
||||
'/embark-api/files',
|
||||
(req, res) => {
|
||||
try {
|
||||
this.apiGuardBadFile(req.body.path);
|
||||
} catch (error) {
|
||||
return res.send({error: error.message});
|
||||
}
|
||||
|
||||
fs.writeFileSync(req.body.path, req.body.content, { encoding: 'utf8'});
|
||||
const name = path.basename(req.body.path);
|
||||
res.send({name, path: req.body.path, content: req.body.content});
|
||||
}
|
||||
);
|
||||
|
||||
plugin.registerAPICall(
|
||||
'delete',
|
||||
'/embark-api/file',
|
||||
(req, res) => {
|
||||
try {
|
||||
this.apiGuardBadFile(req.query.path);
|
||||
} catch (error) {
|
||||
return res.send({error: error.message});
|
||||
}
|
||||
fs.removeSync(req.query.path);
|
||||
res.send();
|
||||
}
|
||||
);
|
||||
|
||||
plugin.registerAPICall(
|
||||
'get',
|
||||
'/embark-api/files/lastfiddle',
|
||||
'/embark-api/files',
|
||||
(req, res) => {
|
||||
fs.readFile(fs.dappPath('.embark/fiddles/temp.sol'), 'utf8', (err, source) => {
|
||||
if (err) return res.send({error: err.message});
|
||||
res.send(source);
|
||||
});
|
||||
const rootPath = fs.dappPath();
|
||||
const walk = (dir, filelist = []) => fs.readdirSync(dir).map(name => {
|
||||
let isRoot = rootPath === dir;
|
||||
if (fs.statSync(path.join(dir, name)).isDirectory()) {
|
||||
return { isRoot, name, dirname: dir, children: walk(path.join(dir, name), filelist)};
|
||||
}
|
||||
);
|
||||
|
||||
plugin.registerAPICall(
|
||||
'get',
|
||||
'/embark-api/files/',
|
||||
(req, res) => {
|
||||
const walk = (dir, filelist = []) => fs.readdirSync(dir).map(file => {
|
||||
if (fs.statSync(path.join(dir, file)).isDirectory()) {
|
||||
return {name: file, children: walk(path.join(dir, file), filelist)};
|
||||
}
|
||||
return {name: file};
|
||||
return {name, isRoot, path: path.join(dir, name), dirname: dir};
|
||||
});
|
||||
const files = walk(fs.dappPath());
|
||||
res.send(files);
|
||||
|
@ -75,6 +86,13 @@ class Pipeline {
|
|||
);
|
||||
}
|
||||
|
||||
apiGuardBadFile(pathToCheck) {
|
||||
const dir = path.dirname(pathToCheck);
|
||||
if (!fs.existsSync(pathToCheck) || !dir.startsWith(fs.dappPath())) {
|
||||
throw new Error('Path is invalid');
|
||||
}
|
||||
}
|
||||
|
||||
build({modifiedAssets}, callback) {
|
||||
let self = this;
|
||||
const importsList = {};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
let async = require('../../utils/async_extend.js');
|
||||
let SolcW = require('./solcW.js');
|
||||
const fs = require('../../core/fs');
|
||||
|
||||
class Solidity {
|
||||
|
||||
|
@ -20,18 +19,12 @@ class Solidity {
|
|||
'post',
|
||||
'/embark-api/contract/compile',
|
||||
(req, res) => {
|
||||
if(typeof req.body.codeToCompile !== 'string'){
|
||||
return res.send({error: 'Body parameter \'codeToCompile\' must be a string'});
|
||||
if(typeof req.body.code !== 'string'){
|
||||
return res.send({error: 'Body parameter \'code\' must be a string'});
|
||||
}
|
||||
const input = {'fiddle': {content: req.body.codeToCompile.replace(/\r\n/g, '\n')}};
|
||||
this.compile_solidity_code(input, {}, true, (errors, compilationResult) => {
|
||||
// write code to filesystem so we can view the source after page refresh
|
||||
const className = !compilationResult ? 'temp' : Object.keys(compilationResult).join('_');
|
||||
this._writeFiddleToFile(req.body.codeToCompile, className, Boolean(compilationResult), (err) => {
|
||||
if(err) this.logger.trace('Error writing fiddle to filesystem: ', err);
|
||||
}); // async, do not need to wait
|
||||
|
||||
const responseData = {errors: errors, compilationResult: compilationResult};
|
||||
const input = {[req.body.name]: {content: req.body.code.replace(/\r\n/g, '\n')}};
|
||||
this.compile_solidity_code(input, {}, true, (errors, result) => {
|
||||
const responseData = {errors: errors, result: result};
|
||||
this.logger.trace(`POST response /embark-api/contract/compile:\n ${JSON.stringify(responseData)}`);
|
||||
res.send(responseData);
|
||||
});
|
||||
|
@ -39,26 +32,6 @@ class Solidity {
|
|||
);
|
||||
}
|
||||
|
||||
_writeFiddleToFile(code, className, isCompiled, cb){
|
||||
fs.mkdirp('.embark/fiddles', (err) => {
|
||||
if(err) return cb(err);
|
||||
|
||||
// always write to temp.sol file
|
||||
const filePath = Solidity._getFiddlePath('temp');
|
||||
fs.writeFile(filePath, code, 'utf8', cb);
|
||||
|
||||
// if it's compiled, also write to [classname].sol
|
||||
if(isCompiled){
|
||||
const filePath = Solidity._getFiddlePath(className);
|
||||
fs.writeFile(filePath, code, 'utf8', cb);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static _getFiddlePath(className){
|
||||
return fs.dappPath(`.embark/fiddles/${className}.sol`);
|
||||
}
|
||||
|
||||
_compile(jsonObj, returnAllErrors, callback) {
|
||||
const self = this;
|
||||
self.solcW.compile(jsonObj, function (err, output) {
|
||||
|
@ -153,7 +126,6 @@ class Solidity {
|
|||
|
||||
const className = contractName;
|
||||
let filename = contractFile;
|
||||
if(filename === 'fiddle') filename = Solidity._getFiddlePath(className);
|
||||
|
||||
compiled_object[className] = {};
|
||||
compiled_object[className].code = contract.evm.bytecode.object;
|
||||
|
|
Loading…
Reference in New Issue