Merge branch 'master' into feature/deployment-strategy

This commit is contained in:
Iuri Matias 2018-11-08 16:35:04 -05:00 committed by GitHub
commit 087a2e8c6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 407 additions and 162 deletions

View File

@ -1,24 +0,0 @@
## Overview
**TL;DR**
<One to two sentence description of the issue you are encountering.>
### Extra Detail
#### Steps to reproduce
1.
2.
3.
#### Screenshots
<please upload images of the error to better showcase the problem.>
#### Logs
<please upload logs of the error to better showcase the problem. You can use the following options: --nodashboard --logfile log.txt --loglevel trace>
#### Context (Environment)
* OS:
* Embark Version:
* Node Version:
* NPM Version:
### Questions?
- Leave a comment on this issue! Make sure to use @ mentions if you want a specific person's attention!

44
.github/ISSUE_TEMPLATE/Bug.md vendored Normal file
View File

@ -0,0 +1,44 @@
---
name: 🐞 Bug Report
about: Something is broken? 🔨
---
### Bug Report
#### Summary
<!-- Provide a summary describing the problem you are experiencing. -->
#### Current behavior
<!--
What is the current (buggy) behavior? Feel free to add as much information as possible.
Also, a screenshot often says more than a thousand words. :)
-->
#### How to reproduce
<!--
Provide steps to reproduce the bug.
Adding a failing Unit or Functional Test would help us a lot - you can submit one in a Pull Request separately, referencing this bug report.
-->
#### Expected behavior
<!-- What was the expected (correct) behavior? -->
#### If you encounter an error, please create a logfile using the following command and post the output here
```
$ embark run --nodashboard --logfile log.txt --loglevel trace
```
#### Please provide additional information about your system
**OS**:
**Embark Version**:
**Node Version**:
**NPM Version**:
#### Sometimes issues are related to Embark's installation. Can you provide information on how Embark was installed?

View File

@ -0,0 +1,13 @@
---
name: 🎉 Feature Request
about: You have a neat idea that should be implemented? 🎩
---
### Feature Request
<!-- Fill in the relevant information below to help triage your issue. -->
#### Summary
<!-- Provide a summary of the feature you would like to see implemented. -->

9
.github/ISSUE_TEMPLATE/Proposal.md vendored Normal file
View File

@ -0,0 +1,9 @@
---
name: 🤔 Proposal
about: Have a nice proposal?
---
### Proposal description
<!-- Describe the proposal you want here. -->

View File

@ -1,11 +0,0 @@
## Overview
**TL;DR**
<One to two sentence description of the issue you are encountering or trying to solve.>
### Questions
<If relevant, write a list of questions that you would like to discuss related to the changes that you have made.>
### Review
<use @mentions for quick questions, specific feedback, and progress updates.>
### Cool Spaceship Picture

20
.github/PULL_REQUEST_TEMPLATE/Bugfix.md vendored Normal file
View File

@ -0,0 +1,20 @@
---
name: ⚙ Bugfix
about: Fixed a bug? 🐞
---
### What bug have you fixed?
<!-- Fill in the relevant information below to help triage your issue. -->
#### How did you fix it (give a brief summary)?
<!-- Provide a summary of the improvement you are submitting. -->
### Questions
<If relevant, write a list of questions that you would like to discuss related to the changes that you have made.>
### Review
<use @mentions for quick questions, specific feedback, and progress updates.>
### Cool Spaceship Picture

View File

@ -0,0 +1,20 @@
---
name: ⚙ Feature
about: Implemented a new feature? 🎁
---
### What feature did you implement?
<!-- Fill in the relevant information below to help triage your issue. -->
#### Anything that needs special attention (breaking changes etc)?
<!-- Provide a summary of the improvement you are submitting. -->
### Questions
<If relevant, write a list of questions that you would like to discuss related to the changes that you have made.>
### Review
<use @mentions for quick questions, specific feedback, and progress updates.>
### Cool Spaceship Picture

View File

@ -10121,6 +10121,11 @@
} }
} }
}, },
"re-resizable": {
"version": "4.9.3",
"resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-4.9.3.tgz",
"integrity": "sha512-JKzmZdUAYWs85YErkmZNB7hjGR9qUOHFUZUtEplZlEZBFHRguiWck5J+HFTy/NjlMJtqQsYPQq57nQAO2BuRRg=="
},
"react": { "react": {
"version": "16.4.2", "version": "16.4.2",
"resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz", "resolved": "https://registry.npmjs.org/react/-/react-16.4.2.tgz",

View File

@ -50,6 +50,7 @@
"prop-types": "^15.6.2", "prop-types": "^15.6.2",
"qs": "^6.5.2", "qs": "^6.5.2",
"raf": "3.4.0", "raf": "3.4.0",
"re-resizable": "^4.9.3",
"react": "^16.4.2", "react": "^16.4.2",
"react-blockies": "^1.4.0", "react-blockies": "^1.4.0",
"react-bootstrap-typeahead": "^3.2.4", "react-bootstrap-typeahead": "^3.2.4",

View File

@ -42,7 +42,7 @@ const LayoutContract = ({contract, children, cardTitle}) => (
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>
<span className={orderClassName(contract.address)}>{contract.index + 1}</span> <span className={orderClassName(contract.address)}>{contract.deployIndex + 1}</span>
{cardTitle} {cardTitle}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
@ -55,7 +55,7 @@ const LayoutContract = ({contract, children, cardTitle}) => (
LayoutContract.propTypes = { LayoutContract.propTypes = {
contract: PropTypes.object, contract: PropTypes.object,
children: PropTypes.array, children: PropTypes.array,
cardTitle: PropTypes.string cardTitle: PropTypes.object
}; };
const DeploymentResult = ({deployment}) => { const DeploymentResult = ({deployment}) => {
@ -81,7 +81,7 @@ const DeploymentResult = ({deployment}) => {
DeploymentResult.propTypes = { DeploymentResult.propTypes = {
deployment: PropTypes.object deployment: PropTypes.object
} };
const GasEstimateResult = ({gasEstimate}) => { const GasEstimateResult = ({gasEstimate}) => {
if (gasEstimate.running) { if (gasEstimate.running) {
@ -244,7 +244,10 @@ const ContractsHeader = ({deploymentPipeline, updateDeploymentPipeline}) => (
); );
ContractsHeader.propTypes = { ContractsHeader.propTypes = {
deploymentPipeline: PropTypes.object, deploymentPipeline: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
updateDeploymentPipeline: PropTypes.func updateDeploymentPipeline: PropTypes.func
}; };
@ -262,13 +265,16 @@ const Contract = ({web3, contract, deploymentPipeline, web3Deploy, web3EstimateG
web3Deploy={web3Deploy} web3Deploy={web3Deploy}
web3EstimateGas={web3EstimateGas}/>; web3EstimateGas={web3EstimateGas}/>;
default: default:
return <React.Fragment></React.Fragment>; return <React.Fragment/>;
} }
}; };
Contract.propTypes = { Contract.propTypes = {
contract: PropTypes.object, contract: PropTypes.object,
deploymentPipeline: PropTypes.object, deploymentPipeline: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
toggleContractOverview: PropTypes.func, toggleContractOverview: PropTypes.func,
web3: PropTypes.object, web3: PropTypes.object,
web3Deploy: PropTypes.func, web3Deploy: PropTypes.func,
@ -300,12 +306,15 @@ class ContractsDeployment extends React.Component {
<Col> <Col>
<ContractsHeader deploymentPipeline={this.props.deploymentPipeline} <ContractsHeader deploymentPipeline={this.props.deploymentPipeline}
updateDeploymentPipeline={this.props.updateDeploymentPipeline}/> updateDeploymentPipeline={this.props.updateDeploymentPipeline}/>
{this.props.contracts.sort((a, b) => a.index - b.index).map(contract => ( {this.props.contracts.filter(contract => contract.code || contract.deploy)
<Contract key={contract.index} .sort((a, b) => a.index - b.index).map((contract, index) => {
contract.deployIndex = index;
return (<Contract key={contract.deployIndex}
contract={contract} contract={contract}
toggleContractOverview={(contract) => this.toggleContractOverview(contract)} toggleContractOverview={(contract) => this.toggleContractOverview(contract)}
{...this.props} /> {...this.props} />);
))} }
)}
</Col> </Col>
{this.isContractOverviewOpen() && {this.isContractOverviewOpen() &&
<Col xs={6} md={3}> <Col xs={6} md={3}>
@ -322,9 +331,13 @@ class ContractsDeployment extends React.Component {
} }
} }
ContractsDeployment.propTypes = { ContractsDeployment.propTypes = {
contracts: PropTypes.array, contracts: PropTypes.array,
deploymentPipeline: PropTypes.string, deploymentPipeline: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
updateDeploymentPipeline: PropTypes.func, updateDeploymentPipeline: PropTypes.func,
web3Deployments: PropTypes.object, web3Deployments: PropTypes.object,
web3GasEstimates: PropTypes.object, web3GasEstimates: PropTypes.object,

View File

@ -21,7 +21,8 @@ const style = (theme) => ({
top: 0, top: 0,
bottom: '40px', bottom: '40px',
left: 0, left: 0,
right: 0 right: 0,
minWidth: '175px'
}, },
node: { node: {
base: { base: {
@ -214,7 +215,7 @@ class FileExplorer extends React.Component {
style={style(this.props.theme)} style={style(this.props.theme)}
/> />
<Label className="hidden-toogle mb-0 pt-2 pr-2 pb-1 border-top text-right"> <Label className="hidden-toggle mb-0 pt-2 pr-2 pb-1 border-top text-right">
<span className="mr-2 align-top" style={{"fontSize": "12px"}}>Show hidden files</span> <span className="mr-2 align-top" style={{"fontSize": "12px"}}>Show hidden files</span>
<AppSwitch color="success" variant="pill" size="sm" onChange={this.props.toggleShowHiddenFiles}/> <AppSwitch color="success" variant="pill" size="sm" onChange={this.props.toggleShowHiddenFiles}/>
</Label> </Label>

View File

@ -47,8 +47,11 @@ class Login extends React.Component {
name="token" name="token"
placeholder="Enter token" placeholder="Enter token"
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
autoComplete="off"
value={this.state.token}/> value={this.state.token}/>
<small className="form-text text-muted">Execute <code>embark run</code> in the command line to get your token.</small> <small className="form-text text-muted">Execute <code>embark run</code> in the command line to get
your token.
</small>
</div> </div>
<button type="submit" className="btn btn-pill btn-dark">Enter Cockpit</button> <button type="submit" className="btn btn-pill btn-dark">Enter Cockpit</button>
</form> </form>

View File

@ -46,6 +46,10 @@ class TextEditor extends React.Component {
}); });
} }
componentWillUnmount() {
window.removeEventListener("resize", this.handleResize);
}
handleResize = () => editor.layout(); handleResize = () => editor.layout();
@ -134,6 +138,10 @@ class TextEditor extends React.Component {
this.handleResize(); this.handleResize();
} }
addEditorTabs(e, file) {
e.preventDefault(); this.props.addEditorTabs(file);
}
renderTabs() { renderTabs() {
return ( return (
<ul className="list-inline m-0 p-0"> <ul className="list-inline m-0 p-0">
@ -144,7 +152,7 @@ class TextEditor extends React.Component {
})}> })}>
<a <a
href="#switch-tab" href="#switch-tab"
onClick={() => this.props.addEditorTabs(file)} onClick={(e) => this.addEditorTabs(e, file)}
className="p-2 text-muted" className="p-2 text-muted"
> >
{file.name} {file.name}

View File

@ -4,12 +4,12 @@ import {Button, Nav, NavLink} from 'reactstrap';
import classnames from 'classnames'; import classnames from 'classnames';
import FontAwesomeIcon from 'react-fontawesome'; import FontAwesomeIcon from 'react-fontawesome';
const TextEditorToolbarTabs = { export const TextEditorToolbarTabs = {
Overview: 'overview', Interact: { label: 'Interact', icon: 'bolt' },
Detail: 'detail', Details: { label: 'Details', icon: 'info-circle' },
Transactions: 'transactions', Debugger: { label: 'Debugger', icon: 'bug' },
Debugger: 'debugger', Transactions: { label: 'Transactions', icon: 'list-alt' },
Browser: 'browser' Browser: { label: 'Browser', icon: 'eye' }
}; };
class TextEditorToolbar extends Component { class TextEditorToolbar extends Component {
@ -18,6 +18,18 @@ class TextEditorToolbar extends Component {
return this.props.activeTab === tab; return this.props.activeTab === tab;
} }
isBrowserTab(tab) {
return tab === TextEditorToolbarTabs.Browser;
}
renderTab(tab) {
return (
<NavLink key={tab.label} className={classnames('btn', { active: this.isActiveTab(tab)})} onClick={() => this.props.openAsideTab(tab)}>
<FontAwesomeIcon className="mr-2" name={tab.icon} /> {tab.label}
</NavLink>
);
}
render() { render() {
return ( return (
<ol className="breadcrumb mb-0"> <ol className="breadcrumb mb-0">
@ -33,27 +45,9 @@ class TextEditorToolbar extends Component {
</li> </li>
<li className="breadcrumb-menu"> <li className="breadcrumb-menu">
<Nav className="btn-group"> <Nav className="btn-group">
{this.props.isContract && {this.props.isContract && Object.values(TextEditorToolbarTabs).map(tab => !this.isBrowserTab(tab) && this.renderTab(tab))}
<React.Fragment> {this.renderTab(TextEditorToolbarTabs.Browser)}
<NavLink className={classnames('btn', { active: this.isActiveTab(TextEditorToolbarTabs.Overview)})} onClick={() => this.props.openAsideTab(TextEditorToolbarTabs.Overview)}>
<FontAwesomeIcon className="mr-2" name="bolt" />Interact
</NavLink>
<NavLink className={classnames('btn', { active: this.isActiveTab(TextEditorToolbarTabs.Detail)})} href="#" onClick={() => this.props.openAsideTab(TextEditorToolbarTabs.Detail)}>
<FontAwesomeIcon className="mr-2" name="info-circle" />Details
</NavLink>
<NavLink className={classnames('btn', { active: this.isActiveTab(TextEditorToolbarTabs.Transactions)})} href="#" onClick={() => this.props.openAsideTab(TextEditorToolbarTabs.Transactions)}>
<FontAwesomeIcon className="mr-2" name="list-alt" />Transactions
</NavLink>
<NavLink className={classnames('btn', { active: this.isActiveTab(TextEditorToolbarTabs.Debugger)})} href="#" onClick={() => this.props.openAsideTab(TextEditorToolbarTabs.Debugger)}>
<FontAwesomeIcon className="mr-2" name="bug" />Debugger
</NavLink>
</React.Fragment>
}
<NavLink className={classnames('btn', { active: this.isActiveTab(TextEditorToolbarTabs.Browser)})} href="#" onClick={() => this.props.openAsideTab(TextEditorToolbarTabs.Browser)}>
<FontAwesomeIcon className="mr-2" name="eye" /> Preview
</NavLink>
</Nav> </Nav>
</li> </li>
</ol> </ol>
); );
@ -66,7 +60,7 @@ TextEditorToolbar.propTypes = {
remove: PropTypes.func, remove: PropTypes.func,
toggleShowHiddenFiles: PropTypes.func, toggleShowHiddenFiles: PropTypes.func,
openAsideTab: PropTypes.func, openAsideTab: PropTypes.func,
activeTab: PropTypes.string activeTab: PropTypes.object
}; };
export default TextEditorToolbar; export default TextEditorToolbar;

View File

@ -8,3 +8,7 @@ export const DEPLOYMENT_PIPELINES = {
embark: 'embark' embark: 'embark'
}; };
export const DEFAULT_HOST = process.env.NODE_ENV === 'development' ? 'localhost:8000' : window.location.host; export const DEFAULT_HOST = process.env.NODE_ENV === 'development' ? 'localhost:8000' : window.location.host;
export const OPERATIONS = {
MORE: 1,
LESS: -1
};

View File

@ -46,7 +46,10 @@ function mapStateToProps(state) {
DeploymentContainer.propTypes = { DeploymentContainer.propTypes = {
contracts: PropTypes.array, contracts: PropTypes.array,
deploymentPipeline: PropTypes.object, deploymentPipeline: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string
]),
fetchContracts: PropTypes.func, fetchContracts: PropTypes.func,
updateDeploymentPipeline: PropTypes.func, updateDeploymentPipeline: PropTypes.func,
web3: PropTypes.object, web3: PropTypes.object,

View File

@ -4,11 +4,12 @@
margin-top: -1.5rem !important; margin-top: -1.5rem !important;
} }
.hidden-toogle { .hidden-toggle {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: 0; right: 0;
left: 0; left: 0;
height: 40px;
} }
.text-editor__debuggerLine { .text-editor__debuggerLine {
@ -34,8 +35,12 @@
.editor-aside { .editor-aside {
position: relative; position: relative;
} }
}
.aside-opened .text-editor-container { .resizer-handle {
max-height: 500px; width: 15px !important;
} }
.resize-handle-horizontal {
height: 15px !important;
} }

View File

@ -9,17 +9,47 @@ import TextEditorToolbarContainer from './TextEditorToolbarContainer';
import {fetchEditorTabs as fetchEditorTabsAction} from '../actions'; import {fetchEditorTabs as fetchEditorTabsAction} from '../actions';
import {getCurrentFile} from '../reducers/selectors'; import {getCurrentFile} from '../reducers/selectors';
import classnames from 'classnames'; import classnames from 'classnames';
import Resizable from 're-resizable';
import {OPERATIONS} from '../constants';
import './EditorContainer.css'; import './EditorContainer.css';
class EditorContainer extends React.Component { class EditorContainer extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {currentAsideTab: '', showHiddenFiles: false, currentFile: this.props.currentFile}; this.DEFAULT_EDITOR_WIDTH = 85;
this.DEFAULT_EDITOR_WIDTH_SMALL = 75;
this.DEFAULT_HEIGHT = 500;
this.SMALL_SIZE = 768;
this.windowWidth = window.innerWidth;
this.state = {
currentAsideTab: {}, showHiddenFiles: false, currentFile: this.props.currentFile,
editorHeight: this.DEFAULT_HEIGHT,
editorWidth: ((this.windowWidth < this.SMALL_SIZE) ? this.DEFAULT_EDITOR_WIDTH_SMALL : this.DEFAULT_EDITOR_WIDTH) + '%',
asideHeight: '100%', asideWidth: '25%',
isSmallSize: (this.windowWidth < this.SMALL_SIZE)
};
} }
componentDidMount() { componentDidMount() {
this.props.fetchEditorTabs(); this.props.fetchEditorTabs();
window.addEventListener("resize", this.updateDimensions.bind(this));
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions.bind(this));
}
updateDimensions() {
this.windowWidth = window.innerWidth;
const isSmallSize = (this.windowWidth < this.SMALL_SIZE);
if (this.state.isSmallSize !== isSmallSize) {
this.setState({isSmallSize});
this.changeEditorWidth(isSmallSize ? OPERATIONS.MORE : OPERATIONS.LESS);
this.editor.handleResize();
}
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -43,38 +73,116 @@ class EditorContainer extends React.Component {
} }
openAsideTab(newTab) { openAsideTab(newTab) {
if (newTab === this.state.currentAsideTab) { if (!this.state.isSmallSize) {
return this.setState({currentAsideTab: ''}); this.changeEditorWidth((!this.state.currentAsideTab.label) ? OPERATIONS.LESS : OPERATIONS.MORE);
} }
if (newTab.label === this.state.currentAsideTab.label) {
this.editor.handleResize();
return this.setState({currentAsideTab: {}});
}
this.setState({currentAsideTab: newTab}); this.setState({currentAsideTab: newTab});
} }
textEditorMdSize() { changeEditorWidth(operation) {
return this.state.currentAsideTab.length ? 7 : 10; this.setState({
editorWidth: (parseFloat(this.state.editorWidth) + (operation * parseFloat(this.state.asideWidth))) + '%'
});
}
renderTextEditor() {
const height = !!(this.state.isSmallSize && this.state.currentAsideTab.label) ? this.state.editorHeight : 'auto';
return (
<Resizable
size={{width: this.state.editorWidth, height: height}}
minWidth="20%" maxWidth="90%"
handleClasses={{left: 'resizer-handle', right: 'resizer-handle', bottom: 'resize-handle-horizontal'}}
onResizeStop={(e, direction, ref, d) => {
this.setState({
editorWidth: ref.style.width,
editorHeight: this.state.editorHeight + d.height
});
this.editor.handleResize();
}}
className="text-editor-container border-left"
enable={{
top: false,
right: false,
bottom: !!(this.state.isSmallSize && this.state.currentAsideTab.label),
left: true,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false
}}>
<TextEditorContainer
ref={instance => {
this.editor = instance ? instance.getWrappedInstance().editor : null;
}}
currentFile={this.props.currentFile}
onFileContentChange={(newContent) => this.onFileContentChange(newContent)}/>
</Resizable>
);
}
renderAside() {
const aside = (
<div className="editor-aside">
<TextEditorAsideContainer currentAsideTab={this.state.currentAsideTab}
currentFile={this.props.currentFile}/>
</div>
);
if (this.windowWidth < this.SMALL_SIZE) {
return (<Col sm={12} className="border-left-0 relative">
{aside}
</Col>);
}
return (
<Resizable defaultSize={{width: this.state.asideWidth, height: 'auto'}}
maxWidth='60%' minWidth='17%'
handleClasses={{left: 'resizer-handle', right: 'resizer-handle'}}
className="border-left-0 relative"
enable={{
top: false,
right: false,
bottom: false,
left: true,
topRight: false,
bottomRight: false,
bottomLeft: false,
topLeft: false
}}
onResize={(e, direction, ref, _d) => {
this.setState({
editorWidth: this.DEFAULT_EDITOR_WIDTH - parseFloat(ref.style.width) + '%'
});
}}>
{aside}
</Resizable>
);
} }
render() { render() {
return ( return (
<Row noGutters className={classnames('h-100', 'editor--grid', {'aside-opened': this.state.currentAsideTab.length})}> <Row noGutters
className={classnames('h-100', 'editor--grid', {'aside-opened': this.state.currentAsideTab.label})}>
<Col xs={12}> <Col xs={12}>
<TextEditorToolbarContainer openAsideTab={(newTab) => this.openAsideTab(newTab)} <TextEditorToolbarContainer openAsideTab={(newTab) => this.openAsideTab(newTab)}
isContract={this.isContract()} isContract={this.isContract()}
currentFile={this.props.currentFile} currentFile={this.props.currentFile}
activeTab={this.state.currentAsideTab}/> activeTab={this.state.currentAsideTab}/>
</Col> </Col>
<Col sm={4} md={2} xl={2} lg={2} className="border-right">
<FileExplorerContainer showHiddenFiles={this.state.showHiddenFiles} toggleShowHiddenFiles={() => this.toggleShowHiddenFiles()} /> <Col className="border-right">
<FileExplorerContainer showHiddenFiles={this.state.showHiddenFiles}
toggleShowHiddenFiles={() => this.toggleShowHiddenFiles()}/>
</Col> </Col>
<Col sm={8} md={this.textEditorMdSize()} className="text-editor-container">
<TextEditorContainer currentFile={this.props.currentFile} onFileContentChange={(newContent) => this.onFileContentChange(newContent)} /> {this.renderTextEditor()}
</Col>
{this.state.currentAsideTab && {this.state.currentAsideTab.label && this.renderAside()}
<Col sm={12} md={3} className="border-left-0 relative">
<div className="editor-aside">
<TextEditorAsideContainer currentAsideTab={this.state.currentAsideTab}
currentFile={this.props.currentFile}/>
</div>
</Col>}
</Row> </Row>
); );
} }
@ -95,6 +203,6 @@ EditorContainer.propTypes = {
export default connect( export default connect(
mapStateToProps, mapStateToProps,
{fetchEditorTabs: fetchEditorTabsAction.request}, {fetchEditorTabs: fetchEditorTabsAction.request}
)(EditorContainer); )(EditorContainer);

View File

@ -10,6 +10,7 @@ import ContractDetail from '../components/ContractDetail';
import ContractTransactionsContainer from './ContractTransactionsContainer'; import ContractTransactionsContainer from './ContractTransactionsContainer';
import ContractOverviewContainer from '../containers/ContractOverviewContainer'; import ContractOverviewContainer from '../containers/ContractOverviewContainer';
import ContractDebuggerContainer from '../containers/ContractDebuggerContainer'; import ContractDebuggerContainer from '../containers/ContractDebuggerContainer';
import { TextEditorToolbarTabs } from '../components/TextEditorToolbar';
class TextEditorAsideContainer extends Component { class TextEditorAsideContainer extends Component {
componentDidMount() { componentDidMount() {
@ -17,29 +18,29 @@ class TextEditorAsideContainer extends Component {
} }
renderContent(contract, index) { renderContent(contract, index) {
switch (this.props.currentAsideTab) { switch (this.props.currentAsideTab.label) {
case 'debugger': case TextEditorToolbarTabs.Debugger.label:
return ( return (
<React.Fragment> <React.Fragment>
<h2>{contract.className} - Debugger</h2> <h2>{contract.className} - Debugger</h2>
<ContractDebuggerContainer key={index} contract={contract}/> <ContractDebuggerContainer key={index} contract={contract}/>
</React.Fragment> </React.Fragment>
); );
case 'detail': case TextEditorToolbarTabs.Details.label:
return ( return (
<React.Fragment> <React.Fragment>
<h2>{contract.className} - Details</h2> <h2>{contract.className} - Details</h2>
<ContractDetail key={index} contract={contract}/> <ContractDetail key={index} contract={contract}/>
</React.Fragment> </React.Fragment>
); );
case 'transactions': case TextEditorToolbarTabs.Transactions.label:
return ( return (
<React.Fragment> <React.Fragment>
<h2>{contract.className} - Transactions</h2> <h2>{contract.className} - Transactions</h2>
<ContractTransactionsContainer key={index} contract={contract}/> <ContractTransactionsContainer key={index} contract={contract}/>
</React.Fragment> </React.Fragment>
); );
case 'overview': case TextEditorToolbarTabs.Interact.label:
return ( return (
<React.Fragment> <React.Fragment>
<h2>{contract.className} - Interact</h2> <h2>{contract.className} - Interact</h2>
@ -52,7 +53,7 @@ class TextEditorAsideContainer extends Component {
} }
render() { render() {
if (this.props.currentAsideTab === 'browser') { if (this.props.currentAsideTab.label === TextEditorToolbarTabs.Browser.label) {
return <Preview/>; return <Preview/>;
} }
return this.props.contracts.map((contract, index) => { return this.props.contracts.map((contract, index) => {
@ -75,7 +76,7 @@ function mapStateToProps(state, props) {
TextEditorAsideContainer.propTypes = { TextEditorAsideContainer.propTypes = {
currentFile: PropTypes.object, currentFile: PropTypes.object,
currentAsideTab: PropTypes.string, currentAsideTab: PropTypes.object,
contract: PropTypes.array, contract: PropTypes.array,
fetchContracts: PropTypes.func, fetchContracts: PropTypes.func,
contracts: PropTypes.array contracts: PropTypes.array

View File

@ -27,7 +27,10 @@ class TextEditorContainer extends React.Component {
addEditorTabs={this.props.addEditorTabs} addEditorTabs={this.props.addEditorTabs}
debuggerLine={this.props.debuggerLine} debuggerLine={this.props.debuggerLine}
onFileContentChange={this.props.onFileContentChange} onFileContentChange={this.props.onFileContentChange}
theme={this.props.theme} /> theme={this.props.theme}
ref={instance => {
if (instance) this.editor = instance;
}}/>
); );
} }
} }
@ -45,7 +48,7 @@ TextEditorContainer.propTypes = {
onFileContentChange: PropTypes.func, onFileContentChange: PropTypes.func,
toggleBreakpoints: PropTypes.func, toggleBreakpoints: PropTypes.func,
breakpoints: PropTypes.array, breakpoints: PropTypes.array,
toggleBreakpoint: PropTypes.object, toggleBreakpoint: PropTypes.func,
fetchEditorTabs: PropTypes.func, fetchEditorTabs: PropTypes.func,
removeEditorTabs: PropTypes.func, removeEditorTabs: PropTypes.func,
addEditorTabs: PropTypes.func, addEditorTabs: PropTypes.func,
@ -57,9 +60,11 @@ TextEditorContainer.propTypes = {
export default connect( export default connect(
mapStateToProps, mapStateToProps,
{ {
toggleBreakpoint, toggleBreakpoint: toggleBreakpoint.request,
fetchEditorTabs: fetchEditorTabsAction.request, fetchEditorTabs: fetchEditorTabsAction.request,
removeEditorTabs: removeEditorTabsAction.request, removeEditorTabs: removeEditorTabsAction.request,
addEditorTabs: addEditorTabsAction.request addEditorTabs: addEditorTabsAction.request
}, },
null,
{ withRef: true }
)(TextEditorContainer); )(TextEditorContainer);

View File

@ -34,7 +34,7 @@ TextEditorToolbarContainer.propTypes = {
removeFile: PropTypes.func, removeFile: PropTypes.func,
toggleShowHiddenFiles: PropTypes.func, toggleShowHiddenFiles: PropTypes.func,
openAsideTab: PropTypes.func, openAsideTab: PropTypes.func,
activeTab: PropTypes.string activeTab: PropTypes.object
}; };
export default connect( export default connect(

View File

@ -153,6 +153,10 @@ export function getEnsRecords(state) {
return state.entities.ensRecords; return state.entities.ensRecords;
} }
export function getEnsRecordForName(state, name) {
return state.entities.ensRecords.find((record) => record.name === name);
}
export function getEnsErrors(state) { export function getEnsErrors(state) {
return state.errorEntities.ensRecords; return state.errorEntities.ensRecords;
} }

View File

@ -1,16 +1,28 @@
import {put, select} from "redux-saga/effects"; import {put, select} from "redux-saga/effects";
import {getAccounts, getBlocks, getTransactions, getContracts} from "../reducers/selectors"; import {getAccounts, getBlocks, getTransactions, getContracts, getEnsRecordForName} from "../reducers/selectors";
import {fetchAccounts, fetchBlocks, fetchTransactions, fetchContracts} from "./index"; import {fetchAccounts, fetchBlocks, fetchTransactions, fetchContracts, fetchEnsRecord} from "./index";
import {ELEMENTS_LIMIT} from '../constants'; import {ELEMENTS_LIMIT} from '../constants';
export function *searchExplorer(entity, payload) { export function *searchExplorer(entity, payload) {
let result; let result;
let searchValue = payload.searchValue;
let isENSName = false;
if (searchValue.endsWith('.eth')) {
isENSName = true;
yield fetchEnsRecord({name: payload.searchValue});
const ensRecord = yield select(getEnsRecordForName, searchValue);
if (!ensRecord) {
return yield put(entity.success({error: 'No ENS record for that name'}));
}
searchValue = ensRecord.address;
}
// Accounts // Accounts
yield fetchAccounts({}); yield fetchAccounts({});
const accounts = yield select(getAccounts); const accounts = yield select(getAccounts);
result = accounts.find(account => { result = accounts.find(account => {
return account.address === payload.searchValue; return account.address === searchValue;
}); });
if (result) { if (result) {
@ -21,7 +33,7 @@ export function *searchExplorer(entity, payload) {
yield fetchContracts({}); yield fetchContracts({});
const contracts = yield select(getContracts); const contracts = yield select(getContracts);
result = contracts.find(contract => { result = contracts.find(contract => {
return contract.address === payload.searchValue; return contract.address === searchValue;
}); });
if (result) { if (result) {
@ -31,9 +43,9 @@ export function *searchExplorer(entity, payload) {
// Blocks // Blocks
yield fetchBlocks({limit: ELEMENTS_LIMIT}); yield fetchBlocks({limit: ELEMENTS_LIMIT});
const blocks = yield select(getBlocks); const blocks = yield select(getBlocks);
const intSearchValue = parseInt(payload.searchValue, 10); const intSearchValue = parseInt(searchValue, 10);
result = blocks.find(block => { result = blocks.find(block => {
return block.hash === payload.searchValue || block.number === intSearchValue; return block.hash === searchValue || block.number === intSearchValue;
}); });
if (result) { if (result) {
@ -44,12 +56,16 @@ export function *searchExplorer(entity, payload) {
yield fetchTransactions({blockLimit: ELEMENTS_LIMIT}); yield fetchTransactions({blockLimit: ELEMENTS_LIMIT});
const transactions = yield select(getTransactions); const transactions = yield select(getTransactions);
result = transactions.find(transaction => { result = transactions.find(transaction => {
return transaction.hash === payload.searchValue; return transaction.hash === searchValue;
}); });
if (result) { if (result) {
return yield put(entity.success(result)); return yield put(entity.success(result));
} }
if (isENSName) {
return yield put(entity.success({error: `ENS resolved to ${searchValue}, but Embark couldn't find what this address represents`}));
}
return yield put(entity.success({error: `No result found in transactions, accounts, contracts, or blocks. Please note: We limit the search to the last ${ELEMENTS_LIMIT} elements for performance`})); return yield put(entity.success({error: `No result found in transactions, accounts, contracts, or blocks. Please note: We limit the search to the last ${ELEMENTS_LIMIT} elements for performance`}));
} }

View File

@ -60,5 +60,6 @@
"deploymentStrategy": { "deploymentStrategy": {
"implicit": "implicit", "implicit": "implicit",
"explicit": "explicit" "explicit": "explicit"
} },
"embarkResourceOrigin": "http://embark"
} }

View File

@ -148,10 +148,8 @@ Config.prototype._updateBlockchainCors = function(){
corsParts.push(utils.buildUrlFromConfig(storageConfig.upload)); corsParts.push(utils.buildUrlFromConfig(storageConfig.upload));
} }
} }
// add whisper cors // Add cors for the proxy and whisper
if(this.communicationConfig && this.communicationConfig.enabled && this.communicationConfig.provider === 'whisper'){ corsParts.push(constants.embarkResourceOrigin);
corsParts.push('http://embark');
}
let cors = corsParts.join(','); let cors = corsParts.join(',');
if(blockchainConfig.rpcCorsDomain === 'auto'){ if(blockchainConfig.rpcCorsDomain === 'auto'){
@ -208,7 +206,8 @@ Config.prototype.loadBlockchainConfigFile = function() {
"default": { "default": {
"enabled": true, "enabled": true,
"rpcCorsDomain": "auto", "rpcCorsDomain": "auto",
"wsOrigins": "auto" "wsOrigins": "auto",
"proxy": true
} }
}; };

View File

@ -1,6 +1,7 @@
const async = require('async'); const async = require('async');
const AccountParser = require('../../utils/accountParser'); const AccountParser = require('../../utils/accountParser');
const fundAccount = require('./fundAccount'); const fundAccount = require('./fundAccount');
const constants = require('../../constants');
class Provider { class Provider {
constructor(options) { constructor(options) {
@ -21,10 +22,10 @@ class Provider {
} else if (this.type === 'ws') { } else if (this.type === 'ws') {
// Note: don't pass to the provider things like {headers: {Origin: "embark"}}. Origin header is for browser to fill // Note: don't pass to the provider things like {headers: {Origin: "embark"}}. Origin header is for browser to fill
// to protect user, it has no meaning if it is used server-side. See here for more details: https://github.com/ethereum/go-ethereum/issues/16608 // to protect user, it has no meaning if it is used server-side. See here for more details: https://github.com/ethereum/go-ethereum/issues/16608
// Moreover, Parity reject origins that are not urls so if you try to connect with Origin: "embark" it gives the followin error: // Moreover, Parity reject origins that are not urls so if you try to connect with Origin: "embark" it gives the following error:
// << Blocked connection to WebSockets server from untrusted origin: Some("embark") >> // << Blocked connection to WebSockets server from untrusted origin: Some("embark") >>
// The best choice is to use void origin, BUT Geth rejects void origin, so to keep both clients happy we can use http://embark // The best choice is to use void origin, BUT Geth rejects void origin, so to keep both clients happy we can use http://embark
self.provider = new this.web3.providers.WebsocketProvider(self.web3Endpoint, {headers: {Origin: "http://embark"}}); self.provider = new this.web3.providers.WebsocketProvider(self.web3Endpoint, {headers: {Origin: constants.embarkResourceOrigin}});
self.provider.on('error', () => self.logger.error('Websocket Error')); self.provider.on('error', () => self.logger.error('Websocket Error'));
self.provider.on('end', () => self.logger.error('Websocket connection ended')); self.provider.on('end', () => self.logger.error('Websocket connection ended'));

View File

@ -12,7 +12,7 @@ const Ipc = require('../../core/ipc');
const {defaultHost, dockerHostSwap} = require('../../utils/host'); const {defaultHost, dockerHostSwap} = require('../../utils/host');
const Logger = require('../../core/logger'); const Logger = require('../../core/logger');
// time between IPC connection attmpts (in ms) // time between IPC connection attempts (in ms)
const IPC_CONNECT_INTERVAL = 2000; const IPC_CONNECT_INTERVAL = 2000;
/*eslint complexity: ["error", 42]*/ /*eslint complexity: ["error", 42]*/
@ -60,7 +60,7 @@ var Blockchain = function(userConfig, clientClass) {
targetGasLimit: this.userConfig.targetGasLimit || false, targetGasLimit: this.userConfig.targetGasLimit || false,
syncMode: this.userConfig.syncMode || this.userConfig.syncmode, syncMode: this.userConfig.syncMode || this.userConfig.syncmode,
verbosity: this.userConfig.verbosity, verbosity: this.userConfig.verbosity,
proxy: this.userConfig.proxy || true proxy: this.userConfig.proxy
}; };
if (this.userConfig === {} || this.userConfig.default || JSON.stringify(this.userConfig) === '{"enabled":true}') { if (this.userConfig === {} || this.userConfig.default || JSON.stringify(this.userConfig) === '{"enabled":true}') {

View File

@ -2,6 +2,7 @@ const async = require('async');
const Web3 = require('web3'); const Web3 = require('web3');
const {buildUrl} = require('../../utils/utils.js'); const {buildUrl} = require('../../utils/utils.js');
const {readFileSync, dappPath} = require('../../core/fs'); const {readFileSync, dappPath} = require('../../core/fs');
const constants = require('../../constants');
class DevFunds { class DevFunds {
constructor(options) { constructor(options) {
@ -11,7 +12,7 @@ class DevFunds {
this.password = this.blockchainConfig.account.password ? readFileSync(dappPath(this.blockchainConfig.account.password), 'utf8').replace('\n', '') : 'dev_password'; this.password = this.blockchainConfig.account.password ? readFileSync(dappPath(this.blockchainConfig.account.password), 'utf8').replace('\n', '') : 'dev_password';
this.networkId = null; this.networkId = null;
this.balance = Web3.utils.toWei("1", "ether"); this.balance = Web3.utils.toWei("1", "ether");
this.provider = options.provider || new Web3.providers.WebsocketProvider(buildUrl('ws', this.blockchainConfig.wsHost, this.blockchainConfig.wsPort), {headers: {Origin: "http://embark"}}); this.provider = options.provider || new Web3.providers.WebsocketProvider(buildUrl('ws', this.blockchainConfig.wsHost, this.blockchainConfig.wsPort), {headers: {Origin: constants.embarkResourceOrigin}});
this.web3 = new Web3(this.provider); this.web3 = new Web3(this.provider);
if (this.blockchainConfig.account.balance) { if (this.blockchainConfig.account.balance) {
this.balance = this.blockchainConfig.account.balance; this.balance = this.blockchainConfig.account.balance;

View File

@ -270,7 +270,7 @@ class ContractsManager {
function getGasPriceForNetwork(callback) { function getGasPriceForNetwork(callback) {
return callback(null, self.contractsConfig.gasPrice); return callback(null, self.contractsConfig.gasPrice);
}, },
function prepareContractsFromCompilation(gasPrice, callback) { function prepareContractsForCompilation(gasPrice, callback) {
let className, compiledContract, contractConfig, contract; let className, compiledContract, contractConfig, contract;
for (className in self.compiledContracts) { for (className in self.compiledContracts) {
compiledContract = self.compiledContracts[className]; compiledContract = self.compiledContracts[className];

View File

@ -4,6 +4,7 @@ let Web3 = require('web3');
const {parallel} = require('async'); const {parallel} = require('async');
const {sendMessage, listenTo} = require('./js/communicationFunctions'); const {sendMessage, listenTo} = require('./js/communicationFunctions');
const messageEvents = require('./js/message_events'); const messageEvents = require('./js/message_events');
const constants = require('../../constants');
const {canonicalHost, defaultHost} = require('../../utils/host'); const {canonicalHost, defaultHost} = require('../../utils/host');
@ -44,7 +45,7 @@ class Whisper {
// Moreover, Parity reject origins that are not urls so if you try to connect with Origin: "embark" it gives the followin error: // Moreover, Parity reject origins that are not urls so if you try to connect with Origin: "embark" it gives the followin error:
// << Blocked connection to WebSockets server from untrusted origin: Some("embark") >> // << Blocked connection to WebSockets server from untrusted origin: Some("embark") >>
// The best choice is to use void origin, BUT Geth rejects void origin, so to keep both clients happy we can use http://embark // The best choice is to use void origin, BUT Geth rejects void origin, so to keep both clients happy we can use http://embark
this.web3.setProvider(new Web3.providers.WebsocketProvider(web3Endpoint, {headers: {Origin: "http://embark"}})); this.web3.setProvider(new Web3.providers.WebsocketProvider(web3Endpoint, {headers: {Origin: constants.embarkResourceOrigin}}));
} }
waitForWeb3Ready(cb) { waitForWeb3Ready(cb) {
@ -133,7 +134,7 @@ class Whisper {
const code = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(config)});`; const code = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(config)});`;
this.embark.addProviderInit('communication', code, shouldInit); this.embark.addProviderInit('communication', code, shouldInit);
const consoleConfig = Object.assign({}, config, {providerOptions: {headers: {Origin: "http://embark"}}}); const consoleConfig = Object.assign({}, config, {providerOptions: {headers: {Origin: constants.embarkResourceOrigin}}});
const consoleCode = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(consoleConfig)});`; const consoleCode = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(consoleConfig)});`;
this.embark.addConsoleProviderInit('communication', consoleCode, shouldInit); this.embark.addConsoleProviderInit('communication', consoleCode, shouldInit);
} }

6
npm-shrinkwrap.json generated
View File

@ -5163,9 +5163,9 @@
} }
}, },
"embarkjs": { "embarkjs": {
"version": "0.4.4", "version": "0.4.5",
"resolved": "https://registry.npmjs.org/embarkjs/-/embarkjs-0.4.4.tgz", "resolved": "https://registry.npmjs.org/embarkjs/-/embarkjs-0.4.5.tgz",
"integrity": "sha512-HIetHNqngbhUgdESZ8V1gQiRzoti7LMu/P5xcfemn+8b4KwasUyrybaBdvqmo+ock9rExXZFHIgaYA+85s9DkA==", "integrity": "sha512-WAW9tlvCiSa6DEC6M+ZfFS9WemE91DunOUP4rn7urtFgOpT0m08dFKB0F+Qfcmonb7cozEPfNv5C78xSt1JUsA==",
"requires": { "requires": {
"@babel/runtime-corejs2": "7.0.0-rc.1", "@babel/runtime-corejs2": "7.0.0-rc.1",
"async": "2.6.1", "async": "2.6.1",

View File

@ -68,7 +68,7 @@
"decompress": "4.2.0", "decompress": "4.2.0",
"deep-equal": "1.0.1", "deep-equal": "1.0.1",
"ejs": "2.6.1", "ejs": "2.6.1",
"embarkjs": "0.4.4", "embarkjs": "0.4.5",
"eth-ens-namehash": "2.0.8", "eth-ens-namehash": "2.0.8",
"eth-lib": "0.2.8", "eth-lib": "0.2.8",
"ethereumjs-wallet": "0.6.0", "ethereumjs-wallet": "0.6.0",

View File

@ -41,12 +41,9 @@ describe('embark.Blockchain', function() {
targetGasLimit: false, targetGasLimit: false,
syncMode: undefined, syncMode: undefined,
verbosity: undefined, verbosity: undefined,
proxy: true, proxy: undefined,
silent: undefined silent: undefined
}; };
// We check also proxy's ports because proxy is set to true
expectedConfig.wsPort += constants.blockchain.servicePortOnProxy;
expectedConfig.rpcPort += constants.blockchain.servicePortOnProxy;
assert.deepEqual(blockchain.config, expectedConfig); assert.deepEqual(blockchain.config, expectedConfig);
done(); done();

View File

@ -25,6 +25,7 @@ describe('embark.Config', function () {
"isDev": false, "isDev": false,
"mineWhenNeeded": true, "mineWhenNeeded": true,
"nodiscover": true, "nodiscover": true,
"proxy": true,
"rpcHost": "localhost", "rpcHost": "localhost",
"rpcPort": 8545, "rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000", "rpcCorsDomain": "http://localhost:8000",
@ -48,6 +49,7 @@ describe('embark.Config', function () {
"gasPrice": "8000000", "gasPrice": "8000000",
"mineWhenNeeded": true, "mineWhenNeeded": true,
"nodiscover": true, "nodiscover": true,
"proxy": true,
"rpcHost": "localhost", "rpcHost": "localhost",
"rpcPort": 8545, "rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000", "rpcCorsDomain": "http://localhost:8000",
@ -81,6 +83,7 @@ describe('embark.Config', function () {
"gasPrice": "8000000", "gasPrice": "8000000",
"mineWhenNeeded": true, "mineWhenNeeded": true,
"nodiscover": true, "nodiscover": true,
"proxy": true,
"rpcHost": "localhost", "rpcHost": "localhost",
"rpcPort": 8545, "rpcPort": 8545,
"rpcCorsDomain": "http://localhost:8000", "rpcCorsDomain": "http://localhost:8000",