Merge branch 'master' into jg-signer-api-queries-2

This commit is contained in:
Jaco Greeff
2016-11-22 09:48:32 +01:00
89 changed files with 3078 additions and 385 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "0.2.53",
"version": "0.2.58",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",
@@ -102,9 +102,10 @@
"postcss-nested": "^1.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.2",
"react-copy-to-clipboard": "^4.2.3",
"react-hot-loader": "^1.3.0",
"react-dom": "~15.3.2",
"react-hot-loader": "~1.3.0",
"rucksack-css": "^0.8.6",
"sinon": "^1.17.4",
"sinon-as-promised": "^4.0.2",
@@ -114,7 +115,7 @@
"webpack": "^1.13.2",
"webpack-dev-server": "^1.15.2",
"webpack-error-notification": "0.1.6",
"webpack-hot-middleware": "^2.7.1",
"webpack-hot-middleware": "~2.13.2",
"websocket": "^1.0.23"
},
"dependencies": {
@@ -134,7 +135,7 @@
"js-sha3": "^0.5.2",
"lodash": "^4.11.1",
"marked": "^0.3.6",
"material-ui": "^0.16.1",
"material-ui": "0.16.1",
"material-ui-chip-input": "^0.8.0",
"mobx": "^2.6.1",
"mobx-react": "^3.5.8",
@@ -142,16 +143,16 @@
"moment": "^2.14.1",
"phoneformat.js": "^1.0.3",
"qs": "^6.3.0",
"react": "^15.2.1",
"react": "~15.3.2",
"react-ace": "^4.0.0",
"react-addons-css-transition-group": "^15.2.1",
"react-addons-css-transition-group": "~15.3.2",
"react-chartjs-2": "^1.5.0",
"react-dom": "^15.2.1",
"react-dom": "~15.3.2",
"react-dropzone": "^3.7.3",
"react-redux": "^4.4.5",
"react-router": "^2.6.1",
"react-router-redux": "^4.0.5",
"react-tap-event-plugin": "^1.0.0",
"react-tap-event-plugin": "~1.0.0",
"react-tooltip": "^2.0.3",
"recharts": "^0.15.2",
"redux": "^3.5.2",

View File

@@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { inAddress, inData, inHex, inNumber16, inOptions } from '../../format/input';
import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers } from '../../format/output';
import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers, outTransaction } from '../../format/output';
export default class Parity {
constructor (transport) {
@@ -117,16 +117,29 @@ export default class Parity {
.execute('parity_hashContent', url);
}
importGethAccounts (accounts) {
return this._transport
.execute('parity_importGethAccounts', (accounts || []).map(inAddress))
.then((accounts) => (accounts || []).map(outAddress));
}
listGethAccounts () {
return this._transport
.execute('parity_listGethAccounts')
.then((accounts) => (accounts || []).map(outAddress));
}
importGethAccounts (accounts) {
localTransactions () {
return this._transport
.execute('parity_importGethAccounts', (accounts || []).map(inAddress))
.then((accounts) => (accounts || []).map(outAddress));
.execute('parity_localTransactions')
.then(transactions => {
Object.values(transactions)
.filter(tx => tx.transaction)
.map(tx => {
tx.transaction = outTransaction(tx.transaction);
});
return transactions;
});
}
minGasPrice () {
@@ -192,6 +205,17 @@ export default class Parity {
.execute('parity_nodeName');
}
pendingTransactions () {
return this._transport
.execute('parity_pendingTransactions')
.then(data => data.map(outTransaction));
}
pendingTransactionsStats () {
return this._transport
.execute('parity_pendingTransactionsStats');
}
phraseToAddress (phrase) {
return this._transport
.execute('parity_phraseToAddress', phrase)

View File

@@ -105,7 +105,7 @@ export function attachInstances () {
])
.then(([registryAddress, netChain]) => {
const registry = api.newContract(abis.registry, registryAddress).instance;
isTest = netChain === 'morden' || netChain === 'testnet';
isTest = ['morden', 'ropsten', 'testnet'].includes(netChain);
console.log(`contract was found at registry=${registryAddress}`);
console.log(`running on ${netChain}, isTest=${isTest}`);

View File

@@ -15,12 +15,17 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.container {
.body {
text-align: center;
background: #333;
color: #fff;
}
.container {
font-family: 'Roboto';
vertical-align: middle;
padding: 4em 0;
text-align: center;
margin: 0 0 2em 0;
}
.form {
@@ -98,7 +103,7 @@
color: #333;
background: #eee;
border: none;
border-radius: 5px;
border-radius: 0.5em;
width: 100%;
font-size: 1em;
text-align: center;
@@ -113,20 +118,29 @@
}
.hashError, .hashWarning, .hashOk {
padding-top: 0.5em;
margin: 0.5em 0;
text-align: center;
padding: 1em 0;
border: 0.25em solid #333;
border-radius: 0.5em;
}
.hashError {
border-color: #f66;
color: #f66;
background: rgba(255, 102, 102, 0.25);
}
.hashWarning {
border-color: #f80;
color: #f80;
background: rgba(255, 236, 0, 0.25);
}
.hashOk {
opacity: 0.5;
border-color: #6f6;
color: #6f6;
background: rgba(102, 255, 102, 0.25);
}
.typeButtons {

View File

@@ -19,6 +19,7 @@ import React, { Component } from 'react';
import { api } from '../parity';
import { attachInterface } from '../services';
import Button from '../Button';
import Events from '../Events';
import IdentityIcon from '../IdentityIcon';
import Loading from '../Loading';
@@ -27,6 +28,8 @@ import styles from './application.css';
const INVALID_URL_HASH = '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
let nextEventId = 0;
export default class Application extends Component {
state = {
fromAddress: null,
@@ -43,7 +46,9 @@ export default class Application extends Component {
registerState: '',
registerType: 'file',
repo: '',
repoError: null
repoError: null,
events: {},
eventIds: []
}
componentDidMount () {
@@ -75,7 +80,7 @@ export default class Application extends Component {
let hashClass = null;
if (contentHashError) {
hashClass = contentHashOwner !== fromAddress ? styles.hashError : styles.hashWarning;
} else {
} else if (contentHash) {
hashClass = styles.hashOk;
}
@@ -116,29 +121,34 @@ export default class Application extends Component {
}
return (
<div className={ styles.container }>
<div className={ styles.form }>
<div className={ styles.typeButtons }>
<Button
disabled={ registerBusy }
invert={ registerType !== 'file' }
onClick={ this.onClickTypeNormal }>File Link</Button>
<Button
disabled={ registerBusy }
invert={ registerType !== 'content' }
onClick={ this.onClickTypeContent }>Content Bundle</Button>
</div>
<div className={ styles.box }>
<div className={ styles.description }>
Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc.
<div className={ styles.body }>
<div className={ styles.container }>
<div className={ styles.form }>
<div className={ styles.typeButtons }>
<Button
disabled={ registerBusy }
invert={ registerType !== 'file' }
onClick={ this.onClickTypeNormal }>File Link</Button>
<Button
disabled={ registerBusy }
invert={ registerType !== 'content' }
onClick={ this.onClickTypeContent }>Content Bundle</Button>
</div>
{ valueInputs }
<div className={ hashClass }>
{ contentHashError || contentHash }
<div className={ styles.box }>
<div className={ styles.description }>
Provide a valid URL to register. The content information can be used in other contracts that allows for reverse lookups, e.g. image registries, dapp registries, etc.
</div>
{ valueInputs }
<div className={ hashClass }>
{ contentHashError || contentHash }
</div>
{ registerBusy ? this.renderProgress() : this.renderButtons() }
</div>
{ registerBusy ? this.renderProgress() : this.renderButtons() }
</div>
</div>
<Events
eventIds={ this.state.eventIds }
events={ this.state.events } />
</div>
);
}
@@ -285,15 +295,29 @@ export default class Application extends Component {
}
}
trackRequest (promise) {
trackRequest (eventId, promise) {
return promise
.then((signerRequestId) => {
this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' });
this.setState({
events: Object.assign({}, this.state.events, {
[eventId]: Object.assign({}, this.state.events[eventId], {
signerRequestId,
registerState: 'Transaction posted, Waiting for transaction authorization'
})
})
});
return api.pollMethod('parity_checkRequest', signerRequestId);
})
.then((txHash) => {
this.setState({ txHash, registerState: 'Transaction authorized, Waiting for network confirmations' });
this.setState({
events: Object.assign({}, this.state.events, {
[eventId]: Object.assign({}, this.state.events[eventId], {
txHash,
registerState: 'Transaction authorized, Waiting for network confirmations'
})
})
});
return api.pollMethod('eth_getTransactionReceipt', txHash, (receipt) => {
if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) {
@@ -304,27 +328,72 @@ export default class Application extends Component {
});
})
.then((txReceipt) => {
this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', commit: '', repo: '', commitError: null, contentHash: '', contentHashOwner: null, contentHashError: null });
this.setState({
events: Object.assign({}, this.state.events, {
[eventId]: Object.assign({}, this.state.events[eventId], {
txReceipt,
registerBusy: false,
registerState: 'Network confirmed, Received transaction receipt'
})
})
});
})
.catch((error) => {
console.error('onSend', error);
this.setState({ registerError: error.message });
this.setState({
events: Object.assign({}, this.state.events, {
[eventId]: Object.assign({}, this.state.events[eventId], {
registerState: error.message,
registerError: true,
registerBusy: false
})
})
});
});
}
registerContent (repo, commit) {
registerContent (contentRepo, contentCommit) {
const { contentHash, fromAddress, instance } = this.state;
contentCommit = contentCommit.substr(0, 2) === '0x' ? contentCommit : `0x${contentCommit}`;
this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' });
const values = [contentHash, repo, commit.substr(0, 2) === '0x' ? commit : `0x${commit}`];
const eventId = nextEventId++;
const values = [contentHash, contentRepo, contentCommit];
const options = { from: fromAddress };
this.setState({
eventIds: [eventId].concat(this.state.eventIds),
events: Object.assign({}, this.state.events, {
[eventId]: {
contentHash,
contentRepo,
contentCommit,
fromAddress,
registerBusy: true,
registerState: 'Estimating gas for the transaction',
timestamp: new Date()
}
}),
url: '',
commit: '',
repo: '',
commitError: null,
contentHash: '',
contentHashOwner: null,
contentHashError: null
});
this.trackRequest(
instance
eventId, instance
.hint.estimateGas(options, values)
.then((gas) => {
this.setState({ registerState: 'Gas estimated, Posting transaction to the network' });
this.setState({
events: Object.assign({}, this.state.events, {
[eventId]: Object.assign({}, this.state.events[eventId], {
registerState: 'Gas estimated, Posting transaction to the network'
})
})
});
const gasPassed = gas.mul(1.2);
options.gas = gasPassed.toFixed(0);
@@ -335,19 +404,45 @@ export default class Application extends Component {
);
}
registerUrl (url) {
registerUrl (contentUrl) {
const { contentHash, fromAddress, instance } = this.state;
this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' });
const values = [contentHash, url];
const eventId = nextEventId++;
const values = [contentHash, contentUrl];
const options = { from: fromAddress };
this.setState({
eventIds: [eventId].concat(this.state.eventIds),
events: Object.assign({}, this.state.events, {
[eventId]: {
contentHash,
contentUrl,
fromAddress,
registerBusy: true,
registerState: 'Estimating gas for the transaction',
timestamp: new Date()
}
}),
url: '',
commit: '',
repo: '',
commitError: null,
contentHash: '',
contentHashOwner: null,
contentHashError: null
});
this.trackRequest(
instance
eventId, instance
.hintURL.estimateGas(options, values)
.then((gas) => {
this.setState({ registerState: 'Gas estimated, Posting transaction to the network' });
this.setState({
events: Object.assign({}, this.state.events, {
[eventId]: Object.assign({}, this.state.events[eventId], {
registerState: 'Gas estimated, Posting transaction to the network'
})
})
});
const gasPassed = gas.mul(1.2);
options.gas = gasPassed.toFixed(0);

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/>.
*/
.list {
border: none;
margin: 0 auto;
text-align: left;
vertical-align: top;
tr {
&[data-busy="true"] {
opacity: 0.5;
}
&[data-error="true"] {
color: #f66;
}
}
td {
padding: 0.5em;
}
}

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/>.
import React, { Component, PropTypes } from 'react';
import moment from 'moment';
import styles from './events.css';
export default class Events extends Component {
static propTypes = {
eventIds: PropTypes.array.isRequired,
events: PropTypes.array.isRequired
}
render () {
return (
<table className={ styles.list }>
<tbody>
{ this.props.eventIds.map((id) => this.renderEvent(id, this.props.events[id])) }
</tbody>
</table>
);
}
renderEvent = (eventId, event) => {
return (
<tr key={ `event_${eventId}` } data-busy={ event.registerBusy } data-error={ event.registerError }>
<td>
<div>{ moment(event.timestamp).fromNow() }</div>
<div>{ event.registerState }</div>
</td>
<td>
<div>{ event.contentUrl || `${event.contentRepo}/${event.contentCommit}` }</div>
<div>{ event.contentHash }</div>
</td>
</tr>
);
}
}

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 './events';

17
js/src/dapps/localtx.html Normal file
View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="icon" href="/parity-logo-black-no-text.png" type="image/png">
<title>Local transactions Viewer</title>
</head>
<body>
<div id="container"></div>
<script src="vendor.js"></script>
<script src="commons.js"></script>
<script src="/parity-utils/parity.js"></script>
<script src="localtx.js"></script>
</body>
</html>

33
js/src/dapps/localtx.js Normal file
View File

@@ -0,0 +1,33 @@
// 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 ReactDOM from 'react-dom';
import React from 'react';
import injectTapEventPlugin from 'react-tap-event-plugin';
injectTapEventPlugin();
import Application from './localtx/Application';
import '../../assets/fonts/Roboto/font.css';
import '../../assets/fonts/RobotoMono/font.css';
import './style.css';
import './localtx.html';
ReactDOM.render(
<Application />,
document.querySelector('#container')
);

View File

@@ -0,0 +1,19 @@
.container {
padding: 1rem 2rem;
text-align: center;
h1 {
margin-top: 3rem;
margin-bottom: 1rem;
}
table {
text-align: left;
margin: auto;
max-width: 90vw;
th {
text-align: center;
}
}
}

View File

@@ -0,0 +1,203 @@
// 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 BigNumber from 'bignumber.js';
import React, { Component } from 'react';
import { api } from '../parity';
import styles from './application.css';
import { Transaction, LocalTransaction } from '../Transaction';
export default class Application extends Component {
state = {
loading: true,
transactions: [],
localTransactions: {},
blockNumber: 0
}
componentDidMount () {
const poll = () => this.fetchTransactionData().then(poll).catch(poll);
this._timeout = setTimeout(poll, 2000);
}
componentWillUnmount () {
clearTimeout(this._timeout);
}
fetchTransactionData () {
return Promise.all([
api.parity.pendingTransactions(),
api.parity.pendingTransactionsStats(),
api.parity.localTransactions(),
api.eth.blockNumber()
]).then(([pending, stats, local, blockNumber]) => {
// Combine results together
const transactions = pending.map(tx => {
return {
transaction: tx,
stats: stats[tx.hash],
isLocal: !!local[tx.hash]
};
});
// Add transaction data to locals
transactions
.filter(tx => tx.isLocal)
.map(data => {
const tx = data.transaction;
local[tx.hash].transaction = tx;
local[tx.hash].stats = data.stats;
});
// Convert local transactions to array
const localTransactions = Object.keys(local).map(hash => {
const data = local[hash];
data.txHash = hash;
return data;
});
// Sort local transactions by nonce (move future to the end)
localTransactions.sort((a, b) => {
a = a.transaction || {};
b = b.transaction || {};
if (a.from && b.from && a.from !== b.from) {
return a.from < b.from;
}
if (!a.nonce || !b.nonce) {
return !a.nonce ? 1 : -1;
}
return new BigNumber(a.nonce).comparedTo(new BigNumber(b.nonce));
});
this.setState({
loading: false,
transactions,
localTransactions,
blockNumber
});
});
}
render () {
const { loading } = this.state;
if (loading) {
return (
<div className={ styles.container }>Loading...</div>
);
}
return (
<div className={ styles.container }>
<h1>Your local transactions</h1>
{ this.renderLocals() }
<h1>Transactions in the queue</h1>
{ this.renderQueueSummary() }
{ this.renderQueue() }
</div>
);
}
renderQueueSummary () {
const { transactions } = this.state;
if (!transactions.length) {
return null;
}
const count = transactions.length;
const locals = transactions.filter(tx => tx.isLocal).length;
const fee = transactions
.map(tx => tx.transaction)
.map(tx => tx.gasPrice.mul(tx.gas))
.reduce((sum, fee) => sum.add(fee), new BigNumber(0));
return (
<h3>
Count: <strong>{ locals ? `${count} (${locals})` : count }</strong>
&nbsp;
Total Fee: <strong>{ api.util.fromWei(fee).toFixed(3) } ETH</strong>
</h3>
);
}
renderQueue () {
const { blockNumber, transactions } = this.state;
if (!transactions.length) {
return (
<h3>The queue seems is empty.</h3>
);
}
return (
<table cellSpacing='0'>
<thead>
{ Transaction.renderHeader() }
</thead>
<tbody>
{
transactions.map((tx, idx) => (
<Transaction
key={ tx.transaction.hash }
idx={ idx + 1 }
isLocal={ tx.isLocal }
transaction={ tx.transaction }
stats={ tx.stats }
blockNumber={ blockNumber }
/>
))
}
</tbody>
</table>
);
}
renderLocals () {
const { localTransactions } = this.state;
if (!localTransactions.length) {
return (
<h3>You haven't sent any transactions yet.</h3>
);
}
return (
<table cellSpacing='0'>
<thead>
{ LocalTransaction.renderHeader() }
</thead>
<tbody>
{
localTransactions.map(tx => (
<LocalTransaction
key={ tx.txHash }
hash={ tx.txHash }
transaction={ tx.transaction }
status={ tx.status }
stats={ tx.stats }
details={ tx }
/>
))
}
</tbody>
</table>
);
}
}

View File

@@ -0,0 +1,32 @@
// 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 from 'react';
import { shallow } from 'enzyme';
import '../../../environment/tests';
import Application from './application';
describe('localtx/Application', () => {
describe('rendering', () => {
it('renders without crashing', () => {
const rendered = shallow(<Application />);
expect(rendered).to.be.defined;
});
});
});

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 './application';

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 { Transaction, LocalTransaction } from './transaction';

View File

@@ -0,0 +1,31 @@
.from {
white-space: nowrap;
img {
vertical-align: middle;
}
}
.transaction {
td {
padding: 7px 15px;
}
td:first-child {
padding: 7px 0;
}
&.local {
background: #8bc34a;
}
}
.nowrap {
white-space: nowrap;
}
.edit {
label, input {
display: block;
}
}

View File

@@ -0,0 +1,382 @@
// 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 BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import classnames from 'classnames';
import { api } from '../parity';
import styles from './transaction.css';
import IdentityIcon from '../../githubhint/IdentityIcon';
class BaseTransaction extends Component {
shortHash (hash) {
return `${hash.substr(0, 5)}..${hash.substr(hash.length - 3)}`;
}
renderHash (hash) {
return (
<code title={ hash }>
{ this.shortHash(hash) }
</code>
);
}
renderFrom (transaction) {
if (!transaction) {
return '-';
}
return (
<div title={ transaction.from } className={ styles.from }>
<IdentityIcon
address={ transaction.from }
/>
0x{ transaction.nonce.toString(16) }
</div>
);
}
renderGasPrice (transaction) {
if (!transaction) {
return '-';
}
return (
<span title={ `${transaction.gasPrice.toFormat(0)} wei` }>
{ api.util.fromWei(transaction.gasPrice, 'shannon').toFormat(2) }&nbsp;shannon
</span>
);
}
renderGas (transaction) {
if (!transaction) {
return '-';
}
return (
<span title={ `${transaction.gas.toFormat(0)} Gas` }>
{ transaction.gas.div(10 ** 6).toFormat(3) }&nbsp;MGas
</span>
);
}
renderPropagation (stats) {
const noOfPeers = Object.keys(stats.propagatedTo).length;
const noOfPropagations = Object.values(stats.propagatedTo).reduce((sum, val) => sum + val, 0);
return (
<span className={ styles.nowrap }>
{ noOfPropagations } ({ noOfPeers } peers)
</span>
);
}
}
export class Transaction extends BaseTransaction {
static propTypes = {
idx: PropTypes.number.isRequired,
transaction: PropTypes.object.isRequired,
blockNumber: PropTypes.object.isRequired,
isLocal: PropTypes.bool,
stats: PropTypes.object
};
static defaultProps = {
isLocal: false,
stats: {
firstSeen: 0,
propagatedTo: {}
}
};
static renderHeader () {
return (
<tr className={ styles.header }>
<th></th>
<th>
Transaction
</th>
<th>
From
</th>
<th>
Gas Price
</th>
<th>
Gas
</th>
<th>
First propagation
</th>
<th>
# Propagated
</th>
<th>
</th>
</tr>
);
}
render () {
const { isLocal, stats, transaction, idx } = this.props;
const blockNo = new BigNumber(stats.firstSeen);
const clazz = classnames(styles.transaction, {
[styles.local]: isLocal
});
return (
<tr className={ clazz }>
<td>
{ idx }.
</td>
<td>
{ this.renderHash(transaction.hash) }
</td>
<td>
{ this.renderFrom(transaction) }
</td>
<td>
{ this.renderGasPrice(transaction) }
</td>
<td>
{ this.renderGas(transaction) }
</td>
<td title={ blockNo.toFormat(0) }>
{ this.renderTime(stats.firstSeen) }
</td>
<td>
{ this.renderPropagation(stats) }
</td>
</tr>
);
}
renderTime (firstSeen) {
const { blockNumber } = this.props;
if (!firstSeen) {
return 'never';
}
const timeInMinutes = blockNumber.sub(firstSeen).mul(14).div(60).toFormat(1);
return `${timeInMinutes} minutes ago`;
}
}
export class LocalTransaction extends BaseTransaction {
static propTypes = {
hash: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
transaction: PropTypes.object,
isLocal: PropTypes.bool,
stats: PropTypes.object,
details: PropTypes.object
};
static defaultProps = {
stats: {
propagatedTo: {}
}
};
static renderHeader () {
return (
<tr className={ styles.header }>
<th></th>
<th>
Transaction
</th>
<th>
From
</th>
<th>
Gas Price / Gas
</th>
<th>
Status
</th>
</tr>
);
}
state = {
isSending: false,
isResubmitting: false,
gasPrice: null,
gas: null
};
toggleResubmit = () => {
const { transaction } = this.props;
const { isResubmitting, gasPrice } = this.state;
this.setState({
isResubmitting: !isResubmitting
});
if (gasPrice === null) {
this.setState({
gasPrice: `0x${transaction.gasPrice.toString(16)}`,
gas: `0x${transaction.gas.toString(16)}`
});
}
};
setGasPrice = el => {
this.setState({
gasPrice: el.target.value
});
};
setGas = el => {
this.setState({
gas: el.target.value
});
};
sendTransaction = () => {
const { transaction } = this.props;
const { gasPrice, gas } = this.state;
const newTransaction = {
from: transaction.from,
to: transaction.to,
nonce: transaction.nonce,
value: transaction.value,
data: transaction.data,
gasPrice, gas
};
this.setState({
isResubmitting: false,
isSending: true
});
const closeSending = () => this.setState({
isSending: false,
gasPrice: null,
gas: null
});
api.eth.sendTransaction(newTransaction)
.then(closeSending)
.catch(closeSending);
};
render () {
if (this.state.isResubmitting) {
return this.renderResubmit();
}
const { stats, transaction, hash, status } = this.props;
const { isSending } = this.state;
const resubmit = isSending ? (
'sending...'
) : (
<a href='javascript:void' onClick={ this.toggleResubmit }>
resubmit
</a>
);
return (
<tr className={ styles.transaction }>
<td>
{ !transaction ? null : resubmit }
</td>
<td>
{ this.renderHash(hash) }
</td>
<td>
{ this.renderFrom(transaction) }
</td>
<td>
{ this.renderGasPrice(transaction) }
<br />
{ this.renderGas(transaction) }
</td>
<td>
{ this.renderStatus() }
<br />
{ status === 'pending' ? this.renderPropagation(stats) : null }
</td>
</tr>
);
}
renderStatus () {
const { details } = this.props;
let state = {
'pending': () => 'In queue: Pending',
'future': () => 'In queue: Future',
'mined': () => 'Mined',
'dropped': () => 'Dropped because of queue limit',
'invalid': () => 'Transaction is invalid',
'rejected': () => `Rejected: ${details.error}`,
'replaced': () => `Replaced by ${this.shortHash(details.hash)}`
}[this.props.status];
return state ? state() : 'unknown';
}
// TODO [ToDr] Gas Price / Gas selection is not needed
// when signer supports gasPrice/gas tunning.
renderResubmit () {
const { transaction } = this.props;
const { gasPrice, gas } = this.state;
return (
<tr className={ styles.transaction }>
<td>
<a href='javascript:void' onClick={ this.toggleResubmit }>
cancel
</a>
</td>
<td>
{ this.renderHash(transaction.hash) }
</td>
<td>
{ this.renderFrom(transaction) }
</td>
<td className={ styles.edit }>
<input
type='text'
value={ gasPrice }
onChange={ this.setGasPrice }
/>
<input
type='text'
value={ gas }
onChange={ this.setGas }
/>
</td>
<td colSpan='2'>
<a href='javascript:void' onClick={ this.sendTransaction }>
Send
</a>
</td>
</tr>
);
}
}

View File

@@ -0,0 +1,67 @@
// 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 from 'react';
import { shallow } from 'enzyme';
import '../../../environment/tests';
import EthApi from '../../../api';
// Mock API for tests
import * as Api from '../parity';
Api.api = {
util: EthApi.prototype.util
};
import BigNumber from 'bignumber.js';
import { Transaction, LocalTransaction } from './transaction';
describe('localtx/Transaction', () => {
describe('rendering', () => {
it('renders without crashing', () => {
const transaction = {
hash: '0x1234567890',
nonce: 15,
gasPrice: new BigNumber(10),
gas: new BigNumber(10)
};
const rendered = shallow(
<Transaction
isLocal={ false }
transaction={ transaction }
blockNumber={ new BigNumber(0) }
/>
);
expect(rendered).to.be.defined;
});
});
});
describe('localtx/LocalTransaction', () => {
describe('rendering', () => {
it('renders without crashing', () => {
const rendered = shallow(
<LocalTransaction
hash={ '0x1234567890' }
status={ 'pending' }
/>
);
expect(rendered).to.be.defined;
});
});
});

View File

@@ -0,0 +1,21 @@
// 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/>.
const api = window.parent.secureApi;
export {
api
};

View File

@@ -14,7 +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/>.
import { sha3, toWei } from '../parity.js';
import { sha3 } from '../parity.js';
const alreadyQueued = (queue, action, name) =>
!!queue.find((entry) => entry.action === action && entry.name === name);
@@ -29,6 +29,8 @@ export const reserve = (name) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
const fee = state.fee;
if (!contract || !account) return;
if (alreadyQueued(state.names.queue, 'reserve', name)) return;
const reserve = contract.functions.find((f) => f.name === 'reserve');
@@ -36,7 +38,7 @@ export const reserve = (name) => (dispatch, getState) => {
name = name.toLowerCase();
const options = {
from: account.address,
value: toWei(1).toString()
value: fee
};
const values = [ sha3(name) ];

View File

@@ -224,15 +224,6 @@ export default {
}
},
listGethAccounts: {
desc: 'Returns a list of the accounts available from Geth',
params: [],
returns: {
type: Array,
desc: '20 Bytes addresses owned by the client.'
}
},
importGethAccounts: {
desc: 'Imports a list of accounts from geth',
params: [
@@ -247,6 +238,24 @@ export default {
}
},
listGethAccounts: {
desc: 'Returns a list of the accounts available from Geth',
params: [],
returns: {
type: Array,
desc: '20 Bytes addresses owned by the client.'
}
},
localTransactions: {
desc: 'Returns an object of current and past local transactions.',
params: [],
returns: {
type: Object,
desc: 'Mapping of `tx hash` into status object.'
}
},
minGasPrice: {
desc: 'Returns currently set minimal gas price',
params: [],
@@ -379,6 +388,24 @@ export default {
}
},
pendingTransactions: {
desc: 'Returns a list of transactions currently in the queue.',
params: [],
returns: {
type: Array,
desc: 'Transactions ordered by priority'
}
},
pendingTransactionsStats: {
desc: 'Returns propagation stats for transactions in the queue',
params: [],
returns: {
type: Object,
desc: 'mapping of `tx hash` into `stats`'
}
},
phraseToAddress: {
desc: 'Converts a secret phrase into the corresponting address',
params: [

View File

@@ -103,7 +103,7 @@ export default class DetailsStep extends Component {
label='contract name'
hint='a name for the deployed contract'
error={ nameError }
value={ name }
value={ name || '' }
onChange={ this.onNameChange } />
<Input
@@ -169,6 +169,10 @@ export default class DetailsStep extends Component {
const contractName = Object.keys(contracts)[index];
const contract = contracts[contractName];
if (!this.props.name || this.props.name.trim() === '') {
this.onNameChange(null, contractName);
}
const { abi, bin } = contract;
const code = /^0x/.test(bin) ? bin : `0x${bin}`;

View File

@@ -251,7 +251,7 @@ export default class Status {
.then(([
clientVersion, defaultExtraData, netChain, netPort, rpcSettings, enode
]) => {
const isTest = netChain === 'morden' || netChain === 'testnet';
const isTest = netChain === 'morden' || netChain === 'ropsten' || netChain === 'testnet';
const longStatus = {
clientVersion,

View File

@@ -31,7 +31,7 @@ const initialState = {
gasLimit: new BigNumber(0),
hashrate: new BigNumber(0),
minGasPrice: new BigNumber(0),
netChain: 'morden',
netChain: 'ropsten',
netPeers: {
active: new BigNumber(0),
connected: new BigNumber(0),

View File

@@ -19,5 +19,5 @@
}
.layout>div {
padding-bottom: 0.25em;
padding-bottom: 0.75em;
}

View File

@@ -37,7 +37,7 @@ export function validateAbi (abi, api) {
try {
abiParsed = JSON.parse(abi);
if (!api.util.isArray(abiParsed) || !abiParsed.length) {
if (!api.util.isArray(abiParsed)) {
abiError = ERRORS.invalidAbi;
return { abi, abiError, abiParsed };
}

View File

@@ -18,3 +18,22 @@
.description {
margin-top: .5em !important;
}
.list {
.background {
background: rgba(255, 255, 255, 0.2);
margin: 0 -1.5em;
padding: 0.5em 1.5em;
}
.header {
text-transform: uppercase;
}
.byline {
font-size: 0.75em;
padding-top: 0.5em;
line-height: 1.5em;
opacity: 0.75;
}
}

View File

@@ -51,16 +51,37 @@ export default class AddDapps extends Component {
] }
visible
scroll>
<List>
{ store.apps.map(this.renderApp) }
</List>
<div className={ styles.warning }>
</div>
{ this.renderList(store.sortedLocal, 'Applications locally available', 'All applications installed locally on the machine by the user for access by the Parity client.') }
{ this.renderList(store.sortedBuiltin, 'Applications bundled with Parity', 'Experimental applications developed by the Parity team to show off dapp capabilities, integration, experimental features and to control certain network-wide client behaviour.') }
{ this.renderList(store.sortedNetwork, 'Applications on the global network', 'These applications are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each application before interacting.') }
</Modal>
);
}
renderList (items, header, byline) {
if (!items || !items.length) {
return null;
}
return (
<div className={ styles.list }>
<div className={ styles.background }>
<div className={ styles.header }>{ header }</div>
<div className={ styles.byline }>{ byline }</div>
</div>
<List>
{ items.map(this.renderApp) }
</List>
</div>
);
}
renderApp = (app) => {
const { store } = this.props;
const isHidden = store.hidden.includes(app.id);
const isHidden = !store.displayApps[app.id].visible;
const onCheck = () => {
if (isHidden) {
store.showApp(app.id);

View File

@@ -17,7 +17,7 @@
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { Container, ContainerTitle } from '../../../ui';
import { Container, ContainerTitle, Tags } from '../../../ui';
import styles from './summary.css';
@@ -49,6 +49,7 @@ export default class Summary extends Component {
return (
<Container className={ styles.container }>
{ image }
<Tags tags={ [app.type] } />
<div className={ styles.description }>
<ContainerTitle
className={ styles.title }

View File

@@ -5,7 +5,8 @@
"name": "Token Deployment",
"description": "Deploy new basic tokens that you are able to send around",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
"version": "1.0.0",
"visible": true
},
{
"id": "0xd1adaede68d344519025e2ff574650cd99d3830fe6d274c7a7843cdc00e17938",
@@ -13,7 +14,8 @@
"name": "Registry",
"description": "A global registry of addresses on the network",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
"version": "1.0.0",
"visible": true
},
{
"id": "0x0a8048117e51e964628d0f2d26342b3cd915248b59bcce2721e1d05f5cfa2208",
@@ -21,7 +23,8 @@
"name": "Token Registry",
"description": "A registry of transactable tokens on the network",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
"version": "1.0.0",
"visible": true
},
{
"id": "0xf49089046f53f5d2e5f3513c1c32f5ff57d986e46309a42d2b249070e4e72c46",
@@ -29,7 +32,8 @@
"name": "Method Registry",
"description": "A registry of method signatures for lookups on transactions",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0"
"version": "1.0.0",
"visible": true
},
{
"id": "0x058740ee9a5a3fb9f1cfa10752baec87e09cc45cd7027fd54708271aca300c75",
@@ -38,6 +42,16 @@
"description": "A mapping of GitHub URLs to hashes for use in contracts as references",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0",
"visible": false,
"secure": true
},
{
"id": "0xae74ad174b95cdbd01c88ac5b73a296d33e9088fc2a200e76bcedf3a94a7815d",
"url": "localtx",
"name": "TxQueue Viewer",
"description": "Have a peak on internals of transaction queue of your node.",
"author": "Parity Team <admin@ethcore.io>",
"version": "1.0.0",
"secure": true
}
]

View File

@@ -18,6 +18,7 @@
display: flex;
flex-wrap: wrap;
margin: -0.125em;
position: relative;
}
.list+.list {
@@ -29,3 +30,25 @@
flex: 0 1 50%;
box-sizing: border-box;
}
.overlay {
background: rgba(0, 0, 0, 0.85);
bottom: 0.5em;
left: -0.125em;
position: absolute;
right: -0.125em;
top: -0.25em;
z-index: 100;
padding: 1em;
.body {
line-height: 1.5em;
margin: 0 auto;
text-align: left;
max-width: 980px;
&>div:first-child {
padding-bottom: 1em;
}
}
}

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Checkbox } from 'material-ui';
import { observer } from 'mobx-react';
import { Actionbar, Page } from '../../ui';
@@ -37,6 +38,24 @@ export default class Dapps extends Component {
store = new DappsStore(this.context.api);
render () {
let externalOverlay = null;
if (this.store.externalOverlayVisible) {
externalOverlay = (
<div className={ styles.overlay }>
<div className={ styles.body }>
<div>Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.</div>
<div>
<Checkbox
className={ styles.accept }
label='I understand that these applications are not affiliated with Parity'
checked={ false }
onCheck={ this.onClickAcceptExternal } />
</div>
</div>
</div>
);
}
return (
<div>
<AddDapps store={ this.store } />
@@ -53,14 +72,27 @@ export default class Dapps extends Component {
] }
/>
<Page>
<div className={ styles.list }>
{ this.store.visible.map(this.renderApp) }
</div>
{ this.renderList(this.store.visibleLocal) }
{ this.renderList(this.store.visibleBuiltin) }
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
</Page>
</div>
);
}
renderList (items, overlay) {
if (!items || !items.length) {
return null;
}
return (
<div className={ styles.list }>
{ overlay }
{ items.map(this.renderApp) }
</div>
);
}
renderApp = (app) => {
return (
<div
@@ -70,4 +102,8 @@ export default class Dapps extends Component {
</div>
);
}
onClickAcceptExternal = () => {
this.store.closeExternalOverlay();
}
}

View File

@@ -14,39 +14,65 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import { action, computed, observable, transaction } from 'mobx';
import store from 'store';
import Contracts from '../../contracts';
import { hashToImageUrl } from '../../redux/util';
import builtinApps from './builtin.json';
const LS_KEY_HIDDEN = 'hiddenApps';
const LS_KEY_EXTERNAL = 'externalApps';
const LS_KEY_DISPLAY = 'displayApps';
const LS_KEY_EXTERNAL_ACCEPT = 'acceptExternal';
export default class DappsStore {
@observable apps = [];
@observable externalApps = [];
@observable hiddenApps = [];
@observable displayApps = {};
@observable modalOpen = false;
@observable externalOverlayVisible = true;
constructor (api) {
this._api = api;
this._readHiddenApps();
this._readExternalApps();
this.loadExternalOverlay();
this.readDisplayApps();
this._fetchBuiltinApps();
this._fetchLocalApps();
this._fetchRegistryApps();
Promise
.all([
this._fetchBuiltinApps(),
this._fetchLocalApps(),
this._fetchRegistryApps()
])
.then(this.writeDisplayApps);
}
@computed get visible () {
return this.apps
.filter((app) => {
return this.externalApps.includes(app.id) || !this.hiddenApps.includes(app.id);
})
.sort((a, b) => a.name.localeCompare(b.name));
@computed get sortedBuiltin () {
return this.apps.filter((app) => app.type === 'builtin');
}
@computed get sortedLocal () {
return this.apps.filter((app) => app.type === 'local');
}
@computed get sortedNetwork () {
return this.apps.filter((app) => app.type === 'network');
}
@computed get visibleApps () {
return this.apps.filter((app) => this.displayApps[app.id] && this.displayApps[app.id].visible);
}
@computed get visibleBuiltin () {
return this.visibleApps.filter((app) => app.type === 'builtin');
}
@computed get visibleLocal () {
return this.visibleApps.filter((app) => app.type === 'local');
}
@computed get visibleNetwork () {
return this.visibleApps.filter((app) => app.type === 'network');
}
@action openModal = () => {
@@ -57,14 +83,48 @@ export default class DappsStore {
this.modalOpen = false;
}
@action closeExternalOverlay = () => {
this.externalOverlayVisible = false;
store.set(LS_KEY_EXTERNAL_ACCEPT, true);
}
@action loadExternalOverlay () {
this.externalOverlayVisible = !(store.get(LS_KEY_EXTERNAL_ACCEPT) || false);
}
@action hideApp = (id) => {
this.hiddenApps = this.hiddenApps.concat(id);
this._writeHiddenApps();
this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: false } });
this.writeDisplayApps();
}
@action showApp = (id) => {
this.hiddenApps = this.hiddenApps.filter((_id) => _id !== id);
this._writeHiddenApps();
this.displayApps = Object.assign({}, this.displayApps, { [id]: { visible: true } });
this.writeDisplayApps();
}
@action readDisplayApps = () => {
this.displayApps = store.get(LS_KEY_DISPLAY) || {};
}
@action writeDisplayApps = () => {
store.set(LS_KEY_DISPLAY, this.displayApps);
}
@action addApps = (apps) => {
transaction(() => {
this.apps = this.apps
.concat(apps || [])
.sort((a, b) => a.name.localeCompare(b.name));
const visibility = {};
apps.forEach((app) => {
if (!this.displayApps[app.id]) {
visibility[app.id] = { visible: app.visible };
}
});
this.displayApps = Object.assign({}, this.displayApps, visibility);
});
}
_getHost (api) {
@@ -79,13 +139,16 @@ export default class DappsStore {
return Promise
.all(builtinApps.map((app) => dappReg.getImage(app.id)))
.then((imageIds) => {
transaction(() => {
builtinApps.forEach((app, index) => {
this.addApps(
builtinApps.map((app, index) => {
app.type = 'builtin';
app.image = hashToImageUrl(imageIds[index]);
this.apps.push(app);
});
});
return app;
})
);
})
.catch((error) => {
console.warn('DappsStore:fetchBuiltinApps', error);
});
}
@@ -100,15 +163,12 @@ export default class DappsStore {
return apps
.map((app) => {
app.type = 'local';
app.visible = true;
return app;
})
.filter((app) => app.id && !['ui'].includes(app.id));
})
.then((apps) => {
transaction(() => {
(apps || []).forEach((app) => this.apps.push(app));
});
})
.then(this.addApps)
.catch((error) => {
console.warn('DappsStore:fetchLocal', error);
});
@@ -132,7 +192,9 @@ export default class DappsStore {
.then((appsInfo) => {
const appIds = appsInfo
.map(([appId, owner]) => this._api.util.bytesToHex(appId))
.filter((appId) => !builtinApps.find((app) => app.id === appId));
.filter((appId) => {
return (new BigNumber(appId)).gt(0) && !builtinApps.find((app) => app.id === appId);
});
return Promise
.all([
@@ -147,7 +209,8 @@ export default class DappsStore {
image: hashToImageUrl(imageIds[index]),
contentHash: this._api.util.bytesToHex(contentIds[index]).substr(2),
manifestHash: this._api.util.bytesToHex(manifestIds[index]).substr(2),
type: 'network'
type: 'network',
visible: true
};
return app;
@@ -179,11 +242,7 @@ export default class DappsStore {
});
});
})
.then((apps) => {
transaction(() => {
(apps || []).forEach((app) => this.apps.push(app));
});
})
.then(this.addApps)
.catch((error) => {
console.warn('DappsStore:fetchRegistry', error);
});
@@ -201,44 +260,4 @@ export default class DappsStore {
return null;
});
}
_readHiddenApps () {
const stored = localStorage.getItem(LS_KEY_HIDDEN);
if (stored) {
try {
this.hiddenApps = JSON.parse(stored);
} catch (error) {
console.warn('DappsStore:readHiddenApps', error);
}
}
}
_readExternalApps () {
const stored = localStorage.getItem(LS_KEY_EXTERNAL);
if (stored) {
try {
this.externalApps = JSON.parse(stored);
} catch (error) {
console.warn('DappsStore:readExternalApps', error);
}
}
}
_writeExternalApps () {
try {
localStorage.setItem(LS_KEY_EXTERNAL, JSON.stringify(this.externalApps));
} catch (error) {
console.error('DappsStore:writeExternalApps', error);
}
}
_writeHiddenApps () {
try {
localStorage.setItem(LS_KEY_HIDDEN, JSON.stringify(this.hiddenApps));
} catch (error) {
console.error('DappsStore:writeHiddenApps', error);
}
}
}

View File

@@ -40,6 +40,7 @@ module.exports = {
'githubhint': ['./dapps/githubhint.js'],
'registry': ['./dapps/registry.js'],
'signaturereg': ['./dapps/signaturereg.js'],
'localtx': ['./dapps/localtx.js'],
'tokenreg': ['./dapps/tokenreg.js'],
// app
'index': ['./index.js']

View File

@@ -26,7 +26,6 @@ const DEST = process.env.BUILD_DEST || '.build';
module.exports = {
context: path.join(__dirname, './src'),
target: 'node',
entry: {
// library
'inject': ['./web3.js'],
@@ -39,14 +38,7 @@ module.exports = {
library: '[name].js',
libraryTarget: 'umd'
},
externals: {
'node-fetch': 'node-fetch',
'vertx': 'vertx'
},
module: {
noParse: [
/babel-polyfill/
],
loaders: [
{
test: /\.js$/,