diff --git a/Cargo.lock b/Cargo.lock index 472e11fe5..2a4c20835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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)", ] diff --git a/js/package.json b/js/package.json index e798e3334..750030578 100644 --- a/js/package.json +++ b/js/package.json @@ -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 ", diff --git a/js/src/shared/redux/middleware.js b/js/src/shared/redux/middleware.js index db61e0861..32abb3d53 100644 --- a/js/src/shared/redux/middleware.js +++ b/js/src/shared/redux/middleware.js @@ -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); } diff --git a/js/src/shared/redux/providers/balances.js b/js/src/shared/redux/providers/balances.js index 57d9cb4fd..568e63f3f 100644 --- a/js/src/shared/redux/providers/balances.js +++ b/js/src/shared/redux/providers/balances.js @@ -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); diff --git a/js/src/shared/redux/providers/certifications/middleware.js b/js/src/shared/redux/providers/certifications/middleware.js index ef661d438..fa47c0f7e 100644 --- a/js/src/shared/redux/providers/certifications/middleware.js +++ b/js/src/shared/redux/providers/certifications/middleware.js @@ -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); diff --git a/js/src/shared/redux/providers/registry/middleware.js b/js/src/shared/redux/providers/registry/middleware.js index b0a21038a..8e3d3e0a3 100644 --- a/js/src/shared/redux/providers/registry/middleware.js +++ b/js/src/shared/redux/providers/registry/middleware.js @@ -15,131 +15,150 @@ // along with Parity. If not, see . 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); +} diff --git a/js/src/shared/redux/providers/statusReducer.js b/js/src/shared/redux/providers/statusReducer.js index 12fe9654d..3a6968548 100644 --- a/js/src/shared/redux/providers/statusReducer.js +++ b/js/src/shared/redux/providers/statusReducer.js @@ -33,7 +33,7 @@ const initialState = { netVersion: '0', nodeKind: null, nodeKindFull: null, - syncing: true, + syncing: null, isConnected: false, isConnecting: false, isTest: undefined, diff --git a/js/src/shared/util/messages.js b/js/src/shared/util/messages.js new file mode 100644 index 000000000..c34028f72 --- /dev/null +++ b/js/src/shared/util/messages.js @@ -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 . + +/** + * 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; +} diff --git a/js/src/shell/Application/application.js b/js/src/shell/Application/application.js index 8e32d0608..e27cbde5c 100644 --- a/js/src/shell/Application/application.js +++ b/js/src/shell/Application/application.js @@ -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 + ? + : null + } diff --git a/js/src/shell/SyncWarning/index.js b/js/src/shell/SyncWarning/index.js new file mode 100644 index 000000000..098605b2a --- /dev/null +++ b/js/src/shell/SyncWarning/index.js @@ -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 . + +export default, { showSyncWarning } from './syncWarning'; diff --git a/js/src/shell/SyncWarning/syncWarning.css b/js/src/shell/SyncWarning/syncWarning.css new file mode 100644 index 000000000..828036499 --- /dev/null +++ b/js/src/shell/SyncWarning/syncWarning.css @@ -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 . +*/ + +.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; + } +} diff --git a/js/src/shell/SyncWarning/syncWarning.js b/js/src/shell/SyncWarning/syncWarning.js new file mode 100644 index 000000000..67deff075 --- /dev/null +++ b/js/src/shell/SyncWarning/syncWarning.js @@ -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 . + +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 ( +
+
+
+
+ + + +
+ + } + checked={ dontShowAgain } + onCheck={ this.handleCheck } + /> +
+
+
+
+ ); + } + + 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); diff --git a/js/src/shell/SyncWarning/syncWarning.spec.js b/js/src/shell/SyncWarning/syncWarning.spec.js new file mode 100644 index 000000000..2a52fb6c8 --- /dev/null +++ b/js/src/shell/SyncWarning/syncWarning.spec.js @@ -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 . + +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( + , + { 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); + }); +}); diff --git a/js/src/ui/Button/button.css b/js/src/ui/Button/button.css new file mode 100644 index 000000000..45c235929 --- /dev/null +++ b/js/src/ui/Button/button.css @@ -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 . +*/ + +.tooltip { + text-transform: uppercase; +} diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js index cb6883c33..cf7a21621 100644 --- a/js/src/ui/Form/AddressSelect/addressSelect.js +++ b/js/src/ui/Form/AddressSelect/addressSelect.js @@ -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 ( diff --git a/js/src/ui/MethodDecoding/methodDecoding.js b/js/src/ui/MethodDecoding/methodDecoding.js index 3da09fdc2..ea3f39cb6 100644 --- a/js/src/ui/MethodDecoding/methodDecoding.js +++ b/js/src/ui/MethodDecoding/methodDecoding.js @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +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 = ( - #{ condition.block.toFormat(0) } + #{ blockCondition.toFormat(0) } ); diff --git a/js/src/views/ContractDevelop/contractDevelop.js b/js/src/views/ContractDevelop/contractDevelop.js index 1af924f0e..0d7345215 100644 --- a/js/src/views/ContractDevelop/contractDevelop.js +++ b/js/src/views/ContractDevelop/contractDevelop.js @@ -347,12 +347,13 @@ class ContractDevelop extends Component { } onClick={ this.store.handleCompile } primary={ false } - disabled={ compiling } + disabled={ compiling || this.store.isPristine } /> { contract ? (