Merge branch 'master' into tx-broadcast
Conflicts: ethcore/light/src/net/context.rs ethcore/light/src/net/tests/mod.rs
This commit is contained in:
34
js/npm/etherscan/README.md
Normal file
34
js/npm/etherscan/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# @parity/etherscan
|
||||
|
||||
A thin, lightweight promise wrapper for the api.etherscan.io/apis service, exposing a common endpoint for use in JavaScript applications.
|
||||
|
||||
[https://github.com/ethcore/parity/tree/master/js/src/3rdparty/etherscan](https://github.com/ethcore/parity/tree/master/js/src/3rdparty/etherscan)
|
||||
|
||||
## usage
|
||||
|
||||
installation -
|
||||
|
||||
```
|
||||
npm install --save @parity/etherscan
|
||||
```
|
||||
|
||||
Usage -
|
||||
|
||||
```
|
||||
const etherscan = require('@parity/etherscan');
|
||||
|
||||
// api calls goes here
|
||||
```
|
||||
|
||||
## api
|
||||
|
||||
account (exposed on etherscan.account) -
|
||||
|
||||
- `balance(address)`
|
||||
- `balances(addresses)` (array or addresses)
|
||||
- `transactions(address, page)` (page offset starts at 0, returns 25)
|
||||
|
||||
stats (exposed on etherscan.stats) -
|
||||
|
||||
- `price()`
|
||||
- `supply()`
|
||||
33
js/npm/etherscan/package.json
Normal file
33
js/npm/etherscan/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@parity/etherscan",
|
||||
"description": "The Parity Promise-based library for interfacing with Etherscan over HTTP",
|
||||
"version": "0.0.0",
|
||||
"main": "library.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
"maintainers": [
|
||||
"Jaco Greeff"
|
||||
],
|
||||
"contributors": [],
|
||||
"license": "GPL-3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ethcore/parity.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Ethereum",
|
||||
"ABI",
|
||||
"API",
|
||||
"RPC",
|
||||
"Parity",
|
||||
"Promise"
|
||||
],
|
||||
"scripts": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "3.5.0",
|
||||
"mocha": "3.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": "~1.6.3"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
# parity.js
|
||||
# @parity/parity.js
|
||||
|
||||
Parity.js is a thin, fast, Promise-based wrapper around the Ethereum APIs.
|
||||
|
||||
[https://github.com/ethcore/parity/tree/master/js/src/api](https://github.com/ethcore/parity/tree/master/js/src/api)
|
||||
|
||||
## installation
|
||||
|
||||
Install the package with `npm install --save @parity/parity.js`
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@parity/parity.js",
|
||||
"description": "The Parity Promise-base API & ABI library for interfacing with Ethereum over RPC",
|
||||
"description": "The Parity Promise-based API & ABI library for interfacing with Ethereum over RPC",
|
||||
"version": "0.0.0",
|
||||
"main": "library.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
@@ -26,8 +26,8 @@
|
||||
"devDependencies": {
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "^2.3.0",
|
||||
"js-sha3": "^0.5.2",
|
||||
"node-fetch": "^1.6.3"
|
||||
"bignumber.js": "~2.3.0",
|
||||
"js-sha3": "~0.5.2",
|
||||
"node-fetch": "~1.6.3"
|
||||
}
|
||||
}
|
||||
26
js/npm/parity/test/smoke.spec.js
Normal file
26
js/npm/parity/test/smoke.spec.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 parity = require('../');
|
||||
|
||||
describe('load the Parity library', function () {
|
||||
it('should no throw any error', () => {
|
||||
expect(parity).to.be.ok;
|
||||
|
||||
expect(parity.Api).to.be.ok;
|
||||
expect(parity.Abi).to.be.ok;
|
||||
});
|
||||
});
|
||||
34
js/npm/shapeshift/README.md
Normal file
34
js/npm/shapeshift/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# @parity/shapeshift
|
||||
|
||||
A thin ES6 promise wrapper around the shapeshift.io APIs as documented at https://shapeshift.io/api
|
||||
|
||||
[https://github.com/ethcore/parity/tree/master/js/src/3rdparty/shapeshift](https://github.com/ethcore/parity/tree/master/js/src/3rdparty/shapeshift)
|
||||
|
||||
## usage
|
||||
|
||||
installation -
|
||||
|
||||
```
|
||||
npm install --save @parity/shapeshift
|
||||
```
|
||||
|
||||
Usage -
|
||||
|
||||
```
|
||||
const APIKEY = 'private affiliate key or undefined';
|
||||
const shapeshift = require('@parity/shapeshift')(APIKEY);
|
||||
|
||||
// api calls goes here
|
||||
```
|
||||
|
||||
## api
|
||||
|
||||
queries -
|
||||
|
||||
- `getCoins()` [https://shapeshift.io/api#api-104](https://shapeshift.io/api#api-104)
|
||||
- `getMarketInfo(pair)` [https://shapeshift.io/api#api-103](https://shapeshift.io/api#api-103)
|
||||
- `getStatus(depositAddress)` [https://shapeshift.io/api#api-5](https://shapeshift.io/api#api-5)
|
||||
|
||||
transactions -
|
||||
|
||||
- `shift(toAddress, returnAddress, pair)` [https://shapeshift.io/api#api-7](https://shapeshift.io/api#api-7)
|
||||
31
js/npm/shapeshift/package.json
Normal file
31
js/npm/shapeshift/package.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "@parity/shapeshift",
|
||||
"description": "The Parity Promise-based library for interfacing with ShapeShift over HTTP",
|
||||
"version": "0.0.0",
|
||||
"main": "library.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
"maintainers": [
|
||||
"Jaco Greeff"
|
||||
],
|
||||
"contributors": [],
|
||||
"license": "GPL-3.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ethcore/parity.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Ethereum",
|
||||
"ABI",
|
||||
"API",
|
||||
"RPC",
|
||||
"Parity",
|
||||
"Promise"
|
||||
],
|
||||
"scripts": {
|
||||
},
|
||||
"devDependencies": {
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": "~1.6.3"
|
||||
}
|
||||
}
|
||||
29
js/npm/test/mocha.config.js
Normal file
29
js/npm/test/mocha.config.js
Normal 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/>.
|
||||
|
||||
const chai = require('chai');
|
||||
// const chaiAsPromised from 'chai-as-promised';
|
||||
// const chaiEnzyme from 'chai-enzyme';
|
||||
// const sinonChai from 'sinon-chai';
|
||||
|
||||
// chai.use(chaiAsPromised);
|
||||
// chai.use(chaiEnzyme());
|
||||
// chai.use(sinonChai);
|
||||
|
||||
// expose expect to global so we won't have to manually import & define it in every test
|
||||
global.expect = chai.expect;
|
||||
|
||||
module.exports = {};
|
||||
1
js/npm/test/mocha.opts
Normal file
1
js/npm/test/mocha.opts
Normal file
@@ -0,0 +1 @@
|
||||
-r ./test/mocha.config
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "parity.js",
|
||||
"version": "0.2.105",
|
||||
"version": "0.2.112",
|
||||
"main": "release/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"author": "Parity Team <admin@parity.io>",
|
||||
@@ -43,7 +43,7 @@
|
||||
"test": "NODE_ENV=test mocha 'src/**/*.spec.js'",
|
||||
"test:coverage": "NODE_ENV=test istanbul cover _mocha -- 'src/**/*.spec.js'",
|
||||
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",
|
||||
"test:npm": "(cd .npmjs && npm i) && node test/npmLibrary && (rm -rf .npmjs/node_modules)",
|
||||
"test:npm": "(cd .npmjs && npm i) && node test/npmParity && (rm -rf .npmjs/node_modules)",
|
||||
"prepush": "npm run lint:cached"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -59,6 +59,7 @@
|
||||
"babel-plugin-transform-runtime": "6.15.0",
|
||||
"babel-plugin-webpack-alias": "2.1.2",
|
||||
"babel-polyfill": "6.20.0",
|
||||
"babel-preset-env": "1.0.2",
|
||||
"babel-preset-es2015": "6.18.0",
|
||||
"babel-preset-es2016": "6.16.0",
|
||||
"babel-preset-es2017": "6.16.0",
|
||||
@@ -146,6 +147,7 @@
|
||||
"mobx-react-devtools": "4.2.10",
|
||||
"moment": "2.17.0",
|
||||
"phoneformat.js": "1.0.3",
|
||||
"push.js": "0.0.11",
|
||||
"qs": "6.3.0",
|
||||
"react": "15.4.1",
|
||||
"react-ace": "4.1.0",
|
||||
|
||||
33
js/scripts/dryrun-npm.sh
Executable file
33
js/scripts/dryrun-npm.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# variables
|
||||
PACKAGES=( "parity" "etherscan" "shapeshift" )
|
||||
|
||||
# change into the build directory
|
||||
BASEDIR=`dirname $0`
|
||||
cd $BASEDIR/..
|
||||
|
||||
# build all packages
|
||||
echo "*** Building packages for npmjs"
|
||||
echo "$NPM_TOKEN" >> ~/.npmrc
|
||||
|
||||
for PACKAGE in ${PACKAGES[@]}
|
||||
do
|
||||
echo "*** Building $PACKAGE"
|
||||
LIBRARY=$PACKAGE npm run ci:build:npm
|
||||
DIRECTORY=.npmjs/$PACKAGE
|
||||
|
||||
cd $DIRECTORY
|
||||
echo "*** Executing $PACKAGE tests from $DIRECTORY"
|
||||
npm test
|
||||
|
||||
echo "*** Publishing $PACKAGE from $DIRECTORY"
|
||||
echo "npm publish --access public || true"
|
||||
cd ../..
|
||||
|
||||
done
|
||||
cd ..
|
||||
|
||||
# exit with exit code
|
||||
exit 0
|
||||
@@ -3,7 +3,7 @@ set -e
|
||||
|
||||
# variables
|
||||
UTCDATE=`date -u "+%Y%m%d-%H%M%S"`
|
||||
PACKAGES=( "parity.js" )
|
||||
PACKAGES=( "parity" "etherscan" "shapeshift" )
|
||||
BRANCH=$CI_BUILD_REF_NAME
|
||||
GIT_JS_PRECOMPILED="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-precompiled.git"
|
||||
GIT_PARITY="https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git"
|
||||
@@ -59,19 +59,27 @@ git reset --hard origin/$BRANCH 2>$GITLOG
|
||||
|
||||
if [ "$BRANCH" == "master" ]; then
|
||||
cd js
|
||||
|
||||
echo "*** Bumping package.json patch version"
|
||||
npm --no-git-tag-version version
|
||||
npm version patch
|
||||
|
||||
echo "*** Building packages for npmjs"
|
||||
# echo -e "$NPM_USERNAME\n$NPM_PASSWORD\n$NPM_EMAIL" | npm login
|
||||
echo "$NPM_TOKEN" >> ~/.npmrc
|
||||
npm run ci:build:npm
|
||||
|
||||
echo "*** Publishing $PACKAGE to npmjs"
|
||||
cd .npmjs
|
||||
npm publish --access public || true
|
||||
cd ../..
|
||||
for PACKAGE in ${PACKAGES[@]}
|
||||
do
|
||||
echo "*** Building $PACKAGE"
|
||||
LIBRARY=$PACKAGE npm run ci:build:npm
|
||||
DIRECTORY=.npmjs/$PACKAGE
|
||||
|
||||
echo "*** Publishing $PACKAGE from $DIRECTORY"
|
||||
cd $DIRECTORY
|
||||
npm publish --access public || true
|
||||
cd ../..
|
||||
done
|
||||
|
||||
cd ..
|
||||
fi
|
||||
|
||||
echo "*** Updating cargo parity-ui-precompiled#$PRECOMPILED_HASH"
|
||||
|
||||
6
js/src/3rdparty/etherscan/account.spec.js
vendored
6
js/src/3rdparty/etherscan/account.spec.js
vendored
@@ -14,11 +14,13 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import etherscan from './';
|
||||
const etherscan = require('./');
|
||||
|
||||
const TESTADDR = '0xbf885e2b55c6bcc84556a3c5f07d3040833c8d00';
|
||||
|
||||
describe.skip('etherscan/account', () => {
|
||||
describe.skip('etherscan/account', function () {
|
||||
this.timeout(60 * 1000);
|
||||
|
||||
const checkBalance = function (balance, addr) {
|
||||
expect(balance).to.be.ok;
|
||||
expect(balance.account).to.equal(addr);
|
||||
|
||||
6
js/src/3rdparty/etherscan/stats.spec.js
vendored
6
js/src/3rdparty/etherscan/stats.spec.js
vendored
@@ -14,9 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import etherscan from './';
|
||||
const etherscan = require('./');
|
||||
|
||||
describe.skip('etherscan/stats', function () {
|
||||
this.timeout(60 * 1000);
|
||||
|
||||
describe.skip('etherscan/stats', () => {
|
||||
it('retrieves the latest price', () => {
|
||||
return etherscan.stats
|
||||
.price()
|
||||
|
||||
17
js/src/3rdparty/shapeshift/helpers.spec.js
vendored
17
js/src/3rdparty/shapeshift/helpers.spec.js
vendored
@@ -14,22 +14,15 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import chai from 'chai';
|
||||
import nock from 'nock';
|
||||
const nock = require('nock');
|
||||
|
||||
global.expect = chai.expect; // eslint-disable-line no-undef
|
||||
|
||||
import 'isomorphic-fetch';
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
import initShapeshift from './';
|
||||
import initRpc from './rpc';
|
||||
const ShapeShift = require('./');
|
||||
const initShapeshift = (ShapeShift.default || ShapeShift);
|
||||
|
||||
const APIKEY = '0x123454321';
|
||||
|
||||
const shapeshift = initShapeshift(APIKEY);
|
||||
const rpc = initRpc(APIKEY);
|
||||
const rpc = shapeshift.getRpc();
|
||||
|
||||
function mockget (requests) {
|
||||
let scope = nock(rpc.ENDPOINT);
|
||||
@@ -62,7 +55,7 @@ function mockpost (requests) {
|
||||
return scope;
|
||||
}
|
||||
|
||||
export {
|
||||
module.exports = {
|
||||
APIKEY,
|
||||
mockget,
|
||||
mockpost,
|
||||
|
||||
7
js/src/3rdparty/shapeshift/rpc.spec.js
vendored
7
js/src/3rdparty/shapeshift/rpc.spec.js
vendored
@@ -14,7 +14,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { APIKEY, mockget, mockpost, rpc } from './helpers.spec.js';
|
||||
const helpers = require('./helpers.spec.js');
|
||||
|
||||
const APIKEY = helpers.APIKEY;
|
||||
const mockget = helpers.mockget;
|
||||
const mockpost = helpers.mockpost;
|
||||
const rpc = helpers.rpc;
|
||||
|
||||
describe('shapeshift/rpc', () => {
|
||||
describe('GET', () => {
|
||||
|
||||
5
js/src/3rdparty/shapeshift/shapeshift.js
vendored
5
js/src/3rdparty/shapeshift/shapeshift.js
vendored
@@ -26,6 +26,10 @@ export default function (rpc) {
|
||||
return rpc.get(`marketinfo/${pair}`);
|
||||
}
|
||||
|
||||
function getRpc () {
|
||||
return rpc;
|
||||
}
|
||||
|
||||
function getStatus (depositAddress) {
|
||||
return rpc.get(`txStat/${depositAddress}`);
|
||||
}
|
||||
@@ -103,6 +107,7 @@ export default function (rpc) {
|
||||
return {
|
||||
getCoins,
|
||||
getMarketInfo,
|
||||
getRpc,
|
||||
getStatus,
|
||||
shift,
|
||||
subscribe,
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { mockget, mockpost, shapeshift } from './helpers.spec.js';
|
||||
const helpers = require('./helpers.spec.js');
|
||||
|
||||
const mockget = helpers.mockget;
|
||||
const mockpost = helpers.mockpost;
|
||||
const shapeshift = helpers.shapeshift;
|
||||
|
||||
describe('shapeshift/calls', () => {
|
||||
describe('getCoins', () => {
|
||||
|
||||
@@ -62,6 +62,10 @@ export default class Contracts {
|
||||
}
|
||||
|
||||
static create (api) {
|
||||
if (instance) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
return new Contracts(api);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
|
||||
const api = new SecureApi(`ws://${parityUrl}`, token);
|
||||
ContractInstances.create(api);
|
||||
|
||||
const store = initStore(api);
|
||||
const store = initStore(api, hashHistory);
|
||||
store.dispatch({ type: 'initAll', api });
|
||||
store.dispatch(setApi(api));
|
||||
|
||||
|
||||
34
js/src/library.etherscan.js
Normal file
34
js/src/library.etherscan.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 'babel-polyfill/dist/polyfill.js';
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
const isNode = typeof global !== 'undefined' && typeof global !== 'undefined';
|
||||
const isBrowser = typeof self !== 'undefined' && typeof self.window !== 'undefined';
|
||||
|
||||
if (isBrowser) {
|
||||
require('whatwg-fetch');
|
||||
}
|
||||
|
||||
if (isNode) {
|
||||
global.fetch = require('node-fetch');
|
||||
}
|
||||
|
||||
import Etherscan from './3rdparty/etherscan';
|
||||
|
||||
module.exports = Etherscan;
|
||||
34
js/src/library.shapeshift.js
Normal file
34
js/src/library.shapeshift.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 'babel-polyfill/dist/polyfill.js';
|
||||
import es6Promise from 'es6-promise';
|
||||
es6Promise.polyfill();
|
||||
|
||||
const isNode = typeof global !== 'undefined' && typeof global !== 'undefined';
|
||||
const isBrowser = typeof self !== 'undefined' && typeof self.window !== 'undefined';
|
||||
|
||||
if (isBrowser) {
|
||||
require('whatwg-fetch');
|
||||
}
|
||||
|
||||
if (isNode) {
|
||||
global.fetch = require('node-fetch');
|
||||
}
|
||||
|
||||
import ShapeShift from './3rdparty/shapeshift';
|
||||
|
||||
module.exports = ShapeShift;
|
||||
@@ -15,7 +15,7 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Redirect, Router, Route } from 'react-router';
|
||||
import { Redirect, Router, Route, IndexRoute } from 'react-router';
|
||||
|
||||
import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, WriteContract, Wallet, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from '~/views';
|
||||
|
||||
@@ -26,6 +26,23 @@ export default class MainApplication extends Component {
|
||||
routerHistory: PropTypes.any.isRequired
|
||||
};
|
||||
|
||||
handleDeprecatedRoute = (nextState, replace) => {
|
||||
const { address } = nextState.params;
|
||||
const redirectMap = {
|
||||
account: 'accounts',
|
||||
address: 'addresses',
|
||||
contract: 'contracts'
|
||||
};
|
||||
|
||||
const oldRoute = nextState.routes[0].path;
|
||||
const newRoute = Object.keys(redirectMap).reduce((newRoute, key) => {
|
||||
return newRoute.replace(new RegExp(`^/${key}`), '/' + redirectMap[key]);
|
||||
}, oldRoute);
|
||||
|
||||
console.warn(`Route "${oldRoute}" is deprecated. Please use "${newRoute}"`);
|
||||
replace(newRoute.replace(':address', address));
|
||||
}
|
||||
|
||||
render () {
|
||||
const { routerHistory } = this.props;
|
||||
|
||||
@@ -34,26 +51,46 @@ export default class MainApplication extends Component {
|
||||
<Redirect from='/' to='/accounts' />
|
||||
<Redirect from='/auth' to='/accounts' query={ {} } />
|
||||
<Redirect from='/settings' to='/settings/views' />
|
||||
|
||||
{ /** Backward Compatible links */ }
|
||||
<Route path='/account/:address' onEnter={ this.handleDeprecatedRoute } />
|
||||
<Route path='/address/:address' onEnter={ this.handleDeprecatedRoute } />
|
||||
<Route path='/contract/:address' onEnter={ this.handleDeprecatedRoute } />
|
||||
|
||||
<Route path='/' component={ Application }>
|
||||
<Route path='accounts' component={ Accounts } />
|
||||
<Route path='account/:address' component={ Account } />
|
||||
<Route path='wallet/:address' component={ Wallet } />
|
||||
<Route path='addresses' component={ Addresses } />
|
||||
<Route path='address/:address' component={ Address } />
|
||||
<Route path='accounts'>
|
||||
<IndexRoute component={ Accounts } />
|
||||
<Route path=':address' component={ Account } />
|
||||
<Route path='/wallet/:address' component={ Wallet } />
|
||||
</Route>
|
||||
|
||||
<Route path='addresses'>
|
||||
<IndexRoute component={ Addresses } />
|
||||
<Route path=':address' component={ Address } />
|
||||
</Route>
|
||||
|
||||
<Route path='apps' component={ Dapps } />
|
||||
<Route path='app/:id' component={ Dapp } />
|
||||
<Route path='contracts' component={ Contracts } />
|
||||
<Route path='contracts/write' component={ WriteContract } />
|
||||
<Route path='contract/:address' component={ Contract } />
|
||||
|
||||
<Route path='contracts'>
|
||||
<IndexRoute component={ Contracts } />
|
||||
<Route path='develop' component={ WriteContract } />
|
||||
<Route path=':address' component={ Contract } />
|
||||
</Route>
|
||||
|
||||
<Route path='settings' component={ Settings }>
|
||||
<Route path='background' component={ SettingsBackground } />
|
||||
<Route path='proxy' component={ SettingsProxy } />
|
||||
<Route path='views' component={ SettingsViews } />
|
||||
<Route path='parity' component={ SettingsParity } />
|
||||
</Route>
|
||||
|
||||
<Route path='signer' component={ Signer } />
|
||||
<Route path='status' component={ Status } />
|
||||
<Route path='status/:subpage' component={ Status } />
|
||||
|
||||
<Route path='status'>
|
||||
<IndexRoute component={ Status } />
|
||||
<Route path=':subpage' component={ Status } />
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
);
|
||||
|
||||
@@ -28,6 +28,7 @@ export default class AddAddress extends Component {
|
||||
|
||||
static propTypes = {
|
||||
contacts: PropTypes.object.isRequired,
|
||||
address: PropTypes.string,
|
||||
onClose: PropTypes.func
|
||||
};
|
||||
|
||||
@@ -39,6 +40,12 @@ export default class AddAddress extends Component {
|
||||
description: ''
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
if (this.props.address) {
|
||||
this.onEditAddress(null, this.props.address);
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Modal
|
||||
@@ -77,6 +84,8 @@ export default class AddAddress extends Component {
|
||||
hint='the network address for the entry'
|
||||
error={ addressError }
|
||||
value={ address }
|
||||
disabled={ !!this.props.address }
|
||||
allowCopy={ false }
|
||||
onChange={ this.onEditAddress } />
|
||||
<Input
|
||||
label='address name'
|
||||
|
||||
@@ -30,10 +30,12 @@ export default class WalletInfo extends Component {
|
||||
owners: PropTypes.array.isRequired,
|
||||
required: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.object,
|
||||
PropTypes.number
|
||||
]).isRequired,
|
||||
daylimit: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.object,
|
||||
PropTypes.number
|
||||
]).isRequired,
|
||||
|
||||
|
||||
@@ -28,26 +28,25 @@ export default class DetailsStep extends Component {
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
|
||||
onFromAddressChange: PropTypes.func.isRequired,
|
||||
onNameChange: PropTypes.func.isRequired,
|
||||
onDescriptionChange: PropTypes.func.isRequired,
|
||||
onAbiChange: PropTypes.func.isRequired,
|
||||
onCodeChange: PropTypes.func.isRequired,
|
||||
onParamsChange: PropTypes.func.isRequired,
|
||||
onDescriptionChange: PropTypes.func.isRequired,
|
||||
onFromAddressChange: PropTypes.func.isRequired,
|
||||
onInputsChange: PropTypes.func.isRequired,
|
||||
onNameChange: PropTypes.func.isRequired,
|
||||
onParamsChange: PropTypes.func.isRequired,
|
||||
|
||||
abi: PropTypes.string,
|
||||
abiError: PropTypes.string,
|
||||
balances: PropTypes.object,
|
||||
code: PropTypes.string,
|
||||
codeError: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
descriptionError: PropTypes.string,
|
||||
fromAddress: PropTypes.string,
|
||||
fromAddressError: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
nameError: PropTypes.string,
|
||||
description: PropTypes.string,
|
||||
descriptionError: PropTypes.string,
|
||||
abi: PropTypes.string,
|
||||
abiError: PropTypes.string,
|
||||
code: PropTypes.string,
|
||||
codeError: PropTypes.string,
|
||||
|
||||
readOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
@@ -77,6 +76,7 @@ export default class DetailsStep extends Component {
|
||||
render () {
|
||||
const {
|
||||
accounts,
|
||||
balances,
|
||||
readOnly,
|
||||
|
||||
fromAddress, fromAddressError,
|
||||
@@ -94,24 +94,28 @@ export default class DetailsStep extends Component {
|
||||
<AddressSelect
|
||||
label='from account (contract owner)'
|
||||
hint='the owner account for this contract'
|
||||
value={ fromAddress }
|
||||
error={ fromAddressError }
|
||||
accounts={ accounts }
|
||||
onChange={ this.onFromAddressChange } />
|
||||
balances={ balances }
|
||||
error={ fromAddressError }
|
||||
onChange={ this.onFromAddressChange }
|
||||
value={ fromAddress }
|
||||
/>
|
||||
|
||||
<Input
|
||||
label='contract name'
|
||||
hint='a name for the deployed contract'
|
||||
error={ nameError }
|
||||
onChange={ this.onNameChange }
|
||||
value={ name || '' }
|
||||
onChange={ this.onNameChange } />
|
||||
/>
|
||||
|
||||
<Input
|
||||
label='contract description (optional)'
|
||||
hint='a description for the contract'
|
||||
error={ descriptionError }
|
||||
onChange={ this.onDescriptionChange }
|
||||
value={ description }
|
||||
onChange={ this.onDescriptionChange } />
|
||||
/>
|
||||
|
||||
{ this.renderContractSelect() }
|
||||
|
||||
@@ -119,17 +123,19 @@ export default class DetailsStep extends Component {
|
||||
label='abi / solc combined-output'
|
||||
hint='the abi of the contract to deploy or solc combined-output'
|
||||
error={ abiError }
|
||||
value={ solcOutput }
|
||||
onChange={ this.onSolcChange }
|
||||
onSubmit={ this.onSolcSubmit }
|
||||
readOnly={ readOnly } />
|
||||
readOnly={ readOnly }
|
||||
value={ solcOutput }
|
||||
/>
|
||||
<Input
|
||||
label='code'
|
||||
hint='the compiled code of the contract to deploy'
|
||||
error={ codeError }
|
||||
value={ code }
|
||||
onSubmit={ this.onCodeChange }
|
||||
readOnly={ readOnly || solc } />
|
||||
readOnly={ readOnly || solc }
|
||||
value={ code }
|
||||
/>
|
||||
|
||||
</Form>
|
||||
);
|
||||
|
||||
@@ -15,8 +15,10 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '~/ui';
|
||||
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
||||
@@ -36,7 +38,7 @@ const STEPS = {
|
||||
COMPLETED: { title: 'completed' }
|
||||
};
|
||||
|
||||
export default class DeployContract extends Component {
|
||||
class DeployContract extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired,
|
||||
store: PropTypes.object.isRequired
|
||||
@@ -45,6 +47,7 @@ export default class DeployContract extends Component {
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
balances: PropTypes.object,
|
||||
abi: PropTypes.string,
|
||||
code: PropTypes.string,
|
||||
readOnly: PropTypes.bool,
|
||||
@@ -192,7 +195,7 @@ export default class DeployContract extends Component {
|
||||
}
|
||||
|
||||
renderStep () {
|
||||
const { accounts, readOnly } = this.props;
|
||||
const { accounts, readOnly, balances } = this.props;
|
||||
const { address, deployError, step, deployState, txhash, rejected } = this.state;
|
||||
|
||||
if (deployError) {
|
||||
@@ -216,6 +219,7 @@ export default class DeployContract extends Component {
|
||||
<DetailsStep
|
||||
{ ...this.state }
|
||||
accounts={ accounts }
|
||||
balances={ balances }
|
||||
readOnly={ readOnly }
|
||||
onFromAddressChange={ this.onFromAddressChange }
|
||||
onDescriptionChange={ this.onDescriptionChange }
|
||||
@@ -394,3 +398,17 @@ export default class DeployContract extends Component {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const fromAddresses = Object.keys(initProps.accounts);
|
||||
|
||||
return (state) => {
|
||||
const balances = pick(state.balances.balances, fromAddresses);
|
||||
return { balances };
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(DeployContract);
|
||||
|
||||
|
||||
@@ -32,25 +32,27 @@ export default class DetailsStep extends Component {
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
contract: PropTypes.object.isRequired,
|
||||
amount: PropTypes.string,
|
||||
amountError: PropTypes.string,
|
||||
onAmountChange: PropTypes.func.isRequired,
|
||||
fromAddress: PropTypes.string,
|
||||
fromAddressError: PropTypes.string,
|
||||
gasEdit: PropTypes.bool,
|
||||
onFromAddressChange: PropTypes.func.isRequired,
|
||||
func: PropTypes.object,
|
||||
funcError: PropTypes.string,
|
||||
onFuncChange: PropTypes.func,
|
||||
onGasEditClick: PropTypes.func,
|
||||
onValueChange: PropTypes.func.isRequired,
|
||||
values: PropTypes.array.isRequired,
|
||||
valuesError: PropTypes.array.isRequired,
|
||||
warning: PropTypes.string,
|
||||
onValueChange: PropTypes.func.isRequired
|
||||
|
||||
amount: PropTypes.string,
|
||||
amountError: PropTypes.string,
|
||||
balances: PropTypes.object,
|
||||
fromAddress: PropTypes.string,
|
||||
fromAddressError: PropTypes.string,
|
||||
func: PropTypes.object,
|
||||
funcError: PropTypes.string,
|
||||
gasEdit: PropTypes.bool,
|
||||
onFuncChange: PropTypes.func,
|
||||
onGasEditClick: PropTypes.func,
|
||||
warning: PropTypes.string
|
||||
}
|
||||
|
||||
render () {
|
||||
const { accounts, amount, amountError, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
|
||||
const { accounts, amount, amountError, balances, fromAddress, fromAddressError, gasEdit, onGasEditClick, onFromAddressChange, onAmountChange } = this.props;
|
||||
|
||||
return (
|
||||
<Form>
|
||||
@@ -61,6 +63,7 @@ export default class DetailsStep extends Component {
|
||||
value={ fromAddress }
|
||||
error={ fromAddressError }
|
||||
accounts={ accounts }
|
||||
balances={ balances }
|
||||
onChange={ onFromAddressChange } />
|
||||
{ this.renderFunctionSelect() }
|
||||
{ this.renderParameters() }
|
||||
|
||||
@@ -18,6 +18,8 @@ import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { observer } from 'mobx-react';
|
||||
import { pick } from 'lodash';
|
||||
|
||||
import ActionDoneAll from 'material-ui/svg-icons/action/done-all';
|
||||
import ContentClear from 'material-ui/svg-icons/content/clear';
|
||||
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
|
||||
@@ -57,6 +59,7 @@ class ExecuteContract extends Component {
|
||||
isTest: PropTypes.bool,
|
||||
fromAddress: PropTypes.string,
|
||||
accounts: PropTypes.object,
|
||||
balances: PropTypes.object,
|
||||
contract: PropTypes.object,
|
||||
gasLimit: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
@@ -362,10 +365,15 @@ class ExecuteContract extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { gasLimit } = state.nodeStatus;
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const fromAddresses = Object.keys(initProps.accounts);
|
||||
|
||||
return { gasLimit };
|
||||
return (state) => {
|
||||
const balances = pick(state.balances.balances, fromAddresses);
|
||||
const { gasLimit } = state.nodeStatus;
|
||||
|
||||
return { gasLimit, balances };
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
|
||||
@@ -134,6 +134,7 @@ export default class Details extends Component {
|
||||
images: PropTypes.object.isRequired,
|
||||
sender: PropTypes.string,
|
||||
senderError: PropTypes.string,
|
||||
sendersBalances: PropTypes.object,
|
||||
recipient: PropTypes.string,
|
||||
recipientError: PropTypes.string,
|
||||
tag: PropTypes.string,
|
||||
@@ -203,7 +204,7 @@ export default class Details extends Component {
|
||||
}
|
||||
|
||||
renderFromAddress () {
|
||||
const { sender, senderError, senders } = this.props;
|
||||
const { sender, senderError, senders, sendersBalances } = this.props;
|
||||
|
||||
if (!senders) {
|
||||
return null;
|
||||
@@ -218,6 +219,7 @@ export default class Details extends Component {
|
||||
hint='the sender address'
|
||||
value={ sender }
|
||||
onChange={ this.onEditSender }
|
||||
balances={ sendersBalances }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -54,6 +54,7 @@ export default class TransferStore {
|
||||
|
||||
@observable sender = '';
|
||||
@observable senderError = null;
|
||||
@observable sendersBalances = {};
|
||||
|
||||
@observable total = '0.0';
|
||||
@observable totalError = null;
|
||||
@@ -66,8 +67,6 @@ export default class TransferStore {
|
||||
onClose = null;
|
||||
|
||||
senders = null;
|
||||
sendersBalances = null;
|
||||
|
||||
isWallet = false;
|
||||
wallet = null;
|
||||
|
||||
|
||||
@@ -155,8 +155,8 @@ class Transfer extends Component {
|
||||
|
||||
renderDetailsPage () {
|
||||
const { account, balance, images, senders } = this.props;
|
||||
const { valueAll, extras, recipient, recipientError, sender, senderError } = this.store;
|
||||
const { tag, total, totalError, value, valueError } = this.store;
|
||||
const { recipient, recipientError, sender, senderError, sendersBalances } = this.store;
|
||||
const { valueAll, extras, tag, total, totalError, value, valueError } = this.store;
|
||||
|
||||
return (
|
||||
<Details
|
||||
@@ -170,6 +170,7 @@ class Transfer extends Component {
|
||||
recipientError={ recipientError }
|
||||
sender={ sender }
|
||||
senderError={ senderError }
|
||||
sendersBalances={ sendersBalances }
|
||||
tag={ tag }
|
||||
total={ total }
|
||||
totalError={ totalError }
|
||||
|
||||
@@ -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/>.
|
||||
import thunk from 'redux-thunk';
|
||||
import { routerMiddleware } from 'react-router-redux';
|
||||
|
||||
import ErrorsMiddleware from '~/ui/Errors/middleware';
|
||||
import SettingsMiddleware from '~/views/Settings/middleware';
|
||||
@@ -22,12 +23,13 @@ import SignerMiddleware from './providers/signerMiddleware';
|
||||
import statusMiddleware from '~/views/Status/middleware';
|
||||
import CertificationsMiddleware from './providers/certifications/middleware';
|
||||
|
||||
export default function (api) {
|
||||
export default function (api, browserHistory) {
|
||||
const errors = new ErrorsMiddleware();
|
||||
const signer = new SignerMiddleware(api);
|
||||
const settings = new SettingsMiddleware();
|
||||
const status = statusMiddleware();
|
||||
const certifications = new CertificationsMiddleware();
|
||||
const routeMiddleware = routerMiddleware(browserHistory);
|
||||
|
||||
const middleware = [
|
||||
settings.toMiddleware(),
|
||||
@@ -36,5 +38,5 @@ export default function (api) {
|
||||
certifications.toMiddleware()
|
||||
];
|
||||
|
||||
return middleware.concat(status, thunk);
|
||||
return middleware.concat(status, routeMiddleware, thunk);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,14 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { range, uniq, isEqual } from 'lodash';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { push } from 'react-router-redux';
|
||||
|
||||
import { hashToImageUrl } from './imagesReducer';
|
||||
import { setAddressImage } from './imagesActions';
|
||||
|
||||
import * as ABIS from '~/contracts/abi';
|
||||
import { notifyTransaction } from '~/util/notifications';
|
||||
import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png';
|
||||
|
||||
const ETH = {
|
||||
@@ -28,7 +31,64 @@ const ETH = {
|
||||
image: imagesEthereum
|
||||
};
|
||||
|
||||
export function setBalances (balances) {
|
||||
function setBalances (_balances) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
|
||||
const accounts = state.personal.accounts;
|
||||
const nextBalances = _balances;
|
||||
const prevBalances = state.balances.balances;
|
||||
const balances = { ...prevBalances };
|
||||
|
||||
Object.keys(nextBalances).forEach((address) => {
|
||||
if (!balances[address]) {
|
||||
balances[address] = Object.assign({}, nextBalances[address]);
|
||||
return;
|
||||
}
|
||||
|
||||
const balance = Object.assign({}, balances[address]);
|
||||
const { tokens, txCount = balance.txCount } = nextBalances[address];
|
||||
const nextTokens = [].concat(balance.tokens);
|
||||
|
||||
tokens.forEach((t) => {
|
||||
const { token, value } = t;
|
||||
const { tag } = token;
|
||||
|
||||
const tokenIndex = nextTokens.findIndex((tok) => tok.token.tag === tag);
|
||||
|
||||
if (tokenIndex === -1) {
|
||||
nextTokens.push({
|
||||
token,
|
||||
value
|
||||
});
|
||||
} else {
|
||||
const oldValue = nextTokens[tokenIndex].value;
|
||||
|
||||
// If received a token/eth (old value < new value), notify
|
||||
if (oldValue.lt(value) && accounts[address]) {
|
||||
const account = accounts[address];
|
||||
const txValue = value.minus(oldValue);
|
||||
|
||||
const redirectToAccount = () => {
|
||||
const route = `/account/${account.address}`;
|
||||
dispatch(push(route));
|
||||
};
|
||||
|
||||
notifyTransaction(account, token, txValue, redirectToAccount);
|
||||
}
|
||||
|
||||
nextTokens[tokenIndex] = { token, value };
|
||||
}
|
||||
});
|
||||
|
||||
balances[address] = { txCount: txCount || new BigNumber(0), tokens: nextTokens };
|
||||
});
|
||||
|
||||
dispatch(_setBalances(balances));
|
||||
};
|
||||
}
|
||||
|
||||
function _setBalances (balances) {
|
||||
return {
|
||||
type: 'setBalances',
|
||||
balances
|
||||
@@ -123,14 +183,14 @@ export function fetchBalances (_addresses) {
|
||||
|
||||
const fullFetch = addresses.length === 1;
|
||||
|
||||
const fetchedAddresses = uniq(addresses.concat(Object.keys(accounts)));
|
||||
const addressesToFetch = uniq(addresses.concat(Object.keys(accounts)));
|
||||
|
||||
return Promise
|
||||
.all(fetchedAddresses.map((addr) => fetchAccount(addr, api, fullFetch)))
|
||||
.all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch)))
|
||||
.then((accountsBalances) => {
|
||||
const balances = {};
|
||||
|
||||
fetchedAddresses.forEach((addr, idx) => {
|
||||
addressesToFetch.forEach((addr, idx) => {
|
||||
balances[addr] = accountsBalances[idx];
|
||||
});
|
||||
|
||||
@@ -146,10 +206,12 @@ export function fetchBalances (_addresses) {
|
||||
export function updateTokensFilter (_addresses, _tokens) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, balances, personal } = getState();
|
||||
const { visibleAccounts } = personal;
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
const { tokensFilter } = balances;
|
||||
|
||||
const addresses = uniq(_addresses || visibleAccounts || []).sort();
|
||||
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||
const addresses = uniq(_addresses || addressesToFetch || []).sort();
|
||||
|
||||
const tokens = _tokens || Object.values(balances.tokens) || [];
|
||||
const tokenAddresses = tokens.map((t) => t.address).sort();
|
||||
|
||||
@@ -221,8 +283,10 @@ export function updateTokensFilter (_addresses, _tokens) {
|
||||
export function queryTokensFilter (tokensFilter) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, personal, balances } = getState();
|
||||
const { visibleAccounts } = personal;
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
|
||||
const visibleAddresses = visibleAccounts.map((a) => a.toLowerCase());
|
||||
const addressesToFetch = uniq(visibleAddresses.concat(Object.keys(accounts)));
|
||||
|
||||
Promise
|
||||
.all([
|
||||
@@ -237,18 +301,16 @@ export function queryTokensFilter (tokensFilter) {
|
||||
.concat(logsTo)
|
||||
.forEach((log) => {
|
||||
const tokenAddress = log.address;
|
||||
|
||||
const fromAddress = '0x' + log.topics[1].slice(-40);
|
||||
const toAddress = '0x' + log.topics[2].slice(-40);
|
||||
|
||||
const fromIdx = visibleAddresses.indexOf(fromAddress);
|
||||
const toIdx = visibleAddresses.indexOf(toAddress);
|
||||
|
||||
if (fromIdx > -1) {
|
||||
addresses.push(visibleAccounts[fromIdx]);
|
||||
if (addressesToFetch.includes(fromAddress)) {
|
||||
addresses.push(fromAddress);
|
||||
}
|
||||
|
||||
if (toIdx > -1) {
|
||||
addresses.push(visibleAccounts[toIdx]);
|
||||
if (addressesToFetch.includes(toAddress)) {
|
||||
addresses.push(toAddress);
|
||||
}
|
||||
|
||||
tokenAddresses.push(tokenAddress);
|
||||
@@ -269,9 +331,10 @@ export function queryTokensFilter (tokensFilter) {
|
||||
export function fetchTokensBalances (_addresses = null, _tokens = null) {
|
||||
return (dispatch, getState) => {
|
||||
const { api, personal, balances } = getState();
|
||||
const { visibleAccounts } = personal;
|
||||
const { visibleAccounts, accounts } = personal;
|
||||
|
||||
const addresses = _addresses || visibleAccounts;
|
||||
const addressesToFetch = uniq(visibleAccounts.concat(Object.keys(accounts)));
|
||||
const addresses = _addresses || addressesToFetch;
|
||||
const tokens = _tokens || Object.values(balances.tokens);
|
||||
|
||||
if (addresses.length === 0) {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { handleActions } from 'redux-actions';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
const initialState = {
|
||||
balances: {},
|
||||
@@ -26,39 +25,7 @@ const initialState = {
|
||||
|
||||
export default handleActions({
|
||||
setBalances (state, action) {
|
||||
const nextBalances = action.balances;
|
||||
const prevBalances = state.balances;
|
||||
const balances = { ...prevBalances };
|
||||
|
||||
Object.keys(nextBalances).forEach((address) => {
|
||||
if (!balances[address]) {
|
||||
balances[address] = Object.assign({}, nextBalances[address]);
|
||||
return;
|
||||
}
|
||||
|
||||
const balance = Object.assign({}, balances[address]);
|
||||
const { tokens, txCount = balance.txCount } = nextBalances[address];
|
||||
const nextTokens = [].concat(balance.tokens);
|
||||
|
||||
tokens.forEach((t) => {
|
||||
const { token, value } = t;
|
||||
const { tag } = token;
|
||||
|
||||
const tokenIndex = nextTokens.findIndex((tok) => tok.token.tag === tag);
|
||||
|
||||
if (tokenIndex === -1) {
|
||||
nextTokens.push({
|
||||
token,
|
||||
value
|
||||
});
|
||||
} else {
|
||||
nextTokens[tokenIndex] = { token, value };
|
||||
}
|
||||
});
|
||||
|
||||
balances[address] = Object.assign({}, { txCount: txCount || new BigNumber(0), tokens: nextTokens });
|
||||
});
|
||||
|
||||
const { balances } = action;
|
||||
return Object.assign({}, state, { balances });
|
||||
},
|
||||
|
||||
|
||||
@@ -21,11 +21,11 @@ export Status from './status';
|
||||
|
||||
export apiReducer from './apiReducer';
|
||||
export balancesReducer from './balancesReducer';
|
||||
export blockchainReducer from './blockchainReducer';
|
||||
export compilerReducer from './compilerReducer';
|
||||
export imagesReducer from './imagesReducer';
|
||||
export personalReducer from './personalReducer';
|
||||
export signerReducer from './signerReducer';
|
||||
export statusReducer from './statusReducer';
|
||||
export blockchainReducer from './blockchainReducer';
|
||||
export compilerReducer from './compilerReducer';
|
||||
export snackbarReducer from './snackbarReducer';
|
||||
export statusReducer from './statusReducer';
|
||||
export walletReducer from './walletReducer';
|
||||
|
||||
@@ -54,7 +54,10 @@ export default class Status {
|
||||
this._api.eth
|
||||
.getBlockByNumber(blockNumber)
|
||||
.then((block) => {
|
||||
this._store.dispatch(statusCollection({ gasLimit: block.gasLimit }));
|
||||
this._store.dispatch(statusCollection({
|
||||
blockTimestamp: block.timestamp,
|
||||
gasLimit: block.gasLimit
|
||||
}));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn('status._subscribeBlockNumber', 'getBlockByNumber', error);
|
||||
|
||||
@@ -19,6 +19,7 @@ import { handleActions } from 'redux-actions';
|
||||
|
||||
const initialState = {
|
||||
blockNumber: new BigNumber(0),
|
||||
blockTimestamp: new Date(),
|
||||
devLogs: [],
|
||||
devLogsLevels: null,
|
||||
devLogsEnabled: false,
|
||||
|
||||
@@ -17,7 +17,12 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { routerReducer } from 'react-router-redux';
|
||||
|
||||
import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer, walletReducer } from './providers';
|
||||
import {
|
||||
apiReducer, balancesReducer, blockchainReducer,
|
||||
compilerReducer, imagesReducer, personalReducer,
|
||||
signerReducer, statusReducer as nodeStatusReducer,
|
||||
snackbarReducer, walletReducer
|
||||
} from './providers';
|
||||
import certificationsReducer from './providers/certifications/reducer';
|
||||
|
||||
import errorReducer from '~/ui/Errors/reducers';
|
||||
|
||||
@@ -32,9 +32,9 @@ const storeCreation = window.devToolsExtension
|
||||
? window.devToolsExtension()(createStore)
|
||||
: createStore;
|
||||
|
||||
export default function (api) {
|
||||
export default function (api, browserHistory) {
|
||||
const reducers = initReducers();
|
||||
const middleware = initMiddleware(api);
|
||||
const middleware = initMiddleware(api, browserHistory);
|
||||
const store = applyMiddleware(...middleware)(storeCreation)(reducers);
|
||||
|
||||
new BalancesProvider(store, api).start();
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import styles from './actionbar.css';
|
||||
|
||||
export default class Actionbar extends Component {
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
title: nodeOrStringProptype(),
|
||||
buttons: PropTypes.array,
|
||||
children: PropTypes.node,
|
||||
className: PropTypes.string
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
.account {
|
||||
padding: 4px 0 0 0;
|
||||
padding: 0.25em 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
@@ -27,6 +29,11 @@
|
||||
padding: 0 0 0 1em;
|
||||
}
|
||||
|
||||
.balance {
|
||||
color: #aaa;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.image {
|
||||
display: inline-block;
|
||||
height: 32px;
|
||||
|
||||
@@ -21,6 +21,8 @@ import AutoComplete from '../AutoComplete';
|
||||
import IdentityIcon from '../../IdentityIcon';
|
||||
import IdentityName from '../../IdentityName';
|
||||
|
||||
import { fromWei } from '~/api/util/wei';
|
||||
|
||||
import styles from './addressSelect.css';
|
||||
|
||||
export default class AddressSelect extends Component {
|
||||
@@ -40,27 +42,46 @@ export default class AddressSelect extends Component {
|
||||
value: PropTypes.string,
|
||||
tokens: PropTypes.object,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
allowInput: PropTypes.bool
|
||||
allowInput: PropTypes.bool,
|
||||
balances: PropTypes.object
|
||||
}
|
||||
|
||||
state = {
|
||||
autocompleteEntries: [],
|
||||
entries: {},
|
||||
addresses: [],
|
||||
value: ''
|
||||
}
|
||||
|
||||
entriesFromProps (props = this.props) {
|
||||
const { accounts, contacts, contracts, wallets } = props;
|
||||
const entries = Object.assign({}, accounts || {}, wallets || {}, contacts || {}, contracts || {});
|
||||
return entries;
|
||||
const { accounts = {}, contacts = {}, contracts = {}, wallets = {} } = props;
|
||||
|
||||
const autocompleteEntries = [].concat(
|
||||
Object.values(wallets),
|
||||
'divider',
|
||||
Object.values(accounts),
|
||||
'divider',
|
||||
Object.values(contacts),
|
||||
'divider',
|
||||
Object.values(contracts)
|
||||
);
|
||||
|
||||
const entries = {
|
||||
...wallets,
|
||||
...accounts,
|
||||
...contacts,
|
||||
...contracts
|
||||
};
|
||||
|
||||
return { autocompleteEntries, entries };
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { value } = this.props;
|
||||
const entries = this.entriesFromProps();
|
||||
const { entries, autocompleteEntries } = this.entriesFromProps();
|
||||
const addresses = Object.keys(entries).sort();
|
||||
|
||||
this.setState({ entries, addresses, value });
|
||||
this.setState({ autocompleteEntries, entries, addresses, value });
|
||||
}
|
||||
|
||||
componentWillReceiveProps (newProps) {
|
||||
@@ -71,7 +92,7 @@ export default class AddressSelect extends Component {
|
||||
|
||||
render () {
|
||||
const { allowInput, disabled, error, hint, label } = this.props;
|
||||
const { entries, value } = this.state;
|
||||
const { autocompleteEntries, value } = this.state;
|
||||
|
||||
const searchText = this.getSearchText();
|
||||
const icon = this.renderIdentityIcon(value);
|
||||
@@ -89,7 +110,7 @@ export default class AddressSelect extends Component {
|
||||
onUpdateInput={ allowInput && this.onUpdateInput }
|
||||
value={ searchText }
|
||||
filter={ this.handleFilter }
|
||||
entries={ entries }
|
||||
entries={ autocompleteEntries }
|
||||
entry={ this.getEntry() || {} }
|
||||
renderItem={ this.renderItem }
|
||||
/>
|
||||
@@ -129,7 +150,34 @@ export default class AddressSelect extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
renderBalance (address) {
|
||||
const { balances = {} } = this.props;
|
||||
const balance = balances[address];
|
||||
|
||||
if (!balance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ethToken = balance.tokens.find((tok) => tok.token && tok.token.tag && tok.token.tag.toLowerCase() === 'eth');
|
||||
|
||||
if (!ethToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = fromWei(ethToken.value);
|
||||
|
||||
return (
|
||||
<div className={ styles.balance }>
|
||||
{ value.toFormat(3) }<small> { 'ETH' }</small>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderMenuItem (address) {
|
||||
const balance = this.props.balances
|
||||
? this.renderBalance(address)
|
||||
: null;
|
||||
|
||||
const item = (
|
||||
<div className={ styles.account }>
|
||||
<IdentityIcon
|
||||
@@ -139,6 +187,7 @@ export default class AddressSelect extends Component {
|
||||
<IdentityName
|
||||
className={ styles.name }
|
||||
address={ address } />
|
||||
{ balance }
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -155,11 +204,10 @@ export default class AddressSelect extends Component {
|
||||
|
||||
getSearchText () {
|
||||
const entry = this.getEntry();
|
||||
const { value } = this.state;
|
||||
|
||||
return entry && entry.name
|
||||
? entry.name.toUpperCase()
|
||||
: value;
|
||||
: this.state.value;
|
||||
}
|
||||
|
||||
getEntry () {
|
||||
|
||||
@@ -16,11 +16,24 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import keycode from 'keycode';
|
||||
import { MenuItem, AutoComplete as MUIAutoComplete } from 'material-ui';
|
||||
import { MenuItem, AutoComplete as MUIAutoComplete, Divider as MUIDivider } from 'material-ui';
|
||||
import { PopoverAnimationVertical } from 'material-ui/Popover';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
// Hack to prevent "Unknown prop `disableFocusRipple` on <hr> tag" error
|
||||
class Divider extends Component {
|
||||
static muiName = MUIDivider.muiName;
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div style={ { margin: '0.25em 0' } }>
|
||||
<MUIDivider style={ { height: 2 } } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default class AutoComplete extends Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
@@ -38,15 +51,17 @@ export default class AutoComplete extends Component {
|
||||
PropTypes.array,
|
||||
PropTypes.object
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
lastChangedValue: undefined,
|
||||
entry: null,
|
||||
open: false,
|
||||
fakeBlur: false,
|
||||
dataSource: []
|
||||
}
|
||||
dataSource: [],
|
||||
dividerBreaks: []
|
||||
};
|
||||
|
||||
dividersVisibility = {};
|
||||
|
||||
componentWillMount () {
|
||||
const dataSource = this.getDataSource();
|
||||
@@ -64,7 +79,7 @@ export default class AutoComplete extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { disabled, error, hint, label, value, className, filter, onUpdateInput } = this.props;
|
||||
const { disabled, error, hint, label, value, className, onUpdateInput } = this.props;
|
||||
const { open, dataSource } = this.state;
|
||||
|
||||
return (
|
||||
@@ -78,9 +93,9 @@ export default class AutoComplete extends Component {
|
||||
onUpdateInput={ onUpdateInput }
|
||||
searchText={ value }
|
||||
onFocus={ this.onFocus }
|
||||
onBlur={ this.onBlur }
|
||||
onClose={ this.onClose }
|
||||
animation={ PopoverAnimationVertical }
|
||||
filter={ filter }
|
||||
filter={ this.handleFilter }
|
||||
popoverProps={ { open } }
|
||||
openOnFocus
|
||||
menuCloseDelay={ 0 }
|
||||
@@ -100,18 +115,76 @@ export default class AutoComplete extends Component {
|
||||
? entries
|
||||
: Object.values(entries);
|
||||
|
||||
if (renderItem && typeof renderItem === 'function') {
|
||||
return entriesArray.map(entry => renderItem(entry));
|
||||
let currentDivider = 0;
|
||||
let firstSet = false;
|
||||
|
||||
const dataSource = entriesArray.map((entry, index) => {
|
||||
// Render divider
|
||||
if (typeof entry === 'string' && entry.toLowerCase() === 'divider') {
|
||||
// Don't add divider if nothing before
|
||||
if (!firstSet) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const item = {
|
||||
text: '',
|
||||
divider: currentDivider,
|
||||
isDivider: true,
|
||||
value: (
|
||||
<Divider />
|
||||
)
|
||||
};
|
||||
|
||||
currentDivider++;
|
||||
return item;
|
||||
}
|
||||
|
||||
let item;
|
||||
|
||||
if (renderItem && typeof renderItem === 'function') {
|
||||
item = renderItem(entry);
|
||||
} else {
|
||||
item = {
|
||||
text: entry,
|
||||
value: (
|
||||
<MenuItem
|
||||
primaryText={ entry }
|
||||
/>
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
if (!firstSet) {
|
||||
item.first = true;
|
||||
firstSet = true;
|
||||
}
|
||||
|
||||
item.divider = currentDivider;
|
||||
|
||||
return item;
|
||||
}).filter((item) => item !== undefined);
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
handleFilter = (searchText, name, item) => {
|
||||
if (item.isDivider) {
|
||||
return this.dividersVisibility[item.divider];
|
||||
}
|
||||
|
||||
return entriesArray.map(entry => ({
|
||||
text: entry,
|
||||
value: (
|
||||
<MenuItem
|
||||
primaryText={ entry }
|
||||
/>
|
||||
)
|
||||
}));
|
||||
if (item.first) {
|
||||
this.dividersVisibility = {};
|
||||
}
|
||||
|
||||
const { filter } = this.props;
|
||||
const show = filter(searchText, name, item);
|
||||
|
||||
// Show the related divider
|
||||
if (show) {
|
||||
this.dividersVisibility[item.divider] = true;
|
||||
}
|
||||
|
||||
return show;
|
||||
}
|
||||
|
||||
onKeyDown = (event) => {
|
||||
@@ -121,7 +194,6 @@ export default class AutoComplete extends Component {
|
||||
case 'down':
|
||||
const { menu } = muiAutocomplete.refs;
|
||||
menu && menu.handleKeyDown(event);
|
||||
this.setState({ fakeBlur: true });
|
||||
break;
|
||||
|
||||
case 'enter':
|
||||
@@ -155,22 +227,12 @@ export default class AutoComplete extends Component {
|
||||
this.setState({ entry, open: false });
|
||||
}
|
||||
|
||||
onBlur = (event) => {
|
||||
onClose = (event) => {
|
||||
const { onUpdateInput } = this.props;
|
||||
|
||||
// TODO: Handle blur gracefully where we use onUpdateInput (currently replaces
|
||||
// input where text is allowed with the last selected value from the dropdown)
|
||||
if (!onUpdateInput) {
|
||||
window.setTimeout(() => {
|
||||
const { entry, fakeBlur } = this.state;
|
||||
|
||||
if (fakeBlur) {
|
||||
this.setState({ fakeBlur: false });
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleOnChange(entry);
|
||||
}, 200);
|
||||
const { entry } = this.state;
|
||||
this.handleOnChange(entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
*/
|
||||
|
||||
.layout {
|
||||
padding: 0.25em 0.25em 1em 0.25em;
|
||||
padding: 0.25em;
|
||||
|
||||
> * {
|
||||
&>div {
|
||||
margin-bottom: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,38 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import Actionbar from '../Actionbar';
|
||||
import { nodeOrStringProptype } from '~/util/proptypes';
|
||||
|
||||
import styles from './page.css';
|
||||
|
||||
export default class Page extends Component {
|
||||
static propTypes = {
|
||||
buttons: PropTypes.array,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
children: PropTypes.node,
|
||||
title: nodeOrStringProptype()
|
||||
};
|
||||
|
||||
render () {
|
||||
const { className, children } = this.props;
|
||||
const { buttons, className, children, title } = this.props;
|
||||
const classes = `${styles.layout} ${className}`;
|
||||
let actionbar = null;
|
||||
|
||||
if (title || buttons) {
|
||||
actionbar = (
|
||||
<Actionbar
|
||||
buttons={ buttons }
|
||||
title={ title } />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ classes }>
|
||||
{ children }
|
||||
<div>
|
||||
{ actionbar }
|
||||
<div className={ classes }>
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
45
js/src/util/notifications.js
Normal file
45
js/src/util/notifications.js
Normal 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/>.
|
||||
|
||||
import Push from 'push.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { noop } from 'lodash';
|
||||
|
||||
import { fromWei } from '~/api/util/wei';
|
||||
|
||||
import ethereumIcon from '~/../assets/images/contracts/ethereum-black-64x64.png';
|
||||
import unkownIcon from '~/../assets/images/contracts/unknown-64x64.png';
|
||||
|
||||
export function notifyTransaction (account, token, _value, onClick) {
|
||||
const name = account.name || account.address;
|
||||
const value = token.tag.toLowerCase() === 'eth'
|
||||
? fromWei(_value)
|
||||
: _value.div(new BigNumber(token.format || 1));
|
||||
|
||||
const icon = token.tag.toLowerCase() === 'eth'
|
||||
? ethereumIcon
|
||||
: (token.image || unkownIcon);
|
||||
|
||||
Push.create(`${name}`, {
|
||||
body: `You just received ${value.toFormat()} ${token.tag.toUpperCase()}`,
|
||||
icon: {
|
||||
x16: icon,
|
||||
x32: icon
|
||||
},
|
||||
timeout: 20000,
|
||||
onClick: onClick || noop
|
||||
});
|
||||
}
|
||||
@@ -31,6 +31,10 @@
|
||||
.infoline,
|
||||
.uuidline {
|
||||
line-height: 1.618em;
|
||||
|
||||
&.bigaddress {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.infoline,
|
||||
|
||||
@@ -32,18 +32,20 @@ export default class Header extends Component {
|
||||
balance: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
isContract: PropTypes.bool
|
||||
isContract: PropTypes.bool,
|
||||
hideName: PropTypes.bool
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
children: null,
|
||||
isContract: false
|
||||
isContract: false,
|
||||
hideName: false
|
||||
};
|
||||
|
||||
render () {
|
||||
const { api } = this.context;
|
||||
const { account, balance, className, children } = this.props;
|
||||
const { account, balance, className, children, hideName } = this.props;
|
||||
const { address, meta, uuid } = account;
|
||||
|
||||
if (!account) {
|
||||
@@ -60,17 +62,20 @@ export default class Header extends Component {
|
||||
<IdentityIcon
|
||||
address={ address } />
|
||||
<div className={ styles.floatleft }>
|
||||
<ContainerTitle title={ <IdentityName address={ address } unknown /> } />
|
||||
<div className={ styles.addressline }>
|
||||
{ this.renderName(address) }
|
||||
|
||||
<div className={ [ hideName ? styles.bigaddress : '', styles.addressline ].join(' ') }>
|
||||
<CopyToClipboard data={ address } />
|
||||
<div className={ styles.address }>{ address }</div>
|
||||
</div>
|
||||
|
||||
{ uuidText }
|
||||
<div className={ styles.infoline }>
|
||||
{ meta.description }
|
||||
</div>
|
||||
{ this.renderTxCount() }
|
||||
</div>
|
||||
|
||||
<div className={ styles.tags }>
|
||||
<Tags tags={ meta.tags } />
|
||||
</div>
|
||||
@@ -89,6 +94,18 @@ export default class Header extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
renderName (address) {
|
||||
const { hideName } = this.props;
|
||||
|
||||
if (hideName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ContainerTitle title={ <IdentityName address={ address } unknown /> } />
|
||||
);
|
||||
}
|
||||
|
||||
renderTxCount () {
|
||||
const { balance, isContract } = this.props;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import VerifyIcon from 'material-ui/svg-icons/action/verified-user';
|
||||
import { EditMeta, DeleteAccount, Shapeshift, SMSVerification, Transfer, PasswordManager } from '~/modals';
|
||||
import { Actionbar, Button, Page } from '~/ui';
|
||||
|
||||
import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png';
|
||||
import shapeshiftBtn from '~/../assets/images/shapeshift-btn.png';
|
||||
|
||||
import Header from './Header';
|
||||
import Transactions from './Transactions';
|
||||
|
||||
@@ -153,7 +153,7 @@ export default class Summary extends Component {
|
||||
const { link, noLink, account, name } = this.props;
|
||||
|
||||
const { address } = account;
|
||||
const viewLink = `/${link || 'account'}/${address}`;
|
||||
const viewLink = `/${link || 'accounts'}/${address}`;
|
||||
|
||||
const content = (
|
||||
<IdentityName address={ address } name={ name } unknown />
|
||||
|
||||
@@ -19,8 +19,9 @@ import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import ActionDelete from 'material-ui/svg-icons/action/delete';
|
||||
import ContentCreate from 'material-ui/svg-icons/content/create';
|
||||
import ContentAdd from 'material-ui/svg-icons/content/add';
|
||||
|
||||
import { EditMeta } from '~/modals';
|
||||
import { EditMeta, AddAddress } from '~/modals';
|
||||
import { Actionbar, Button, Page } from '~/ui';
|
||||
|
||||
import Header from '../Account/Header';
|
||||
@@ -32,7 +33,7 @@ class Address extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired,
|
||||
router: PropTypes.object.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
setVisibleAccounts: PropTypes.func.isRequired,
|
||||
@@ -40,12 +41,13 @@ class Address extends Component {
|
||||
contacts: PropTypes.object,
|
||||
balances: PropTypes.object,
|
||||
params: PropTypes.object
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
showDeleteDialog: false,
|
||||
showEditDialog: false
|
||||
}
|
||||
showEditDialog: false,
|
||||
showAdd: false
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
this.setVisibleAccounts();
|
||||
@@ -73,32 +75,69 @@ class Address extends Component {
|
||||
render () {
|
||||
const { contacts, balances } = this.props;
|
||||
const { address } = this.props.params;
|
||||
const { showDeleteDialog } = this.state;
|
||||
|
||||
if (Object.keys(contacts).length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contact = (contacts || {})[address];
|
||||
const balance = (balances || {})[address];
|
||||
|
||||
if (!contact) {
|
||||
return (
|
||||
<div>
|
||||
{ this.renderAddAddress(contact, address) }
|
||||
{ this.renderEditDialog(contact) }
|
||||
{ this.renderActionbar(contact) }
|
||||
{ this.renderDelete(contact) }
|
||||
<Page>
|
||||
<Header
|
||||
account={ contact || { address, meta: {} } }
|
||||
balance={ balance }
|
||||
hideName={ !contact }
|
||||
/>
|
||||
<Transactions
|
||||
address={ address }
|
||||
/>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAddAddress (contact, address) {
|
||||
if (contact) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { contacts } = this.props;
|
||||
const { showAdd } = this.state;
|
||||
|
||||
if (!showAdd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ this.renderEditDialog(contact) }
|
||||
{ this.renderActionbar(contact) }
|
||||
<Delete
|
||||
account={ contact }
|
||||
visible={ showDeleteDialog }
|
||||
route='/addresses'
|
||||
onClose={ this.closeDeleteDialog } />
|
||||
<Page>
|
||||
<Header
|
||||
account={ contact }
|
||||
balance={ balance } />
|
||||
<Transactions
|
||||
address={ address } />
|
||||
</Page>
|
||||
</div>
|
||||
<AddAddress
|
||||
contacts={ contacts }
|
||||
onClose={ this.onCloseAdd }
|
||||
address={ address }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderDelete (contact) {
|
||||
if (!contact) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { showDeleteDialog } = this.state;
|
||||
|
||||
return (
|
||||
<Delete
|
||||
account={ contact }
|
||||
visible={ showDeleteDialog }
|
||||
route='/addresses'
|
||||
onClose={ this.closeDeleteDialog }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,17 +155,27 @@ class Address extends Component {
|
||||
onClick={ this.showDeleteDialog } />
|
||||
];
|
||||
|
||||
const addToBook = (
|
||||
<Button
|
||||
key='newAddress'
|
||||
icon={ <ContentAdd /> }
|
||||
label='save address'
|
||||
onClick={ this.onOpenAdd }
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Actionbar
|
||||
title='Address Information'
|
||||
buttons={ !contact ? [] : buttons } />
|
||||
buttons={ !contact ? [ addToBook ] : buttons }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderEditDialog (contact) {
|
||||
const { showEditDialog } = this.state;
|
||||
|
||||
if (!showEditDialog) {
|
||||
if (!contact || !showEditDialog) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -151,6 +200,16 @@ class Address extends Component {
|
||||
showDeleteDialog = () => {
|
||||
this.setState({ showDeleteDialog: true });
|
||||
}
|
||||
|
||||
onOpenAdd = () => {
|
||||
this.setState({
|
||||
showAdd: true
|
||||
});
|
||||
}
|
||||
|
||||
onCloseAdd = () => {
|
||||
this.setState({ showAdd: false });
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
|
||||
@@ -23,7 +23,7 @@ import { uniq, isEqual } from 'lodash';
|
||||
import List from '../Accounts/List';
|
||||
import Summary from '../Accounts/Summary';
|
||||
import { AddAddress } from '~/modals';
|
||||
import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page } from '~/ui';
|
||||
import { Actionbar, ActionbarExport, ActionbarImport, ActionbarSearch, ActionbarSort, Button, Page, Loading } from '~/ui';
|
||||
import { setVisibleAccounts } from '~/redux/providers/personalActions';
|
||||
|
||||
import styles from './addresses.css';
|
||||
@@ -72,27 +72,40 @@ class Addresses extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { balances, contacts, hasContacts } = this.props;
|
||||
const { searchValues, sortOrder } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{ this.renderActionbar() }
|
||||
{ this.renderAddAddress() }
|
||||
<Page>
|
||||
<List
|
||||
link='address'
|
||||
search={ searchValues }
|
||||
accounts={ contacts }
|
||||
balances={ balances }
|
||||
empty={ !hasContacts }
|
||||
order={ sortOrder }
|
||||
handleAddSearchToken={ this.onAddSearchToken } />
|
||||
{ this.renderAccountsList() }
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderAccountsList () {
|
||||
const { balances, contacts, hasContacts } = this.props;
|
||||
const { searchValues, sortOrder } = this.state;
|
||||
|
||||
if (hasContacts && Object.keys(balances).length === 0) {
|
||||
return (
|
||||
<Loading />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
link='addresses'
|
||||
search={ searchValues }
|
||||
accounts={ contacts }
|
||||
balances={ balances }
|
||||
empty={ !hasContacts }
|
||||
order={ sortOrder }
|
||||
handleAddSearchToken={ this.onAddSearchToken }
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderSortButton () {
|
||||
const onChange = (sortOrder) => {
|
||||
this.setState({ sortOrder });
|
||||
|
||||
@@ -30,24 +30,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
.tabs button,
|
||||
.tabLink {
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
|
||||
&.tabactive, &.tabactive:hover {
|
||||
background: rgba(0, 0, 0, 0.25) !important;
|
||||
border-radius: 4px 4px 0 0;
|
||||
|
||||
* {
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabLink,
|
||||
.settings,
|
||||
.logo,
|
||||
.last {
|
||||
background: rgba(0, 0, 0, 0.5) !important; /* rgba(0, 0, 0, 0.25) !important; */
|
||||
}
|
||||
|
||||
.tabs button:hover {
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
}
|
||||
|
||||
button.tabactive,
|
||||
button.tabactive:hover {
|
||||
color: white !important;
|
||||
background: rgba(0, 0, 0, 0.25) !important;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.tabbarTooltip {
|
||||
left: 3.3em;
|
||||
top: 0.5em;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { Link } from 'react-router';
|
||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||
import { Tab as MUITab } from 'material-ui/Tabs';
|
||||
import { isEqual } from 'lodash';
|
||||
@@ -26,41 +26,26 @@ import { Badge, Tooltip } from '~/ui';
|
||||
import styles from './tabBar.css';
|
||||
import imagesEthcoreBlock from '../../../../assets/images/parity-logo-white-no-text.svg';
|
||||
|
||||
const TABMAP = {
|
||||
accounts: 'account',
|
||||
wallet: 'account',
|
||||
addresses: 'address',
|
||||
apps: 'app',
|
||||
contracts: 'contract',
|
||||
deploy: 'contract'
|
||||
};
|
||||
|
||||
class Tab extends Component {
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
view: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
pendings: PropTypes.number,
|
||||
onChange: PropTypes.func
|
||||
pendings: PropTypes.number
|
||||
};
|
||||
|
||||
shouldComponentUpdate (nextProps) {
|
||||
return nextProps.active !== this.props.active ||
|
||||
(nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings);
|
||||
return (nextProps.view.id === 'signer' && nextProps.pendings !== this.props.pendings);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { active, view, children } = this.props;
|
||||
const { view, children } = this.props;
|
||||
|
||||
const label = this.getLabel(view);
|
||||
|
||||
return (
|
||||
<MUITab
|
||||
className={ active ? styles.tabactive : '' }
|
||||
selected={ active }
|
||||
icon={ view.icon }
|
||||
label={ label }
|
||||
onTouchTap={ this.handleClick }
|
||||
>
|
||||
{ children }
|
||||
</MUITab>
|
||||
@@ -118,11 +103,6 @@ class Tab extends Component {
|
||||
|
||||
return this.renderLabel(label, null);
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
const { onChange, view } = this.props;
|
||||
onChange(view);
|
||||
}
|
||||
}
|
||||
|
||||
class TabBar extends Component {
|
||||
@@ -132,7 +112,6 @@ class TabBar extends Component {
|
||||
|
||||
static propTypes = {
|
||||
views: PropTypes.array.isRequired,
|
||||
hash: PropTypes.string.isRequired,
|
||||
pending: PropTypes.array,
|
||||
isTest: PropTypes.bool,
|
||||
netChain: PropTypes.string
|
||||
@@ -142,34 +121,11 @@ class TabBar extends Component {
|
||||
pending: []
|
||||
};
|
||||
|
||||
state = {
|
||||
activeViewId: ''
|
||||
};
|
||||
|
||||
setActiveView (props = this.props) {
|
||||
const { hash, views } = props;
|
||||
const view = views.find((view) => view.value === hash);
|
||||
|
||||
this.setState({ activeViewId: view.id });
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.setActiveView();
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.hash !== this.props.hash) {
|
||||
this.setActiveView(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
const prevViews = this.props.views.map((v) => v.id).sort();
|
||||
const nextViews = nextProps.views.map((v) => v.id).sort();
|
||||
|
||||
return (nextProps.hash !== this.props.hash) ||
|
||||
(nextProps.pending.length !== this.props.pending.length) ||
|
||||
(nextState.activeViewId !== this.state.activeViewId) ||
|
||||
return (nextProps.pending.length !== this.props.pending.length) ||
|
||||
(!isEqual(prevViews, nextViews));
|
||||
}
|
||||
|
||||
@@ -206,7 +162,6 @@ class TabBar extends Component {
|
||||
|
||||
renderTabs () {
|
||||
const { views, pending } = this.props;
|
||||
const { activeViewId } = this.state;
|
||||
|
||||
const items = views
|
||||
.map((view, index) => {
|
||||
@@ -216,60 +171,66 @@ class TabBar extends Component {
|
||||
)
|
||||
: null;
|
||||
|
||||
const active = activeViewId === view.id;
|
||||
|
||||
return (
|
||||
<Tab
|
||||
active={ active }
|
||||
view={ view }
|
||||
onChange={ this.onChange }
|
||||
<Link
|
||||
key={ view.id }
|
||||
pendings={ pending.length }
|
||||
to={ view.route }
|
||||
activeClassName={ styles.tabactive }
|
||||
className={ styles.tabLink }
|
||||
>
|
||||
{ body }
|
||||
</Tab>
|
||||
<Tab
|
||||
view={ view }
|
||||
pendings={ pending.length }
|
||||
>
|
||||
{ body }
|
||||
</Tab>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ styles.tabs }
|
||||
onChange={ this.onChange }>
|
||||
<div className={ styles.tabs }>
|
||||
{ items }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
onChange = (view) => {
|
||||
const { router } = this.context;
|
||||
|
||||
router.push(view.route);
|
||||
this.setState({ activeViewId: view.id });
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const { views } = state.settings;
|
||||
function mapStateToProps (initState) {
|
||||
const { views } = initState.settings;
|
||||
|
||||
const filteredViews = Object
|
||||
let filteredViewIds = Object
|
||||
.keys(views)
|
||||
.filter((id) => views[id].fixed || views[id].active)
|
||||
.filter((id) => views[id].fixed || views[id].active);
|
||||
|
||||
let filteredViews = filteredViewIds
|
||||
.map((id) => ({
|
||||
...views[id],
|
||||
id
|
||||
}));
|
||||
|
||||
const windowHash = (window.location.hash || '').split('?')[0].split('/')[1];
|
||||
const hash = TABMAP[windowHash] || windowHash;
|
||||
return (state) => {
|
||||
const { views } = state.settings;
|
||||
|
||||
return { views: filteredViews, hash };
|
||||
}
|
||||
const viewIds = Object
|
||||
.keys(views)
|
||||
.filter((id) => views[id].fixed || views[id].active);
|
||||
|
||||
function mapDispatchToProps (dispatch) {
|
||||
return bindActionCreators({}, dispatch);
|
||||
if (isEqual(viewIds, filteredViewIds)) {
|
||||
return { views: filteredViews };
|
||||
}
|
||||
|
||||
filteredViewIds = viewIds;
|
||||
filteredViews = viewIds
|
||||
.map((id) => ({
|
||||
...views[id],
|
||||
id
|
||||
}));
|
||||
|
||||
return { views: filteredViews };
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
mapStateToProps
|
||||
)(TabBar);
|
||||
|
||||
@@ -20,3 +20,7 @@
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ import Status from './Status';
|
||||
import Store from './store';
|
||||
import TabBar from './TabBar';
|
||||
|
||||
import styles from './application.css';
|
||||
|
||||
const inFrame = window.parent !== window && window.parent.frames.length !== 0;
|
||||
|
||||
@observer
|
||||
@@ -79,7 +81,9 @@ class Application extends Component {
|
||||
netChain={ netChain }
|
||||
isTest={ isTest }
|
||||
pending={ pending } />
|
||||
{ children }
|
||||
<div className={ styles.content }>
|
||||
{ children }
|
||||
</div>
|
||||
{ blockNumber ? (<Status />) : null }
|
||||
<Snackbar />
|
||||
</Container>
|
||||
|
||||
@@ -85,7 +85,7 @@ class Contracts extends Component {
|
||||
{ this.renderDeployContract() }
|
||||
<Page>
|
||||
<List
|
||||
link='contract'
|
||||
link='contracts'
|
||||
search={ searchValues }
|
||||
accounts={ contracts }
|
||||
balances={ balances }
|
||||
@@ -142,12 +142,12 @@ class Contracts extends Component {
|
||||
label='deploy contract'
|
||||
onClick={ this.onDeployContract } />,
|
||||
<Link
|
||||
to='/contracts/write'
|
||||
to='/contracts/develop'
|
||||
key='writeContract'
|
||||
>
|
||||
<Button
|
||||
icon={ <FileIcon /> }
|
||||
label='write contract'
|
||||
label='develop contract'
|
||||
/>
|
||||
</Link>,
|
||||
|
||||
|
||||
@@ -51,9 +51,16 @@ export default class Dapp extends Component {
|
||||
src = `${dappsUrl}/${app.contentHash}/`;
|
||||
break;
|
||||
default:
|
||||
const dapphost = process.env.NODE_ENV === 'production' && !app.secure
|
||||
? `${dappsUrl}/ui`
|
||||
: '';
|
||||
let dapphost = process.env.DAPPS_URL || (
|
||||
process.env.NODE_ENV === 'production' && !app.secure
|
||||
? `${dappsUrl}/ui`
|
||||
: ''
|
||||
);
|
||||
|
||||
if (dapphost === '/') {
|
||||
dapphost = '';
|
||||
}
|
||||
|
||||
src = `${dapphost}/${app.url}.html`;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -72,9 +72,17 @@ export default class Dapps extends Component {
|
||||
] }
|
||||
/>
|
||||
<Page>
|
||||
{ this.renderList(this.store.visibleLocal) }
|
||||
{ this.renderList(this.store.visibleBuiltin) }
|
||||
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
|
||||
<div>
|
||||
{ this.renderList(this.store.visibleLocal) }
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{ this.renderList(this.store.visibleBuiltin) }
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{ this.renderList(this.store.visibleNetwork, externalOverlay) }
|
||||
</div>
|
||||
</Page>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,8 +23,7 @@ export default class Signer extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<Actionbar
|
||||
title='Trusted Signer' />
|
||||
<Actionbar title='Trusted Signer' />
|
||||
<RequestsPage />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,12 +30,6 @@ export default class Store {
|
||||
}
|
||||
}
|
||||
|
||||
@action unsubscribe () {
|
||||
if (this._timeoutId) {
|
||||
clearTimeout(this._timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
@action setBalance = (address, balance) => {
|
||||
this.setBalances({ [address]: balance });
|
||||
}
|
||||
@@ -50,6 +44,12 @@ export default class Store {
|
||||
}
|
||||
}
|
||||
|
||||
@action unsubscribe () {
|
||||
if (this._timeoutId) {
|
||||
clearTimeout(this._timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
fetchBalance (address) {
|
||||
this._api.eth
|
||||
.getBalance(address)
|
||||
|
||||
@@ -22,7 +22,7 @@ import ReorderIcon from 'material-ui/svg-icons/action/reorder';
|
||||
|
||||
import { Container } from '~/ui';
|
||||
|
||||
import styles from './Debug.css';
|
||||
import styles from './debug.css';
|
||||
|
||||
export default class Debug extends Component {
|
||||
static propTypes = {
|
||||
@@ -14,4 +14,4 @@
|
||||
// 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 './Debug';
|
||||
export default from './debug';
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
// 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 './MiningSettings';
|
||||
export default from './miningSettings';
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
// 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 './Status';
|
||||
export default from './status';
|
||||
|
||||
@@ -28,10 +28,19 @@
|
||||
content: '';
|
||||
}
|
||||
|
||||
.blockinfo {
|
||||
font-size: 24px;
|
||||
.blockInfo {
|
||||
color: #aaa;
|
||||
line-height: 24px;
|
||||
font-size: 1.5em;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.blockByline {
|
||||
color: #aaa;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.padBottom {
|
||||
padding-bottom: 1.25em !important;
|
||||
}
|
||||
|
||||
.col,
|
||||
@@ -14,14 +14,16 @@
|
||||
// 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 bytes from 'bytes';
|
||||
import moment from 'moment';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { Container, ContainerTitle, Input } from '~/ui';
|
||||
|
||||
import styles from './Status.css';
|
||||
import MiningSettings from '../MiningSettings';
|
||||
|
||||
import styles from './status.css';
|
||||
|
||||
export default class Status extends Component {
|
||||
static propTypes = {
|
||||
nodeStatus: PropTypes.object.isRequired,
|
||||
@@ -44,23 +46,26 @@ export default class Status extends Component {
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.row }>
|
||||
<div className={ styles.col3 }>
|
||||
<div className={ styles.col12 }>
|
||||
<div className={ `${styles.col12} ${styles.padBottom}` }>
|
||||
<ContainerTitle title='best block' />
|
||||
<h2 { ...this._test('best-block') } className={ styles.blockinfo }>
|
||||
<div { ...this._test('best-block') } className={ styles.blockInfo }>
|
||||
#{ nodeStatus.blockNumber.toFormat() }
|
||||
</h2>
|
||||
</div>
|
||||
<div className={ styles.blockByline }>
|
||||
{ moment().calendar(nodeStatus.blockTimestamp) }
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.col12 }>
|
||||
<div className={ `${styles.col12} ${styles.padBottom}` }>
|
||||
<ContainerTitle title='peers' />
|
||||
<h2 { ...this._test('peers') } className={ styles.blockinfo }>
|
||||
<div { ...this._test('peers') } className={ styles.blockInfo }>
|
||||
{ peers }
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.col12 }>
|
||||
<div className={ `${styles.col12} ${styles.padBottom}` }>
|
||||
<ContainerTitle title='hash rate' />
|
||||
<h2 { ...this._test('hashrate') } className={ styles.blockinfo }>
|
||||
<div { ...this._test('hashrate') } className={ styles.blockInfo }>
|
||||
{ `${hashrate} H/s` }
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.col5 }>
|
||||
@@ -14,4 +14,4 @@
|
||||
// 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 './StatusPage';
|
||||
export default from './statusPage';
|
||||
|
||||
@@ -14,5 +14,9 @@
|
||||
/* You should have received a copy of the GNU General Public License
|
||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
.container {
|
||||
|
||||
.body {
|
||||
&>div {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from '~/redux/
|
||||
import Debug from '../../components/Debug';
|
||||
import Status from '../../components/Status';
|
||||
|
||||
import styles from './statusPage.css';
|
||||
|
||||
class StatusPage extends Component {
|
||||
static propTypes = {
|
||||
nodeStatus: PropTypes.object.isRequired,
|
||||
@@ -39,7 +41,7 @@ class StatusPage extends Component {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div className={ styles.body }>
|
||||
<Status { ...this.props } />
|
||||
<Debug { ...this.props } />
|
||||
</div>
|
||||
@@ -16,22 +16,16 @@
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { Actionbar, Page } from '~/ui';
|
||||
import { Page } from '~/ui';
|
||||
|
||||
import StatusPage from './containers/StatusPage';
|
||||
|
||||
import styles from './status.css';
|
||||
|
||||
export default class Status extends Component {
|
||||
render () {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<Actionbar
|
||||
title='status' />
|
||||
<Page>
|
||||
<StatusPage />
|
||||
</Page>
|
||||
</div>
|
||||
<Page title='status'>
|
||||
<StatusPage />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
try {
|
||||
var Api = require('../.npmjs/library.js').Api;
|
||||
var Abi = require('../.npmjs/library.js').Abi;
|
||||
var Api = require('../.npmjs/parity/library.js').Api;
|
||||
var Abi = require('../.npmjs/parity/library.js').Abi;
|
||||
|
||||
if (typeof Api !== 'function') {
|
||||
throw new Error('No Api');
|
||||
@@ -20,6 +20,7 @@ const path = require('path');
|
||||
const WebpackErrorNotificationPlugin = require('webpack-error-notification');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
|
||||
const Shared = require('./shared');
|
||||
const DAPPS = require('../src/dapps');
|
||||
@@ -41,7 +42,7 @@ module.exports = {
|
||||
output: {
|
||||
publicPath: '/',
|
||||
path: path.join(__dirname, '../', DEST),
|
||||
filename: '[name].[hash].js'
|
||||
filename: '[name].[hash:10].js'
|
||||
},
|
||||
|
||||
module: {
|
||||
@@ -85,13 +86,20 @@ module.exports = {
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: [ /src/ ],
|
||||
// exclude: [ /src\/dapps/ ],
|
||||
loader: isProd ? ExtractTextPlugin.extract([
|
||||
// 'style-loader',
|
||||
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||
'postcss-loader'
|
||||
]) : undefined,
|
||||
// use: [ 'happypack/loader?id=css' ]
|
||||
use: [
|
||||
use: isProd ? undefined : [
|
||||
'style-loader',
|
||||
'css-loader?modules&sourceMap&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
|
||||
'postcss-loader'
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
test: /\.css$/,
|
||||
exclude: [ /src/ ],
|
||||
@@ -99,11 +107,15 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg)$/,
|
||||
use: [ 'file-loader?name=[name].[hash].[ext]' ]
|
||||
use: [ 'file-loader?&name=assets/[name].[hash:10].[ext]' ]
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)|ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [ 'file-loader' ]
|
||||
test: /\.(woff(2)|ttf|eot|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [ 'file-loader?name=fonts/[name][hash:10].[ext]' ]
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||
use: [ 'file-loader?name=assets/[name].[hash:10].[ext]' ]
|
||||
}
|
||||
],
|
||||
noParse: [
|
||||
@@ -153,13 +165,20 @@ module.exports = {
|
||||
if (!isProd) {
|
||||
plugins.push(
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
filename: 'commons.[hash].js',
|
||||
filename: 'commons.[hash:10].js',
|
||||
name: 'commons',
|
||||
minChunks: Infinity
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (isProd) {
|
||||
plugins.push(new ExtractTextPlugin({
|
||||
filename: 'styles/[name].[hash:10].css',
|
||||
allChunks: true
|
||||
}));
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}())
|
||||
};
|
||||
|
||||
@@ -23,14 +23,27 @@ const Shared = require('./shared');
|
||||
const ENV = process.env.NODE_ENV || 'development';
|
||||
const isProd = ENV === 'production';
|
||||
|
||||
const LIBRARY = process.env.LIBRARY;
|
||||
if (!LIBRARY) {
|
||||
process.exit(-1);
|
||||
}
|
||||
const SRC = LIBRARY.toLowerCase();
|
||||
const OUTPUT_PATH = path.join(__dirname, '../.npmjs', SRC);
|
||||
|
||||
const TEST_CONTEXT = SRC === 'parity'
|
||||
? '../npm/parity/test/'
|
||||
: `../src/3rdparty/${SRC}/`;
|
||||
|
||||
console.log(`Building ${LIBRARY} from library.${SRC}.js to .npmjs/${SRC}`);
|
||||
|
||||
module.exports = {
|
||||
context: path.join(__dirname, '../src'),
|
||||
target: 'node',
|
||||
entry: 'library.js',
|
||||
entry: `library.${SRC}.js`,
|
||||
output: {
|
||||
path: path.join(__dirname, '../.npmjs'),
|
||||
path: OUTPUT_PATH,
|
||||
filename: 'library.js',
|
||||
library: 'Parity',
|
||||
library: LIBRARY,
|
||||
libraryTarget: 'umd',
|
||||
umdNamedDefine: true
|
||||
},
|
||||
@@ -66,19 +79,52 @@ module.exports = {
|
||||
plugins: Shared.getPlugins().concat([
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: '../parity.package.json',
|
||||
from: `../npm/${SRC}/package.json`,
|
||||
to: 'package.json',
|
||||
transform: function (content, path) {
|
||||
const json = JSON.parse(content.toString());
|
||||
json.version = packageJson.version;
|
||||
|
||||
// Add tests dependencies to Dev Deps
|
||||
json.devDependencies.chai = packageJson.devDependencies.chai;
|
||||
json.devDependencies.mocha = packageJson.devDependencies.mocha;
|
||||
json.devDependencies.nock = packageJson.devDependencies.nock;
|
||||
|
||||
// Add test script
|
||||
json.scripts.test = 'mocha \'test/*.spec.js\'';
|
||||
|
||||
return new Buffer(JSON.stringify(json, null, ' '), 'utf-8');
|
||||
}
|
||||
},
|
||||
{
|
||||
from: '../LICENSE'
|
||||
},
|
||||
|
||||
// Copy the base test config
|
||||
{
|
||||
from: '../parity.md',
|
||||
from: '../npm/test',
|
||||
to: 'test'
|
||||
},
|
||||
|
||||
// Copy the actual tests
|
||||
{
|
||||
context: TEST_CONTEXT,
|
||||
from: '**/*.spec.js',
|
||||
to: 'test',
|
||||
transform: function (content, path) {
|
||||
let output = content.toString();
|
||||
|
||||
// Don't skip tests
|
||||
output = output.replace(/describe\.skip/, 'describe');
|
||||
|
||||
// Require parent library
|
||||
output = output.replace('require(\'./\')', 'require(\'../\')');
|
||||
|
||||
return new Buffer(output, 'utf-8');
|
||||
}
|
||||
},
|
||||
{
|
||||
from: `../npm/${SRC}/README.md`,
|
||||
to: 'README.md'
|
||||
}
|
||||
], { copyUnmodified: true })
|
||||
|
||||
@@ -36,6 +36,29 @@ function getBabelrc () {
|
||||
// [ "es2015", { "modules": false } ]
|
||||
babelrc.presets[es2015Index] = [ 'es2015', { modules: false } ];
|
||||
babelrc['babelrc'] = false;
|
||||
|
||||
const BABEL_PRESET_ENV = process.env.BABEL_PRESET_ENV;
|
||||
const npmStart = process.env.npm_lifecycle_event === 'start';
|
||||
const npmStartApp = process.env.npm_lifecycle_event === 'start:app';
|
||||
|
||||
if (BABEL_PRESET_ENV && (npmStart || npmStartApp)) {
|
||||
console.log('using babel-preset-env');
|
||||
|
||||
babelrc.presets = [
|
||||
// 'es2017',
|
||||
'stage-0', 'react',
|
||||
[
|
||||
'env',
|
||||
{
|
||||
targets: { browsers: ['last 2 Chrome versions'] },
|
||||
modules: false,
|
||||
loose: true,
|
||||
useBuiltIns: true
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
return babelrc;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user