Add toolbar to editor

Rename overview to detail

React to save file for local storage

Add switch for hidden files

move units to ether instead of wei

separate description from link itself which makes UI cleaner

remove unneded explorer header

switch nav menu color to white

replace contract address table with simple paragraph instead

improve contract functions page

improve contract functions view

style/fix functions tab

fix rebase issue

re-style contracts sidebar

improve contract detail sidebar
This commit is contained in:
Anthony Laibe 2018-10-23 12:18:02 +01:00 committed by Iuri Matias
parent f30803946c
commit 00a8d9baea
38 changed files with 627 additions and 555 deletions

View File

@ -288,10 +288,8 @@ export const file = {
export const SAVE_FILE = createRequestTypes('SAVE_FILE'); export const SAVE_FILE = createRequestTypes('SAVE_FILE');
export const saveFile = { export const saveFile = {
request: ({name, path, content}) => { request: ({name, path, content}) => action(SAVE_FILE[REQUEST], {name, path, content}),
return action(SAVE_FILE[REQUEST], {name, path, content}); success: (_, {name, path, content}) => action(SAVE_FILE[SUCCESS], {name, path, content}),
},
success: () => action(SAVE_FILE[SUCCESS]),
failure: (error) => action(SAVE_FILE[FAILURE], {error}) failure: (error) => action(SAVE_FILE[FAILURE], {error})
}; };

View File

@ -10,7 +10,7 @@ const Account = ({account}) => (
<Col> <Col>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitleIdenticon id={account.address}>Account {account.address}</CardTitleIdenticon> <CardTitleIdenticon id={account.address}>{account.address}</CardTitleIdenticon>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<dl className="row"> <dl className="row">

View File

@ -12,15 +12,15 @@ const Accounts = ({accounts}) => (
{accounts.map(account => ( {accounts.map(account => (
<Card key={account.address}> <Card key={account.address}>
<CardHeader> <CardHeader>
<Link to={`/embark/explorer/accounts/${account.address}`}> <CardTitleIdenticon id={account.address}>Account&nbsp;
<CardTitleIdenticon id={account.address}>Account {account.address}</CardTitleIdenticon> <Link to={`/embark/explorer/accounts/${account.address}`}>{account.address}</Link>
</Link> </CardTitleIdenticon>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<Row> <Row>
<Col> <Col>
<strong>Balance</strong> <strong>Balance</strong>
<div>{account.balance} Wei</div> <div>{account.balance} Ether</div>
</Col> </Col>
<Col> <Col>
<strong>Tx Count</strong> <strong>Tx Count</strong>

View File

@ -12,7 +12,11 @@ const Blocks = ({blocks}) => (
{blocks.map(block => ( {blocks.map(block => (
<Card key={block.number}> <Card key={block.number}>
<CardHeader> <CardHeader>
<Link to={`/embark/explorer/blocks/${block.number}`}> <CardTitleIdenticon id={block.hash}>Block {block.number}</CardTitleIdenticon></Link> <CardTitleIdenticon id={block.hash}>Block&nbsp;
<Link to={`/embark/explorer/blocks/${block.number}`}>
{block.number}
</Link>
</CardTitleIdenticon>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<Row> <Row>

View File

@ -0,0 +1,36 @@
import PropTypes from "prop-types";
import React from 'react';
import ReactJson from "react-json-view";
import {Row, Col, Table} from "reactstrap";
import {formatContractForDisplay} from '../utils/presentation';
import CopyButton from './CopyButton';
const ContractDetail = ({contract}) => {
const contractDisplay = formatContractForDisplay(contract);
return (
<Row>
<Col>
<strong>ABI</strong>
<div className="relative">
<CopyButton text={JSON.stringify(contract.abiDefinition)}
title="Copy bytecode to clipboard"/>
{contract.abiDefinition && <ReactJson src={contract.abiDefinition} theme="monokai" sortKeys={true} collapsed={1} />}
</div>
<br />
<strong>Bytecode</strong>
<div className="text-wrap logs relative">
<CopyButton text={contract.runtimeBytecode}
title="Copy bytecode to clipboard"/>
{contract.runtimeBytecode}
</div>
</Col>
</Row>
);
};
ContractDetail.propTypes = {
contract: PropTypes.object
};
export default ContractDetail;

View File

@ -3,6 +3,7 @@ import React, {Component} from 'react';
import { import {
Row, Row,
Col, Col,
Form,
FormGroup, FormGroup,
Label, Label,
Input, Input,
@ -12,6 +13,7 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
CardFooter, CardFooter,
Collapse,
ListGroup, ListGroup,
ListGroupItem ListGroupItem
} from "reactstrap"; } from "reactstrap";
@ -19,13 +21,17 @@ import {
class ContractFunction extends Component { class ContractFunction extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {inputs: {}}; this.state = {inputs: {}, optionsCollapse: false, functionCollapse: false};
} }
static isPureCall(method) { static isPureCall(method) {
return (method.mutability === 'view' || method.mutability === 'pure'); return (method.mutability === 'view' || method.mutability === 'pure');
} }
static isEvent(method) {
return !this.isPureCall(method) && (method.type === 'event');
}
buttonTitle() { buttonTitle() {
const {method} = this.props; const {method} = this.props;
if (method.name === 'constructor') { if (method.name === 'constructor') {
@ -56,30 +62,65 @@ class ContractFunction extends Component {
return this.inputsAsArray().length !== this.props.method.inputs.length; return this.inputsAsArray().length !== this.props.method.inputs.length;
} }
toggleOptions() {
this.setState({
optionsCollapse: !this.state.optionsCollapse,
});
}
toggleFunction() {
this.setState({
functionCollapse: !this.state.functionCollapse,
});
}
render() { render() {
return ( return (
<Col xs={12}> <Col xs={12}>
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>{this.props.method.name}</CardTitle> <CardTitle className="collapsable contractFunction" onClick={() => this.toggleFunction()}>
{ContractFunction.isPureCall(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#ff4500", "border-color": "#ff4500", "float": "right" }}>call</button>
}
{ContractFunction.isEvent(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#aad450", "border-color": "#aad450", "float": "right" }}>event</button>
}
{this.props.method.name}({this.props.method.inputs.map(input => input.name).join(', ')})
</CardTitle>
</CardHeader> </CardHeader>
<CardBody> <Collapse isOpen={this.state.functionCollapse}>
{this.props.method.inputs.map(input => ( <CardBody>
<FormGroup key={input.name}> <Form action="" method="post" inline>
<Label for={input.name}>{input.name}</Label> {this.props.method.inputs.map(input => (
<Input name={input.name} id={input.name} placeholder={input.type} onChange={(e) => this.handleChange(e, input.name)}/> <FormGroup key={input.name} className="pr-1">
</FormGroup> <Label for={input.name} className="pr-1">{input.name}: </Label>
))} <Input name={input.name} id={input.name} placeholder={input.type} onChange={(e) => this.handleChange(e, input.name)}/>
{!ContractFunction.isPureCall(this.props.method) && </FormGroup>
<FormGroup key="gasPrice"> ))}
<Label for="gasPrice">Gas Price (in GWei)(optional)</Label> </Form>
<Input name="gasPrice" id="gasPrice" onChange={(e) => this.handleChange(e, 'gasPrice')}/> {!ContractFunction.isPureCall(this.props.method) &&
</FormGroup> <Col xs={12} style={{"margin-bottom": "5px", "margin-top": "5px"}}>
} <Row>
<Button color="primary" disabled={this.callDisabled()} onClick={(e) => this.handleCall(e)}> <strong className="collapsable" onClick={() => this.toggleOptions()}><i className={this.state.optionsCollapse ? 'fa fa-caret-down' : 'fa fa-caret-right'}/>Advanced Options</strong>
{this.buttonTitle()} <Collapse isOpen={this.state.optionsCollapse}>
</Button> <Form action="" method="post" inline>
</CardBody> <FormGroup key="gasPrice" className="pr-1">
<Label for="gasPrice" className="pr-1">Gas Price (in GWei)(optional)</Label>
<Input name="gasPrice" id="gasPrice" onChange={(e) => this.handleChange(e, 'gasPrice')}/>
</FormGroup>
</Form>
</Collapse>
</Row>
</Col>
}
<div align="right">
<Button color="primary" disabled={this.callDisabled()} onClick={(e) => this.handleCall(e)} >
{this.buttonTitle()}
</Button>
</div>
</CardBody>
</Collapse>
{this.props.contractFunctions && this.props.contractFunctions.length > 0 && <CardFooter> {this.props.contractFunctions && this.props.contractFunctions.length > 0 && <CardFooter>
<ListGroup> <ListGroup>
{this.props.contractFunctions.map(contractFunction => ( {this.props.contractFunctions.map(contractFunction => (

View File

@ -3,9 +3,9 @@ import React from 'react';
import { TabContent, TabPane, Nav, NavItem, NavLink, Card, CardBody, CardTitle } from 'reactstrap'; import { TabContent, TabPane, Nav, NavItem, NavLink, Card, CardBody, CardTitle } from 'reactstrap';
import classnames from 'classnames'; import classnames from 'classnames';
import ContractOverview from '../components/ContractOverview'; import ContractDetail from '../components/ContractDetail';
import ContractLoggerContainer from '../containers/ContractLoggerContainer'; import ContractLoggerContainer from '../containers/ContractLoggerContainer';
import ContractFunctionsContainer from '../containers/ContractFunctionsContainer'; import ContractOverviewContainer from '../containers/ContractOverviewContainer';
class ContractLayout extends React.Component { class ContractLayout extends React.Component {
constructor(props) { constructor(props) {
@ -44,7 +44,7 @@ class ContractLayout extends React.Component {
className={classnames({ active: this.state.activeTab === '2' })} className={classnames({ active: this.state.activeTab === '2' })}
onClick={() => { this.toggle('2'); }} onClick={() => { this.toggle('2'); }}
> >
Functions Detail
</NavLink> </NavLink>
</NavItem> </NavItem>
<NavItem> <NavItem>
@ -58,10 +58,10 @@ class ContractLayout extends React.Component {
</Nav> </Nav>
<TabContent activeTab={this.state.activeTab}> <TabContent activeTab={this.state.activeTab}>
<TabPane tabId="1"> <TabPane tabId="1">
<ContractOverview contract={this.props.contract} /> <ContractOverviewContainer contract={this.props.contract} />
</TabPane> </TabPane>
<TabPane tabId="2"> <TabPane tabId="2">
<ContractFunctionsContainer contract={this.props.contract} /> <ContractDetail contract={this.props.contract} />
</TabPane> </TabPane>
<TabPane tabId="3"> <TabPane tabId="3">
<ContractLoggerContainer contract={this.props.contract} /> <ContractLoggerContainer contract={this.props.contract} />

View File

@ -1,54 +1,191 @@
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import React from 'react'; import React, {Component} from 'react';
import ReactJson from "react-json-view"; import {
import {Row, Col, Table} from "reactstrap"; Row,
Col,
Form,
FormGroup,
Label,
Input,
Button,
Card,
CardBody,
CardHeader,
CardTitle,
CardFooter,
Collapse,
ListGroup,
ListGroupItem
} from "reactstrap";
import {formatContractForDisplay} from '../utils/presentation'; import {formatContractForDisplay} from '../utils/presentation';
import CopyButton from './CopyButton';
const Contract = ({contract}) => { class ContractFunction extends Component {
constructor(props) {
super(props);
this.state = {inputs: {}, optionsCollapse: false, functionCollapse: false};
}
static isPureCall(method) {
return (method.mutability === 'view' || method.mutability === 'pure');
}
static isEvent(method) {
return !this.isPureCall(method) && (method.type === 'event');
}
buttonTitle() {
const {method} = this.props;
if (method.name === 'constructor') {
return 'Deploy';
}
return ContractFunction.isPureCall(method) ? 'Call' : 'Send';
}
inputsAsArray() {
return this.props.method.inputs
.map(input => this.state.inputs[input.name])
.filter(value => value);
}
handleChange(e, name) {
let newInputs = this.state.inputs;
newInputs[name] = e.target.value;
this.setState({inputs: newInputs});
}
handleCall(e) {
e.preventDefault();
this.props.postContractFunction(this.props.contractProfile.name, this.props.method.name, this.inputsAsArray(), this.state.inputs.gasPrice * 1000000000);
}
callDisabled() {
return this.inputsAsArray().length !== this.props.method.inputs.length;
}
toggleOptions() {
this.setState({
optionsCollapse: !this.state.optionsCollapse,
});
}
toggleFunction() {
this.setState({
functionCollapse: !this.state.functionCollapse,
});
}
render() {
return (
<Card>
<CardHeader>
<CardTitle className="collapsable contractFunction" onClick={() => this.toggleFunction()}>
{ContractFunction.isPureCall(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#ff4500", "border-color": "#ff4500", "float": "right" }}>call</button>
}
{ContractFunction.isEvent(this.props.method) &&
<button class="btn btn-brand btn-sm" style={{ "color": "#fff", "background-color": "#aad450", "border-color": "#aad450", "float": "right" }}>event</button>
}
{this.props.method.name}({this.props.method.inputs.map(input => input.name).join(', ')})
</CardTitle>
</CardHeader>
<Collapse isOpen={this.state.functionCollapse}>
<CardBody>
<Form action="" method="post" inline>
{this.props.method.inputs.map(input => (
<FormGroup key={input.name} className="pr-1">
<Label for={input.name} className="pr-1">{input.name}: </Label>
<Input name={input.name} id={input.name} placeholder={input.type} onChange={(e) => this.handleChange(e, input.name)}/>
</FormGroup>
))}
</Form>
{!ContractFunction.isPureCall(this.props.method) &&
<Col xs={12} style={{"margin-bottom": "5px", "margin-top": "5px"}}>
<Row>
<strong className="collapsable" onClick={() => this.toggleOptions()}><i className={this.state.optionsCollapse ? 'fa fa-caret-down' : 'fa fa-caret-right'}/>Advanced Options</strong>
<Col xs={12} style={{"margin-bottom": "5px", "margin-top": "5px"}}>
<Row>
<Collapse isOpen={this.state.optionsCollapse}>
<Form action="" method="post" inline>
<FormGroup key="gasPrice" className="pr-1">
<Label for="gasPrice" className="pr-1">Gas Price (in GWei)(optional)</Label>
<Input name="gasPrice" id="gasPrice" onChange={(e) => this.handleChange(e, 'gasPrice')}/>
</FormGroup>
</Form>
</Collapse>
</Row>
</Col>
</Row>
</Col>
}
<div align="right">
<Button color="primary" disabled={this.callDisabled()} onClick={(e) => this.handleCall(e)}>
{this.buttonTitle()}
</Button>
</div>
</CardBody>
</Collapse>
{this.props.contractFunctions && this.props.contractFunctions.length > 0 && <CardFooter>
<ListGroup>
{this.props.contractFunctions.map(contractFunction => (
<ListGroupItem key={contractFunction.result}>
{contractFunction.inputs.length > 0 && <p>Inputs: {contractFunction.inputs.join(', ')}</p>}
<strong>Result: {contractFunction.result}</strong>
</ListGroupItem>
))}
</ListGroup>
</CardFooter>}
</Card>
);
}
}
ContractFunction.propTypes = {
contractProfile: PropTypes.object,
method: PropTypes.object,
contractFunctions: PropTypes.arrayOf(PropTypes.object),
postContractFunction: PropTypes.func
};
const filterContractFunctions = (contractFunctions, contractName, method) => {
return contractFunctions.filter((contractFunction) => (
contractFunction.contractName === contractName && contractFunction.method === method
));
};
const ContractOverview = (props) => {
const {contractProfile, contract} = props;
const contractDisplay = formatContractForDisplay(contract); const contractDisplay = formatContractForDisplay(contract);
return ( return (
<Row> <div>
<Col> {(contractDisplay.state === 'Deployed') && <div>Deployed at {contractDisplay.address}</div>}
<Table {(contractDisplay.state !== 'Deployed') && <div>{contractDisplay.address}</div>}
responsive <br />
className="text-nowrap" {contractProfile.methods
> .filter((method) => {
<thead> return props.onlyConstructor ? method.type === 'constructor' : method.type !== 'constructor';
<tr> })
<th>Name</th> .map(method => <ContractFunction key={method.name}
<th>Address</th> method={method}
<th>State</th> contractFunctions={filterContractFunctions(props.contractFunctions, contractProfile.name, method.name)}
</tr> contractProfile={contractProfile}
</thead> postContractFunction={props.postContractFunction} />)}
<tbody> </div>
<tr className={contractDisplay.stateColor}>
<td>{contract.className}</td>
<td>{contractDisplay.address}</td>
<td>{contractDisplay.state}</td>
</tr>
</tbody>
</Table>
<h2>ABI</h2>
<div className="relative">
<CopyButton text={JSON.stringify(contract.abiDefinition)}
title="Copy bytecode to clipboard"/>
{contract.abiDefinition && <ReactJson src={contract.abiDefinition} theme="monokai" sortKeys={true} collapsed={1} />}
</div>
<h2>Bytecode</h2>
<div className="text-wrap logs relative">
<CopyButton text={contract.runtimeBytecode}
title="Copy bytecode to clipboard"/>
{contract.runtimeBytecode}
</div>
</Col>
</Row>
); );
}; };
Contract.propTypes = { ContractOverview.propTypes = {
contract: PropTypes.object contract: PropTypes.object,
onlyConstructor: PropTypes.bool,
contractProfile: PropTypes.object,
contractFunctions: PropTypes.arrayOf(PropTypes.object),
postContractFunction: PropTypes.func
}; };
export default Contract; ContractOverview.defaultProps = {
onlyConstructor: false
};
export default ContractOverview;

View File

@ -5,7 +5,7 @@ import ContractsList from './ContractsList';
const Contracts = ({contracts, title = "Contracts"}) => ( const Contracts = ({contracts, title = "Contracts"}) => (
<Row> <Row>
<Col> <Col className="mt-3">
<Card> <Card>
<CardBody> <CardBody>
<Row> <Row>
@ -13,7 +13,7 @@ const Contracts = ({contracts, title = "Contracts"}) => (
<CardTitle className="mb-0">Contracts</CardTitle> <CardTitle className="mb-0">Contracts</CardTitle>
</Col> </Col>
</Row> </Row>
<div style={{ marginTop: 40 + 'px' }}> <div className="mt-5">
<ContractsList contracts={contracts}></ContractsList> <ContractsList contracts={contracts}></ContractsList>
</div> </div>
</CardBody> </CardBody>

View File

@ -10,11 +10,6 @@ import TransactionsContainer from '../containers/TransactionsContainer';
const ExplorerDashboardLayout = () => ( const ExplorerDashboardLayout = () => (
<React.Fragment> <React.Fragment>
<Row>
<Col>
<h1 className="my-5">Explorer</h1>
</Col>
</Row>
<Row> <Row>
<Col> <Col>
<AccountsContainer /> <AccountsContainer />

View File

@ -1,4 +0,0 @@
.fiddle--grid {
margin-left: -30px;
margin-right: -30px;
}

View File

@ -1,29 +0,0 @@
import React from 'react';
import {Row, Col} from 'reactstrap';
import TextEditorAsideContainer from '../containers/TextEditorAsideContainer';
import TextEditorContainer from '../containers/TextEditorContainer';
import FileExplorerContainer from '../containers/FileExplorerContainer';
import './FiddleLayout.css';
const DEFAULT_FILE = {name: 'newContract.sol', content: ''};
class FiddleLayout extends React.Component {
render() {
return (
<Row noGutters className="h-100 fiddle--grid">
<Col sm={4} md={2}>
<FileExplorerContainer />
</Col>
<Col sm={8} md={6}>
<TextEditorContainer defaultFile={DEFAULT_FILE} />
</Col>
<Col sm={12} md={4}>
<TextEditorAsideContainer defaultFile={DEFAULT_FILE} />
</Col>
</Row>
);
}
}
export default FiddleLayout;

View File

@ -1,7 +1,6 @@
import React from 'react'; 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 {Input, Label, FormGroup} from 'reactstrap';
const style = { const style = {
tree: { tree: {
@ -107,9 +106,7 @@ decorators.Header = Header;
class FileExplorer extends React.Component { class FileExplorer extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {cursor: null};
showHidden: false
};
} }
onToggle(node, toggled) { onToggle(node, toggled) {
@ -122,10 +119,6 @@ class FileExplorer extends React.Component {
this.setState({cursor: node}); this.setState({cursor: node});
} }
onHiddenToggle(e) {
this.setState({showHidden: e.target.checked});
}
nodeEquals(a, b) { nodeEquals(a, b) {
return a && b && a.path && b.path && a.name && b.name && return a && b && a.path && b.path && a.name && b.name &&
a.path === b.path && a.path === b.path &&
@ -138,20 +131,21 @@ class FileExplorer extends React.Component {
b.path.indexOf(a.path) > -1; b.path.indexOf(a.path) > -1;
} }
filterHidden(nodes) { data(nodes) {
let filtered = []; let filtered = [];
if (!Array.isArray(nodes)) return filtered; if (!Array.isArray(nodes)) return filtered;
const cursor = this.state.cursor;
const showHidden = this.props.showHiddenFiles;
// we need a foreach to build an array instead of a // we need a foreach to build an array instead of a
// filter to prevent mutating the original object (in props) // filter to prevent mutating the original object (in props)
nodes.forEach(node => { nodes.forEach(node => {
const {showHidden, cursor} = this.state;
if (!showHidden && node.isHidden) return; if (!showHidden && node.isHidden) return;
let updatedNode = {...node}; let updatedNode = {...node};
// if it's a folder, filter the children // if it's a folder, filter the children
if (node.children) { if (node.children) {
const children = this.filterHidden(node.children); const children = this.data(node.children);
if (children.length) { if (children.length) {
updatedNode.children = children; updatedNode.children = children;
} }
@ -178,14 +172,8 @@ class FileExplorer extends React.Component {
render() { render() {
return ( return (
<div className="h-100 d-flex flex-column"> <div className="h-100 d-flex flex-column">
<FormGroup check>
<Label check>
<Input type="checkbox" onChange={this.onHiddenToggle.bind(this)} />
Show hidden files
</Label>
</FormGroup>
<Treebeard <Treebeard
data={this.filterHidden(this.props.files)} data={this.data(this.props.files)}
decorators={decorators} decorators={decorators}
onToggle={this.onToggle.bind(this)} onToggle={this.onToggle.bind(this)}
style={style} style={style}
@ -197,7 +185,8 @@ class FileExplorer extends React.Component {
FileExplorer.propTypes = { FileExplorer.propTypes = {
files: PropTypes.array, files: PropTypes.array,
fetchFile: PropTypes.func fetchFile: PropTypes.func,
showHiddenFiles: PropTypes.bool,
}; };
export default FileExplorer; export default FileExplorer;

View File

@ -131,7 +131,7 @@ class Layout extends React.Component {
<DropdownToggle nav> <DropdownToggle nav>
<FontAwesome name='bars'/> <FontAwesome name='bars'/>
</DropdownToggle> </DropdownToggle>
<DropdownMenu left style={{ left: 'auto' }}> <DropdownMenu>
{HEADER_NAV_ITEMS.map((item) => ( {HEADER_NAV_ITEMS.map((item) => (
<DropdownItem key={item.to} to={item.to} tag={Link}> <DropdownItem key={item.to} to={item.to} tag={Link}>
<FontAwesome className="mr-2" name={item.icon} /> <FontAwesome className="mr-2" name={item.icon} />
@ -220,7 +220,7 @@ class Layout extends React.Component {
{searchResult.error} {searchResult.error}
</Alert> </Alert>
<Container fluid className="h-100" style={{marginTop: '24px'}}> <Container fluid className="h-100">
{children} {children}
</Container> </Container>
</main> </main>

View File

@ -29,7 +29,7 @@ Process.propTypes = {
}; };
const Processes = ({processes}) => ( const Processes = ({processes}) => (
<Row> <Row className="mt-3">
{processes.map((process) => <Process key={process.name} process={process} />)} {processes.map((process) => <Process key={process.name} process={process} />)}
</Row> </Row>
); );

View File

@ -7,7 +7,7 @@ import './TextEditor.css';
const SUPPORTED_LANGUAGES = ['css', 'sol', 'html', 'json']; const SUPPORTED_LANGUAGES = ['css', 'sol', 'html', 'json'];
const DEFAULT_LANGUAGE = 'javascript'; const DEFAULT_LANGUAGE = 'javascript';
const EDITOR_ID = 'react-monaco-editor-container'; const EDITOR_ID = 'react-monaco-editor-container';
const GUTTER_GLYPH_MARGIN = 3; const GUTTER_GLYPH_MARGIN = 2;
let editor; let editor;
@ -62,19 +62,20 @@ class TextEditor extends React.Component {
} }
updateMarkers() { updateMarkers() {
const {errors, warnings} = this.props.contractCompile; // TODO: Fetch Result of compilation in embark, via ws
const markers = [].concat(errors).concat(warnings).filter((e) => e).map((e) => { // const {errors, warnings} = this.props.contractCompile;
const {row, col} = this.extractRowCol(e.formattedMessage); // const markers = [].concat(errors).concat(warnings).filter((e) => e).map((e) => {
return { // const {row, col} = this.extractRowCol(e.formattedMessage);
startLineNumber: row, // return {
startColumn: col, // startLineNumber: row,
endLineNumber: row, // startColumn: col,
endColumn: col + 1, // endLineNumber: row,
message: e.formattedMessage, // endColumn: col + 1,
severity: e.severity // message: e.formattedMessage,
}; // severity: e.severity
}); // };
monaco.editor.setModelMarkers(editor.getModel(), 'test', markers); // });
// monaco.editor.setModelMarkers(editor.getModel(), 'test', markers);
} }
updateLanguage() { updateLanguage() {
@ -121,6 +122,7 @@ class TextEditor extends React.Component {
this.updateDecorations(); this.updateDecorations();
} }
this.updateLanguage(); this.updateLanguage();
this.handleResize();
} }
render() { render() {
@ -135,7 +137,6 @@ class TextEditor extends React.Component {
TextEditor.propTypes = { TextEditor.propTypes = {
onFileContentChange: PropTypes.func, onFileContentChange: PropTypes.func,
file: PropTypes.object, file: PropTypes.object,
contractCompile: PropTypes.object,
toggleBreakpoint: PropTypes.func, toggleBreakpoint: PropTypes.func,
breakpoints: PropTypes.array, breakpoints: PropTypes.array,
debuggerLine: PropTypes.number debuggerLine: PropTypes.number

View File

@ -1,38 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Card, CardHeader, CardTitle, CardBody} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
import ContractFunctions from '../components/ContractFunctions';
const TextEditorContractDeploy = (props) => {
const name = Object.keys(props.result)[0];
const profile = {
name,
methods: props.result[name].abiDefinition
};
return (
<Card className="bg-success">
<CardHeader>
<CardTitle>
<FontAwesomeIcon className="mr-1" name="check"/>
Deploy Contract
</CardTitle>
</CardHeader>
<CardBody>
<ContractFunctions contractProfile={profile}
contractFunctions={props.contractDeploys}
onlyConstructor
postContractFunction={props.postContractDeploy}/>
</CardBody>
</Card>
);
};
TextEditorContractDeploy.propTypes = {
result: PropTypes.object,
postContractDeploy: PropTypes.func,
contractDeploys: PropTypes.array
};
export default TextEditorContractDeploy;

View File

@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Card, CardBody, CardHeader, CardTitle, ListGroup, ListGroupItem} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
const TextEditorContractErrors = (props) => (
<Card className="bg-danger">
<CardHeader>
<CardTitle>
<FontAwesomeIcon className="mr-1" name="minus-circle"/>
Failed to compile
</CardTitle>
</CardHeader>
<CardBody>
<ListGroup>
{props.errors.map((error, index) => <ListGroupItem key={index}>{error.formattedMessage}</ListGroupItem>)}
</ListGroup>
</CardBody>
</Card>
);
TextEditorContractErrors.propTypes = {
errors: PropTypes.array
};
export default TextEditorContractErrors;

View File

@ -1,32 +0,0 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Col, Badge} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
import TextEditorToolbar from './TextEditorToolbar';
class TextEditorContractToolbar extends Component {
render(){
return (
<React.Fragment>
<TextEditorToolbar {...this.props} />
<Col md={6} className="text-right">
{this.props.compilingContract &&
<Badge color="warning"><FontAwesomeIcon name="exclamation-triangle " className="mr-1" />compiling</Badge>}
{!this.props.compilingContract && this.props.contractCompile.result &&
<Badge color="success"><FontAwesomeIcon name="check" className="mr-1" />compiled</Badge>}
</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;

View File

@ -1,26 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Card, CardHeader, CardBody, CardTitle, ListGroup, ListGroupItem} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
const TextEditorContractWarnings = (props) => (
<Card className="bg-warning">
<CardHeader>
<CardTitle color="warning">
<FontAwesomeIcon className="mr-1" name="exclamation-triangle"/>
Warning during compilation
</CardTitle>
</CardHeader>
<CardBody>
<ListGroup>
{props.warnings.map((warning, index) => <ListGroupItem key={index}>{warning.formattedMessage}</ListGroupItem>)}
</ListGroup>
</CardBody>
</Card>
);
TextEditorContractWarnings.propTypes = {
warnings: PropTypes.array
};
export default TextEditorContractWarnings;

View File

@ -1,21 +1,63 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {Col, Button} from 'reactstrap'; import {Row, Label, Col, Button} from 'reactstrap';
import FontAwesomeIcon from 'react-fontawesome';
import { AppSwitch } from '@coreui/react'
const TextEditorToolbar = (props) => ( const TextEditorToolbar = (props) => (
<Col md={6}> <Row>
<strong>{props.currentFile.name}</strong> <Col sm={4} md={2}>
<span className="mx-2">|</span> <Label className="mb-0 pt-1">
<Button color="green" size="sm" icon="save" onClick={props.save}>Save</Button> <AppSwitch color='success' variant='pill' size='sm' onChange={props.toggleShowHiddenFiles}/>
<span className="mx-2">|</span> <span className="ml-1 align-top">Show hidden files</span>
<Button color="red" size="sm" icon="delete" onClick={props.remove}>Delete</Button> </Label>
</Col> </Col>
<Col sm={4} md={6}>
<strong>{props.currentFile.name}</strong>
<span className="mx-2">|</span>
<Button color="success" size="sm" onClick={props.save}>
<FontAwesomeIcon className="mr-2" name="save"/>
Save
</Button>
<span className="mx-2">|</span>
<Button color="danger" size="sm" onClick={props.remove}>
<FontAwesomeIcon className="mr-2" name="trash"/>
Delete
</Button>
</Col>
<Col sm={4} md={4}>
<div className="float-right mr-2">
{props.isContract &&
<React.Fragment>
<Button size="sm" color="primary" onClick={() => props.openAsideTab('overview')}>
Overview
</Button>
<span className="mx-2">|</span>
<Button size="sm" color="primary" onClick={() => props.openAsideTab('detail')}>
Detail
</Button>
<span className="mx-2">|</span>
<Button size="sm" color="primary" onClick={() => props.openAsideTab('logger')}>
Logger
</Button>
<span className="mx-2">|</span>
</React.Fragment>
}
<Button size="sm" color="primary" onClick={() => props.openAsideTab('browser')}>
Browser
</Button>
</div>
</Col>
</Row>
); );
TextEditorToolbar.propTypes = { TextEditorToolbar.propTypes = {
currentFile: PropTypes.object, currentFile: PropTypes.object,
isContract: PropTypes.bool,
save: PropTypes.func, save: PropTypes.func,
remove: PropTypes.func remove: PropTypes.func,
toggleShowHiddenFiles: PropTypes.func,
openAsideTab: PropTypes.func
}; };
export default TextEditorToolbar; export default TextEditorToolbar;

View File

@ -5,6 +5,8 @@ import PropTypes from 'prop-types';
import Description from './Description'; import Description from './Description';
import CardTitleIdenticon from './CardTitleIdenticon'; import CardTitleIdenticon from './CardTitleIdenticon';
import {utils} from 'web3';
const Transaction = ({transaction}) => ( const Transaction = ({transaction}) => (
<Row> <Row>
@ -18,7 +20,7 @@ const Transaction = ({transaction}) => (
<Description label="Block" value={<Link to={`/embark/explorer/blocks/${transaction.blockNumber}`}>{transaction.blockNumber}</Link>} /> <Description label="Block" value={<Link to={`/embark/explorer/blocks/${transaction.blockNumber}`}>{transaction.blockNumber}</Link>} />
<Description label="From" value={transaction.from} /> <Description label="From" value={transaction.from} />
<Description label="To" value={transaction.to} /> <Description label="To" value={transaction.to} />
<Description label="Value" value={`${transaction.value} Wei`}/> <Description label="Value" value={`${utils.fromWei(transaction.value)} Ether`}/>
<Description label="Input" value={transaction.input} /> <Description label="Input" value={transaction.input} />
<Description label="Gas" value={transaction.gas} /> <Description label="Gas" value={transaction.gas} />
<Description label="Gas price" value={`${transaction.gasPrice} Wei`} /> <Description label="Gas price" value={`${transaction.gasPrice} Wei`} />

View File

@ -12,9 +12,11 @@ const Transactions = ({transactions}) => (
{transactions.map(transaction => ( {transactions.map(transaction => (
<Card key={transaction.hash}> <Card key={transaction.hash}>
<CardHeader> <CardHeader>
<Link to={`/embark/explorer/transactions/${transaction.hash}`}> <CardTitleIdenticon id={transaction.hash}>Transaction&nbsp;
<CardTitleIdenticon id={transaction.hash}>Transaction {transaction.hash}</CardTitleIdenticon> <Link to={`/embark/explorer/transactions/${transaction.hash}`}>
</Link> {transaction.hash}
</Link>
</CardTitleIdenticon>
</CardHeader> </CardHeader>
<CardBody> <CardBody>
<Row> <Row>

View File

@ -1,36 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {withRouter} from 'react-router-dom';
import Contract from '../components/Contract';
import DataWrapper from "../components/DataWrapper";
import {getContract} from "../reducers/selectors";
class ContractContainer extends Component {
render() {
return (
<DataWrapper shouldRender={this.props.contract !== undefined } {...this.props} render={({contract}) => (
<Contract contract={contract} />
)} />
);
}
}
function mapStateToProps(state, props) {
return {
contract: getContract(state, props.match.params.contractName),
error: state.errorMessage,
loading: state.loading
};
}
ContractContainer.propTypes = {
match: PropTypes.object,
contract: PropTypes.object,
error: PropTypes.string
};
export default withRouter(connect(
mapStateToProps
)(ContractContainer));

View File

@ -3,12 +3,12 @@ import {connect} from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {contractProfile as contractProfileAction, contractFunction as contractFunctionAction} from '../actions'; import {contractProfile as contractProfileAction, contractFunction as contractFunctionAction} from '../actions';
import ContractFunctions from '../components/ContractFunctions'; import ContractOverview from '../components/ContractOverview';
import DataWrapper from "../components/DataWrapper"; import DataWrapper from "../components/DataWrapper";
import GasStationContainer from "../containers/GasStationContainer"; import GasStationContainer from "../containers/GasStationContainer";
import {getContractProfile, getContractFunctions} from "../reducers/selectors"; import {getContractProfile, getContractFunctions} from "../reducers/selectors";
class ContractFunctionsContainer extends Component { class ContractOverviewContainer extends Component {
componentDidMount() { componentDidMount() {
this.props.fetchContractProfile(this.props.contract.className); this.props.fetchContractProfile(this.props.contract.className);
} }
@ -19,8 +19,9 @@ class ContractFunctionsContainer extends Component {
{...this.props} {...this.props}
render={({contractProfile, contractFunctions, postContractFunction}) => ( render={({contractProfile, contractFunctions, postContractFunction}) => (
<React.Fragment> <React.Fragment>
<ContractFunctions contractProfile={contractProfile} <ContractOverview contractProfile={contractProfile}
contractFunctions={contractFunctions} contractFunctions={contractFunctions}
contract={this.props.contract}
postContractFunction={postContractFunction}/> postContractFunction={postContractFunction}/>
<GasStationContainer/> <GasStationContainer/>
@ -39,7 +40,7 @@ function mapStateToProps(state, props) {
}; };
} }
ContractFunctionsContainer.propTypes = { ContractOverviewContainer.propTypes = {
contract: PropTypes.object, contract: PropTypes.object,
contractProfile: PropTypes.object, contractProfile: PropTypes.object,
contractFunctions: PropTypes.arrayOf(PropTypes.object), contractFunctions: PropTypes.arrayOf(PropTypes.object),
@ -54,4 +55,4 @@ export default connect(
fetchContractProfile: contractProfileAction.request, fetchContractProfile: contractProfileAction.request,
postContractFunction: contractFunctionAction.post postContractFunction: contractFunctionAction.post
} }
)(ContractFunctionsContainer); )(ContractOverviewContainer);

View File

@ -0,0 +1,4 @@
.editor--grid {
margin-left: -30px !important;
margin-right: -30px !important;
}

View File

@ -0,0 +1,103 @@
import React from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {Row, Col} from 'reactstrap';
import TextEditorAsideContainer from './TextEditorAsideContainer';
import TextEditorContainer from './TextEditorContainer';
import FileExplorerContainer from './FileExplorerContainer';
import TextEditorToolbarContainer from './TextEditorToolbarContainer';
import {currentFile as currentFileAction} from '../actions';
import {getCurrentFile} from '../reducers/selectors';
import './EditorContainer.css';
const DEFAULT_FILE = {name: 'newContract.sol', content: ''};
class EditorContainer extends React.Component {
constructor(props) {
super(props)
this.state = {currentAsideTab: '', showHiddenFiles: false, 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});
}
toggleShowHiddenFiles() {
this.setState({showHiddenFiles: !this.state.showHiddenFiles});
}
openAsideTab(newTab) {
if (newTab === this.state.currentAsideTab) {
return this.setState({currentAsideTab: ''})
}
this.setState({currentAsideTab: newTab})
}
textEditorMdSize() {
return this.state.currentAsideTab.length ? 5 : 10
}
textEditorXsSize() {
return this.state.currentAsideTab.length ? 2 : 8
}
render() {
return (
<Row noGutters className="h-100 editor--grid">
<Col xs={12}>
<TextEditorToolbarContainer toggleShowHiddenFiles={() => this.toggleShowHiddenFiles()}
openAsideTab={(newTab) => this.openAsideTab(newTab)}
isContract={this.isContract()}
currentFile={this.props.currentFile} />
</Col>
<Col xs={4} md={2}>
<FileExplorerContainer showHiddenFiles={this.state.showHiddenFiles} />
</Col>
<Col xs={this.textEditorXsSize()} md={this.textEditorMdSize()}>
<TextEditorContainer currentFile={this.props.currentFile} onFileContentChange={(newContent)=> this.onFileContentChange(newContent)} />
</Col>
{this.state.currentAsideTab && <Col xs={6} md={5}>
<TextEditorAsideContainer currentAsideTab={this.state.currentAsideTab} currentFile={this.props.currentFile} />
</Col>}
</Row>
);
}
}
function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state) || DEFAULT_FILE;
return {
currentFile
};
}
EditorContainer.propTypes = {
currentFile: PropTypes.object,
fetchCurrentFile: PropTypes.func
};
export default connect(
mapStateToProps,
{fetchCurrentFile: currentFileAction.request},
)(EditorContainer);

View File

@ -1,41 +0,0 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {contracts as contractsAction} from '../actions';
import ContractLayout from '../components/ContractLayout';
import {getContractsByPath} from "../reducers/selectors";
class FileContractsContainer extends Component {
componentDidMount() {
this.props.fetchContracts();
}
render() {
return (
this.props.contracts.map(contract => <ContractLayout contract={contract} />)
)
}
}
function mapStateToProps(state, props) {
return {
contracts: getContractsByPath(state, props.currentFile.path),
error: state.errorMessage,
loading: state.loading
};
}
FileContractsContainer.propTypes = {
contracts: PropTypes.arrayOf(PropTypes.object),
fetchContractsByPath: PropTypes.func,
error: PropTypes.string,
loading: PropTypes.bool
};
export default connect(
mapStateToProps,
{
fetchContracts: contractsAction.request
}
)(FileContractsContainer);

View File

@ -14,21 +14,22 @@ class FileExplorerContainer extends Component {
render() { render() {
return ( return (
<DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile}) => ( <DataWrapper shouldRender={this.props.files.length > 0} {...this.props} render={({files, fetchFile, showHiddenFiles}) => (
<FileExplorer files={files} fetchFile={fetchFile} /> <FileExplorer files={files} fetchFile={fetchFile} showHiddenFiles={showHiddenFiles} />
)} /> )} />
); );
} }
} }
function mapStateToProps(state) { function mapStateToProps(state) {
return {files: getFiles(state), error: state.errorMessage, loading: state.loading}; return {files: getFiles(state)};
} }
FileExplorerContainer.propTypes = { FileExplorerContainer.propTypes = {
files: PropTypes.array, files: PropTypes.array,
fetchFiles: PropTypes.func, fetchFiles: PropTypes.func,
fetchFile: PropTypes.func fetchFile: PropTypes.func,
showHiddenFiles: PropTypes.bool,
}; };
export default connect( export default connect(

View File

@ -1,59 +1,79 @@
import React, {Component} from 'react'; 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 { import { Card, CardBody, CardTitle } from 'reactstrap';
currentFile as currentFileAction,
} from '../actions';
import {getCurrentFile} from '../reducers/selectors';
import Preview from '../components/Preview'; import Preview from '../components/Preview';
import FileContractsContainer from './FileContractsContainer'; import {contracts as contractsAction} from '../actions';
import {getContractsByPath} from "../reducers/selectors";
import ContractDetail from '../components/ContractDetail';
import ContractLoggerContainer from '../containers/ContractLoggerContainer';
import ContractOverviewContainer from '../containers/ContractOverviewContainer';
class TextEditorAsideContainer extends Component { class TextEditorAsideContainer extends Component {
constructor(props) {
super(props);
this.state = {currentFile: this.props.currentFile};
}
componentDidMount() { componentDidMount() {
this.props.fetchCurrentFile(); this.props.fetchContracts();
}
componentDidUpdate(prevProps) {
if(this.props.currentFile.path !== prevProps.currentFile.path) {
this.setState({currentFile: this.props.currentFile});
}
}
isContract() {
return this.state.currentFile.name.endsWith('.sol');
} }
render() { render() {
return this.isContract() ? <FileContractsContainer currentFile={this.state.currentFile} /> : <Preview /> switch(this.props.currentAsideTab) {
case 'browser':
return <Preview />
case 'detail':
return this.props.contracts.map((contract, index) => {
return (
<Card>
<CardBody>
<CardTitle style={{"font-size": "2em"}}>{contract.className} - Details</CardTitle>
<ContractDetail key={index} contract={contract} />
</CardBody>
</Card>
)
})
case 'logger':
return this.props.contracts.map((contract, index) => {
return (
<Card>
<CardBody>
<CardTitle style={{"font-size": "2em"}}>{contract.className} - Transactions</CardTitle>
<ContractLoggerContainer key={index} contract={contract} />)
</CardBody>
</Card>
)
})
case 'overview':
return this.props.contracts.map((contract, index) => {
return (
<Card>
<CardBody>
<CardTitle style={{"font-size": "2em"}}>{contract.className} - Overview</CardTitle>
<ContractOverviewContainer key={index} contract={contract} />
</CardBody>
</Card>
)
})
default:
return <React.Fragment></React.Fragment>;
}
} }
} }
function mapStateToProps(state, props) { function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state) || props.defaultFile
return { return {
currentFile, contracts: getContractsByPath(state, props.currentFile.path)
loading: state.loading,
error: state.errorMessage
}; };
} }
TextEditorAsideContainer.propTypes = { TextEditorAsideContainer.propTypes = {
currentFile: PropTypes.object, currentFile: PropTypes.object,
fetchCurrentFile: PropTypes.func, currentAsideTab: PropTypes.string,
loading: PropTypes.bool, contract: PropTypes.array,
error: PropTypes.string, fetchContracts: PropTypes.func
defaultFile: PropTypes.object
}; };
export default connect( export default connect(
mapStateToProps, mapStateToProps,
{ {
fetchCurrentFile: currentFileAction.request, fetchContracts: contractsAction.request,
}, },
)(TextEditorAsideContainer); )(TextEditorAsideContainer);

View File

@ -1,157 +1,32 @@
import React, {Component} from 'react'; import React from 'react';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import TextEditor from '../components/TextEditor'; 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 { import {
currentFile as currentFileAction,
saveCurrentFile as saveCurrentFileAction,
saveFile as saveFileAction,
removeFile as removeFileAction,
contractCompile as contractCompileAction,
contractDeploy as postContractDeploy,
toggleBreakpoint, toggleBreakpoint,
} from '../actions'; } from '../actions';
import {getCurrentFile, getContractCompile, getContractDeploys, getBreakpointsByFilename} from '../reducers/selectors'; import {getBreakpointsByFilename} from '../reducers/selectors';
class TextEditorContainer extends Component { const TextEditorContainer = (props) => (
constructor(props) { <TextEditor file={props.currentFile}
super(props); breakpoints={props.breakpoints}
this.state = {currentFile: this.props.currentFile}; toggleBreakpoint={props.toggleBreakpoint}
} onFileContentChange={props.onFileContentChange} />
)
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: this.props.defaultFile});
}
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}
postContractDeploy={this.props.postContractDeploy}
contractDeploys={this.props.contractDeploys}/>);
}
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 (
<TextEditor file={this.state.currentFile}
contractCompile={this.props.contractCompile}
breakpoints={this.props.breakpoints}
toggleBreakpoint={this.props.toggleBreakpoint}
onFileContentChange={(newContent) => this.onFileContentChange(newContent)} />
);
}
}
function mapStateToProps(state, props) { function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state) || props.defaultFile; const breakpoints = getBreakpointsByFilename(state, props.currentFile.name);
const contractCompile = getContractCompile(state, currentFile) || {}; return {breakpoints};
const contractName = contractCompile.result && Object.keys(contractCompile.result)[0];
const contractDeploys = getContractDeploys(state, contractName);
const breakpoints = getBreakpointsByFilename(state, currentFile.name);
return {
compilingContract: state.compilingContract,
loading: state.loading,
error: state.errorMessage,
currentFile,
contractCompile,
contractDeploys,
breakpoints,
};
} }
TextEditorContainer.propTypes = { TextEditorContainer.propTypes = {
defaultFile: PropTypes.object,
currentFile: PropTypes.object, currentFile: PropTypes.object,
contractCompile: PropTypes.object, onFileContentChange: PropTypes.func,
saveCurrentFile: PropTypes.func,
fetchCurrentFile: PropTypes.func,
saveFile: PropTypes.func,
removeFile: PropTypes.func,
postContractDeploy: PropTypes.func,
compileContract: PropTypes.func,
compilingContract: PropTypes.bool,
contractDeploys: PropTypes.array,
loading: PropTypes.bool,
error: PropTypes.string,
toggleBreakpoints: PropTypes.func, toggleBreakpoints: PropTypes.func,
breakpoints: PropTypes.array, breakpoints: PropTypes.array,
}; };
export default connect( export default connect(
mapStateToProps, mapStateToProps,
{ {toggleBreakpoint},
fetchCurrentFile: currentFileAction.request,
saveCurrentFile: saveCurrentFileAction.request,
saveFile: saveFileAction.request,
removeFile: removeFileAction.request,
postContractDeploy: postContractDeploy.post,
compileContract: contractCompileAction.post,
toggleBreakpoint,
},
)(TextEditorContainer); )(TextEditorContainer);

View File

@ -0,0 +1,45 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import TextEditorToolbar from '../components/TextEditorToolbar';
import {
saveFile as saveFileAction,
removeFile as removeFileAction,
} from '../actions';
class TextEditorToolbarContainer extends Component {
save() {
this.props.saveFile(this.props.currentFile);
}
remove() {
this.props.removeFile(this.props.currentFile);
}
render() {
return <TextEditorToolbar currentFile={this.props.currentFile}
isContract={this.props.isContract}
toggleShowHiddenFiles={this.props.toggleShowHiddenFiles}
openAsideTab={this.props.openAsideTab}
save={() => this.save()}
remove={() => this.remove()} />;
}
}
TextEditorToolbarContainer.propTypes = {
currentFile: PropTypes.object,
isContract: PropTypes.bool,
saveFile: PropTypes.func,
removeFile: PropTypes.func,
toggleShowHiddenFiles: PropTypes.func,
openAsideTab: PropTypes.func
};
export default connect(
null,
{
saveFile: saveFileAction.request,
removeFile: removeFileAction.request
},
)(TextEditorToolbarContainer);

View File

@ -1,6 +1,6 @@
.nav-tabs { .nav-tabs {
.nav-link { .nav-link {
color: $gray-600; color: $white;
&:hover { &:hover {
cursor: pointer; cursor: pointer;
} }

View File

@ -33,7 +33,7 @@ $navbar-brand-minimized-width: 50px !default;
$navbar-brand-minimized-bg: $navbar-brand-bg !default; $navbar-brand-minimized-bg: $navbar-brand-bg !default;
$navbar-brand-minimized-border: $navbar-brand-border !default; $navbar-brand-minimized-border: $navbar-brand-border !default;
$navbar-color: $gray-300 !default; $navbar-color: $white !default;
$navbar-hover-color: $gray-200 !default; $navbar-hover-color: $gray-200 !default;
$navbar-active-color: $gray-200 !default; $navbar-active-color: $gray-200 !default;
$navbar-disabled-color: $gray-300 !default; $navbar-disabled-color: $gray-300 !default;

View File

@ -28,3 +28,11 @@
.react-json-view { .react-json-view {
border-radius: .25rem; border-radius: .25rem;
} }
.collapsable:hover {
cursor: pointer;
}
.contractFunction {
margin-bottom: 0px;
}

View File

@ -4,11 +4,11 @@ import {Route, Switch} from 'react-router-dom';
import HomeContainer from './containers/HomeContainer'; import HomeContainer from './containers/HomeContainer';
import ContractsContainer from './containers/ContractsContainer'; import ContractsContainer from './containers/ContractsContainer';
import ContractLayoutContainer from './containers/ContractLayoutContainer'; import ContractLayoutContainer from './containers/ContractLayoutContainer';
import EditorContainer from './containers/EditorContainer';
import DeploymentContainer from './containers/DeploymentContainer'; import DeploymentContainer from './containers/DeploymentContainer';
import NoMatch from './components/NoMatch'; import NoMatch from './components/NoMatch';
import ExplorerDashboardLayout from './components/ExplorerDashboardLayout'; import ExplorerDashboardLayout from './components/ExplorerDashboardLayout';
import ExplorerLayout from './components/ExplorerLayout'; import ExplorerLayout from './components/ExplorerLayout';
import FiddleLayout from './components/FiddleLayout';
import UtilsLayout from './components/UtilsLayout'; import UtilsLayout from './components/UtilsLayout';
const routes = ( const routes = (
@ -20,7 +20,7 @@ const routes = (
<Route path="/embark/deployment/" component={DeploymentContainer} /> <Route path="/embark/deployment/" component={DeploymentContainer} />
<Route path="/embark/contracts/:contractName" component={ContractLayoutContainer} /> <Route path="/embark/contracts/:contractName" component={ContractLayoutContainer} />
<Route path="/embark/contracts" component={ContractsContainer} /> <Route path="/embark/contracts" component={ContractsContainer} />
<Route path="/embark/editor" component={FiddleLayout} /> <Route path="/embark/editor" component={EditorContainer} />
<Route path="/embark/utilities" component={UtilsLayout} /> <Route path="/embark/utilities" component={UtilsLayout} />
<Route component={NoMatch} /> <Route component={NoMatch} />
</Switch> </Switch>

View File

@ -200,6 +200,10 @@ export function *watchPostFile() {
yield takeEvery(actions.SAVE_FILE[actions.REQUEST], postFile); yield takeEvery(actions.SAVE_FILE[actions.REQUEST], postFile);
} }
export function *watchPostFileSuccess() {
yield takeEvery(actions.SAVE_FILE[actions.SUCCESS], postCurrentFile);
}
export function *watchDeleteFile() { export function *watchDeleteFile() {
yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile); yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile);
} }
@ -217,10 +221,6 @@ export function *watchFetchCurrentFile() {
yield takeEvery(actions.CURRENT_FILE[actions.REQUEST], fetchCurrentFile); yield takeEvery(actions.CURRENT_FILE[actions.REQUEST], fetchCurrentFile);
} }
export function *watchPostCurrentFile() {
yield takeEvery(actions.SAVE_CURRENT_FILE[actions.REQUEST], postCurrentFile);
}
export function *watchFetchEthGas() { export function *watchFetchEthGas() {
yield takeEvery(actions.GAS_ORACLE[actions.REQUEST], fetchEthGas); yield takeEvery(actions.GAS_ORACLE[actions.REQUEST], fetchEthGas);
} }
@ -433,7 +433,7 @@ export default function *root() {
fork(watchDeleteFileSuccess), fork(watchDeleteFileSuccess),
fork(watchFetchFileSuccess), fork(watchFetchFileSuccess),
fork(watchFetchCurrentFile), fork(watchFetchCurrentFile),
fork(watchPostCurrentFile), fork(watchPostFileSuccess),
fork(watchFetchCredentials), fork(watchFetchCredentials),
fork(watchFetchEthGas), fork(watchFetchEthGas),
fork(watchAuthenticate), fork(watchAuthenticate),