Merge branch 'master' into ui-2

# Conflicts:
#	js/src/shared/redux/middleware.js
#	js/src/shared/redux/providers/registry/middleware.js
#	js/src/shell/Application/application.js
#	js/src/ui/Actionbar/actionbar.js
#	js/src/ui/Button/button.js
#	js/src/ui/Form/AddressSelect/addressSelect.js
#	js/src/ui/Form/Input/input.js
#	js/src/ui/MethodDecoding/methodDecoding.js
This commit is contained in:
Jaco Greeff 2017-05-10 17:29:07 +02:00
commit 566b6a1967
19 changed files with 493 additions and 129 deletions

2
Cargo.lock generated
View File

@ -1781,7 +1781,7 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/paritytech/js-precompiled.git#cff0aec1877a4b75f51de3facee9fe439a41a90d"
source = "git+https://github.com/paritytech/js-precompiled.git#eef7f1ac0dfc44527ef1925968235c5e69add959"
dependencies = [
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

View File

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

View File

@ -37,8 +37,8 @@ export default function (api, browserHistory, forEmbed = false) {
];
if (!forEmbed) {
const certifications = new CertificationsMiddleware().toMiddleware(api);
const registry = new RegistryMiddleware(api);
const certifications = new CertificationsMiddleware(api).toMiddleware();
const registry = new RegistryMiddleware(api).toMiddleware();
middleware.push(certifications, registry);
}

View File

@ -241,7 +241,7 @@ export default class Balances {
// If syncing, only retrieve balances once every
// few seconds
if (syncing) {
if (syncing || syncing === null) {
this.shortThrottledFetch.cancel();
this.longThrottledFetch(skipNotifications);

View File

@ -58,10 +58,14 @@ const updatableFilter = (api, onFilter) => {
};
export default class CertificationsMiddleware {
toMiddleware (api) {
const badgeReg = Contracts.get(api).badgeReg;
constructor (api) {
this._api = api;
}
const contract = new Contract(api, CertifierABI);
toMiddleware () {
const badgeReg = Contracts.get(this._api).badgeReg;
const contract = new Contract(this._api, CertifierABI);
const Confirmed = contract.events.find((e) => e.name === 'Confirmed');
const Revoked = contract.events.find((e) => e.name === 'Revoked');
@ -73,12 +77,12 @@ export default class CertificationsMiddleware {
let badgeRegFilter = null;
let fetchCertifiersPromise = null;
const updateFilter = updatableFilter(api, (filterId) => {
const updateFilter = updatableFilter(this._api, (filterId) => {
filterChanged = true;
filter = filterId;
});
const badgeRegUpdateFilter = updatableFilter(api, (filterId) => {
const badgeRegUpdateFilter = updatableFilter(this._api, (filterId) => {
filterChanged = true;
badgeRegFilter = filterId;
});
@ -96,7 +100,7 @@ export default class CertificationsMiddleware {
.then(() => {
shortFetchChanges();
api.subscribe('eth_blockNumber', (err) => {
this._api.subscribe('eth_blockNumber', (err) => {
if (err) {
return;
}
@ -141,12 +145,12 @@ export default class CertificationsMiddleware {
filterChanged = false;
api.eth[method](badgeRegFilter)
this._api.eth[method](badgeRegFilter)
.then(onBadgeRegLogs)
.catch((err) => {
console.error('Failed to fetch badge reg events:', err);
})
.then(() => api.eth[method](filter))
.then(() => this._api.eth[method](filter))
.then(onLogs)
.catch((err) => {
console.error('Failed to fetch new certifier events:', err);

View File

@ -15,131 +15,150 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { debounce } from 'lodash';
import store from 'store';
import lsstore from 'store';
import Contracts from '@parity/shared/contracts';
import registryABI from '@parity/shared/contracts/abi/registry.json';
import subscribeToEvents from '@parity/shared/util/subscribe-to-events';
import Contracts from '~/shared/contracts';
import registryABI from '~/shared/contracts/abi/registry.json';
import subscribeToEvents from '~/shared/util/subscribe-to-events';
import { setReverse } from './actions';
import { setReverse, startCachingReverses } from './actions';
const STORE_KEY = '_parity::reverses';
const read = (chain) => {
const reverses = store.get(`${STORE_KEY}::${chain}::data`);
const lastBlock = store.get(`${STORE_KEY}::${chain}::lastBlock`);
export default class RegistryMiddleware {
contract;
interval;
store;
subscription;
timeout;
if (!reverses || !lastBlock) {
return null;
addressesToCheck = {};
constructor (api) {
this._api = api;
}
return { reverses, lastBlock };
};
const write = debounce((getChain, getReverses, getLastBlock) => {
const chain = getChain();
const reverses = getReverses();
const lastBlock = getLastBlock();
toMiddleware () {
return (store) => {
this.store = store;
store.set(`${STORE_KEY}::${chain}::data`, reverses);
store.set(`${STORE_KEY}::${chain}::lastBlock`, lastBlock);
}, 20000);
return (next) => (action) => {
switch (action.type) {
case 'initAll':
next(action);
store.dispatch(startCachingReverses());
break;
export default (api) => (store) => {
let contract;
let subscription;
let timeout;
let interval;
case 'startCachingReverses':
this.cacheReverses();
break;
let addressesToCheck = {};
case 'stopCachingReverses':
if (this.subscription) {
this.subscription.unsubscribe();
}
if (this.interval) {
clearInterval(this.interval);
}
if (this.timeout) {
clearTimeout(this.timeout);
}
const onLog = (log) => {
switch (log.event) {
case 'ReverseConfirmed':
addressesToCheck[log.params.reverse.value] = true;
this.write.flush();
break;
break;
case 'ReverseRemoved':
delete addressesToCheck[log.params.reverse.value];
case 'setReverse':
this.write(
() => store.getState().nodeStatus.netChain,
() => store.getState().registry.reverse,
() => +store.getState().nodeStatus.blockNumber
);
next(action);
break;
break;
default:
next(action);
}
};
};
}
cacheReverses () {
const { registry } = Contracts.get();
const cached = this.read(this.store.getState().nodeStatus.netChain);
if (cached) {
Object
.entries(cached.reverses)
.forEach(([ address, reverse ]) => this.store.dispatch(setReverse(address, reverse)));
}
};
const checkReverses = () => {
registry.getInstance()
.then((instance) => this._api.newContract(registryABI, instance.address))
.then((_contract) => {
this.contract = _contract;
this.subscription = subscribeToEvents(this.contract, [
'ReverseConfirmed', 'ReverseRemoved'
], {
from: cached ? cached.lastBlock : 0
});
this.subscription.on('log', this.onLog);
this.timeout = setTimeout(this.checkReverses, 10000);
this.interval = setInterval(this.checkReverses, 20000);
})
.catch((err) => {
console.error('Failed to start caching reverses:', err);
throw err;
});
}
checkReverses = () => {
Object
.keys(addressesToCheck)
.keys(this.addressesToCheck)
.forEach((address) => {
contract
this.contract
.instance
.reverse
.call({}, [ address ])
.then((reverse) => {
store.dispatch(setReverse(address, reverse));
this.store.dispatch(setReverse(address, reverse));
});
});
addressesToCheck = {};
this.addressesToCheck = {};
};
return (next) => (action) => {
switch (action.type) {
case 'startCachingReverses':
const { registry } = Contracts.get(api);
const cached = read(store.getState().nodeStatus.netChain);
if (cached) {
Object
.entries(cached.reverses)
.forEach(([ address, reverse ]) => store.dispatch(setReverse(address, reverse)));
}
registry.getInstance()
.then((instance) => api.newContract(registryABI, instance.address))
.then((_contract) => {
contract = _contract;
subscription = subscribeToEvents(_contract, [
'ReverseConfirmed', 'ReverseRemoved'
], {
from: cached ? cached.lastBlock : 0
});
subscription.on('log', onLog);
timeout = setTimeout(checkReverses, 10000);
interval = setInterval(checkReverses, 20000);
})
.catch((err) => {
console.error('Failed to start caching reverses:', err);
throw err;
});
onLog = (log) => {
switch (log.event) {
case 'ReverseConfirmed':
this.addressesToCheck[log.params.reverse.value] = true;
break;
case 'ReverseRemoved':
delete this.addressesToCheck[log.params.reverse.value];
case 'stopCachingReverses':
if (subscription) {
subscription.unsubscribe();
}
if (interval) {
clearInterval(interval);
}
if (timeout) {
clearTimeout(timeout);
}
write.flush();
break;
case 'setReverse':
write(
() => store.getState().nodeStatus.netChain,
() => store.getState().registry.reverse,
() => +store.getState().nodeStatus.blockNumber
);
next(action);
break;
default:
next(action);
}
};
};
read = (chain) => {
const reverses = lsstore.get(`${STORE_KEY}::${chain}::data`);
const lastBlock = lsstore.get(`${STORE_KEY}::${chain}::lastBlock`);
if (!reverses || !lastBlock) {
return null;
}
return { reverses, lastBlock };
};
write = debounce((getChain, getReverses, getLastBlock) => {
const chain = getChain();
const reverses = getReverses();
const lastBlock = getLastBlock();
lsstore.set(`${STORE_KEY}::${chain}::data`, reverses);
lsstore.set(`${STORE_KEY}::${chain}::lastBlock`, lastBlock);
}, 20000);
}

View File

@ -33,7 +33,7 @@ const initialState = {
netVersion: '0',
nodeKind: null,
nodeKindFull: null,
syncing: true,
syncing: null,
isConnected: false,
isConnecting: false,
isTest: undefined,

View File

@ -0,0 +1,44 @@
// Copyright 2015-2017 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/>.
/**
* Convert a String or a FormattedMessage
* element into a string
*
* @param {Object} context - The React `context`
* @param {String|Object} value - A String or a FormattedMessage
* element
* @return {String}
*/
export function toString (context, value) {
if (!context.intl) {
console.warn(`remember to add:
static contextTypes = {
intl: React.PropTypes.object.isRequired
};
to your component`);
return value;
}
const textValue = typeof value !== 'string' && (value && value.props)
? context.intl.formatMessage(
value.props,
value.props.values || {}
)
: value || '';
return textValue;
}

View File

@ -30,11 +30,13 @@ import Snackbar from '../Snackbar';
import Status from '../Status';
import UpgradeParity from '../UpgradeParity';
import UpgradeStore from '../UpgradeParity/store';
import SyncWarning, { showSyncWarning } from '../SyncWarning';
import Store from './store';
import styles from './application.css';
const inFrame = window.parent !== window && window.parent.frames.length !== 0;
const doShowSyncWarning = showSyncWarning();
@observer
class Application extends Component {
@ -74,6 +76,11 @@ class Application extends Component {
? this.renderMinimized()
: this.renderApp()
}
{
doShowSyncWarning
? <SyncWarning />
: null
}
<Connection />
<Requests />
<ParityBar dapp={ isMinimized } />

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 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/>.
export default, { showSyncWarning } from './syncWarning';

View File

@ -0,0 +1,60 @@
/* Copyright 2015-2017 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/>.
*/
.overlay {
background: rgba(255, 255, 255, 0.75);
bottom: 0;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 10000;
}
.modal {
align-items: flex-start;
bottom: 0;
display: flex;
left: 0;
position: fixed;
right: 0;
top: 0;
z-index: 10001;
}
.body {
background: rgba(25, 25, 25, 0.75);
box-shadow: rgba(0, 0, 0, 0.25) 0 14px 45px, rgba(0, 0, 0, 0.22) 0 10px 18px;
color: rgb(208, 208, 208);
display: flex;
flex-direction: column;
margin: 0 auto;
max-width: 20em;
padding: 2em 4em;
text-align: center;
}
.button {
margin-top: 1em;
}
.body,
.button {
> * {
margin: 0.5em 0;
}
}

View File

@ -0,0 +1,130 @@
// Copyright 2015-2017 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 { Checkbox } from 'material-ui';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import store from 'store';
import { Button } from '~/ui';
import styles from './syncWarning.css';
const LS_DONT_SHOW_AGAIN = '_parity::syncWarning::dontShowAgain';
export const showSyncWarning = () => {
const dontShowAgain = store.get(LS_DONT_SHOW_AGAIN);
if (dontShowAgain === undefined || dontShowAgain === null) {
return true;
}
return !dontShowAgain;
};
class SyncWarning extends Component {
static propTypes = {
isSyncing: PropTypes.bool
};
state = {
dontShowAgain: false,
show: true
};
render () {
const { isSyncing } = this.props;
const { dontShowAgain, show } = this.state;
if (!isSyncing || isSyncing === null || !show) {
return null;
}
return (
<div>
<div className={ styles.overlay } />
<div className={ styles.modal }>
<div className={ styles.body }>
<FormattedMessage
id='syncWarning.message.line1'
defaultMessage={ `
Your Parity node is still syncing to the chain.
` }
/>
<FormattedMessage
id='syncWarning.message.line2'
defaultMessage={ `
Some of the shown information might be out-of-date.
` }
/>
<div className={ styles.button }>
<Checkbox
label={
<FormattedMessage
id='syncWarning.dontShowAgain.label'
defaultMessage='Do not show this warning again'
/>
}
checked={ dontShowAgain }
onCheck={ this.handleCheck }
/>
<Button
label={
<FormattedMessage
id='syncWarning.understandBtn.label'
defaultMessage='I understand'
/>
}
onClick={ this.handleAgreeClick }
/>
</div>
</div>
</div>
</div>
);
}
handleCheck = () => {
this.setState({ dontShowAgain: !this.state.dontShowAgain });
}
handleAgreeClick = () => {
if (this.state.dontShowAgain) {
store.set(LS_DONT_SHOW_AGAIN, true);
}
this.setState({ show: false });
}
}
function mapStateToProps (state) {
const { syncing } = state.nodeStatus;
// syncing could be an Object, false, or null
const isSyncing = syncing
? true
: syncing;
return {
isSyncing
};
}
export default connect(
mapStateToProps,
null
)(SyncWarning);

View File

@ -0,0 +1,57 @@
// Copyright 2015-2017 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 { shallow } from 'enzyme';
import React from 'react';
import SyncWarning from './';
let component;
function createRedux (syncing = null) {
return {
getState: () => {
return {
nodeStatus: {
syncing
}
};
}
};
}
function render (store) {
component = shallow(
<SyncWarning />,
{ context: { store: store || createRedux() } }
).find('SyncWarning').shallow();
return component;
}
describe('shell/SyncWarning', () => {
it('renders defaults', () => {
expect(render()).to.be.ok;
});
it('does render when syncing', () => {
expect(render(createRedux({})).find('div')).to.have.length.gte(1);
});
it('does not render when not syncing', () => {
expect(render(createRedux(false)).find('div')).to.have.length(0);
});
});

View File

@ -0,0 +1,20 @@
/* Copyright 2015-2017 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/>.
*/
.tooltip {
text-transform: uppercase;
}

View File

@ -24,6 +24,7 @@ import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
import apiutil from '@parity/api/util';
import { nodeOrStringProptype } from '@parity/shared/util/proptypes';
import { toString } from '@parity/shared/util/messages';
import { validateAddress } from '@parity/shared/util/validation';
import AccountCard from '~/ui/AccountCard';
@ -186,12 +187,7 @@ class AddressSelect extends Component {
}
const id = `addressSelect_${++currentId}`;
const ilHint = typeof hint === 'string' || !(hint && hint.props)
? (hint || '')
: this.context.intl.formatMessage(
hint.props,
hint.props.values || {}
);
const ilHint = toString(this.context, hint);
return (
<Portal

View File

@ -20,6 +20,7 @@ import { noop } from 'lodash';
import keycode from 'keycode';
import { nodeOrStringProptype } from '@parity/shared/util/proptypes';
import { toString } from '@parity/shared/util/messages';
import CopyToClipboard from '~/ui/CopyToClipboard';
@ -149,12 +150,7 @@ export default class Input extends Component {
? UNDERLINE_FOCUSED
: readOnly && typeof focused !== 'boolean' ? { display: 'none' } : null;
const textValue = typeof value !== 'string' && (value && value.props)
? this.context.intl.formatMessage(
value.props,
value.props.values || {}
)
: value;
const textValue = toString(this.context, value);
return (
<div className={ styles.container } style={ style }>

View File

@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import moment from 'moment';
import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
@ -177,10 +178,12 @@ class MethodDecoding extends Component {
return null;
}
if (condition.block && condition.block.gt(0)) {
const blockCondition = new BigNumber(condition.block || 0);
if (blockCondition.gt(0)) {
const blockNumber = (
<span className={ styles.highlight }>
#{ condition.block.toFormat(0) }
#{ blockCondition.toFormat(0) }
</span>
);

View File

@ -347,12 +347,13 @@ class ContractDevelop extends Component {
}
onClick={ this.store.handleCompile }
primary={ false }
disabled={ compiling }
disabled={ compiling || this.store.isPristine }
/>
{
contract
? (
<Button
disabled={ compiling || !this.store.isPristine }
icon={ <SendIcon /> }
label={
<FormattedMessage

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { debounce } from 'lodash';
import { action, observable, transaction } from 'mobx';
import { action, computed, observable, transaction } from 'mobx';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import store from 'store';
@ -140,7 +140,7 @@ export default class ContractDevelopStore {
this.worker = worker;
return Promise.all([
this.fetchSolidityVersions(),
this.fetchSolidityVersions().then(() => this.handleCompile()),
this.reloadContracts(undefined, undefined, false)
]);
}
@ -316,6 +316,18 @@ export default class ContractDevelopStore {
});
}
@computed get isPristine () {
return this.getHash() === this.lastCompilation.hash;
}
getHash () {
const build = this.builds[this.selectedBuild];
const version = build.longVersion;
const sourcecode = this.sourcecode.replace(/\s+/g, ' ');
return sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
}
@action handleCompile = () => {
transaction(() => {
this.compiled = false;
@ -324,9 +336,7 @@ export default class ContractDevelopStore {
});
const build = this.builds[this.selectedBuild];
const version = build.longVersion;
const sourcecode = this.sourcecode.replace(/\s+/g, ' ');
const hash = sha3(JSON.stringify({ version, sourcecode, optimize: this.optimize }));
const hash = this.getHash();
let promise = Promise.resolve(null);