merge master into jr-reverse-caching

This commit is contained in:
Jannis R
2017-01-09 12:50:26 +01:00
68 changed files with 1067 additions and 721 deletions

View File

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

View File

@@ -75,6 +75,10 @@ export default class Contract {
return this._functions;
}
get receipt () {
return this._receipt;
}
get instance () {
this._instance.address = this._address;
return this._instance;
@@ -139,6 +143,7 @@ export default class Contract {
}
setState({ state: 'hasReceipt', receipt });
this._receipt = receipt;
this._address = receipt.contractAddress;
return this._address;
});

View File

@@ -40,7 +40,7 @@ export default class CreateAccount extends Component {
accountNameError: ERRORS.noName,
accounts: null,
isValidName: false,
isValidPass: false,
isValidPass: true,
passwordHint: '',
password1: '',
password1Error: null,

View File

@@ -37,7 +37,7 @@ export default class NewImport extends Component {
accountName: '',
accountNameError: ERRORS.noName,
isValidFile: false,
isValidPass: false,
isValidPass: true,
isValidName: false,
password: '',
passwordError: null,

View File

@@ -36,7 +36,7 @@ export default class RawKey extends Component {
accountNameError: ERRORS.noName,
isValidKey: false,
isValidName: false,
isValidPass: false,
isValidPass: true,
passwordHint: '',
password1: '',
password1Error: null,
@@ -119,8 +119,6 @@ export default class RawKey extends Component {
const rawKey = event.target.value;
let rawKeyError = null;
console.log(rawKey.length, rawKey);
if (!rawKey || !rawKey.trim().length) {
rawKeyError = ERRORS.noKey;
} else if (rawKey.substr(0, 2) !== '0x' || rawKey.substr(2).length !== 64 || !api.util.isHex(rawKey)) {

View File

@@ -31,9 +31,9 @@ export default class RecoveryPhrase extends Component {
state = {
accountName: '',
accountNameError: ERRORS.noName,
isValidPass: false,
isValidPass: true,
isValidName: false,
isValidPhrase: false,
isValidPhrase: true,
passwordHint: '',
password1: '',
password1Error: null,

View File

@@ -240,6 +240,7 @@ export default class CreateAccount extends Component {
if (createType === 'fromNew' || createType === 'fromPhrase') {
let phrase = this.state.phrase;
if (createType === 'fromPhrase' && windowsPhrase) {
phrase = phrase
.split(' ') // get the words
@@ -271,7 +272,9 @@ export default class CreateAccount extends Component {
this.newError(error);
});
} else if (createType === 'fromRaw') {
}
if (createType === 'fromRaw') {
return api.parity
.newAccountFromSecret(this.state.rawKey, this.state.password)
.then((address) => {
@@ -296,7 +299,9 @@ export default class CreateAccount extends Component {
this.newError(error);
});
} else if (createType === 'fromGeth') {
}
if (createType === 'fromGeth') {
return api.parity
.importGethAccounts(this.state.gethAddresses)
.then((result) => {

View File

@@ -455,10 +455,15 @@ class DeployContract extends Component {
this.setState({ step: 'DEPLOYMENT' });
api
.newContract(abiParsed)
const contract = api.newContract(abiParsed);
contract
.deploy(options, params, this.onDeploymentState)
.then((address) => {
const blockNumber = contract._receipt
? contract.receipt.blockNumber.toNumber()
: null;
return Promise.all([
api.parity.setAccountName(address, name),
api.parity.setAccountMeta(address, {
@@ -466,8 +471,9 @@ class DeployContract extends Component {
contract: true,
timestamp: Date.now(),
deleted: false,
source,
description
blockNumber,
description,
source
})
])
.then(() => {

View File

@@ -133,7 +133,7 @@ export default class Store {
}
testPassword = (password) => {
this.setBusy(false);
this.setBusy(true);
return this._api.parity
.testPassword(this.address, password || this.validatePassword)

View File

@@ -86,7 +86,7 @@ export default class Balances {
// If syncing, only retrieve balances once every
// few seconds
if (syncing) {
this.shortThrottledFetch();
this.shortThrottledFetch.cancel();
return this.longThrottledFetch();
}

View File

@@ -173,18 +173,15 @@ export function fetchTokens (_tokenIds) {
export function fetchBalances (_addresses) {
return (dispatch, getState) => {
const { api, personal } = getState();
const { visibleAccounts, accountsInfo } = personal;
const { visibleAccounts, accounts } = personal;
const addresses = uniq((_addresses || visibleAccounts || []).concat(Object.keys(accountsInfo)));
if (addresses.length === 0) {
return Promise.resolve();
}
const addresses = uniq(_addresses || visibleAccounts || []);
// With only a single account, more info will be displayed.
const fullFetch = addresses.length === 1;
const addressesToFetch = uniq(addresses);
// Add accounts addresses (for notifications, accounts selection, etc.)
const addressesToFetch = uniq(addresses.concat(Object.keys(accounts)));
return Promise
.all(addressesToFetch.map((addr) => fetchAccount(addr, api, fullFetch)))

View File

@@ -218,6 +218,7 @@ export default class CertificationsMiddleware {
const _addresses = action.addresses || [];
addresses = uniq(addresses.concat(_addresses));
fetchConfirmedEvents();
next(action);
break;
default:

View File

@@ -1,69 +0,0 @@
// Copyright 2015, 2016 Parity Technologies (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 PromiseWorker from 'promise-worker';
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
let workerRegistration;
// Setup the Service Worker
if ('serviceWorker' in navigator) {
workerRegistration = runtime
.register()
.then(() => navigator.serviceWorker.ready)
.then((registration) => {
const _worker = registration.active;
_worker.controller = registration.active;
const worker = new PromiseWorker(_worker);
return worker;
});
} else {
workerRegistration = Promise.reject('Service Worker is not available in your browser.');
}
export function setWorker (worker) {
return {
type: 'setWorker',
worker
};
}
export function setError (error) {
return {
type: 'setError',
error
};
}
export function setupWorker () {
return (dispatch, getState) => {
const state = getState();
if (state.compiler.worker) {
return;
}
workerRegistration
.then((worker) => {
dispatch(setWorker(worker));
})
.catch((error) => {
console.error('sw', error);
dispatch(setWorker(null));
});
};
}

View File

@@ -22,7 +22,7 @@ export Status from './status';
export apiReducer from './apiReducer';
export balancesReducer from './balancesReducer';
export blockchainReducer from './blockchainReducer';
export compilerReducer from './compilerReducer';
export workerReducer from './workerReducer';
export imagesReducer from './imagesReducer';
export personalReducer from './personalReducer';
export signerReducer from './signerReducer';

View File

@@ -122,7 +122,7 @@ export function setVisibleAccounts (addresses) {
return;
}
dispatch(fetchBalances(addresses));
dispatch(_setVisibleAccounts(addresses));
dispatch(fetchBalances(addresses));
};
}

View File

@@ -47,7 +47,7 @@ export default handleActions({
setVisibleAccounts (state, action) {
const addresses = (action.addresses || []).sort();
if (isEqual(addresses, state.addresses)) {
if (isEqual(addresses, state.visibleAccounts)) {
return state;
}

View File

@@ -17,7 +17,7 @@
import * as actions from './signerActions';
import { inHex } from '~/api/format/input';
import { Wallet } from '../../util/wallet';
import { Signer } from '../../util/signer';
export default class SignerMiddleware {
constructor (api) {
@@ -58,6 +58,7 @@ export default class SignerMiddleware {
promise
.then((txHash) => {
console.log('confirmRequest', id, txHash);
if (!txHash) {
store.dispatch(actions.errorConfirmRequest({ id, err: 'Unable to confirm.' }));
return;
@@ -73,33 +74,49 @@ export default class SignerMiddleware {
// Sign request in-browser
const transaction = payload.sendTransaction || payload.signTransaction;
if (wallet && transaction) {
(transaction.nonce.isZero()
const noncePromise = transaction.nonce.isZero()
? this._api.parity.nextNonce(transaction.from)
: Promise.resolve(transaction.nonce)
).then(nonce => {
let txData = {
to: inHex(transaction.to),
nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce),
gasPrice: inHex(transaction.gasPrice),
gasLimit: inHex(transaction.gas),
value: inHex(transaction.value),
data: inHex(transaction.data)
};
: Promise.resolve(transaction.nonce);
try {
// NOTE: Derving the key takes significant amount of time,
// make sure to display some kind of "in-progress" state.
const signer = Wallet.fromJson(wallet, password);
const rawTx = signer.signTransaction(txData);
const { worker } = store.getState().worker;
handlePromise(this._api.signer.confirmRequestRaw(id, rawTx));
} catch (error) {
console.error(error);
const signerPromise = worker && worker._worker.state === 'activated'
? worker
.postMessage({
action: 'getSignerSeed',
data: { wallet, password }
})
.then((result) => {
const seed = Buffer.from(result.data);
return new Signer(seed);
})
: Signer.fromJson(wallet, password);
// NOTE: Derving the key takes significant amount of time,
// make sure to display some kind of "in-progress" state.
return Promise
.all([ signerPromise, noncePromise ])
.then(([ signer, nonce ]) => {
const txData = {
to: inHex(transaction.to),
nonce: inHex(transaction.nonce.isZero() ? nonce : transaction.nonce),
gasPrice: inHex(transaction.gasPrice),
gasLimit: inHex(transaction.gas),
value: inHex(transaction.value),
data: inHex(transaction.data)
};
return signer.signTransaction(txData);
})
.then((rawTx) => {
return handlePromise(this._api.signer.confirmRequestRaw(id, rawTx));
})
.catch((error) => {
console.error(error.message);
store.dispatch(actions.errorConfirmRequest({ id, err: error.message }));
}
});
return;
});
}
handlePromise(this._api.signer.confirmRequest(id, { gas, gasPrice }, password));

View File

@@ -125,12 +125,13 @@ export default class Status {
this._store.dispatch(statusCollection(status));
this._status = status;
}
nextTimeout();
})
.catch((error) => {
console.error('_pollStatus', error);
nextTimeout();
});
nextTimeout();
}
/**

View File

@@ -0,0 +1,68 @@
// Copyright 2015, 2016 Parity Technologies (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 PromiseWorker from 'promise-worker';
import runtime from 'serviceworker-webpack-plugin/lib/runtime';
import { setWorker } from './workerActions';
function getWorker () {
// Setup the Service Worker
if ('serviceWorker' in navigator) {
return runtime
.register()
.then(() => navigator.serviceWorker.ready)
.then((registration) => {
const worker = registration.active;
worker.controller = registration.active;
return new PromiseWorker(worker);
});
}
return Promise.reject('Service Worker is not available in your browser.');
}
export const setupWorker = (store) => {
const { dispatch, getState } = store;
const state = getState();
const stateWorker = state.worker.worker;
if (stateWorker !== undefined && !(stateWorker && stateWorker._worker.state === 'redundant')) {
return;
}
getWorker()
.then((worker) => {
if (worker) {
worker._worker.addEventListener('statechange', (event) => {
console.warn('worker state changed to', worker._worker.state);
// Re-install the new Worker
if (worker._worker.state === 'redundant') {
setupWorker(store);
}
});
}
dispatch(setWorker(worker));
})
.catch((error) => {
console.error('sw', error);
dispatch(setWorker(null));
});
};

View File

@@ -14,4 +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/>.
export default from './summary';
export function setWorker (worker) {
return {
type: 'setWorker',
worker
};
}
export function setError (error) {
return {
type: 'setError',
error
};
}

View File

@@ -24,7 +24,7 @@ const initialState = {
export default handleActions({
setWorker (state, action) {
const { worker } = action;
return Object.assign({}, state, { worker });
return Object.assign({}, state, { worker: worker || null });
},
setError (state, action) {

View File

@@ -19,7 +19,7 @@ import { routerReducer } from 'react-router-redux';
import {
apiReducer, balancesReducer, blockchainReducer,
compilerReducer, imagesReducer, personalReducer,
workerReducer, imagesReducer, personalReducer,
signerReducer, statusReducer as nodeStatusReducer,
snackbarReducer, walletReducer
} from './providers';
@@ -41,13 +41,13 @@ export default function () {
balances: balancesReducer,
certifications: certificationsReducer,
blockchain: blockchainReducer,
compiler: compilerReducer,
images: imagesReducer,
nodeStatus: nodeStatusReducer,
personal: personalReducer,
registry: registryReducer,
signer: signerReducer,
snackbar: snackbarReducer,
wallet: walletReducer,
registry: registryReducer
worker: workerReducer
});
}

View File

@@ -20,6 +20,7 @@ import initMiddleware from './middleware';
import initReducers from './reducers';
import { load as loadWallet } from './providers/walletActions';
import { setupWorker } from './providers/worker';
import {
Balances as BalancesProvider,
@@ -43,6 +44,7 @@ export default function (api, browserHistory) {
new StatusProvider(store, api).start();
store.dispatch(loadWallet(api));
setupWorker(store);
return store;
}

View File

@@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import registerPromiseWorker from 'promise-worker/register';
import { Signer } from '~/util/signer';
import SolidityUtils from '~/util/solidity';
const CACHE_NAME = 'parity-cache-v1';
@@ -93,12 +94,21 @@ function handleMessage (message) {
case 'setFiles':
return setFiles(message.data);
case 'getSignerSeed':
return getSignerSeed(message.data);
default:
console.warn(`unknown action "${message.action}"`);
return null;
}
}
function getSignerSeed (data) {
console.log('deriving seed from service-worker');
const { wallet, password } = data;
return Signer.getSeed(wallet, password);
}
function compile (data) {
const { build } = data;

View File

@@ -71,15 +71,15 @@ export default class ActionbarSearch extends Component {
key='searchAccount'>
<div className={ inputContainerClasses.join(' ') }>
<InputChip
addOnBlur
className={ styles.input }
hint='Enter search input...'
ref='inputChip'
tokens={ tokens }
onBlur={ this.handleSearchBlur }
onInputChange={ this.handleInputChange }
onTokensChange={ this.handleTokensChange }
addOnBlur
/>
</div>
@@ -118,6 +118,10 @@ export default class ActionbarSearch extends Component {
handleSearchClick = () => {
const { showSearch } = this.state;
if (!showSearch) {
this.refs.inputChip.focus();
}
this.handleOpenSearch(!showSearch);
}

View File

@@ -27,13 +27,14 @@
}
.toolbuttons {
}
overflow: hidden;
.toolbuttons button {
margin: 10px 0 10px 16px !important;
color: white !important;
}
button {
margin: 10px 0 10px 16px !important;
color: white !important;
}
.toolbuttons svg {
fill: white !important;
svg {
fill: white !important;
}
}

View File

@@ -23,7 +23,7 @@
.empty {
line-height: 24px;
margin: 0.75em 0.5em 0 0;
margin: 0 0.5em 0 0;
opacity: 0.25;
}

View File

@@ -14,7 +14,7 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.byline {
.byline, .description {
overflow: hidden;
position: relative;
line-height: 1.2em;
@@ -31,6 +31,11 @@
}
}
.description {
font-size: 0.75em;
margin: 0.5em 0 0;
}
.title {
text-transform: uppercase;
margin: 0;

View File

@@ -22,13 +22,14 @@ import styles from './title.css';
export default class Title extends Component {
static propTypes = {
byline: nodeOrStringProptype(),
className: PropTypes.string,
title: nodeOrStringProptype(),
byline: nodeOrStringProptype()
description: nodeOrStringProptype(),
title: nodeOrStringProptype()
}
render () {
const { className, title, byline } = this.props;
const { byline, className, title } = this.props;
const byLine = typeof byline === 'string'
? (
@@ -46,6 +47,29 @@ export default class Title extends Component {
<div className={ styles.byline }>
{ byLine }
</div>
{ this.renderDescription() }
</div>
);
}
renderDescription () {
const { description } = this.props;
if (!description) {
return null;
}
const desc = typeof description === 'string'
? (
<span title={ description }>
{ description }
</span>
)
: description;
return (
<div className={ styles.description }>
{ desc }
</div>
);
}

View File

@@ -170,6 +170,10 @@ export default class InputChip extends Component {
.filter(v => v !== value));
this.handleTokensChange(newTokens);
this.focus();
}
focus = () => {
this.refs.chipInput.focus();
}

View File

@@ -38,6 +38,10 @@
justify-content: center;
}
.details {
line-height: 1.75em;
}
.details,
.gasDetails {
color: #aaa;

View File

@@ -196,7 +196,7 @@ class MethodDecoding extends Component {
: text.slice(0, 50) + '...';
return (
<div>
<div className={ styles.details }>
<span>with the </span>
<span
onClick={ this.toggleInputType }

View File

@@ -24,9 +24,26 @@ import { sha3 } from '~/api/util/sha3';
// Adapted from https://github.com/kvhnuke/etherwallet/blob/mercury/app/scripts/myetherwallet.js
export class Wallet {
export class Signer {
static fromJson (json, password) {
return Signer
.getSeed(json, password)
.then((seed) => {
return new Signer(seed);
});
}
static getSeed (json, password) {
try {
const seed = Signer.getSyncSeed(json, password);
return Promise.resolve(seed);
} catch (error) {
return Promise.reject(error);
}
}
static getSyncSeed (json, password) {
if (json.version !== 3) {
throw new Error('Only V3 wallets are supported');
}
@@ -43,15 +60,17 @@ export class Wallet {
if (kdfparams.prf !== 'hmac-sha256') {
throw new Error('Unsupported parameters to PBKDF2');
}
derivedKey = pbkdf2Sync(pwd, salt, kdfparams.c, kdfparams.dklen, 'sha256');
} else {
throw new Error('Unsupported key derivation scheme');
}
const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex');
let mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext]));
if (mac !== inHex(json.crypto.mac)) {
throw new Error('Key derivation failed - possibly wrong passphrase');
throw new Error('Key derivation failed - possibly wrong password');
}
const decipher = createDecipheriv(
@@ -59,6 +78,7 @@ export class Wallet {
derivedKey.slice(0, 16),
Buffer.from(json.crypto.cipherparams.iv, 'hex')
);
let seed = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
while (seed.length < 32) {
@@ -66,7 +86,7 @@ export class Wallet {
seed = Buffer.concat([nullBuff, seed]);
}
return new Wallet(seed);
return seed;
}
constructor (seed) {

View File

@@ -57,7 +57,7 @@ class List extends Component {
}
renderAccounts () {
const { accounts, balances, empty, link, handleAddSearchToken } = this.props;
const { accounts, balances, empty } = this.props;
if (empty) {
return (
@@ -80,20 +80,29 @@ class List extends Component {
return (
<div
className={ styles.item }
key={ address }>
<Summary
link={ link }
account={ account }
balance={ balance }
owners={ owners }
handleAddSearchToken={ handleAddSearchToken }
showCertifications
/>
key={ address }
>
{ this.renderSummary(account, balance, owners) }
</div>
);
});
}
renderSummary (account, balance, owners) {
const { handleAddSearchToken, link } = this.props;
return (
<Summary
account={ account }
balance={ balance }
handleAddSearchToken={ handleAddSearchToken }
link={ link }
owners={ owners }
showCertifications
/>
);
}
getAddresses () {
const filteredAddresses = this.getFilteredAddresses();
return this.sortAddresses(filteredAddresses);
@@ -122,7 +131,15 @@ class List extends Component {
});
}
compareAccounts (accountA, accountB, key) {
compareAccounts (accountA, accountB, key, _reverse = null) {
if (key && key.split(':')[1] === '-1') {
return this.compareAccounts(accountA, accountB, key.split(':')[0], true);
}
if (key === 'timestamp' && _reverse === null) {
return this.compareAccounts(accountA, accountB, key, true);
}
if (key === 'name') {
return accountA.name.localeCompare(accountB.name);
}
@@ -177,7 +194,9 @@ class List extends Component {
return tagsA.localeCompare(tagsB);
}
const reverse = key === 'timestamp' ? -1 : 1;
const reverse = _reverse
? -1
: 1;
const metaA = accountA.meta[key];
const metaB = accountB.meta[key];
@@ -220,8 +239,8 @@ class List extends Component {
const tags = account.meta.tags || [];
const name = account.name || '';
const values = []
.concat(tags, name)
const values = tags
.concat(name)
.map(v => v.toLowerCase());
return searchValues

View File

@@ -19,6 +19,7 @@ import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { isEqual } from 'lodash';
import ReactTooltip from 'react-tooltip';
import { FormattedMessage } from 'react-intl';
import { Balance, Container, ContainerTitle, IdentityIcon, IdentityName, Tags, Input } from '~/ui';
import Certifications from '~/ui/Certifications';
@@ -107,14 +108,22 @@ export default class Summary extends Component {
/>
);
const description = this.getDescription(account.meta);
return (
<Container>
<Tags tags={ tags } handleAddSearchToken={ handleAddSearchToken } />
<IdentityIcon
address={ address } />
<ContainerTitle
title={ this.renderLink() }
byline={ addressComponent } />
<div className={ styles.heading }>
<IdentityIcon
address={ address }
/>
<ContainerTitle
byline={ addressComponent }
className={ styles.main }
description={ description }
title={ this.renderLink() }
/>
</div>
{ this.renderOwners() }
{ this.renderBalance() }
@@ -123,6 +132,26 @@ export default class Summary extends Component {
);
}
getDescription (meta = {}) {
const { blockNumber } = meta;
if (!blockNumber) {
return null;
}
const formattedBlockNumber = (new BigNumber(blockNumber)).toFormat();
return (
<FormattedMessage
id='accounts.summary.minedBlock'
defaultMessage='Mined at block #{blockNumber}'
values={ {
blockNumber: formattedBlockNumber
} }
/>
);
}
renderOwners () {
const { owners } = this.props;
const ownersValid = (owners || []).filter((owner) => owner.address && new BigNumber(owner.address).gt(0));

View File

@@ -56,3 +56,12 @@
}
}
}
.heading {
display: flex;
flex-direction: row;
.main {
flex: 1;
}
}

View File

@@ -74,6 +74,14 @@ export default class Events extends Component {
return (
<Container title='events'>
<table className={ styles.events }>
<thead>
<tr>
<th />
<th className={ styles.origin }>
origin
</th>
</tr>
</thead>
<tbody>{ list }</tbody>
</table>
</Container>

View File

@@ -29,14 +29,36 @@
.event {
td {
vertical-align: top;
padding: 1em 0.5em;
padding: 0 0.5em 1.5em;
div {
white-space: nowrap;
}
&.timestamp {
padding-right: 1.5em;
text-align: right;
line-height: 1.5em;
opacity: 0.5;
white-space: nowrap;
}
}
}
.blockNumber {
color: rgba(255, 255, 255, 0.25);
margin-top: 1.5em;
}
.origin {
text-align: left;
padding-left: 32px;
text-indent: 1em;
color: rgba(255, 255, 255, 0.5);
text-transform: uppercase;
font-size: 0.9em;
}
.txhash {
text-overflow: ellipsis;
width: 20%;
@@ -54,14 +76,6 @@
opacity: 0.5;
}
.timestamp {
padding-top: 1.5em;
text-align: right;
line-height: 1.5em;
opacity: 0.5;
white-space: nowrap;
}
.eventDetails {
}

View File

@@ -17,6 +17,9 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { FormattedMessage } from 'react-intl';
import BigNumber from 'bignumber.js';
import ActionDelete from 'material-ui/svg-icons/action/delete';
import AvPlayArrow from 'material-ui/svg-icons/av/play-arrow';
import ContentCreate from 'material-ui/svg-icons/content/create';
@@ -136,7 +139,9 @@ class Contract extends Component {
account={ account }
balance={ balance }
isContract
/>
>
{ this.renderBlockNumber(account.meta) }
</Header>
<Queries
accountsInfo={ accountsInfo }
@@ -156,6 +161,28 @@ class Contract extends Component {
);
}
renderBlockNumber (meta = {}) {
const { blockNumber } = meta;
if (!blockNumber) {
return null;
}
const formattedBlockNumber = (new BigNumber(blockNumber)).toFormat();
return (
<div className={ styles.blockNumber }>
<FormattedMessage
id='contract.minedBlock'
defaultMessage='Mined at block #{blockNumber}'
values={ {
blockNumber: formattedBlockNumber
} }
/>
</div>
);
}
renderDetails (contract) {
const { showDetailsDialog } = this.state;

View File

@@ -1,52 +0,0 @@
// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { Container, ContainerTitle, IdentityIcon, IdentityName } from '~/ui';
export default class Summary extends Component {
static contextTypes = {
api: React.PropTypes.object.isRequired
}
static propTypes = {
contract: PropTypes.object.isRequired,
children: PropTypes.node
}
render () {
const contract = this.props.contract;
if (!contract) {
return null;
}
const viewLink = `/app/${contract.address}`;
return (
<Container>
<IdentityIcon
address={ contract.address } />
<ContainerTitle
title={ <Link to={ viewLink }>{ <IdentityName address={ contract.address } unknown /> }</Link> }
byline={ contract.address } />
{ this.props.children }
</Container>
);
}
}

View File

@@ -45,7 +45,7 @@ class Contracts extends Component {
state = {
addContract: false,
deployContract: false,
sortOrder: 'timestamp',
sortOrder: 'blockNumber',
searchValues: [],
searchTokens: []
}
@@ -92,7 +92,8 @@ class Contracts extends Component {
empty={ !hasContracts }
order={ sortOrder }
orderFallback='name'
handleAddSearchToken={ this.onAddSearchToken } />
handleAddSearchToken={ this.onAddSearchToken }
/>
</Page>
</div>
);
@@ -109,7 +110,8 @@ class Contracts extends Component {
id='sortContracts'
order={ this.state.sortOrder }
metas={ [
{ key: 'timestamp', label: 'date' }
{ key: 'timestamp', label: 'date' },
{ key: 'blockNumber:-1', label: 'mined block' }
] }
showDefault={ false }
onChange={ onChange } />

View File

@@ -77,13 +77,28 @@ class TransactionPendingFormConfirm extends Component {
}
}
getPasswordHint () {
const { account } = this.props;
const accountHint = account && account.meta && account.meta.passwordHint;
if (accountHint) {
return accountHint;
}
const { wallet } = this.state;
const walletHint = wallet && wallet.meta && wallet.meta.passwordHint;
return walletHint || null;
}
render () {
const { account, address, isSending } = this.props;
const { password, wallet, walletError } = this.state;
const isExternal = !account.uuid;
const passwordHint = account.meta && account.meta.passwordHint
? (<div><span>(hint) </span>{ account.meta.passwordHint }</div>)
const passwordHintText = this.getPasswordHint();
const passwordHint = passwordHintText
? (<div><span>(hint) </span>{ passwordHintText }</div>)
: null;
const isWalletOk = !isExternal || (walletError === null && wallet !== null);
@@ -170,12 +185,26 @@ class TransactionPendingFormConfirm extends Component {
}
onKeySelect = (event) => {
// Check that file have been selected
if (event.target.files.length === 0) {
return this.setState({
wallet: null,
walletError: null
});
}
const fileReader = new FileReader();
fileReader.onload = (e) => {
try {
const wallet = JSON.parse(e.target.result);
try {
if (wallet && typeof wallet.meta === 'string') {
wallet.meta = JSON.parse(wallet.meta);
}
} catch (e) {}
this.setState({
wallet,
walletError: null

View File

@@ -18,7 +18,6 @@ import React, { PropTypes, Component } from 'react';
import { observer } from 'mobx-react';
import { MenuItem, Toggle } from 'material-ui';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import CircularProgress from 'material-ui/CircularProgress';
import moment from 'moment';
import { throttle } from 'lodash';
@@ -32,8 +31,6 @@ import SendIcon from 'material-ui/svg-icons/content/send';
import { Actionbar, ActionbarExport, ActionbarImport, Button, Editor, Page, Select, Input } from '~/ui';
import { DeployContract, SaveContract, LoadContract } from '~/modals';
import { setupWorker } from '~/redux/providers/compilerActions';
import WriteContractStore from './writeContractStore';
import styles from './writeContract.css';
@@ -42,7 +39,6 @@ class WriteContract extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
setupWorker: PropTypes.func.isRequired,
worker: PropTypes.object,
workerError: PropTypes.any
};
@@ -55,8 +51,7 @@ class WriteContract extends Component {
};
componentWillMount () {
const { setupWorker, worker } = this.props;
setupWorker();
const { worker } = this.props;
if (worker !== undefined) {
this.store.setWorker(worker);
@@ -575,17 +570,10 @@ class WriteContract extends Component {
function mapStateToProps (state) {
const { accounts } = state.personal;
const { worker, error } = state.compiler;
const { worker, error } = state.worker;
return { accounts, worker, workerError: error };
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
setupWorker
}, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps
)(WriteContract);