Merge branch 'master' into jg-external-dapps

This commit is contained in:
Jaco Greeff
2016-11-16 12:36:43 +01:00
55 changed files with 8720 additions and 7653 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "0.2.46",
"version": "0.2.48",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",

View File

@@ -40,9 +40,12 @@ export default class Contract {
this._events.forEach((evt) => {
this._instance[evt.name] = evt;
this._instance[evt.signature] = evt;
});
this._functions.forEach((fn) => {
this._instance[fn.name] = fn;
this._instance[fn.signature] = fn;
});
this._sendSubscriptionChanges();

View File

@@ -20,6 +20,7 @@ import sinon from 'sinon';
import { TEST_HTTP_URL, mockHttp } from '../../../test/mockRpc';
import Abi from '../../abi';
import { sha3 } from '../util/sha3';
import Api from '../api';
import Contract from './contract';
@@ -113,7 +114,13 @@ describe('api/contract/Contract', () => {
]);
contract.at('6789');
expect(Object.keys(contract.instance)).to.deep.equal(['Drained', 'balanceOf', 'address']);
expect(Object.keys(contract.instance)).to.deep.equal([
'Drained',
/^(?:0x)(.+)$/.exec(sha3('Drained(uint256)'))[1],
'balanceOf',
/^(?:0x)(.+)$/.exec(sha3('balanceOf(address)'))[1].substr(0, 8),
'address'
]);
expect(contract.address).to.equal('6789');
});
});

View File

@@ -46,11 +46,22 @@ a.link, a.link:hover, a.link:visited {
}
.address {
max-width: 250px;
text-align: left;
div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.description {
text-align: left;
div {
white-space: nowrap;
}
}
.center {

View File

@@ -58,6 +58,7 @@ export default class Event extends Component {
<td className={ styles.description }>
<div>{ isPending ? '' : coin.tla }</div>
<div>{ isPending ? '' : coin.name }</div>
<div>{ this.renderAddress(event.params.coin) }</div>
</td>
<td className={ styles.address }>
{ this.renderAddress(event.params.owner) }

View File

@@ -46,3 +46,9 @@
.icon {
margin: 0 0 -4px 1em;
}
.byline {
opacity: 0.75;
font-size: 0.75em;
padding-top: 0.25em;
}

View File

@@ -68,6 +68,9 @@ export default class Owner extends Component {
<Token
address={ token.address }
tokenreg={ token.tokenreg } />
<div className={ styles.byline }>
{ token.address }
</div>
</div>
));
}

View File

@@ -227,6 +227,7 @@ export default class AddContract extends Component {
onEditAbi = (abiIn) => {
const { api } = this.context;
const { abi, abiError, abiParsed } = validateAbi(abiIn, api);
this.setState({ abi, abiError, abiParsed });
}

View File

@@ -314,7 +314,7 @@ export default class Transfer extends Component {
}
const token = balance.tokens.find((balance) => balance.token.tag === tag).token;
const s = new BigNumber(num).mul(token.format || 1).toString();
const s = new BigNumber(num).mul(token.format || 1).toFixed();
if (s.indexOf('.') !== -1) {
return ERRORS.invalidDecimals;
@@ -516,6 +516,13 @@ export default class Transfer extends Component {
}
recalculateGas = () => {
if (!this.isValid()) {
this.setState({
gas: '0'
}, this.recalculate);
return;
}
(this.state.isEth
? this._estimateGasEth()
: this._estimateGasToken()

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import FileSaver from 'file-saver';
import FileDownloadIcon from 'material-ui/svg-icons/file/file-download';
@@ -38,19 +39,18 @@ class ActionbarExport extends Component {
className={ className }
icon={ <FileDownloadIcon /> }
label='export'
onClick={ this.onDownloadBackup } />
onClick={ this.handleExport }
/>
);
}
onDownloadBackup = () => {
handleExport = () => {
const { filename, content } = this.props;
const text = (typeof content === 'string')
? content
: JSON.stringify(content, null, 4);
const text = JSON.stringify(content, null, 4);
const blob = new Blob([ text ], { type: 'text/plain;charset=utf-8' });
FileSaver.saveAs(blob, filename);
const blob = new Blob([ text ], { type: 'application/json' });
FileSaver.saveAs(blob, `${filename}.json`);
}
}

View File

@@ -15,6 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { observer } from 'mobx-react';
import IconMenu from 'material-ui/IconMenu';
import MenuItem from 'material-ui/MenuItem';
@@ -22,11 +24,15 @@ import SortIcon from 'material-ui/svg-icons/content/sort';
import { Button } from '../../';
import SortStore from './sortStore';
import styles from './sort.css';
@observer
export default class ActionbarSort extends Component {
static propTypes = {
id: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
order: PropTypes.string,
showDefault: PropTypes.bool,
metas: PropTypes.array
@@ -37,8 +43,10 @@ export default class ActionbarSort extends Component {
showDefault: true
}
state = {
menuOpen: false
store = new SortStore(this.props);
componentDidMount () {
this.store.restoreSavedOrder();
}
render () {
@@ -51,12 +59,12 @@ export default class ActionbarSort extends Component {
className={ styles.sortButton }
label=''
icon={ <SortIcon /> }
onClick={ this.handleMenuOpen }
onClick={ this.store.handleMenuOpen }
/>
}
open={ this.state.menuOpen }
onRequestChange={ this.handleMenuChange }
onItemTouchTap={ this.handleSortChange }
open={ this.store.menuOpen }
onRequestChange={ this.store.handleMenuChange }
onItemTouchTap={ this.store.handleSortChange }
targetOrigin={ { horizontal: 'right', vertical: 'top' } }
anchorOrigin={ { horizontal: 'right', vertical: 'top' } }
touchTapCloseDelay={ 0 }
@@ -109,16 +117,4 @@ export default class ActionbarSort extends Component {
);
}
handleSortChange = (event, child) => {
const order = child.props.value;
this.props.onChange(order);
}
handleMenuOpen = () => {
this.setState({ menuOpen: true });
}
handleMenuChange = (open) => {
this.setState({ menuOpen: open });
}
}

View File

@@ -0,0 +1,71 @@
// 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';
const LS_STORE_KEY = '_parity::sortStore';
export default class SortStore {
@observable menuOpen = false;
constructor (props) {
const { id, onChange } = props;
this.onChange = onChange;
this.id = id;
}
@action handleMenuOpen = () => {
this.menuOpen = true;
}
@action handleMenuChange = (open) => {
this.menuOpen = open;
}
@action handleSortChange = (event, child) => {
const order = child.props.value;
this.onChange(order);
this.saveOrder(order);
}
@action restoreSavedOrder = () => {
const order = this.getSavedOrder();
this.onChange(order);
}
getSavedOrder = () => {
return (this.getSavedOrders())[this.id];
}
getSavedOrders = () => {
return store.get(LS_STORE_KEY) || {};
}
setSavedOrders = (orders) => {
store.set(LS_STORE_KEY, orders);
}
saveOrder = (order) => {
const orders = {
...this.getSavedOrders(),
[ this.id ]: order
};
this.setSavedOrders(orders);
}
}

View File

@@ -16,6 +16,7 @@
import React, { Component, PropTypes } from 'react';
import { TextField } from 'material-ui';
import { noop } from 'lodash';
import CopyToClipboard from '../../CopyToClipboard';
@@ -81,7 +82,7 @@ export default class Input extends Component {
}
componentWillReceiveProps (newProps) {
if (newProps.value !== this.props.value) {
if ((newProps.value !== this.props.value) && (newProps.value !== this.state.value)) {
this.setValue(newProps.value);
}
}
@@ -131,6 +132,7 @@ export default class Input extends Component {
onBlur={ this.onBlur }
onChange={ this.onChange }
onKeyDown={ this.onKeyDown }
onPaste={ this.onPaste }
inputStyle={ inputStyle }
min={ min }
max={ max }
@@ -180,9 +182,9 @@ export default class Input extends Component {
}
onChange = (event, value) => {
this.setValue(value);
this.props.onChange && this.props.onChange(event, value);
this.setValue(value, () => {
this.props.onChange && this.props.onChange(event, value);
});
}
onBlur = (event) => {
@@ -196,6 +198,14 @@ export default class Input extends Component {
this.props.onBlur && this.props.onBlur(event);
}
onPaste = (event) => {
const value = event.clipboardData.getData('Text');
window.setTimeout(() => {
this.onSubmit(value);
}, 0);
}
onKeyDown = (event) => {
const { value } = event.target;
@@ -209,12 +219,12 @@ export default class Input extends Component {
}
onSubmit = (value) => {
this.setValue(value);
this.props.onSubmit && this.props.onSubmit(value);
this.setValue(value, () => {
this.props.onSubmit && this.props.onSubmit(value);
});
}
setValue (value) {
this.setState({ value });
setValue (value, cb = noop) {
this.setState({ value }, cb);
}
}

View File

@@ -30,6 +30,10 @@
.iconDisabled {
position: absolute;
top: 35px;
&.noLabel {
top: 10px;
}
}
.icon {

View File

@@ -69,14 +69,19 @@ class InputAddress extends Component {
}
renderIcon () {
const { value, disabled } = this.props;
const { value, disabled, label } = this.props;
if (!value || !value.length || !util.isAddressValid(value)) {
return null;
}
const classes = [disabled ? styles.iconDisabled : styles.icon];
if (!label) {
classes.push(styles.noLabel);
}
return (
<div className={ disabled ? styles.iconDisabled : styles.icon }>
<div className={ classes.join(' ') }>
<IdentityIcon
inline center
address={ value } />

View File

@@ -44,7 +44,7 @@ export function validateAbi (abi, api) {
// Validate each elements of the Array
const invalidIndex = abiParsed
.map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api))
.map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api) || isAbiFallback(o))
.findIndex((valid) => !valid);
if (invalidIndex !== -1) {
@@ -74,6 +74,14 @@ function isValidAbiFunction (object, api) {
(object.inputs && api.util.isArray(object.inputs));
}
function isAbiFallback (object) {
if (!object) {
return false;
}
return object.type === 'fallback';
}
function isValidAbiEvent (object, api) {
if (!object) {
return false;

View File

@@ -22,7 +22,7 @@ import { uniq } from 'lodash';
import List from './List';
import { CreateAccount } from '../../modals';
import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '../../ui';
import { Actionbar, ActionbarExport, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '../../ui';
import styles from './accounts.css';
@@ -90,12 +90,15 @@ class Accounts extends Component {
return (
<ActionbarSort
key='sortAccounts'
id='sortAccounts'
order={ this.state.sortOrder }
onChange={ onChange } />
);
}
renderActionbar () {
const { accounts } = this.props;
const buttons = [
<Button
key='newAccount'
@@ -103,6 +106,11 @@ class Accounts extends Component {
label='new account'
onClick={ this.onNewAccountClick } />,
<ActionbarExport
key='exportAccounts'
content={ accounts }
filename='accounts' />,
this.renderSearchButton(),
this.renderSortButton()
];

View File

@@ -75,6 +75,7 @@ class Addresses extends Component {
return (
<ActionbarSort
key='sortAccounts'
id='sortAddresses'
order={ this.state.sortOrder }
onChange={ onChange } />
);
@@ -106,7 +107,7 @@ class Addresses extends Component {
<ActionbarExport
key='exportAddressbook'
content={ contacts }
filename='addressbook.json' />,
filename='addressbook' />,
<ActionbarImport
key='importAddressbook'

View File

@@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
import LinearProgress from 'material-ui/LinearProgress';
import { Card, CardActions, CardTitle, CardText } from 'material-ui/Card';
import { Button, Input, InputAddressSelect } from '../../../ui';
import { Button, Input, InputAddress, InputAddressSelect } from '../../../ui';
import styles from './queries.css';
@@ -33,6 +33,7 @@ export default class InputQuery extends Component {
inputs: PropTypes.array.isRequired,
outputs: PropTypes.array.isRequired,
name: PropTypes.string.isRequired,
signature: PropTypes.string.isRequired,
className: PropTypes.string
}
@@ -95,32 +96,48 @@ export default class InputQuery extends Component {
}
if (!results || results.length < 1) return null;
return outputs
.map((out, index) => ({
name: out.name,
type: out.type,
value: results[index],
display: this.renderValue(results[index])
}))
.sort((outA, outB) => outA.display.length - outB.display.length)
.map((out, index) => (
<div key={ index }>
<div className={ styles.queryResultName }>
{ out.name }
</div>
.map((out, index) => {
let input = null;
if (out.type === 'address') {
input = (
<InputAddress
className={ styles.queryValue }
disabled
value={ out.display }
/>
);
} else {
input = (
<Input
className={ styles.queryValue }
readOnly
allowCopy
value={ out.display }
/>
);
}
<Input
className={ styles.queryValue }
readOnly
allowCopy
value={ out.display }
/>
<br />
</div>
));
return (
<div key={ index }>
<div className={ styles.queryResultName }>
{ out.name }
</div>
{ input }
</div>
);
});
}
renderInput (input) {
const { values } = this.state;
const { name, type } = input;
const label = `${name ? `${name}: ` : ''}${type}`;
@@ -142,6 +159,7 @@ export default class InputQuery extends Component {
<InputAddressSelect
hint={ type }
label={ label }
value={ values[name] }
required
onChange={ onChange }
/>
@@ -154,6 +172,7 @@ export default class InputQuery extends Component {
<Input
hint={ type }
label={ label }
value={ values[name] }
required
onChange={ onChange }
/>
@@ -177,7 +196,7 @@ export default class InputQuery extends Component {
onClick = () => {
const { values } = this.state;
const { inputs, contract, name, outputs } = this.props;
const { inputs, contract, name, outputs, signature } = this.props;
this.setState({
isLoading: true,
@@ -187,7 +206,7 @@ export default class InputQuery extends Component {
const inputValues = inputs.map(input => values[input.name]);
contract
.instance[name]
.instance[signature]
.call({}, inputValues)
.then(results => {
if (outputs.length === 1) {

View File

@@ -56,18 +56,10 @@
}
.methodResults {
display: flex;
flex-wrap: wrap;
max-width: 24rem;
justify-content: space-around;
}
.methodResults > div {
padding: 0.25rem 0.5rem;
display: flex;
flex-direction: column;
align-items: center;
flex: 1 1 50%;
box-sizing: border-box;
& > div {

View File

@@ -19,7 +19,7 @@ import React, { Component, PropTypes } from 'react';
import { Card, CardTitle, CardText } from 'material-ui/Card';
import InputQuery from './inputQuery';
import { Container, ContainerTitle, Input } from '../../../ui';
import { Container, ContainerTitle, Input, InputAddress } from '../../../ui';
import styles from './queries.css';
@@ -70,7 +70,7 @@ export default class Queries extends Component {
}
renderInputQuery (fn) {
const { abi, name } = fn;
const { abi, name, signature } = fn;
const { contract } = this.props;
return (
@@ -80,6 +80,7 @@ export default class Queries extends Component {
inputs={ abi.inputs }
outputs={ abi.outputs }
name={ name }
signature={ signature }
contract={ contract }
/>
</div>
@@ -99,14 +100,14 @@ export default class Queries extends Component {
<CardText
className={ styles.methodContent }
>
{ this.renderValue(values[fn.name]) }
{ this.renderValue(values[fn.name], fn.outputs[0].kind.type) }
</CardText>
</Card>
</div>
);
}
renderValue (value) {
renderValue (value, type) {
if (typeof value === 'undefined') {
return null;
}
@@ -124,6 +125,16 @@ export default class Queries extends Component {
valueToDisplay = value.toString();
}
if (type === 'address') {
return (
<InputAddress
className={ styles.queryValue }
value={ valueToDisplay }
disabled
/>
);
}
return (
<Input
className={ styles.queryValue }

View File

@@ -82,6 +82,7 @@ class Contracts extends Component {
return (
<ActionbarSort
key='sortAccounts'
id='sortContracts'
order={ this.state.sortOrder }
metas={ [
{ key: 'timestamp', label: 'date' }

View File

@@ -17,6 +17,9 @@
.acc {
text-align: center;
display: inline-block;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.acc > * {
@@ -35,10 +38,13 @@
}
.name {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
display: lock;
display: block;
vertical-align: middle;
text-transform: uppercase;
span {
text-overflow: ellipsis;
overflow: hidden;
}
}

View File

@@ -38,7 +38,9 @@
}
.transactionDetails {
margin-right: 321px;
padding-right: 321px;
width: 100%;
box-sizing: border-box;
}
.isConfirmed {

View File

@@ -19,7 +19,9 @@
}
.transactionDetails {
margin-right: 321px;
padding-right: 321px;
width: 100%;
box-sizing: border-box;
}
.mainContainer {

View File

@@ -15,6 +15,7 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.signer {
width: 916px;
}
.pending {

View File

@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
const HappyPack = require('happypack');
const webpack = require('webpack');
const ENV = process.env.NODE_ENV || 'development';
@@ -22,10 +23,26 @@ const DEST = process.env.BUILD_DEST || '.build';
let modules = [
'babel-polyfill',
'browserify-aes', 'ethereumjs-tx', 'scryptsy',
'react', 'react-dom', 'react-redux', 'react-router',
'redux', 'redux-thunk', 'react-router-redux',
'lodash', 'material-ui', 'moment', 'blockies'
'bignumber.js',
'blockies',
'brace',
'browserify-aes',
'chart.js',
'ethereumjs-tx',
'lodash',
'material-ui',
'mobx',
'mobx-react',
'moment',
'react',
'react-dom',
'react-redux',
'react-router',
'react-router-redux',
'recharts',
'redux',
'redux-thunk',
'scryptsy'
];
if (!isProd) {
@@ -44,6 +61,11 @@ module.exports = {
{
test: /\.json$/,
loaders: ['json']
},
{
test: /\.js$/,
include: /(ethereumjs-tx)/,
loaders: [ 'happypack/loader?id=js' ]
}
]
},
@@ -63,6 +85,12 @@ module.exports = {
'process.env': {
NODE_ENV: JSON.stringify(ENV)
}
}),
new HappyPack({
id: 'js',
threads: 4,
loaders: ['babel']
})
];