Solidity Compiler in UI (#3279)

* Added new Deploy Contract page // Use Brace in React #2276

* Adding Web Wrokers WIP

* Compiling Solidity code // Getting mandatory params #2276

* Working editor and deployment #2276

* WIP : displaying source code

* Added Solidity hightling, editor component in UI

* Re-adding the standard Deploy Modal #2276

* Using MobX in Contract Edition // Save to Localstorage #2276

* User select Solidity version #2276

* Loading Solidity versions and closing worker properly #2276

* Adds export to solidity editor #2276

* Adding Import to Contract Editor #2276

* Persistent Worker => Don't load twice Solidity Code #2276

* UI Fixes

* Editor tweaks

* Added Details with ABI in Contract view

* Adds Save capabilities to contract editor // WIP on Load #3279

* Working Load and Save contracts... #3231

* Adding loader of Snippets // Export with name #3279

* Added snippets / Importing from files and from URL

* Fix wrong ID in saved Contract

* Fix lint

* Fixed Formal errors as warning #3279

* Fixing lint issues

* Use NPM Module for valid URL (fixes linting issue too)

* Don't clobber tests.
This commit is contained in:
Nicolas Gotchac 2016-11-11 15:00:04 +01:00 committed by Jaco Greeff
parent 5d8f74ed57
commit 0e4ef539fc
38 changed files with 3555 additions and 21 deletions

View File

@ -100,6 +100,7 @@
"postcss-loader": "^0.8.1", "postcss-loader": "^0.8.1",
"postcss-nested": "^1.0.0", "postcss-nested": "^1.0.0",
"postcss-simple-vars": "^3.0.0", "postcss-simple-vars": "^3.0.0",
"raw-loader": "^0.5.1",
"react-addons-test-utils": "^15.3.0", "react-addons-test-utils": "^15.3.0",
"react-copy-to-clipboard": "^4.2.3", "react-copy-to-clipboard": "^4.2.3",
"react-hot-loader": "^1.3.0", "react-hot-loader": "^1.3.0",
@ -118,6 +119,7 @@
"dependencies": { "dependencies": {
"bignumber.js": "^2.3.0", "bignumber.js": "^2.3.0",
"blockies": "0.0.2", "blockies": "0.0.2",
"brace": "^0.9.0",
"bytes": "^2.4.0", "bytes": "^2.4.0",
"chart.js": "^2.3.0", "chart.js": "^2.3.0",
"es6-promise": "^3.2.1", "es6-promise": "^3.2.1",
@ -138,9 +140,11 @@
"moment": "^2.14.1", "moment": "^2.14.1",
"qs": "^6.3.0", "qs": "^6.3.0",
"react": "^15.2.1", "react": "^15.2.1",
"react-ace": "^4.0.0",
"react-addons-css-transition-group": "^15.2.1", "react-addons-css-transition-group": "^15.2.1",
"react-chartjs-2": "^1.5.0", "react-chartjs-2": "^1.5.0",
"react-dom": "^15.2.1", "react-dom": "^15.2.1",
"react-dropzone": "^3.7.3",
"react-redux": "^4.4.5", "react-redux": "^4.4.5",
"react-router": "^2.6.1", "react-router": "^2.6.1",
"react-router-redux": "^4.0.5", "react-router-redux": "^4.0.5",
@ -152,10 +156,13 @@
"redux-thunk": "^2.1.0", "redux-thunk": "^2.1.0",
"rlp": "^2.0.0", "rlp": "^2.0.0",
"scryptsy": "^2.0.0", "scryptsy": "^2.0.0",
"solc": "ngotchac/solc-js",
"store": "^1.3.20", "store": "^1.3.20",
"utf8": "^2.1.1", "utf8": "^2.1.1",
"valid-url": "^1.0.9",
"validator": "^5.7.0", "validator": "^5.7.0",
"web3": "^0.17.0-beta", "web3": "^0.17.0-beta",
"whatwg-fetch": "^1.0.0" "whatwg-fetch": "^1.0.0",
"worker-loader": "^0.7.1"
} }
} }

View File

@ -0,0 +1,60 @@
/*
This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans.
In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans.
Imagine coins, currencies, shares, voting weight, etc.
Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners.
1) Initial Finite Supply (upon creation one specifies how much is minted).
2) In the absence of a token registry: Optional Decimal, Symbol & Name.
3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred.
.*/
import "StandardToken.sol";
contract HumanStandardToken is StandardToken {
function () {
//if ether is sent to this address, send it back.
throw;
}
/* Public variables of the token */
/*
NOTE:
The following variables are OPTIONAL vanities. One does not have to include them.
They allow one to customise the token contract & in no way influences the core functionality.
Some wallets/interfaces might not even bother to look at this information.
*/
string public name; //fancy name: eg Simon Bucks
uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
string public symbol; //An identifier: eg SBX
string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme.
function HumanStandardToken(
uint256 _initialAmount,
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol
) {
balances[msg.sender] = _initialAmount; // Give the creator all initial tokens
totalSupply = _initialAmount; // Update total supply
name = _tokenName; // Set the name for display purposes
decimals = _decimalUnits; // Amount of decimals for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
}
/* Approves and then calls the receiving contract */
function approveAndCall(address _spender, uint256 _value, bytes _extraData) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
//call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
//receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
//it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
if(!_spender.call(bytes4(bytes32(sha3("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData)) { throw; }
return true;
}
}

View File

@ -0,0 +1,55 @@
/*
You should inherit from StandardToken or, for a token like you would want to
deploy in something like Mist, see HumanStandardToken.sol.
(This implements ONLY the standard functions and NOTHING else.
If you deploy this, you won't have anything useful.)
Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20
.*/
import "Token.sol";
contract StandardToken is Token {
function transfer(address _to, uint256 _value) returns (bool success) {
//Default assumes totalSupply can't be over max (2^256 - 1).
//If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
//Replace the if with this one instead.
//if (balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
if (balances[msg.sender] >= _value && _value > 0) {
balances[msg.sender] -= _value;
balances[_to] += _value;
Transfer(msg.sender, _to, _value);
return true;
} else { return false; }
}
function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
//same as above. Replace this line with the following if you want to protect against wrapping uints.
//if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]) {
if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && _value > 0) {
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
Transfer(_from, _to, _value);
return true;
} else { return false; }
}
function balanceOf(address _owner) constant returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) returns (bool success) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
}

View File

@ -0,0 +1,47 @@
// Abstract contract for the full ERC 20 Token standard
// https://github.com/ethereum/EIPs/issues/20
contract Token {
/* This is a slight change to the ERC20 base standard.
function totalSupply() constant returns (uint256 supply);
is replaced with:
uint256 public totalSupply;
This automatically creates a getter function for the totalSupply.
This is moved to the base contract since public getter functions are not
currently recognised as an implementation of the matching abstract
function by the compiler.
*/
/// total amount of tokens
uint256 public totalSupply;
/// @param _owner The address from which the balance will be retrieved
/// @return The balance
function balanceOf(address _owner) constant returns (uint256 balance);
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transfer(address _to, uint256 _value) returns (bool success);
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transferFrom(address _from, address _to, uint256 _value) returns (bool success);
/// @notice `msg.sender` approves `_addr` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of wei to be approved for transfer
/// @return Whether the approval was successful or not
function approve(address _spender, uint256 _value) returns (bool success);
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) constant returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}

View File

@ -31,7 +31,7 @@ import ContractInstances from './contracts';
import { initStore } from './redux'; import { initStore } from './redux';
import { ContextProvider, muiTheme } from './ui'; import { ContextProvider, muiTheme } from './ui';
import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views'; import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views';
import { setApi } from './redux/providers/apiActions'; import { setApi } from './redux/providers/apiActions';
@ -76,6 +76,7 @@ ReactDOM.render(
<Route path='apps' component={ Dapps } /> <Route path='apps' component={ Dapps } />
<Route path='app/:id' component={ Dapp } /> <Route path='app/:id' component={ Dapp } />
<Route path='contracts' component={ Contracts } /> <Route path='contracts' component={ Contracts } />
<Route path='contracts/write' component={ WriteContract } />
<Route path='contract/:address' component={ Contract } /> <Route path='contract/:address' component={ Contract } />
<Route path='settings' component={ Settings }> <Route path='settings' component={ Settings }>
<Route path='background' component={ SettingsBackground } /> <Route path='background' component={ SettingsBackground } />

View File

@ -25,7 +25,7 @@ import styles from '../deployContract.css';
export default class DetailsStep extends Component { export default class DetailsStep extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired
} };
static propTypes = { static propTypes = {
accounts: PropTypes.object.isRequired, accounts: PropTypes.object.isRequired,
@ -46,16 +46,33 @@ export default class DetailsStep extends Component {
onFromAddressChange: PropTypes.func.isRequired, onFromAddressChange: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired, onDescriptionChange: PropTypes.func.isRequired,
onNameChange: PropTypes.func.isRequired, onNameChange: PropTypes.func.isRequired,
onParamsChange: PropTypes.func.isRequired onParamsChange: PropTypes.func.isRequired,
} readOnly: PropTypes.bool
};
static defaultProps = {
readOnly: false
};
state = { state = {
inputs: [] inputs: []
} }
componentDidMount () {
const { abi, code } = this.props;
if (abi) {
this.onAbiChange(abi);
}
if (code) {
this.onCodeChange(code);
}
}
render () { render () {
const { accounts } = this.props; const { accounts } = this.props;
const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError } = this.props; const { abi, abiError, code, codeError, fromAddress, fromAddressError, name, nameError, readOnly } = this.props;
return ( return (
<Form> <Form>
@ -77,13 +94,15 @@ export default class DetailsStep extends Component {
hint='the abi of the contract to deploy' hint='the abi of the contract to deploy'
error={ abiError } error={ abiError }
value={ abi } value={ abi }
onSubmit={ this.onAbiChange } /> onSubmit={ this.onAbiChange }
readOnly={ readOnly } />
<Input <Input
label='code' label='code'
hint='the compiled code of the contract to deploy' hint='the compiled code of the contract to deploy'
error={ codeError } error={ codeError }
value={ code } value={ code }
onSubmit={ this.onCodeChange } /> onSubmit={ this.onCodeChange }
readOnly={ readOnly } />
{ this.renderConstructorInputs() } { this.renderConstructorInputs() }
</Form> </Form>
); );

View File

@ -36,8 +36,17 @@ export default class DeployContract extends Component {
static propTypes = { static propTypes = {
accounts: PropTypes.object.isRequired, accounts: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired onClose: PropTypes.func.isRequired,
} abi: PropTypes.string,
code: PropTypes.string,
readOnly: PropTypes.bool,
source: PropTypes.string
};
static defaultProps = {
readOnly: false,
source: ''
};
state = { state = {
abi: '', abi: '',
@ -57,6 +66,31 @@ export default class DeployContract extends Component {
deployError: null deployError: null
} }
componentWillMount () {
const { abi, code } = this.props;
if (abi && code) {
this.setState({ abi, code });
}
}
componentWillReceiveProps (nextProps) {
const { abi, code } = nextProps;
const newState = {};
if (abi !== this.props.abi) {
newState.abi = abi;
}
if (code !== this.props.code) {
newState.code = code;
}
if (Object.keys(newState).length) {
this.setState(newState);
}
}
render () { render () {
const { step, deployError } = this.state; const { step, deployError } = this.state;
@ -115,7 +149,7 @@ export default class DeployContract extends Component {
} }
renderStep () { renderStep () {
const { accounts } = this.props; const { accounts, readOnly } = this.props;
const { address, deployError, step, deployState, txhash } = this.state; const { address, deployError, step, deployState, txhash } = this.state;
if (deployError) { if (deployError) {
@ -129,6 +163,7 @@ export default class DeployContract extends Component {
return ( return (
<DetailsStep <DetailsStep
{ ...this.state } { ...this.state }
readOnly={ readOnly }
accounts={ accounts } accounts={ accounts }
onAbiChange={ this.onAbiChange } onAbiChange={ this.onAbiChange }
onCodeChange={ this.onCodeChange } onCodeChange={ this.onCodeChange }
@ -200,6 +235,7 @@ export default class DeployContract extends Component {
onDeployStart = () => { onDeployStart = () => {
const { api, store } = this.context; const { api, store } = this.context;
const { source } = this.props;
const { abiParsed, code, description, name, params, fromAddress } = this.state; const { abiParsed, code, description, name, params, fromAddress } = this.state;
const options = { const options = {
data: code, data: code,
@ -219,6 +255,7 @@ export default class DeployContract extends Component {
contract: true, contract: true,
timestamp: Date.now(), timestamp: Date.now(),
deleted: false, deleted: false,
source,
description description
}) })
]) ])

View File

@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './loadContract';

View File

@ -0,0 +1,52 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.loadContainer {
display: flex;
flex-direction: row;
> * {
flex: 50%;
width: 0;
}
}
.editor {
display: flex;
flex-direction: column;
padding-left: 1em;
p {
line-height: 48px;
height: 48px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0;
font-size: 1.2em;
}
}
.confirmRemoval {
text-align: center;
.editor {
text-align: left;
margin-top: 0.5em;
}
}

View File

@ -0,0 +1,284 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import ContentClear from 'material-ui/svg-icons/content/clear';
import CheckIcon from 'material-ui/svg-icons/navigation/check';
import DeleteIcon from 'material-ui/svg-icons/action/delete';
import { List, ListItem, makeSelectable } from 'material-ui/List';
import { Subheader, IconButton, Tabs, Tab } from 'material-ui';
import moment from 'moment';
import { Button, Modal, Editor } from '../../ui';
import styles from './loadContract.css';
const SelectableList = makeSelectable(List);
const SELECTED_STYLE = {
backgroundColor: 'rgba(255, 255, 255, 0.1)'
};
export default class LoadContract extends Component {
static propTypes = {
onClose: PropTypes.func.isRequired,
onLoad: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
contracts: PropTypes.object.isRequired,
snippets: PropTypes.object.isRequired
};
state = {
selected: -1,
deleteRequest: false,
deleteId: -1
};
render () {
const { deleteRequest } = this.state;
const title = deleteRequest
? 'confirm removal'
: 'view contracts';
return (
<Modal
title={ title }
actions={ this.renderDialogActions() }
visible
scroll
>
{ this.renderBody() }
</Modal>
);
}
renderBody () {
if (this.state.deleteRequest) {
return this.renderConfirmRemoval();
}
const { contracts, snippets } = this.props;
const contractsTab = Object.keys(contracts).length === 0
? null
: (
<Tab label='Local' >
{ this.renderEditor() }
<SelectableList
onChange={ this.onClickContract }
>
<Subheader>Saved Contracts</Subheader>
{ this.renderContracts(contracts) }
</SelectableList>
</Tab>
);
return (
<div className={ styles.loadContainer }>
<Tabs onChange={ this.handleChangeTab }>
{ contractsTab }
<Tab label='Snippets' >
{ this.renderEditor() }
<SelectableList
onChange={ this.onClickContract }
>
<Subheader>Contract Snippets</Subheader>
{ this.renderContracts(snippets, false) }
</SelectableList>
</Tab>
</Tabs>
</div>
);
}
renderConfirmRemoval () {
const { deleteId } = this.state;
const { name, timestamp, sourcecode } = this.props.contracts[deleteId];
return (
<div className={ styles.confirmRemoval }>
<p>
Are you sure you want to remove the following
contract from your saved contracts?
</p>
<ListItem
primaryText={ name }
secondaryText={ `Saved ${moment(timestamp).fromNow()}` }
style={ { backgroundColor: 'none', cursor: 'default' } }
/>
<div className={ styles.editor }>
<Editor
value={ sourcecode }
maxLines={ 20 }
readOnly
/>
</div>
</div>
);
}
renderEditor () {
const { contracts, snippets } = this.props;
const { selected } = this.state;
const mergedContracts = Object.assign({}, contracts, snippets);
if (selected === -1 || !mergedContracts[selected]) {
return null;
}
const { sourcecode, name } = mergedContracts[selected];
return (
<div className={ styles.editor }>
<p>{ name }</p>
<Editor
value={ sourcecode }
maxLines={ 20 }
readOnly
/>
</div>
);
}
renderContracts (contracts, removable = true) {
const { selected } = this.state;
return Object
.values(contracts)
.map((contract) => {
const { id, name, timestamp, description } = contract;
const onDelete = () => this.onDeleteRequest(id);
const secondaryText = description || `Saved ${moment(timestamp).fromNow()}`;
const remove = removable
? (
<IconButton onClick={ onDelete }>
<DeleteIcon />
</IconButton>
)
: null;
return (
<ListItem
value={ id }
key={ id }
primaryText={ name }
secondaryText={ secondaryText }
style={ selected === id ? SELECTED_STYLE : null }
rightIconButton={ remove }
/>
);
});
}
renderDialogActions () {
const { deleteRequest } = this.state;
if (deleteRequest) {
return [
<Button
icon={ <ContentClear /> }
label='No'
key='No'
onClick={ this.onRejectRemoval }
/>,
<Button
icon={ <DeleteIcon /> }
label='Yes'
key='Yes'
onClick={ this.onConfirmRemoval }
/>
];
}
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose }
/>
);
const loadBtn = (
<Button
icon={ <CheckIcon /> }
label='Load'
onClick={ this.onLoad }
disabled={ this.state.selected === -1 }
/>
);
return [ cancelBtn, loadBtn ];
}
handleChangeTab = () => {
this.setState({ selected: -1 });
}
onClickContract = (_, value) => {
this.setState({ selected: value });
}
onClose = () => {
this.props.onClose();
}
onLoad = () => {
const { contracts, snippets } = this.props;
const { selected } = this.state;
const mergedContracts = Object.assign({}, contracts, snippets);
const contract = mergedContracts[selected];
this.props.onLoad(contract);
this.props.onClose();
}
onDeleteRequest = (id) => {
this.setState({
deleteRequest: true,
deleteId: id
});
}
onConfirmRemoval = () => {
const { deleteId } = this.state;
this.props.onDelete(deleteId);
this.setState({
deleteRequest: false,
deleteId: -1,
selected: -1
});
}
onRejectRemoval = () => {
this.setState({
deleteRequest: false,
deleteId: -1
});
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './saveContract';

View File

@ -0,0 +1,20 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.source {
margin-top: 2em;
}

View File

@ -0,0 +1,109 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import SaveIcon from 'material-ui/svg-icons/content/save';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { Button, Modal, Editor, Form, Input } from '../../ui';
import { ERRORS, validateName } from '../../util/validation';
import styles from './saveContract.css';
export default class SaveContract extends Component {
static propTypes = {
sourcecode: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired
};
state = {
name: '',
nameError: ERRORS.invalidName
};
render () {
const { sourcecode } = this.props;
const { name, nameError } = this.state;
return (
<Modal
title='save contract'
actions={ this.renderDialogActions() }
visible
>
<div>
<Form>
<Input
label='contract name'
hint='choose a name for this contract'
value={ name }
error={ nameError }
onChange={ this.onChangeName }
/>
</Form>
<Editor
className={ styles.source }
value={ sourcecode }
maxLines={ 20 }
readOnly
/>
</div>
</Modal>
);
}
renderDialogActions () {
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Cancel'
onClick={ this.onClose }
/>
);
const confirmBtn = (
<Button
icon={ <SaveIcon /> }
label='Save'
disabled={ !!this.state.nameError }
onClick={ this.onSave }
/>
);
return [ cancelBtn, confirmBtn ];
}
onClose = () => {
this.props.onClose();
}
onSave = () => {
const { name } = this.state;
const { sourcecode } = this.props;
this.props.onSave({ name, sourcecode });
this.onClose();
}
onChangeName = (event, value) => {
const { name, nameError } = validateName(value);
this.setState({ name, nameError });
}
}

View File

@ -24,6 +24,8 @@ import FirstRun from './FirstRun';
import Shapeshift from './Shapeshift'; import Shapeshift from './Shapeshift';
import Transfer from './Transfer'; import Transfer from './Transfer';
import PasswordManager from './PasswordManager'; import PasswordManager from './PasswordManager';
import SaveContract from './SaveContract';
import LoadContract from './LoadContract';
export { export {
AddAddress, AddAddress,
@ -35,5 +37,7 @@ export {
FirstRun, FirstRun,
Shapeshift, Shapeshift,
Transfer, Transfer,
PasswordManager PasswordManager,
LoadContract,
SaveContract
}; };

View File

@ -0,0 +1,37 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import CompilerWorker from 'worker-loader!./compilerWorker.js';
export function setWorker (worker) {
return {
type: 'setWorker',
worker
};
}
export function setupWorker () {
return (dispatch, getState) => {
const state = getState();
if (state.compiler.worker) {
return;
}
const worker = new CompilerWorker();
dispatch(setWorker(worker));
};
}

View File

@ -0,0 +1,29 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { handleActions } from 'redux-actions';
const initialState = {
worker: null
};
export default handleActions({
setWorker (state, action) {
const { worker } = action;
return Object.assign({}, state, { worker });
}
}, initialState);

View File

@ -0,0 +1,177 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import solc from 'solc/browser-wrapper';
import { isWebUri } from 'valid-url';
self.solcVersions = {};
self.files = {};
self.lastCompile = {
sourcecode: '',
result: '',
version: ''
};
// eslint-disable-next-line no-undef
onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.action) {
case 'compile':
compile(message.data);
break;
case 'load':
load(message.data);
break;
case 'setFiles':
setFiles(message.data);
break;
case 'close':
close();
break;
}
};
function setFiles (files) {
const prevFiles = self.files;
const nextFiles = files.reduce((obj, file) => {
obj[file.name] = file.sourcecode;
return obj;
}, {});
self.files = {
...prevFiles,
...nextFiles
};
}
function findImports (path) {
if (self.files[path]) {
if (self.files[path].error) {
return { error: self.files[path].error };
}
return { contents: self.files[path] };
}
if (isWebUri(path)) {
console.log('[worker] fetching', path);
fetch(path)
.then((r) => r.text())
.then((c) => {
console.log('[worker]', 'got content at ' + path);
self.files[path] = c;
postMessage(JSON.stringify({
event: 'try-again'
}));
})
.catch((e) => {
console.error('[worker]', 'fetching', path, e);
self.files[path] = { error: e };
});
return { error: '__parity_tryAgain' };
}
console.log(`[worker] path ${path} not found...`);
return { error: 'File not found' };
}
function compile (data) {
const { sourcecode, build } = data;
const { longVersion } = build;
if (self.lastCompile.sourcecode === sourcecode && self.lastCompile.longVersion === longVersion) {
return postMessage(JSON.stringify({
event: 'compiled',
data: self.lastCompile.result
}));
}
fetchSolc(build)
.then((compiler) => {
const input = {
'': sourcecode
};
const compiled = compiler.compile({ sources: input }, 0, findImports);
self.lastCompile = {
version: longVersion, result: compiled,
sourcecode
};
postMessage(JSON.stringify({
event: 'compiled',
data: compiled
}));
});
}
function load (build) {
postMessage(JSON.stringify({
event: 'loading',
data: true
}));
fetchSolc(build)
.then(() => {
postMessage(JSON.stringify({
event: 'loading',
data: false
}));
})
.catch(() => {
postMessage(JSON.stringify({
event: 'loading',
data: false
}));
});
}
function fetchSolc (build) {
const { path, longVersion } = build;
if (self.solcVersions[path]) {
return Promise.resolve(self.solcVersions[path]);
}
const URL = `https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/${path}`;
console.log(`[worker] fetching solc-bin ${longVersion} at ${URL}`);
return fetch(URL)
.then((r) => r.text())
.then((code) => {
const solcCode = code.replace(/^var Module;/, 'var Module=self.__solcModule;');
self.__solcModule = {};
console.log(`[worker] evaluating ${longVersion}`);
// eslint-disable-next-line no-eval
eval(solcCode);
console.log(`[worker] done evaluating ${longVersion}`);
const compiler = solc(self.__solcModule);
self.solcVersions[path] = compiler;
return compiler;
})
.catch((e) => {
console.error('fetching solc', e);
});
}

View File

@ -26,3 +26,4 @@ export personalReducer from './personalReducer';
export signerReducer from './signerReducer'; export signerReducer from './signerReducer';
export statusReducer from './statusReducer'; export statusReducer from './statusReducer';
export blockchainReducer from './blockchainReducer'; export blockchainReducer from './blockchainReducer';
export compilerReducer from './compilerReducer';

View File

@ -17,7 +17,7 @@
import { combineReducers } from 'redux'; import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux'; import { routerReducer } from 'react-router-redux';
import { apiReducer, balancesReducer, blockchainReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer } from './providers'; import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer } from './providers';
import { errorReducer } from '../ui/Errors'; import { errorReducer } from '../ui/Errors';
import { settingsReducer } from '../views/Settings'; import { settingsReducer } from '../views/Settings';
@ -33,6 +33,7 @@ export default function () {
balances: balancesReducer, balances: balancesReducer,
blockchain: blockchainReducer, blockchain: blockchainReducer,
compiler: compilerReducer,
images: imagesReducer, images: imagesReducer,
nodeStatus: nodeStatusReducer, nodeStatus: nodeStatusReducer,
personal: personalReducer, personal: personalReducer,

45
js/src/ui/Actionbar/Import/import.css vendored Normal file
View File

@ -0,0 +1,45 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.importZone {
width: 100%;
height: 200px;
border-width: 2px;
border-color: #666;
border-style: dashed;
border-radius: 10px;
background-color: rgba(50, 50, 50, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2em;
transition: all 0.5s ease;
&:hover {
cursor: pointer;
border-radius: 0;
background-color: rgba(50, 50, 50, 0.5);
}
}
.desc {
margin-top: 0;
color: #ccc;
}

View File

@ -0,0 +1,198 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import Dropzone from 'react-dropzone';
import FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
import ContentClear from 'material-ui/svg-icons/content/clear';
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
import { Modal, Button } from '../../';
import styles from './import.css';
const initialState = {
step: 0,
show: false,
validate: false,
validationBody: null,
content: ''
};
export default class ActionbarImport extends Component {
static propTypes = {
onConfirm: PropTypes.func.isRequired,
renderValidation: PropTypes.func,
className: PropTypes.string,
title: PropTypes.string
};
static defaultProps = {
title: 'Import from a file'
};
state = Object.assign({}, initialState);
render () {
const { className } = this.props;
return (
<div>
<Button
className={ className }
icon={ <FileUploadIcon /> }
label='import'
onClick={ this.onOpenModal }
/>
{ this.renderModal() }
</div>
);
}
renderModal () {
const { title, renderValidation } = this.props;
const { show, step } = this.state;
if (!show) {
return null;
}
const hasSteps = typeof renderValidation === 'function';
const steps = hasSteps ? [ 'select a file', 'validate' ] : null;
return (
<Modal
actions={ this.renderActions() }
title={ title }
steps={ steps }
current={ step }
visible
>
{ this.renderBody() }
</Modal>
);
}
renderActions () {
const { validate } = this.state;
const cancelBtn = (
<Button
key='cancel'
label='Cancel'
icon={ <ContentClear /> }
onClick={ this.onCloseModal }
/>
);
if (validate) {
const confirmBtn = (
<Button
key='confirm'
label='Confirm'
icon={ <ActionDoneAll /> }
onClick={ this.onConfirm }
/>
);
return [ cancelBtn, confirmBtn ];
}
return [ cancelBtn ];
}
renderBody () {
const { validate } = this.state;
if (validate) {
return this.renderValidation();
}
return this.renderFileSelect();
}
renderFileSelect () {
return (
<div>
<Dropzone
onDrop={ this.onDrop }
multiple={ false }
className={ styles.importZone }
>
<div>Drop a file here, or click to select a file to upload.</div>
</Dropzone>
</div>
);
}
renderValidation () {
const { validationBody } = this.state;
return (
<div>
<p className={ styles.desc }>
Confirm that this is what was intended to import.
</p>
<div>
{ validationBody }
</div>
</div>
);
}
onDrop = (files) => {
const { renderValidation } = this.props;
const file = files[0];
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
if (typeof renderValidation !== 'function') {
this.props.onConfirm(content);
return this.onCloseModal();
}
this.setState({
step: 1,
validate: true,
validationBody: renderValidation(content),
content
});
};
reader.readAsText(file);
}
onConfirm = () => {
const { content } = this.state;
this.props.onConfirm(content);
return this.onCloseModal();
}
onOpenModal = () => {
this.setState({ show: true });
}
onCloseModal = () => {
this.setState(initialState);
}
}

View File

@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './import';

View File

@ -19,6 +19,7 @@ import { FlatButton } from 'material-ui';
export default class Button extends Component { export default class Button extends Component {
static propTypes = { static propTypes = {
backgroundColor: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
icon: PropTypes.node, icon: PropTypes.node,
@ -26,19 +27,25 @@ export default class Button extends Component {
React.PropTypes.string, React.PropTypes.string,
React.PropTypes.object React.PropTypes.object
]), ]),
onClick: PropTypes.func onClick: PropTypes.func,
primary: PropTypes.bool
}
static defaultProps = {
primary: true
} }
render () { render () {
const { className, disabled, icon, label, onClick } = this.props; const { className, backgroundColor, disabled, icon, label, primary, onClick } = this.props;
return ( return (
<FlatButton <FlatButton
className={ className } className={ className }
backgroundColor={ backgroundColor }
disabled={ disabled } disabled={ disabled }
icon={ icon } icon={ icon }
label={ label } label={ label }
primary primary={ primary }
onTouchTap={ onClick } /> onTouchTap={ onClick } />
); );
} }

103
js/src/ui/Editor/editor.js Normal file
View File

@ -0,0 +1,103 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes, Component } from 'react';
import 'brace';
import AceEditor from 'react-ace';
import { noop } from 'lodash';
import 'brace/theme/solarized_dark';
import 'brace/mode/json';
import './mode-solidity';
export default class Editor extends Component {
static propTypes = {
className: PropTypes.string,
value: PropTypes.string,
mode: PropTypes.string,
maxLines: PropTypes.number,
annotations: PropTypes.array,
onExecute: PropTypes.func,
onChange: PropTypes.func,
readOnly: PropTypes.bool
};
static defaultProps = {
className: '',
value: '',
mode: 'javascript',
annotations: [],
onExecute: noop,
onChange: noop,
readOnly: false
};
componentWillMount () {
this.name = `PARITY_EDITOR_${Math.round(Math.random() * 99999)}`;
}
render () {
const { className, annotations, value, readOnly, mode, maxLines } = this.props;
const commands = [
{
name: 'execut',
bindKey: { win: 'Ctrl-Enter', mac: 'Command-Enter' },
exec: this.handleExecute
}
];
const max = (maxLines !== undefined)
? maxLines
: (readOnly ? value.split('\n').length + 1 : null);
return (
<AceEditor
mode={ mode }
theme='solarized_dark'
width='100%'
ref='brace'
style={ { flex: 1 } }
onChange={ this.handleOnChange }
name={ this.name }
editorProps={ { $blockScrolling: Infinity } }
setOptions={ {
useWorker: false,
fontFamily: 'monospace',
fontSize: '0.9em'
} }
maxLines={ max }
enableBasicAutocompletion={ !readOnly }
showPrintMargin={ false }
annotations={ annotations }
value={ value }
commands={ commands }
readOnly={ readOnly }
className={ className }
/>
);
}
handleExecute = () => {
this.props.onExecute();
}
handleOnChange = (value) => {
this.props.onChange(value);
}
}

17
js/src/ui/Editor/index.js Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './editor';

View File

@ -0,0 +1,994 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/**
* This file has been taken from `ethereum/browser-solidity`
*
* @see: https://raw.githubusercontent.com/ethereum/browser-solidity/master/src/mode-solidity.js
*/
/* eslint-disable */
var ace = window.ace;
ace.define("ace/mode/doc_comment_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/text_highlight_rules"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var DocCommentHighlightRules = function() {
this.$rules = {
"start" : [ {
token : "comment.doc.tag",
regex : "@[\\w\\d_]+" // TODO: fix email addresses
},
DocCommentHighlightRules.getTagRule(),
{
defaultToken : "comment.doc",
caseInsensitive: true
}]
};
};
oop.inherits(DocCommentHighlightRules, TextHighlightRules);
DocCommentHighlightRules.getTagRule = function(start) {
return {
token : "comment.doc.tag.storage.type",
regex : "\\b(?:TODO|FIXME|XXX|HACK)\\b"
};
};
DocCommentHighlightRules.getStartRule = function(start) {
return {
token : "comment.doc", // doc comment
regex : "\\/\\*(?=\\*)",
next : start
};
};
DocCommentHighlightRules.getEndRule = function (start) {
return {
token : "comment.doc", // closing comment
regex : "\\*\\/",
next : start
};
};
exports.DocCommentHighlightRules = DocCommentHighlightRules;
});
ace.define("ace/mode/javascript_highlight_rules",["require","exports","module","ace/lib/oop","ace/mode/doc_comment_highlight_rules","ace/mode/text_highlight_rules"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var DocCommentHighlightRules = acequire("./doc_comment_highlight_rules").DocCommentHighlightRules;
var TextHighlightRules = acequire("./text_highlight_rules").TextHighlightRules;
var JavaScriptHighlightRules = function(options) {
var intTypes = 'bytes|int|uint';
for (var width = 8; width <= 256; width += 8)
intTypes += '|bytes' + (width / 8) + '|uint' + width + '|int' + width;
var keywordMapper = this.createKeywordMapper({
"variable.language":
"this|bool|string|byte|bytes|bytes0|address|" + intTypes,
"keyword":
"contract|library|constant|event|modifier|" +
"struct|mapping|enum|break|continue|delete|else|for|function|" +
"if|new|return|returns|var|while|using|" +
"private|public|external|internal|storage|memory",
"storage.type":
"constant|var|function",
"constant.language.boolean": "true|false"
}, "identifier");
var kwBeforeRe = "case|do|else|finally|in|instanceof|return|throw|try|typeof|yield|void";
var identifierRe = "[a-zA-Z\\$_\u00a1-\uffff][a-zA-Z\\d\\$_\u00a1-\uffff]*\\b";
var escapedRe = "\\\\(?:x[0-9a-fA-F]{2}|" + // hex
"u[0-9a-fA-F]{4}|" + // unicode
"[0-2][0-7]{0,2}|" + // oct
"3[0-6][0-7]?|" + // oct
"37[0-7]?|" + // oct
"[4-7][0-7]?|" + //oct
".)";
this.$rules = {
"no_regex" : [
{
token : "comment",
regex : "\\/\\/",
next : "line_comment"
},
DocCommentHighlightRules.getStartRule("doc-start"),
{
token : "comment", // multi line comment
regex : /\/\*/,
next : "comment"
}, {
token : "string",
regex : "'(?=.)",
next : "qstring"
}, {
token : "string",
regex : '"(?=.)',
next : "qqstring"
}, {
token : "constant.numeric", // hex
regex : /0[xX][0-9a-fA-F]+\b/
}, {
token : "constant.numeric", // float
regex : /[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/
}, {
token : [
"storage.type", "punctuation.operator", "support.function",
"punctuation.operator", "entity.name.function", "text","keyword.operator"
],
regex : "(" + identifierRe + ")(\\.)(prototype)(\\.)(" + identifierRe +")(\\s*)(=)",
next: "function_arguments"
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text", "storage.type", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"entity.name.function", "text", "keyword.operator", "text", "storage.type",
"text", "paren.lparen"
],
regex : "(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"storage.type", "punctuation.operator", "entity.name.function", "text",
"keyword.operator", "text",
"storage.type", "text", "entity.name.function", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\.)(" + identifierRe +")(\\s*)(=)(\\s*)(function)(\\s+)(\\w+)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"storage.type", "text", "entity.name.function", "text", "paren.lparen"
],
regex : "(function)(\\s+)(" + identifierRe + ")(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"entity.name.function", "text", "punctuation.operator",
"text", "storage.type", "text", "paren.lparen"
],
regex : "(" + identifierRe + ")(\\s*)(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : [
"text", "text", "storage.type", "text", "paren.lparen"
],
regex : "(:)(\\s*)(function)(\\s*)(\\()",
next: "function_arguments"
}, {
token : "keyword",
regex : "(?:" + kwBeforeRe + ")\\b",
next : "start"
}, {
token : ["punctuation.operator", "support.function"],
regex : /(\.)(s(?:h(?:ift|ow(?:Mod(?:elessDialog|alDialog)|Help))|croll(?:X|By(?:Pages|Lines)?|Y|To)?|t(?:op|rike)|i(?:n|zeToContent|debar|gnText)|ort|u(?:p|b(?:str(?:ing)?)?)|pli(?:ce|t)|e(?:nd|t(?:Re(?:sizable|questHeader)|M(?:i(?:nutes|lliseconds)|onth)|Seconds|Ho(?:tKeys|urs)|Year|Cursor|Time(?:out)?|Interval|ZOptions|Date|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Date|FullYear)|FullYear|Active)|arch)|qrt|lice|avePreferences|mall)|h(?:ome|andleEvent)|navigate|c(?:har(?:CodeAt|At)|o(?:s|n(?:cat|textual|firm)|mpile)|eil|lear(?:Timeout|Interval)?|a(?:ptureEvents|ll)|reate(?:StyleSheet|Popup|EventObject))|t(?:o(?:GMTString|S(?:tring|ource)|U(?:TCString|pperCase)|Lo(?:caleString|werCase))|est|a(?:n|int(?:Enabled)?))|i(?:s(?:NaN|Finite)|ndexOf|talics)|d(?:isableExternalCapture|ump|etachEvent)|u(?:n(?:shift|taint|escape|watch)|pdateCommands)|j(?:oin|avaEnabled)|p(?:o(?:p|w)|ush|lugins.refresh|a(?:ddings|rse(?:Int|Float)?)|r(?:int|ompt|eference))|e(?:scape|nableExternalCapture|val|lementFromPoint|x(?:p|ec(?:Script|Command)?))|valueOf|UTC|queryCommand(?:State|Indeterm|Enabled|Value)|f(?:i(?:nd|le(?:ModifiedDate|Size|CreatedDate|UpdatedDate)|xed)|o(?:nt(?:size|color)|rward)|loor|romCharCode)|watch|l(?:ink|o(?:ad|g)|astIndexOf)|a(?:sin|nchor|cos|t(?:tachEvent|ob|an(?:2)?)|pply|lert|b(?:s|ort))|r(?:ou(?:nd|teEvents)|e(?:size(?:By|To)|calc|turnValue|place|verse|l(?:oad|ease(?:Capture|Events)))|andom)|g(?:o|et(?:ResponseHeader|M(?:i(?:nutes|lliseconds)|onth)|Se(?:conds|lection)|Hours|Year|Time(?:zoneOffset)?|Da(?:y|te)|UTC(?:M(?:i(?:nutes|lliseconds)|onth)|Seconds|Hours|Da(?:y|te)|FullYear)|FullYear|A(?:ttention|llResponseHeaders)))|m(?:in|ove(?:B(?:y|elow)|To(?:Absolute)?|Above)|ergeAttributes|a(?:tch|rgins|x))|b(?:toa|ig|o(?:ld|rderWidths)|link|ack))\b(?=\()/
}, {
token : ["punctuation.operator", "support.function.dom"],
regex : /(\.)(s(?:ub(?:stringData|mit)|plitText|e(?:t(?:NamedItem|Attribute(?:Node)?)|lect))|has(?:ChildNodes|Feature)|namedItem|c(?:l(?:ick|o(?:se|neNode))|reate(?:C(?:omment|DATASection|aption)|T(?:Head|extNode|Foot)|DocumentFragment|ProcessingInstruction|E(?:ntityReference|lement)|Attribute))|tabIndex|i(?:nsert(?:Row|Before|Cell|Data)|tem)|open|delete(?:Row|C(?:ell|aption)|T(?:Head|Foot)|Data)|focus|write(?:ln)?|a(?:dd|ppend(?:Child|Data))|re(?:set|place(?:Child|Data)|move(?:NamedItem|Child|Attribute(?:Node)?)?)|get(?:NamedItem|Element(?:sBy(?:Name|TagName)|ById)|Attribute(?:Node)?)|blur)\b(?=\()/
}, {
token : ["punctuation.operator", "support.constant"],
regex : /(\.)(s(?:ystemLanguage|cr(?:ipts|ollbars|een(?:X|Y|Top|Left))|t(?:yle(?:Sheets)?|atus(?:Text|bar)?)|ibling(?:Below|Above)|ource|uffixes|e(?:curity(?:Policy)?|l(?:ection|f)))|h(?:istory|ost(?:name)?|as(?:h|Focus))|y|X(?:MLDocument|SLDocument)|n(?:ext|ame(?:space(?:s|URI)|Prop))|M(?:IN_VALUE|AX_VALUE)|c(?:haracterSet|o(?:n(?:structor|trollers)|okieEnabled|lorDepth|mp(?:onents|lete))|urrent|puClass|l(?:i(?:p(?:boardData)?|entInformation)|osed|asses)|alle(?:e|r)|rypto)|t(?:o(?:olbar|p)|ext(?:Transform|Indent|Decoration|Align)|ags)|SQRT(?:1_2|2)|i(?:n(?:ner(?:Height|Width)|put)|ds|gnoreCase)|zIndex|o(?:scpu|n(?:readystatechange|Line)|uter(?:Height|Width)|p(?:sProfile|ener)|ffscreenBuffering)|NEGATIVE_INFINITY|d(?:i(?:splay|alog(?:Height|Top|Width|Left|Arguments)|rectories)|e(?:scription|fault(?:Status|Ch(?:ecked|arset)|View)))|u(?:ser(?:Profile|Language|Agent)|n(?:iqueID|defined)|pdateInterval)|_content|p(?:ixelDepth|ort|ersonalbar|kcs11|l(?:ugins|atform)|a(?:thname|dding(?:Right|Bottom|Top|Left)|rent(?:Window|Layer)?|ge(?:X(?:Offset)?|Y(?:Offset)?))|r(?:o(?:to(?:col|type)|duct(?:Sub)?|mpter)|e(?:vious|fix)))|e(?:n(?:coding|abledPlugin)|x(?:ternal|pando)|mbeds)|v(?:isibility|endor(?:Sub)?|Linkcolor)|URLUnencoded|P(?:I|OSITIVE_INFINITY)|f(?:ilename|o(?:nt(?:Size|Family|Weight)|rmName)|rame(?:s|Element)|gColor)|E|whiteSpace|l(?:i(?:stStyleType|n(?:eHeight|kColor))|o(?:ca(?:tion(?:bar)?|lName)|wsrc)|e(?:ngth|ft(?:Context)?)|a(?:st(?:M(?:odified|atch)|Index|Paren)|yer(?:s|X)|nguage))|a(?:pp(?:MinorVersion|Name|Co(?:deName|re)|Version)|vail(?:Height|Top|Width|Left)|ll|r(?:ity|guments)|Linkcolor|bove)|r(?:ight(?:Context)?|e(?:sponse(?:XML|Text)|adyState))|global|x|m(?:imeTypes|ultiline|enubar|argin(?:Right|Bottom|Top|Left))|L(?:N(?:10|2)|OG(?:10E|2E))|b(?:o(?:ttom|rder(?:Width|RightWidth|BottomWidth|Style|Color|TopWidth|LeftWidth))|ufferDepth|elow|ackground(?:Color|Image)))\b/
}, {
token : ["support.constant"],
regex : /that\b/
}, {
token : ["storage.type", "punctuation.operator", "support.function.firebug"],
regex : /(console)(\.)(warn|info|log|error|time|trace|timeEnd|assert)\b/
}, {
token : keywordMapper,
regex : identifierRe
}, {
token : "keyword.operator",
regex : /--|\*\*|\+\+|===|==|=|!=|!==|=>|<=|>=|<<=|>>=|>>>=|<>|<|>|!|&&|\|\||\?\:|[!$%&*+\-~\/^]=?/,
next : "start"
}, {
token : "punctuation.operator",
regex : /[?:,;.]/,
next : "start"
}, {
token : "paren.lparen",
regex : /[\[({]/,
next : "start"
}, {
token : "paren.rparen",
regex : /[\])}]/
}, {
token: "comment",
regex: /^#!.*$/
}
],
"start": [
DocCommentHighlightRules.getStartRule("doc-start"),
{
token : "comment", // multi line comment
regex : "\\/\\*",
next : "comment_regex_allowed"
}, {
token : "comment",
regex : "\\/\\/",
next : "line_comment_regex_allowed"
}, {
token: "string.regexp",
regex: "\\/",
next: "regex"
}, {
token : "text",
regex : "\\s+|^$",
next : "start"
}, {
token: "empty",
regex: "",
next: "no_regex"
}
],
"regex": [
{
token: "regexp.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
}, {
token: "string.regexp",
regex: "/[sxngimy]*",
next: "no_regex"
}, {
token : "invalid",
regex: /\{\d+\b,?\d*\}[+*]|[+*$^?][+*]|[$^][?]|\?{3,}/
}, {
token : "constant.language.escape",
regex: /\(\?[:=!]|\)|\{\d+\b,?\d*\}|[+*]\?|[()$^+*?.]/
}, {
token : "constant.language.delimiter",
regex: /\|/
}, {
token: "constant.language.escape",
regex: /\[\^?/,
next: "regex_character_class"
}, {
token: "empty",
regex: "$",
next: "no_regex"
}, {
defaultToken: "string.regexp"
}
],
"regex_character_class": [
{
token: "regexp.charclass.keyword.operator",
regex: "\\\\(?:u[\\da-fA-F]{4}|x[\\da-fA-F]{2}|.)"
}, {
token: "constant.language.escape",
regex: "]",
next: "regex"
}, {
token: "constant.language.escape",
regex: "-"
}, {
token: "empty",
regex: "$",
next: "no_regex"
}, {
defaultToken: "string.regexp.charachterclass"
}
],
"function_arguments": [
{
token: "variable.parameter",
regex: identifierRe
}, {
token: "punctuation.operator",
regex: "[, ]+"
}, {
token: "punctuation.operator",
regex: "$"
}, {
token: "empty",
regex: "",
next: "no_regex"
}
],
"comment_regex_allowed" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "\\*\\/", next : "start"},
{defaultToken : "comment", caseInsensitive: true}
],
"comment" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "\\*\\/", next : "no_regex"},
{defaultToken : "comment", caseInsensitive: true}
],
"line_comment_regex_allowed" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "$|^", next : "start"},
{defaultToken : "comment", caseInsensitive: true}
],
"line_comment" : [
DocCommentHighlightRules.getTagRule(),
{token : "comment", regex : "$|^", next : "no_regex"},
{defaultToken : "comment", caseInsensitive: true}
],
"qqstring" : [
{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "string",
regex : "\\\\$",
next : "qqstring"
}, {
token : "string",
regex : '"|$',
next : "no_regex"
}, {
defaultToken: "string"
}
],
"qstring" : [
{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "string",
regex : "\\\\$",
next : "qstring"
}, {
token : "string",
regex : "'|$",
next : "no_regex"
}, {
defaultToken: "string"
}
]
};
if (!options || !options.noES6) {
this.$rules.no_regex.unshift({
regex: "[{}]", onMatch: function(val, state, stack) {
this.next = val == "{" ? this.nextState : "";
if (val == "{" && stack.length) {
stack.unshift("start", state);
return "paren";
}
if (val == "}" && stack.length) {
stack.shift();
this.next = stack.shift();
if (this.next.indexOf("string") != -1)
return "paren.quasi.end";
}
return val == "{" ? "paren.lparen" : "paren.rparen";
},
nextState: "start"
}, {
token : "string.quasi.start",
regex : /`/,
push : [{
token : "constant.language.escape",
regex : escapedRe
}, {
token : "paren.quasi.start",
regex : /\${/,
push : "start"
}, {
token : "string.quasi.end",
regex : /`/,
next : "pop"
}, {
defaultToken: "string.quasi"
}]
});
}
this.embedRules(DocCommentHighlightRules, "doc-",
[ DocCommentHighlightRules.getEndRule("no_regex") ]);
this.normalizeRules();
};
oop.inherits(JavaScriptHighlightRules, TextHighlightRules);
exports.JavaScriptHighlightRules = JavaScriptHighlightRules;
});
ace.define("ace/mode/matching_brace_outdent",["require","exports","module","ace/range"], function(acequire, exports, module) {
"use strict";
var Range = acequire("../range").Range;
var MatchingBraceOutdent = function() {};
(function() {
this.checkOutdent = function(line, input) {
if (! /^\s+$/.test(line))
return false;
return /^\s*\}/.test(input);
};
this.autoOutdent = function(doc, row) {
var line = doc.getLine(row);
var match = line.match(/^(\s*\})/);
if (!match) return 0;
var column = match[1].length;
var openBracePos = doc.findMatchingBracket({row: row, column: column});
if (!openBracePos || openBracePos.row == row) return 0;
var indent = this.$getIndent(doc.getLine(openBracePos.row));
doc.replace(new Range(row, 0, row, column-1), indent);
};
this.$getIndent = function(line) {
return line.match(/^\s*/)[0];
};
}).call(MatchingBraceOutdent.prototype);
exports.MatchingBraceOutdent = MatchingBraceOutdent;
});
ace.define("ace/mode/behaviour/cstyle",["require","exports","module","ace/lib/oop","ace/mode/behaviour","ace/token_iterator","ace/lib/lang"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../../lib/oop");
var Behaviour = acequire("../behaviour").Behaviour;
var TokenIterator = acequire("../../token_iterator").TokenIterator;
var lang = acequire("../../lib/lang");
var SAFE_INSERT_IN_TOKENS =
["text", "paren.rparen", "punctuation.operator"];
var SAFE_INSERT_BEFORE_TOKENS =
["text", "paren.rparen", "punctuation.operator", "comment"];
var context;
var contextCache = {};
var initContext = function(editor) {
var id = -1;
if (editor.multiSelect) {
id = editor.selection.index;
if (contextCache.rangeCount != editor.multiSelect.rangeCount)
contextCache = {rangeCount: editor.multiSelect.rangeCount};
}
if (contextCache[id])
return context = contextCache[id];
context = contextCache[id] = {
autoInsertedBrackets: 0,
autoInsertedRow: -1,
autoInsertedLineEnd: "",
maybeInsertedBrackets: 0,
maybeInsertedRow: -1,
maybeInsertedLineStart: "",
maybeInsertedLineEnd: ""
};
};
var CstyleBehaviour = function() {
this.add("braces", "insertion", function(state, action, editor, session, text) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (text == '{') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && selected !== "{" && editor.getWrapBehavioursEnabled()) {
return {
text: '{' + selected + '}',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
if (/[\]\}\)]/.test(line[cursor.column]) || editor.inMultiSelectMode) {
CstyleBehaviour.recordAutoInsert(editor, session, "}");
return {
text: '{}',
selection: [1, 1]
};
} else {
CstyleBehaviour.recordMaybeInsert(editor, session, "{");
return {
text: '{',
selection: [1, 1]
};
}
}
} else if (text == '}') {
initContext(editor);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == '}') {
var matching = session.$findOpeningBracket('}', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
} else if (text == "\n" || text == "\r\n") {
initContext(editor);
var closing = "";
if (CstyleBehaviour.isMaybeInsertedClosing(cursor, line)) {
closing = lang.stringRepeat("}", context.maybeInsertedBrackets);
CstyleBehaviour.clearMaybeInsertedClosing();
}
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar === '}') {
var openBracePos = session.findMatchingBracket({row: cursor.row, column: cursor.column+1}, '}');
if (!openBracePos)
return null;
var next_indent = this.$getIndent(session.getLine(openBracePos.row));
} else if (closing) {
var next_indent = this.$getIndent(line);
} else {
CstyleBehaviour.clearMaybeInsertedClosing();
return;
}
var indent = next_indent + session.getTabString();
return {
text: '\n' + indent + '\n' + next_indent + closing,
selection: [1, indent.length, 1, indent.length]
};
} else {
CstyleBehaviour.clearMaybeInsertedClosing();
}
});
this.add("braces", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '{') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.end.column, range.end.column + 1);
if (rightChar == '}') {
range.end.column++;
return range;
} else {
context.maybeInsertedBrackets--;
}
}
});
this.add("parens", "insertion", function(state, action, editor, session, text) {
if (text == '(') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && editor.getWrapBehavioursEnabled()) {
return {
text: '(' + selected + ')',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
CstyleBehaviour.recordAutoInsert(editor, session, ")");
return {
text: '()',
selection: [1, 1]
};
}
} else if (text == ')') {
initContext(editor);
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == ')') {
var matching = session.$findOpeningBracket(')', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
}
});
this.add("parens", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '(') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == ')') {
range.end.column++;
return range;
}
}
});
this.add("brackets", "insertion", function(state, action, editor, session, text) {
if (text == '[') {
initContext(editor);
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && editor.getWrapBehavioursEnabled()) {
return {
text: '[' + selected + ']',
selection: false
};
} else if (CstyleBehaviour.isSaneInsertion(editor, session)) {
CstyleBehaviour.recordAutoInsert(editor, session, "]");
return {
text: '[]',
selection: [1, 1]
};
}
} else if (text == ']') {
initContext(editor);
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == ']') {
var matching = session.$findOpeningBracket(']', {column: cursor.column + 1, row: cursor.row});
if (matching !== null && CstyleBehaviour.isAutoInsertedClosing(cursor, line, text)) {
CstyleBehaviour.popAutoInsertedClosing();
return {
text: '',
selection: [1, 1]
};
}
}
}
});
this.add("brackets", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && selected == '[') {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == ']') {
range.end.column++;
return range;
}
}
});
this.add("string_dquotes", "insertion", function(state, action, editor, session, text) {
if (text == '"' || text == "'") {
initContext(editor);
var quote = text;
var selection = editor.getSelectionRange();
var selected = session.doc.getTextRange(selection);
if (selected !== "" && selected !== "'" && selected != '"' && editor.getWrapBehavioursEnabled()) {
return {
text: quote + selected + quote,
selection: false
};
} else {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
var leftChar = line.substring(cursor.column-1, cursor.column);
if (leftChar == '\\') {
return null;
}
var tokens = session.getTokens(selection.start.row);
var col = 0, token;
var quotepos = -1; // Track whether we're inside an open quote.
for (var x = 0; x < tokens.length; x++) {
token = tokens[x];
if (token.type == "string") {
quotepos = -1;
} else if (quotepos < 0) {
quotepos = token.value.indexOf(quote);
}
if ((token.value.length + col) > selection.start.column) {
break;
}
col += tokens[x].value.length;
}
if (!token || (quotepos < 0 && token.type !== "comment" && (token.type !== "string" || ((selection.start.column !== token.value.length+col-1) && token.value.lastIndexOf(quote) === token.value.length-1)))) {
if (!CstyleBehaviour.isSaneInsertion(editor, session))
return;
return {
text: quote + quote,
selection: [1,1]
};
} else if (token && token.type === "string") {
var rightChar = line.substring(cursor.column, cursor.column + 1);
if (rightChar == quote) {
return {
text: '',
selection: [1, 1]
};
}
}
}
}
});
this.add("string_dquotes", "deletion", function(state, action, editor, session, range) {
var selected = session.doc.getTextRange(range);
if (!range.isMultiLine() && (selected == '"' || selected == "'")) {
initContext(editor);
var line = session.doc.getLine(range.start.row);
var rightChar = line.substring(range.start.column + 1, range.start.column + 2);
if (rightChar == selected) {
range.end.column++;
return range;
}
}
});
};
CstyleBehaviour.isSaneInsertion = function(editor, session) {
var cursor = editor.getCursorPosition();
var iterator = new TokenIterator(session, cursor.row, cursor.column);
if (!this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS)) {
var iterator2 = new TokenIterator(session, cursor.row, cursor.column + 1);
if (!this.$matchTokenType(iterator2.getCurrentToken() || "text", SAFE_INSERT_IN_TOKENS))
return false;
}
iterator.stepForward();
return iterator.getCurrentTokenRow() !== cursor.row ||
this.$matchTokenType(iterator.getCurrentToken() || "text", SAFE_INSERT_BEFORE_TOKENS);
};
CstyleBehaviour.$matchTokenType = function(token, types) {
return types.indexOf(token.type || token) > -1;
};
CstyleBehaviour.recordAutoInsert = function(editor, session, bracket) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (!this.isAutoInsertedClosing(cursor, line, context.autoInsertedLineEnd[0]))
context.autoInsertedBrackets = 0;
context.autoInsertedRow = cursor.row;
context.autoInsertedLineEnd = bracket + line.substr(cursor.column);
context.autoInsertedBrackets++;
};
CstyleBehaviour.recordMaybeInsert = function(editor, session, bracket) {
var cursor = editor.getCursorPosition();
var line = session.doc.getLine(cursor.row);
if (!this.isMaybeInsertedClosing(cursor, line))
context.maybeInsertedBrackets = 0;
context.maybeInsertedRow = cursor.row;
context.maybeInsertedLineStart = line.substr(0, cursor.column) + bracket;
context.maybeInsertedLineEnd = line.substr(cursor.column);
context.maybeInsertedBrackets++;
};
CstyleBehaviour.isAutoInsertedClosing = function(cursor, line, bracket) {
return context.autoInsertedBrackets > 0 &&
cursor.row === context.autoInsertedRow &&
bracket === context.autoInsertedLineEnd[0] &&
line.substr(cursor.column) === context.autoInsertedLineEnd;
};
CstyleBehaviour.isMaybeInsertedClosing = function(cursor, line) {
return context.maybeInsertedBrackets > 0 &&
cursor.row === context.maybeInsertedRow &&
line.substr(cursor.column) === context.maybeInsertedLineEnd &&
line.substr(0, cursor.column) == context.maybeInsertedLineStart;
};
CstyleBehaviour.popAutoInsertedClosing = function() {
context.autoInsertedLineEnd = context.autoInsertedLineEnd.substr(1);
context.autoInsertedBrackets--;
};
CstyleBehaviour.clearMaybeInsertedClosing = function() {
if (context) {
context.maybeInsertedBrackets = 0;
context.maybeInsertedRow = -1;
}
};
oop.inherits(CstyleBehaviour, Behaviour);
exports.CstyleBehaviour = CstyleBehaviour;
});
ace.define("ace/mode/folding/cstyle",["require","exports","module","ace/lib/oop","ace/range","ace/mode/folding/fold_mode"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../../lib/oop");
var Range = acequire("../../range").Range;
var BaseFoldMode = acequire("./fold_mode").FoldMode;
var FoldMode = exports.FoldMode = function(commentRegex) {
if (commentRegex) {
this.foldingStartMarker = new RegExp(
this.foldingStartMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.start)
);
this.foldingStopMarker = new RegExp(
this.foldingStopMarker.source.replace(/\|[^|]*?$/, "|" + commentRegex.end)
);
}
};
oop.inherits(FoldMode, BaseFoldMode);
(function() {
this.foldingStartMarker = /(\{|\[)[^\}\]]*$|^\s*(\/\*)/;
this.foldingStopMarker = /^[^\[\{]*(\}|\])|^[\s\*]*(\*\/)/;
this.getFoldWidgetRange = function(session, foldStyle, row, forceMultiline) {
var line = session.getLine(row);
var match = line.match(this.foldingStartMarker);
if (match) {
var i = match.index;
if (match[1])
return this.openingBracketBlock(session, match[1], row, i);
var range = session.getCommentFoldRange(row, i + match[0].length, 1);
if (range && !range.isMultiLine()) {
if (forceMultiline) {
range = this.getSectionRange(session, row);
} else if (foldStyle != "all")
range = null;
}
return range;
}
if (foldStyle === "markbegin")
return;
var match = line.match(this.foldingStopMarker);
if (match) {
var i = match.index + match[0].length;
if (match[1])
return this.closingBracketBlock(session, match[1], row, i);
return session.getCommentFoldRange(row, i, -1);
}
};
this.getSectionRange = function(session, row) {
var line = session.getLine(row);
var startIndent = line.search(/\S/);
var startRow = row;
var startColumn = line.length;
row = row + 1;
var endRow = row;
var maxRow = session.getLength();
while (++row < maxRow) {
line = session.getLine(row);
var indent = line.search(/\S/);
if (indent === -1)
continue;
if (startIndent > indent)
break;
var subRange = this.getFoldWidgetRange(session, "all", row);
if (subRange) {
if (subRange.start.row <= startRow) {
break;
} else if (subRange.isMultiLine()) {
row = subRange.end.row;
} else if (startIndent == indent) {
break;
}
}
endRow = row;
}
return new Range(startRow, startColumn, endRow, session.getLine(endRow).length);
};
}).call(FoldMode.prototype);
});
ace.define("ace/mode/javascript",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/javascript_highlight_rules","ace/mode/matching_brace_outdent","ace/range","ace/worker/worker_client","ace/mode/behaviour/cstyle","ace/mode/folding/cstyle"], function(acequire, exports, module) {
"use strict";
var oop = acequire("../lib/oop");
var TextMode = acequire("./text").Mode;
var JavaScriptHighlightRules = acequire("./javascript_highlight_rules").JavaScriptHighlightRules;
var MatchingBraceOutdent = acequire("./matching_brace_outdent").MatchingBraceOutdent;
var Range = acequire("../range").Range;
var WorkerClient = acequire("../worker/worker_client").WorkerClient;
var CstyleBehaviour = acequire("./behaviour/cstyle").CstyleBehaviour;
var CStyleFoldMode = acequire("./folding/cstyle").FoldMode;
var Mode = function() {
this.HighlightRules = JavaScriptHighlightRules;
this.$outdent = new MatchingBraceOutdent();
this.$behaviour = new CstyleBehaviour();
this.foldingRules = new CStyleFoldMode();
};
oop.inherits(Mode, TextMode);
(function() {
this.lineCommentStart = "//";
this.blockComment = {start: "/*", end: "*/"};
this.getNextLineIndent = function(state, line, tab) {
var indent = this.$getIndent(line);
var tokenizedLine = this.getTokenizer().getLineTokens(line, state);
var tokens = tokenizedLine.tokens;
var endState = tokenizedLine.state;
if (tokens.length && tokens[tokens.length-1].type == "comment") {
return indent;
}
if (state == "start" || state == "no_regex") {
var match = line.match(/^.*(?:\bcase\b.*\:|[\{\(\[])\s*$/);
if (match) {
indent += tab;
}
} else if (state == "doc-start") {
if (endState == "start" || endState == "no_regex") {
return "";
}
var match = line.match(/^\s*(\/?)\*/);
if (match) {
if (match[1]) {
indent += " ";
}
indent += "* ";
}
}
return indent;
};
this.checkOutdent = function(state, line, input) {
return this.$outdent.checkOutdent(line, input);
};
this.autoOutdent = function(state, doc, row) {
this.$outdent.autoOutdent(doc, row);
};
// this.createWorker = function(session) {
// var worker = new WorkerClient(["ace"], "ace/mode/javascript_worker", "JavaScriptWorker");
// worker.attachToDocument(session.getDocument());
//
// worker.on("jslint", function(results) {
// session.setAnnotations(results.data);
// });
//
// worker.on("terminate", function() {
// session.clearAnnotations();
// });
//
// return worker;
// };
this.$id = "ace/mode/javascript";
}).call(Mode.prototype);
exports.Mode = Mode;
});

View File

@ -16,6 +16,7 @@
import Actionbar from './Actionbar'; import Actionbar from './Actionbar';
import ActionbarExport from './Actionbar/Export'; import ActionbarExport from './Actionbar/Export';
import ActionbarImport from './Actionbar/Import';
import ActionbarSearch from './Actionbar/Search'; import ActionbarSearch from './Actionbar/Search';
import ActionbarSort from './Actionbar/Sort'; import ActionbarSort from './Actionbar/Sort';
import Badge from './Badge'; import Badge from './Badge';
@ -26,6 +27,7 @@ import ConfirmDialog from './ConfirmDialog';
import Container, { Title as ContainerTitle } from './Container'; import Container, { Title as ContainerTitle } from './Container';
import ContextProvider from './ContextProvider'; import ContextProvider from './ContextProvider';
import CopyToClipboard from './CopyToClipboard'; import CopyToClipboard from './CopyToClipboard';
import Editor from './Editor';
import Errors from './Errors'; import Errors from './Errors';
import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form'; import Form, { AddressSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputInline, Select } from './Form';
import IdentityIcon from './IdentityIcon'; import IdentityIcon from './IdentityIcon';
@ -43,6 +45,7 @@ import TxHash from './TxHash';
export { export {
Actionbar, Actionbar,
ActionbarExport, ActionbarExport,
ActionbarImport,
ActionbarSearch, ActionbarSearch,
ActionbarSort, ActionbarSort,
AddressSelect, AddressSelect,
@ -55,6 +58,7 @@ export {
ContainerTitle, ContainerTitle,
ContextProvider, ContextProvider,
CopyToClipboard, CopyToClipboard,
Editor,
Errors, Errors,
Form, Form,
FormWrap, FormWrap,

View File

@ -145,6 +145,8 @@ export default class List extends Component {
return tagsA.localeCompare(tagsB); return tagsA.localeCompare(tagsB);
} }
const reverse = key === 'timestamp' ? -1 : 1;
const metaA = accountA.meta[key]; const metaA = accountA.meta[key];
const metaB = accountB.meta[key]; const metaB = accountB.meta[key];
@ -152,14 +154,22 @@ export default class List extends Component {
return 0; return 0;
} }
if ((metaA && !metaB) || (metaA < metaB)) { if (metaA && !metaB) {
return -1; return -1;
} }
if ((!metaA && metaB) || (metaA > metaB)) { if (metaA < metaB) {
return -1 * reverse;
}
if (!metaA && metaB) {
return 1; return 1;
} }
if (metaA > metaB) {
return 1 * reverse;
}
return 0; return 0;
} }

View File

@ -29,7 +29,8 @@ const TABMAP = {
accounts: 'account', accounts: 'account',
addresses: 'address', addresses: 'address',
apps: 'app', apps: 'app',
contracts: 'contract' contracts: 'contract',
deploy: 'contract'
}; };
class TabBar extends Component { class TabBar extends Component {

View File

@ -18,4 +18,6 @@
.outer, .outer,
.container { .container {
min-height: 100vh; min-height: 100vh;
display: flex;
flex-direction: column;
} }

View File

@ -90,3 +90,12 @@
margin-bottom: -10px; margin-bottom: -10px;
margin-right: 0.5em; margin-right: 0.5em;
} }
.details {
margin-top: -1.5em;
h4 {
text-transform: uppercase;
margin: 1.5em 0 1em;
}
}

View File

@ -20,10 +20,12 @@ import { bindActionCreators } from 'redux';
import ActionDelete from 'material-ui/svg-icons/action/delete'; import ActionDelete from 'material-ui/svg-icons/action/delete';
import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow'; import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow';
import ContentCreate from 'material-ui/svg-icons/content/create'; import ContentCreate from 'material-ui/svg-icons/content/create';
import EyeIcon from 'material-ui/svg-icons/image/remove-red-eye';
import ContentClear from 'material-ui/svg-icons/content/clear';
import { newError } from '../../redux/actions'; import { newError } from '../../redux/actions';
import { EditMeta, ExecuteContract } from '../../modals'; import { EditMeta, ExecuteContract } from '../../modals';
import { Actionbar, Button, Page } from '../../ui'; import { Actionbar, Button, Page, Modal, Editor } from '../../ui';
import Header from '../Account/Header'; import Header from '../Account/Header';
import Delete from '../Address/Delete'; import Delete from '../Address/Delete';
@ -52,6 +54,7 @@ class Contract extends Component {
showDeleteDialog: false, showDeleteDialog: false,
showEditDialog: false, showEditDialog: false,
showExecuteDialog: false, showExecuteDialog: false,
showDetailsDialog: false,
subscriptionId: -1, subscriptionId: -1,
blockSubscriptionId: -1, blockSubscriptionId: -1,
allEvents: [], allEvents: [],
@ -118,11 +121,69 @@ class Contract extends Component {
<Events <Events
isTest={ isTest } isTest={ isTest }
events={ allEvents } /> events={ allEvents } />
{ this.renderDetails(account) }
</Page> </Page>
</div> </div>
); );
} }
renderDetails (contract) {
const { showDetailsDialog } = this.state;
if (!showDetailsDialog) {
return null;
}
const cancelBtn = (
<Button
icon={ <ContentClear /> }
label='Close'
onClick={ this.closeDetailsDialog } />
);
return (
<Modal
actions={ [ cancelBtn ] }
title={ 'contract details' }
visible
scroll
>
<div className={ styles.details }>
{ this.renderSource(contract) }
<div>
<h4>Contract ABI</h4>
<Editor
value={ JSON.stringify(contract.meta.abi, null, 2) }
mode='json'
maxLines={ 20 }
readOnly
/>
</div>
</div>
</Modal>
);
}
renderSource (contract) {
const { source } = contract.meta;
if (!source) {
return null;
}
return (
<div>
<h4>Contract source code</h4>
<Editor
value={ source }
readOnly
/>
</div>
);
}
renderActionbar (account) { renderActionbar (account) {
const buttons = [ const buttons = [
<Button <Button
@ -139,7 +200,12 @@ class Contract extends Component {
key='delete' key='delete'
icon={ <ActionDelete /> } icon={ <ActionDelete /> }
label='delete contract' label='delete contract'
onClick={ this.showDeleteDialog } /> onClick={ this.showDeleteDialog } />,
<Button
key='viewDetails'
icon={ <EyeIcon /> }
label='view details'
onClick={ this.showDetailsDialog } />
]; ];
return ( return (
@ -235,6 +301,14 @@ class Contract extends Component {
this.setState({ showDeleteDialog: true }); this.setState({ showDeleteDialog: true });
} }
showDetailsDialog = () => {
this.setState({ showDetailsDialog: true });
}
closeDetailsDialog = () => {
this.setState({ showDetailsDialog: false });
}
closeExecuteDialog = () => { closeExecuteDialog = () => {
this.setState({ showExecuteDialog: false }); this.setState({ showExecuteDialog: false });
} }

View File

@ -15,9 +15,11 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import ContentAdd from 'material-ui/svg-icons/content/add'; import ContentAdd from 'material-ui/svg-icons/content/add';
import FileIcon from 'material-ui/svg-icons/action/description';
import { uniq } from 'lodash'; import { uniq } from 'lodash';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui'; import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui';
@ -114,6 +116,15 @@ class Contracts extends Component {
icon={ <ContentAdd /> } icon={ <ContentAdd /> }
label='deploy contract' label='deploy contract'
onClick={ this.onDeployContract } />, onClick={ this.onDeployContract } />,
<Link
to='/contracts/write'
key='writeContract'
>
<Button
icon={ <FileIcon /> }
label='write contract'
/>
</Link>,
this.renderSearchButton(), this.renderSearchButton(),
this.renderSortButton() this.renderSortButton()

View File

@ -0,0 +1,17 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './writeContract';

View File

@ -0,0 +1,174 @@
/* Copyright 2015, 2016 Ethcore (UK) Ltd.
/* This file is part of Parity.
/*
/* Parity is free software: you can redistribute it and/or modify
/* it under the terms of the GNU General Public License as published by
/* the Free Software Foundation, either version 3 of the License, or
/* (at your option) any later version.
/*
/* Parity is distributed in the hope that it will be useful,
/* but WITHOUT ANY WARRANTY; without even the implied warranty of
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
/* GNU General Public License for more details.
/*
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.outer, .page, .editor {
flex: 1;
display: flex;
flex-direction: column;
}
.timestamp {
font-size: 0.75em;
margin-left: 1em;
color: #ccc;
}
.container {
padding: 1em 0;
display: flex;
flex: 1;
flex-direction: row;
> * {
margin: 0;
> h2 {
margin-top: 0;
}
}
}
.mainEditor {
&:global(.ace-solarized-dark) {
background-color: rgba(0, 0, 0, 0.5);
:global(.ace_gutter) {
background-color: rgba(0, 0, 0, 0.7);
}
:global(.ace_content) {
background-color: transparent;
}
}
}
.big {
font-size: 1.2em;
}
.centeredMessage {
width: 100%;
height: 75%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.resizing * {
cursor: ew-resize !important;
user-select: none !important;
}
.editor {
width: 0;
margin-left: 0.5em;
}
.parameters {
width: 0;
display: flex;
flex-direction: column;
margin-right: 0.5em;
.panel {
background-color: rgba(0, 0, 0, 0.5);
padding: 1em;
flex: 1;
display: flex;
flex-direction: column;
}
.compilation {
flex: 1 0 0;
display: flex;
flex-direction: column;
}
.errors {
flex: 1 0 0;
overflow: auto;
margin-right: -0.5em;
margin-top: 0.5em;
}
}
.messageContainer {
padding: 0.5em 0;
margin-right: 0.5em;
&:first-child {
padding-top: 0;
}
&:last-child {
padding-bottom: 0;
}
.errorPosition {
background-color: rgba(0, 0, 0, 0.5);
padding: 0.25em 0.5em;
top: 0;
position: relative;
margin-bottom: 0.25em;
font-size: 0.75em;
}
}
.message {
font-family: monospace;
padding: 0.5em;
font-size: 0.9em;
white-space: pre;
overflow: auto;
&.error {
background-color: rgba(244, 67, 54, 0.5);
}
&.warning {
background-color: rgba(255, 235, 59, 0.5);
}
&.formal {
background-color: rgba(243, 156, 18, 0.5);
}
}
.messagesHeader {
margin-bottom: 0.25em;
text-transform: uppercase;
font-size: 0.9em;
}
.sliderContainer {
flex: 0 0 .8em;
display: flex;
align-items: center;
justify-content: center;
.slider {
width: 0.4em;
height: 3em;
border-radius: 0.75em;
background-color: rgba(0, 0, 0, 0.5);
content: ' ';
&:hover {
cursor: ew-resize;
}
}
}

View File

@ -0,0 +1,502 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes, Component } from 'react';
import { observer } from 'mobx-react';
import { MenuItem } from 'material-ui';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import CircularProgress from 'material-ui/CircularProgress';
import moment from 'moment';
import ContentClear from 'material-ui/svg-icons/content/clear';
import SaveIcon from 'material-ui/svg-icons/content/save';
import ListIcon from 'material-ui/svg-icons/action/view-list';
import SettingsIcon from 'material-ui/svg-icons/action/settings';
import SendIcon from 'material-ui/svg-icons/content/send';
import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '../../ui';
import { DeployContract, SaveContract, LoadContract } from '../../modals';
import { setupWorker } from '../../redux/providers/compilerActions';
import WriteContractStore from './writeContractStore';
import styles from './writeContract.css';
@observer
class WriteContract extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
setupWorker: PropTypes.func.isRequired,
worker: PropTypes.object
};
store = new WriteContractStore();
state = {
resizing: false,
size: 65
};
componentWillMount () {
const { setupWorker, worker } = this.props;
setupWorker();
if (worker) {
this.store.setCompiler(worker);
}
}
componentDidMount () {
this.store.setEditor(this.refs.editor);
// Wait for editor to be loaded
window.setTimeout(() => {
this.store.resizeEditor();
}, 2000);
}
componentWillReceiveProps (nextProps) {
if (!this.props.worker && nextProps.worker) {
this.store.setCompiler(nextProps.worker);
}
}
render () {
const { sourcecode } = this.store;
const { size, resizing } = this.state;
const annotations = this.store.annotations
.slice()
.filter((a) => a.contract === '');
return (
<div className={ styles.outer }>
{ this.renderDeployModal() }
{ this.renderSaveModal() }
{ this.renderLoadModal() }
{ this.renderActionBar() }
<Page className={ styles.page }>
<div
className={ `${styles.container} ${resizing ? styles.resizing : ''}` }
onMouseMove={ this.handleResize }
onMouseUp={ this.handleStopResize }
onMouseLeave={ this.handleStopResize }
>
<div
className={ styles.editor }
style={ { flex: `${size}%` } }
>
<h2>{ this.renderTitle() }</h2>
<Editor
ref='editor'
onChange={ this.store.handleEditSourcecode }
onExecute={ this.store.handleCompile }
annotations={ annotations }
value={ sourcecode }
className={ styles.mainEditor }
/>
</div>
<div className={ styles.sliderContainer }>
<span
className={ styles.slider }
onMouseDown={ this.handleStartResize }
>
</span>
</div>
<div
className={ styles.parameters }
style={ { flex: `${100 - size}%` } }
>
<h2>Parameters</h2>
{ this.renderParameters() }
</div>
</div>
</Page>
</div>
);
}
renderTitle () {
const { selectedContract } = this.store;
if (!selectedContract || !selectedContract.name) {
return 'New Solidity Contract';
}
return (
<span>
{ selectedContract.name }
<span
className={ styles.timestamp }
title={ `saved @ ${(new Date(selectedContract.timestamp)).toISOString()}` }
>
(saved { moment(selectedContract.timestamp).fromNow() })
</span>
</span>
);
}
renderActionBar () {
const { sourcecode, selectedContract } = this.store;
const filename = selectedContract && selectedContract.name
? selectedContract.name
.replace(/[^a-z0-9]+/gi, '-')
.replace(/-$/, '')
.toLowerCase()
: 'contract.sol';
const extension = /\.sol$/.test(filename) ? '' : '.sol';
const buttons = [
<Button
icon={ <ContentClear /> }
label='New'
key='newContract'
onClick={ this.store.handleNewContract }
/>,
<Button
icon={ <ListIcon /> }
label='Load'
key='loadContract'
onClick={ this.store.handleOpenLoadModal }
/>,
<Button
icon={ <SaveIcon /> }
label='Save'
key='saveContract'
onClick={ this.store.handleSaveContract }
/>,
<ActionbarExport
key='exportSourcecode'
content={ sourcecode }
filename={ `${filename}${extension}` }
/>,
<ActionbarImport
key='importSourcecode'
title='Import Solidity code'
onConfirm={ this.store.handleImport }
renderValidation={ this.renderImportValidation }
/>
];
return (
<Actionbar
title='Write a Contract'
buttons={ buttons }
/>
);
}
renderImportValidation = (content) => {
return (
<Editor
readOnly
value={ content }
maxLines={ 20 }
/>
);
}
renderParameters () {
const { compiling, contract, selectedBuild, loading } = this.store;
if (selectedBuild < 0) {
return (
<div className={ `${styles.panel} ${styles.centeredMessage}` }>
<CircularProgress size={ 80 } thickness={ 5 } />
<p>Loading...</p>
</div>
);
}
if (loading) {
const { longVersion } = this.store.builds[selectedBuild];
return (
<div className={ styles.panel }>
<div className={ styles.centeredMessage }>
<CircularProgress size={ 80 } thickness={ 5 } />
<p>Loading Solidity { longVersion }</p>
</div>
</div>
);
}
return (
<div className={ styles.panel }>
<div>
<Button
icon={ <SettingsIcon /> }
label='Compile'
onClick={ this.store.handleCompile }
primary={ false }
disabled={ compiling }
/>
{
contract
? <Button
icon={ <SendIcon /> }
label='Deploy'
onClick={ this.store.handleOpenDeployModal }
primary={ false }
/>
: null
}
</div>
{ this.renderSolidityVersions() }
{ this.renderCompilation() }
</div>
);
}
renderSolidityVersions () {
const { builds, selectedBuild } = this.store;
const buildsList = builds.map((build, index) => (
<MenuItem
key={ index }
value={ index }
label={ build.release ? build.version : build.longVersion }
>
{
build.release
? (<span className={ styles.big }>{ build.version }</span>)
: build.longVersion
}
</MenuItem>
));
return (
<div>
<Select
label='Select a Solidity version'
value={ selectedBuild }
onChange={ this.store.handleSelectBuild }
>
{ buildsList }
</Select>
</div>
);
}
renderDeployModal () {
const { showDeployModal, contract, sourcecode } = this.store;
if (!showDeployModal) {
return null;
}
return (
<DeployContract
abi={ contract.interface }
code={ `0x${contract.bytecode}` }
source={ sourcecode }
accounts={ this.props.accounts }
onClose={ this.store.handleCloseDeployModal }
readOnly
/>
);
}
renderLoadModal () {
const { showLoadModal } = this.store;
if (!showLoadModal) {
return null;
}
return (
<LoadContract
onLoad={ this.store.handleLoadContract }
onDelete={ this.store.handleDeleteContract }
onClose={ this.store.handleCloseLoadModal }
contracts={ this.store.savedContracts }
snippets={ this.store.snippets }
/>
);
}
renderSaveModal () {
const { showSaveModal, sourcecode } = this.store;
if (!showSaveModal) {
return null;
}
return (
<SaveContract
sourcecode={ sourcecode }
onSave={ this.store.handleSaveNewContract }
onClose={ this.store.handleCloseSaveModal }
/>
);
}
renderCompilation () {
const { compiled, contracts, compiling, contractIndex, contract } = this.store;
if (compiling) {
return (
<div className={ styles.centeredMessage }>
<CircularProgress size={ 80 } thickness={ 5 } />
<p>Compiling...</p>
</div>
);
}
if (!compiled) {
return (
<div className={ styles.centeredMessage }>
<p>Please compile the source code.</p>
</div>
);
}
if (!contracts) {
return this.renderErrors();
}
const contractKeys = Object.keys(contracts);
if (contractKeys.length === 0) {
return (
<div className={ styles.centeredMessage }>
<p>No contract has been found.</p>
</div>
);
}
const contractsList = contractKeys.map((name, index) => (
<MenuItem
key={ index }
value={ index }
label={ name }
>
{ name }
</MenuItem>
));
return (
<div className={ styles.compilation }>
<Select
label='Select a contract'
value={ contractIndex }
onChange={ this.store.handleSelectContract }
>
{ contractsList }
</Select>
{ this.renderContract(contract) }
<h4 className={ styles.messagesHeader }>Compiler messages</h4>
{ this.renderErrors() }
</div>
);
}
renderContract (contract) {
const { bytecode } = contract;
const abi = contract.interface;
return (
<div>
<Input
readOnly
value={ abi }
label='ABI Interface'
/>
<Input
readOnly
value={ `0x${bytecode}` }
label='Bytecode'
/>
</div>
);
}
renderErrors () {
const { annotations } = this.store;
const body = annotations.map((annotation, index) => {
const { text, row, column, contract, type, formal } = annotation;
const classType = formal ? 'formal' : type;
const classes = [ styles.message, styles[classType] ];
return (
<div key={ index } className={ styles.messageContainer }>
<div className={ classes.join(' ') }>{ text }</div>
<span className={ styles.errorPosition }>
{ contract ? `[ ${contract} ] ` : '' }
{ row }: { column }
</span>
</div>
);
});
return (
<div className={ styles.errors }>
{ body }
</div>
);
}
handleStartResize = () => {
this.setState({ resizing: true });
}
handleStopResize = () => {
this.setState({ resizing: false });
}
handleResize = (event) => {
if (!this.state.resizing) {
return;
}
const { pageX, currentTarget } = event;
const { width, left } = currentTarget.getBoundingClientRect();
const x = pageX - left;
this.setState({ size: 100 * x / width });
event.stopPropagation();
}
}
function mapStateToProps (state) {
const { accounts } = state.personal;
const { worker } = state.compiler;
return { accounts, worker };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
setupWorker
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(WriteContract);

View File

@ -0,0 +1,373 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx';
import store from 'store';
import { debounce } from 'lodash';
const WRITE_CONTRACT_STORE_KEY = '_parity::writeContractStore';
const SNIPPETS = {
snippet0: {
name: 'Token.sol',
description: 'Standard ERP20 Token Contract',
id: 'snippet0', sourcecode: require('raw!../../contracts/snippets/token.sol')
},
snippet1: {
name: 'StandardToken.sol',
description: 'Implementation of ERP20 Token Contract',
id: 'snippet1', sourcecode: require('raw!../../contracts/snippets/standard-token.sol')
},
snippet2: {
name: 'HumanStandardToken.sol',
description: 'Implementation of the Human Token Contract',
id: 'snippet2', sourcecode: require('raw!../../contracts/snippets/human-standard-token.sol')
}
};
export default class WriteContractStore {
@observable sourcecode = '';
@observable compiled = false;
@observable compiling = false;
@observable loading = true;
@observable contractIndex = -1;
@observable contract = null;
@observable contracts = {};
@observable errors = [];
@observable annotations = [];
@observable builds = [];
@observable selectedBuild = -1;
@observable showDeployModal = false;
@observable showSaveModal = false;
@observable showLoadModal = false;
@observable savedContracts = {};
@observable selectedContract = {};
snippets = SNIPPETS;
constructor () {
this.reloadContracts();
this.fetchSolidityVersions();
this.debouncedCompile = debounce(this.handleCompile, 1000);
}
@action setEditor (editor) {
this.editor = editor;
}
@action setCompiler (compiler) {
this.compiler = compiler;
this.compiler.onmessage = (event) => {
const message = JSON.parse(event.data);
switch (message.event) {
case 'compiled':
this.parseCompiled(message.data);
break;
case 'loading':
this.parseLoading(message.data);
break;
case 'try-again':
this.handleCompile();
break;
}
};
}
fetchSolidityVersions () {
fetch('https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/bin/list.json')
.then((r) => r.json())
.then((data) => {
const { builds, releases, latestRelease } = data;
let latestIndex = -1;
this.builds = builds.reverse().map((build, index) => {
if (releases[build.version] === build.path) {
build.release = true;
if (build.version === latestRelease) {
build.latest = true;
this.loadSolidityVersion(build);
latestIndex = index;
}
}
return build;
});
this.selectedBuild = latestIndex;
});
}
@action closeWorker = () => {
this.compiler.postMessage(JSON.stringify({
action: 'close'
}));
}
@action handleImport = (sourcecode) => {
this.reloadContracts(-1, sourcecode);
}
@action handleSelectBuild = (_, index, value) => {
this.selectedBuild = value;
this.loadSolidityVersion(this.builds[value]);
}
@action loadSolidityVersion = (build) => {
this.compiler.postMessage(JSON.stringify({
action: 'load',
data: build
}));
}
@action handleOpenDeployModal = () => {
this.showDeployModal = true;
}
@action handleCloseDeployModal = () => {
this.showDeployModal = false;
}
@action handleOpenLoadModal = () => {
this.showLoadModal = true;
}
@action handleCloseLoadModal = () => {
this.showLoadModal = false;
}
@action handleOpenSaveModal = () => {
this.showSaveModal = true;
}
@action handleCloseSaveModal = () => {
this.showSaveModal = false;
}
@action handleSelectContract = (_, index, value) => {
this.contractIndex = value;
this.contract = this.contracts[Object.keys(this.contracts)[value]];
}
@action handleCompile = () => {
this.compiled = false;
this.compiling = true;
const build = this.builds[this.selectedBuild];
if (this.compiler && typeof this.compiler.postMessage === 'function') {
this.sendFilesToWorker();
this.compiler.postMessage(JSON.stringify({
action: 'compile',
data: {
sourcecode: this.sourcecode,
build: build
}
}));
}
}
parseErrors = (data, formal = false) => {
const regex = /^(.*):(\d+):(\d+):\s*([a-z]+):\s*((.|[\r\n])+)$/i;
return (data || [])
.filter((e) => regex.test(e))
.map((error, index) => {
const match = regex.exec(error);
const contract = match[1];
const row = parseInt(match[2]) - 1;
const column = parseInt(match[3]);
const type = formal ? 'warning' : match[4].toLowerCase();
const text = match[5];
return {
contract,
row, column,
type, text,
formal
};
});
}
@action parseCompiled = (data) => {
const { contracts } = data;
const { errors = [] } = data;
const errorAnnotations = this.parseErrors(errors);
const formalAnnotations = this.parseErrors(data.formal && data.formal.errors, true);
const annotations = [].concat(
errorAnnotations,
formalAnnotations
);
if (annotations.findIndex((a) => /__parity_tryAgain/.test(a.text)) > -1) {
return;
}
const contractKeys = Object.keys(contracts || {});
this.contract = contractKeys.length ? contracts[contractKeys[0]] : null;
this.contractIndex = contractKeys.length ? 0 : -1;
this.contracts = contracts;
this.errors = errors;
this.annotations = annotations;
this.compiled = true;
this.compiling = false;
}
@action parseLoading = (isLoading) => {
this.loading = isLoading;
if (!isLoading) {
this.handleCompile();
}
}
@action handleEditSourcecode = (value, compile = false) => {
this.sourcecode = value;
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
current: value
});
if (compile) {
this.handleCompile();
} else {
this.debouncedCompile();
}
}
@action handleSaveContract = () => {
if (this.selectedContract && this.selectedContract.id !== undefined) {
return this.handleSaveNewContract({
...this.selectedContract,
sourcecode: this.sourcecode
});
}
return this.handleOpenSaveModal();
}
getId (contracts) {
return Object.values(contracts)
.map((c) => c.id)
.reduce((max, id) => Math.max(max, id), 0) + 1;
}
@action handleSaveNewContract = (data) => {
const { name, sourcecode, id } = data;
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
const savedContracts = localStore.saved || {};
const cId = (id !== undefined)
? id
: this.getId(savedContracts);
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
saved: {
...savedContracts,
[ cId ]: { sourcecode, id: cId, name, timestamp: Date.now() }
}
});
this.reloadContracts(cId);
}
@action reloadContracts = (id, sourcecode) => {
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
this.savedContracts = localStore.saved || {};
const cId = id !== undefined ? id : localStore.currentId;
this.selectedContract = this.savedContracts[cId] || {};
this.sourcecode = sourcecode !== undefined
? sourcecode
: this.selectedContract.sourcecode || localStore.current || '';
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
currentId: this.selectedContract ? cId : null,
current: this.sourcecode
});
this.handleCompile();
this.resizeEditor();
}
@action handleLoadContract = (contract) => {
const { sourcecode, id } = contract;
this.reloadContracts(id, sourcecode);
}
@action handleDeleteContract = (id) => {
const localStore = store.get(WRITE_CONTRACT_STORE_KEY) || {};
const savedContracts = Object.assign({}, localStore.saved || {});
if (savedContracts[id]) {
delete savedContracts[id];
}
store.set(WRITE_CONTRACT_STORE_KEY, {
...localStore,
saved: savedContracts
});
this.reloadContracts();
}
@action handleNewContract = () => {
this.reloadContracts(-1, '');
}
@action resizeEditor = () => {
try {
this.editor.refs.brace.editor.resize();
} catch (e) {}
}
sendFilesToWorker = () => {
const files = [].concat(
Object.values(this.snippets),
Object.values(this.savedContracts)
);
this.compiler.postMessage(JSON.stringify({
action: 'setFiles',
data: files
}));
}
}

View File

@ -21,6 +21,7 @@ import Addresses from './Addresses';
import Application from './Application'; import Application from './Application';
import Contract from './Contract'; import Contract from './Contract';
import Contracts from './Contracts'; import Contracts from './Contracts';
import WriteContract from './WriteContract';
import Dapp from './Dapp'; import Dapp from './Dapp';
import Dapps from './Dapps'; import Dapps from './Dapps';
import ParityBar from './ParityBar'; import ParityBar from './ParityBar';
@ -36,6 +37,7 @@ export {
Application, Application,
Contract, Contract,
Contracts, Contracts,
WriteContract,
Dapp, Dapp,
Dapps, Dapps,
ParityBar, ParityBar,