Merge branch 'master' into rpc-middleware
This commit is contained in:
		
						commit
						3b595a01ad
					
				| @ -468,11 +468,9 @@ test-rust-stable: | ||||
|   tags: | ||||
|     - rust | ||||
|     - rust-stable | ||||
| test-rust-beta: | ||||
| js-test: | ||||
|   stage: test | ||||
|   only: | ||||
|     - triggers | ||||
|   image: ethcore/rust:beta | ||||
|   image: ethcore/rust:stable | ||||
|   before_script: | ||||
|     - git submodule update --init --recursive | ||||
|     - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l) | ||||
| @ -481,7 +479,21 @@ test-rust-beta: | ||||
|   script: | ||||
|     - export RUST_BACKTRACE=1 | ||||
|     - echo $JS_FILES_MODIFIED | ||||
|     - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"&./test.sh $CARGOFLAGS --no-release; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi | ||||
|     - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi | ||||
|   tags: | ||||
|     - rust | ||||
|     - rust-stable | ||||
| test-rust-beta: | ||||
|   stage: test | ||||
|   only: | ||||
|     - triggers | ||||
|   image: ethcore/rust:beta | ||||
|   before_script: | ||||
|     - git submodule update --init --recursive | ||||
|   script: | ||||
|     - export RUST_BACKTRACE=1 | ||||
|     - echo $JS_FILES_MODIFIED | ||||
|     - ./test.sh $CARGOFLAGS --no-release | ||||
|   tags: | ||||
|     - rust | ||||
|     - rust-beta | ||||
| @ -493,13 +505,9 @@ test-rust-nightly: | ||||
|   image: ethcore/rust:nightly | ||||
|   before_script: | ||||
|     - git submodule update --init --recursive | ||||
|     - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF^ $CI_BUILD_REF | grep \.js | wc -l) | ||||
|     - echo $JS_FILES_MODIFIED | ||||
|     - if [ "$JS_FILES_MODIFIED" = 0  ]; then echo "skip js test"; else ./js/scripts/install-deps.sh;fi | ||||
|   script: | ||||
|     - export RUST_BACKTRACE=1 | ||||
|     - echo $JS_FILES_MODIFIED | ||||
|     - if [ "$JS_FILES_MODIFIED" = 0 ]; then echo "skip js test"&./test.sh $CARGOFLAGS --no-release; else echo "skip rust test"&./js/scripts/lint.sh&./js/scripts/test.sh&./js/scripts/build.sh; fi | ||||
|     - ./test.sh $CARGOFLAGS --no-release | ||||
|   tags: | ||||
|     - rust | ||||
|     - rust-nightly | ||||
|  | ||||
| @ -1 +1 @@ | ||||
| Subproject commit e8f4624b7f1a15c63674eecf577c7ab76c3b16be | ||||
| Subproject commit d509c75936ec6cbba683ee1916aa0bca436bc376 | ||||
| @ -15,9 +15,9 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import BigNumber from 'bignumber.js'; | ||||
| import { range } from 'lodash'; | ||||
| 
 | ||||
| import { isArray, isHex, isInstanceOf, isString } from '../util/types'; | ||||
| import { padLeft } from '../util/format'; | ||||
| 
 | ||||
| export function inAddress (address) { | ||||
|   // TODO: address validation if we have upper-lower addresses
 | ||||
| @ -51,19 +51,20 @@ export function inHash (hash) { | ||||
|   return inHex(hash); | ||||
| } | ||||
| 
 | ||||
| export function pad (input, length) { | ||||
|   const value = inHex(input).substr(2, length * 2); | ||||
|   return '0x' + value + range(length * 2 - value.length).map(() => '0').join(''); | ||||
| } | ||||
| 
 | ||||
| export function inTopics (_topics) { | ||||
|   let topics = (_topics || []) | ||||
|     .filter((topic) => topic === null || topic) | ||||
|     .map((topic) => topic === null ? null : pad(topic, 32)); | ||||
|     .map((topic) => { | ||||
|       if (topic === null) { | ||||
|         return null; | ||||
|       } | ||||
| 
 | ||||
|   // while (topics.length < 4) {
 | ||||
|   //   topics.push(null);
 | ||||
|   // }
 | ||||
|       if (Array.isArray(topic)) { | ||||
|         return inTopics(topic); | ||||
|       } | ||||
| 
 | ||||
|       return padLeft(topic, 32); | ||||
|     }); | ||||
| 
 | ||||
|   return topics; | ||||
| } | ||||
|  | ||||
| @ -36,7 +36,8 @@ export const ERROR_CODES = { | ||||
|   REQUEST_NOT_FOUND: -32042, | ||||
|   COMPILATION_ERROR: -32050, | ||||
|   ENCRYPTION_ERROR: -32055, | ||||
|   FETCH_ERROR: -32060 | ||||
|   FETCH_ERROR: -32060, | ||||
|   INVALID_PARAMS: -32602 | ||||
| }; | ||||
| 
 | ||||
| export default class TransportError extends ExtendableError { | ||||
|  | ||||
| @ -79,7 +79,7 @@ export default class Ws extends JsonRpcBase { | ||||
|     this._ws.onclose = this._onClose; | ||||
|     this._ws.onmessage = this._onMessage; | ||||
| 
 | ||||
|     // Get counts in dev mode
 | ||||
|     // Get counts in dev mode only
 | ||||
|     if (process.env.NODE_ENV === 'development') { | ||||
|       this._count = 0; | ||||
|       this._lastCount = { | ||||
| @ -93,8 +93,13 @@ export default class Ws extends JsonRpcBase { | ||||
|         const s = Math.round(1000 * n / t) / 1000; | ||||
| 
 | ||||
|         if (this._debug) { | ||||
|           console.log('::parityWS', `speed: ${s} req/s`, `count: ${this._count}`); | ||||
|           console.log('::parityWS', `speed: ${s} req/s`, `count: ${this._count}`, `(+${n})`); | ||||
|         } | ||||
| 
 | ||||
|         this._lastCount = { | ||||
|           timestamp: Date.now(), | ||||
|           count: this._count | ||||
|         }; | ||||
|       }, 5000); | ||||
| 
 | ||||
|       window._parityWS = this; | ||||
| @ -117,6 +122,7 @@ export default class Ws extends JsonRpcBase { | ||||
|     this._connected = false; | ||||
|     this._connecting = false; | ||||
| 
 | ||||
|     event.timestamp = Date.now(); | ||||
|     this._lastError = event; | ||||
| 
 | ||||
|     if (this._autoConnect) { | ||||
| @ -144,6 +150,8 @@ export default class Ws extends JsonRpcBase { | ||||
|     window.setTimeout(() => { | ||||
|       if (this._connected) { | ||||
|         console.error('ws:onError', event); | ||||
| 
 | ||||
|         event.timestamp = Date.now(); | ||||
|         this._lastError = event; | ||||
|       } | ||||
|     }, 50); | ||||
|  | ||||
| @ -14,6 +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/>.
 | ||||
| 
 | ||||
| import { range } from 'lodash'; | ||||
| import { inHex } from '../format/input'; | ||||
| 
 | ||||
| export function bytesToHex (bytes) { | ||||
|   return '0x' + bytes.map((b) => ('0' + b.toString(16)).slice(-2)).join(''); | ||||
| } | ||||
| @ -33,3 +36,13 @@ export function hex2Ascii (_hex) { | ||||
| export function asciiToHex (string) { | ||||
|   return '0x' + string.split('').map((s) => s.charCodeAt(0).toString(16)).join(''); | ||||
| } | ||||
| 
 | ||||
| export function padRight (input, length) { | ||||
|   const value = inHex(input).substr(2, length * 2); | ||||
|   return '0x' + value + range(length * 2 - value.length).map(() => '0').join(''); | ||||
| } | ||||
| 
 | ||||
| export function padLeft (input, length) { | ||||
|   const value = inHex(input).substr(2, length * 2); | ||||
|   return '0x' + range(length * 2 - value.length).map(() => '0').join('') + value; | ||||
| } | ||||
|  | ||||
| @ -50,12 +50,12 @@ export default class ButtonBar extends Component { | ||||
|           key='delete' | ||||
|           label='Delete' | ||||
|           warning | ||||
|           disabled={ !this.dappsStore.currentApp.isOwner && !this.dappsStore.isContractOwner } | ||||
|           disabled={ !this.dappsStore.currentApp || (!this.dappsStore.currentApp.isOwner && !this.dappsStore.isContractOwner) } | ||||
|           onClick={ this.onDeleteClick } />, | ||||
|         <Button | ||||
|           key='edit' | ||||
|           label='Edit' | ||||
|           disabled={ !this.dappsStore.currentApp.isOwner } | ||||
|           disabled={ !this.dappsStore.currentApp || !this.dappsStore.currentApp.isOwner } | ||||
|           onClick={ this.onEditClick } />, | ||||
|         <Button | ||||
|           key='new' | ||||
|  | ||||
| @ -33,6 +33,10 @@ export default class Dapp extends Component { | ||||
|       ? this.dappsStore.wipApp | ||||
|       : this.dappsStore.currentApp; | ||||
| 
 | ||||
|     if (!app) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.app }> | ||||
|         { this.dappsStore.isNew ? this.renderOwnerSelect(app) : this.renderOwnerStatic(app) } | ||||
|  | ||||
| @ -36,6 +36,10 @@ export default class SelectDapp extends Component { | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (!this.dappsStore.currentApp) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     let overlayImg = null; | ||||
|     if (this.dappsStore.currentApp.imageHash) { | ||||
|       overlayImg = ( | ||||
|  | ||||
| @ -136,7 +136,10 @@ export default class DappsStore { | ||||
|         .sort((a, b) => a.name.localeCompare(b.name)); | ||||
| 
 | ||||
|       this.apps = ownApps.concat(otherApps); | ||||
| 
 | ||||
|       if (this.apps.length) { | ||||
|         this.currentApp = this.apps[0]; | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -328,7 +331,7 @@ export default class DappsStore { | ||||
|       }) | ||||
|       .then(() => { | ||||
|         this.sortApps(); | ||||
|         this.setLoading(this.count === 0); | ||||
|         this.setLoading(false); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.error('Store:loadDapps', error); | ||||
|  | ||||
| @ -127,7 +127,7 @@ export const subscribeEvents = () => (dispatch, getState) => { | ||||
|         const params = log.params; | ||||
| 
 | ||||
|         if (event === 'Registered' && type === 'pending') { | ||||
|           return dispatch(setTokenData(params.id.toNumber(), { | ||||
|           return dispatch(setTokenData(params.id.value.toNumber(), { | ||||
|             tla: '...', | ||||
|             base: -1, | ||||
|             address: params.addr.value, | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { Checkbox } from 'material-ui'; | ||||
| 
 | ||||
| import { Form, Input } from '../../../ui'; | ||||
| 
 | ||||
| @ -37,6 +38,7 @@ export default class RecoveryPhrase extends Component { | ||||
|     password1Error: ERRORS.invalidPassword, | ||||
|     password2: '', | ||||
|     password2Error: ERRORS.noMatchPassword, | ||||
|     windowsPhrase: false, | ||||
|     isValidPass: false, | ||||
|     isValidName: false, | ||||
|     isValidPhrase: false | ||||
| @ -47,7 +49,7 @@ export default class RecoveryPhrase extends Component { | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { accountName, accountNameError, passwordHint, password1, password1Error, password2, password2Error, recoveryPhrase } = this.state; | ||||
|     const { accountName, accountNameError, passwordHint, password1, password1Error, password2, password2Error, recoveryPhrase, windowsPhrase } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <Form> | ||||
| @ -86,20 +88,26 @@ export default class RecoveryPhrase extends Component { | ||||
|               value={ password2 } | ||||
|               onChange={ this.onEditPassword2 } /> | ||||
|           </div> | ||||
|           <Checkbox | ||||
|             className={ styles.checkbox } | ||||
|             label='Key was created with Parity <1.4.5 on Windows' | ||||
|             checked={ windowsPhrase } | ||||
|             onCheck={ this.onToggleWindowsPhrase } /> | ||||
|         </div> | ||||
|       </Form> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   updateParent = () => { | ||||
|     const { isValidName, isValidPass, isValidPhrase, accountName, passwordHint, password1, recoveryPhrase } = this.state; | ||||
|     const { isValidName, isValidPass, isValidPhrase, accountName, passwordHint, password1, recoveryPhrase, windowsPhrase } = this.state; | ||||
|     const isValid = isValidName && isValidPass && isValidPhrase; | ||||
| 
 | ||||
|     this.props.onChange(isValid, { | ||||
|       name: accountName, | ||||
|       passwordHint, | ||||
|       password: password1, | ||||
|       phrase: recoveryPhrase | ||||
|       phrase: recoveryPhrase, | ||||
|       windowsPhrase | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -109,6 +117,12 @@ export default class RecoveryPhrase extends Component { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onToggleWindowsPhrase = (event) => { | ||||
|     this.setState({ | ||||
|       windowsPhrase: !this.state.windowsPhrase | ||||
|     }, this.updateParent); | ||||
|   } | ||||
| 
 | ||||
|   onEditPhrase = (event) => { | ||||
|     const recoveryPhrase = event.target.value | ||||
|       .toLowerCase() // wordlists are lowercase
 | ||||
| @ -116,15 +130,18 @@ export default class RecoveryPhrase extends Component { | ||||
|       .replace(/\s/g, ' ') // replace any whitespace with single space
 | ||||
|       .replace(/ +/g, ' '); // replace multiple spaces with a single space
 | ||||
| 
 | ||||
|     const parts = recoveryPhrase.split(' '); | ||||
|     const phraseParts = recoveryPhrase | ||||
|       .split(' ') | ||||
|       .map((part) => part.trim()) | ||||
|       .filter((part) => part.length); | ||||
|     let recoveryPhraseError = null; | ||||
| 
 | ||||
|     if (!recoveryPhrase || recoveryPhrase.length < 25 || parts.length < 8) { | ||||
|     if (!recoveryPhrase || recoveryPhrase.length < 25 || phraseParts.length < 8) { | ||||
|       recoveryPhraseError = ERRORS.noPhrase; | ||||
|     } | ||||
| 
 | ||||
|     this.setState({ | ||||
|       recoveryPhrase, | ||||
|       recoveryPhrase: phraseParts.join(' '), | ||||
|       recoveryPhraseError, | ||||
|       isValidPhrase: !recoveryPhraseError | ||||
|     }, this.updateParent); | ||||
|  | ||||
| @ -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/>. | ||||
| */ | ||||
| 
 | ||||
| .spaced { | ||||
|   line-height: 1.618em; | ||||
| } | ||||
| @ -67,3 +68,7 @@ | ||||
| .upload>div { | ||||
|   margin-right: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .checkbox { | ||||
|   margin-top: 2em; | ||||
| } | ||||
|  | ||||
| @ -59,6 +59,7 @@ export default class CreateAccount extends Component { | ||||
|     passwordHint: null, | ||||
|     password: null, | ||||
|     phrase: null, | ||||
|     windowsPhrase: false, | ||||
|     rawKey: null, | ||||
|     json: null, | ||||
|     canCreate: false, | ||||
| @ -200,7 +201,7 @@ export default class CreateAccount extends Component { | ||||
|   } | ||||
| 
 | ||||
|   onCreate = () => { | ||||
|     const { createType } = this.state; | ||||
|     const { createType, windowsPhrase } = this.state; | ||||
|     const { api } = this.context; | ||||
| 
 | ||||
|     this.setState({ | ||||
| @ -208,8 +209,16 @@ 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
 | ||||
|           .map((word) => word === 'misjudged' ? word : `${word}\r`) // add \r after each (except last in dict)
 | ||||
|           .join(' '); // re-create string
 | ||||
|       } | ||||
| 
 | ||||
|       return api.parity | ||||
|         .newAccountFromPhrase(this.state.phrase, this.state.password) | ||||
|         .newAccountFromPhrase(phrase, this.state.password) | ||||
|         .then((address) => { | ||||
|           this.setState({ address }); | ||||
|           return api.parity | ||||
| @ -326,7 +335,7 @@ export default class CreateAccount extends Component { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   onChangeDetails = (canCreate, { name, passwordHint, address, password, phrase, rawKey }) => { | ||||
|   onChangeDetails = (canCreate, { name, passwordHint, address, password, phrase, rawKey, windowsPhrase }) => { | ||||
|     this.setState({ | ||||
|       canCreate, | ||||
|       name, | ||||
| @ -334,6 +343,7 @@ export default class CreateAccount extends Component { | ||||
|       address, | ||||
|       password, | ||||
|       phrase, | ||||
|       windowsPhrase: windowsPhrase || false, | ||||
|       rawKey | ||||
|     }); | ||||
|   } | ||||
|  | ||||
| @ -16,68 +16,63 @@ | ||||
| 
 | ||||
| import { throttle } from 'lodash'; | ||||
| 
 | ||||
| import { getBalances, getTokens } from './balancesActions'; | ||||
| import { setAddressImage } from './imagesActions'; | ||||
| import { loadTokens, setTokenReg, fetchBalances, fetchTokens, fetchTokensBalances } from './balancesActions'; | ||||
| import { padRight } from '../../api/util/format'; | ||||
| 
 | ||||
| import Contracts from '../../contracts'; | ||||
| import * as abis from '../../contracts/abi'; | ||||
| 
 | ||||
| import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png'; | ||||
| 
 | ||||
| const ETH = { | ||||
|   name: 'Ethereum', | ||||
|   tag: 'ETH', | ||||
|   image: imagesEthereum | ||||
| }; | ||||
| 
 | ||||
| export default class Balances { | ||||
|   constructor (store, api) { | ||||
|     this._api = api; | ||||
|     this._store = store; | ||||
| 
 | ||||
|     this._tokens = {}; | ||||
|     this._images = {}; | ||||
| 
 | ||||
|     this._accountsInfo = null; | ||||
|     this._tokenreg = null; | ||||
|     this._fetchingBalances = false; | ||||
|     this._fetchingTokens = false; | ||||
|     this._fetchedTokens = false; | ||||
| 
 | ||||
|     this._tokenregSubId = null; | ||||
|     this._tokenregMetaSubId = null; | ||||
| 
 | ||||
|     // Throttled `retrieveTokens` function
 | ||||
|     // that gets called max once every 20s
 | ||||
|     this._throttledRetrieveTokens = throttle( | ||||
|       this._retrieveTokens, | ||||
|       20 * 1000, | ||||
|     // that gets called max once every 40s
 | ||||
|     this.longThrottledFetch = throttle( | ||||
|       this.fetchBalances, | ||||
|       40 * 1000, | ||||
|       { trailing: true } | ||||
|     ); | ||||
| 
 | ||||
|     this.shortThrottledFetch = throttle( | ||||
|       this.fetchBalances, | ||||
|       2 * 1000, | ||||
|       { trailing: true } | ||||
|     ); | ||||
| 
 | ||||
|     // Fetch all tokens every 2 minutes
 | ||||
|     this.throttledTokensFetch = throttle( | ||||
|       this.fetchTokens, | ||||
|       60 * 1000, | ||||
|       { trailing: true } | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   start () { | ||||
|     this._subscribeBlockNumber(); | ||||
|     this._subscribeAccountsInfo(); | ||||
|     this._retrieveTokens(); | ||||
|     this.subscribeBlockNumber(); | ||||
|     this.subscribeAccountsInfo(); | ||||
| 
 | ||||
|     this.loadTokens(); | ||||
|   } | ||||
| 
 | ||||
|   _subscribeAccountsInfo () { | ||||
|   subscribeAccountsInfo () { | ||||
|     this._api | ||||
|       .subscribe('parity_accountsInfo', (error, accountsInfo) => { | ||||
|         if (error) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         this._accountsInfo = accountsInfo; | ||||
|         this._retrieveTokens(); | ||||
|         this.fetchBalances(); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('_subscribeAccountsInfo', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   _subscribeBlockNumber () { | ||||
|   subscribeBlockNumber () { | ||||
|     this._api | ||||
|       .subscribe('eth_blockNumber', (error) => { | ||||
|         if (error) { | ||||
| @ -86,123 +81,63 @@ export default class Balances { | ||||
| 
 | ||||
|         const { syncing } = this._store.getState().nodeStatus; | ||||
| 
 | ||||
|         this.throttledTokensFetch(); | ||||
| 
 | ||||
|         // If syncing, only retrieve balances once every
 | ||||
|         // few seconds
 | ||||
|         if (syncing) { | ||||
|           return this._throttledRetrieveTokens(); | ||||
|           this.shortThrottledFetch(); | ||||
|           return this.longThrottledFetch(); | ||||
|         } | ||||
| 
 | ||||
|         this._throttledRetrieveTokens.cancel(); | ||||
|         this._retrieveTokens(); | ||||
|         this.longThrottledFetch.cancel(); | ||||
|         return this.shortThrottledFetch(); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('_subscribeBlockNumber', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   fetchBalances () { | ||||
|     this._store.dispatch(fetchBalances()); | ||||
|   } | ||||
| 
 | ||||
|   fetchTokens () { | ||||
|     this._store.dispatch(fetchTokensBalances()); | ||||
|   } | ||||
| 
 | ||||
|   getTokenRegistry () { | ||||
|     if (this._tokenreg) { | ||||
|       return Promise.resolve(this._tokenreg); | ||||
|     return Contracts.get().tokenReg.getContract(); | ||||
|   } | ||||
| 
 | ||||
|     return Contracts.get().tokenReg | ||||
|       .getContract() | ||||
|       .then((tokenreg) => { | ||||
|         this._tokenreg = tokenreg; | ||||
|         this.attachToTokens(); | ||||
| 
 | ||||
|         return tokenreg; | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   _retrieveTokens () { | ||||
|     if (this._fetchingTokens) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (this._fetchedTokens) { | ||||
|       return this._retrieveBalances(); | ||||
|     } | ||||
| 
 | ||||
|     this._fetchingTokens = true; | ||||
|     this._fetchedTokens = false; | ||||
| 
 | ||||
|   loadTokens () { | ||||
|     this | ||||
|       .getTokenRegistry() | ||||
|       .then((tokenreg) => { | ||||
|         return tokenreg.instance.tokenCount | ||||
|           .call() | ||||
|           .then((numTokens) => { | ||||
|             const promises = []; | ||||
|         this._store.dispatch(setTokenReg(tokenreg)); | ||||
|         this._store.dispatch(loadTokens()); | ||||
| 
 | ||||
|             for (let i = 0; i < numTokens.toNumber(); i++) { | ||||
|               promises.push(this.fetchTokenInfo(tokenreg, i)); | ||||
|             } | ||||
| 
 | ||||
|             return Promise.all(promises); | ||||
|           }); | ||||
|       }) | ||||
|       .then(() => { | ||||
|         this._fetchingTokens = false; | ||||
|         this._fetchedTokens = true; | ||||
| 
 | ||||
|         this._store.dispatch(getTokens(this._tokens)); | ||||
|         this._retrieveBalances(); | ||||
|         return this.attachToTokens(tokenreg); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('balances::_retrieveTokens', error); | ||||
|         console.warn('balances::loadTokens', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   _retrieveBalances () { | ||||
|     if (this._fetchingBalances) { | ||||
|       return; | ||||
|   attachToTokens (tokenreg) { | ||||
|     return Promise | ||||
|       .all([ | ||||
|         this.attachToTokenMetaChange(tokenreg), | ||||
|         this.attachToNewToken(tokenreg) | ||||
|       ]); | ||||
|   } | ||||
| 
 | ||||
|     if (!this._accountsInfo) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     this._fetchingBalances = true; | ||||
| 
 | ||||
|     const addresses = Object | ||||
|       .keys(this._accountsInfo) | ||||
|       .filter((address) => { | ||||
|         const account = this._accountsInfo[address]; | ||||
|         return !account.meta || !account.meta.deleted; | ||||
|       }); | ||||
| 
 | ||||
|     this._balances = {}; | ||||
| 
 | ||||
|     Promise | ||||
|       .all(addresses.map((a) => this.fetchAccountBalance(a))) | ||||
|       .then((balances) => { | ||||
|         addresses.forEach((a, idx) => { | ||||
|           this._balances[a] = balances[idx]; | ||||
|         }); | ||||
| 
 | ||||
|         this._store.dispatch(getBalances(this._balances)); | ||||
|         this._fetchingBalances = false; | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('_retrieveBalances', error); | ||||
|         this._fetchingBalances = false; | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   attachToTokens () { | ||||
|     this.attachToTokenMetaChange(); | ||||
|     this.attachToNewToken(); | ||||
|   } | ||||
| 
 | ||||
|   attachToNewToken () { | ||||
|   attachToNewToken (tokenreg) { | ||||
|     if (this._tokenregSubId) { | ||||
|       return; | ||||
|       return Promise.resolve(); | ||||
|     } | ||||
| 
 | ||||
|     this._tokenreg | ||||
|       .instance | ||||
|       .Registered | ||||
|     return tokenreg.instance.Registered | ||||
|       .subscribe({ | ||||
|         fromBlock: 0, | ||||
|         toBlock: 'latest', | ||||
| @ -212,138 +147,38 @@ export default class Balances { | ||||
|           return console.error('balances::attachToNewToken', 'failed to attach to tokenreg Registered', error.toString(), error.stack); | ||||
|         } | ||||
| 
 | ||||
|         const promises = logs.map((log) => { | ||||
|           const id = log.params.id.value.toNumber(); | ||||
|           return this.fetchTokenInfo(this._tokenreg, id); | ||||
|         }); | ||||
| 
 | ||||
|         return Promise.all(promises); | ||||
|         this.handleTokensLogs(logs); | ||||
|       }) | ||||
|       .then((tokenregSubId) => { | ||||
|         this._tokenregSubId = tokenregSubId; | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         console.warn('balances::attachToNewToken', e); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   attachToTokenMetaChange () { | ||||
|   attachToTokenMetaChange (tokenreg) { | ||||
|     if (this._tokenregMetaSubId) { | ||||
|       return; | ||||
|       return Promise.resolve(); | ||||
|     } | ||||
| 
 | ||||
|     this._tokenreg | ||||
|       .instance | ||||
|       .MetaChanged | ||||
|     return tokenreg.instance.MetaChanged | ||||
|       .subscribe({ | ||||
|         fromBlock: 0, | ||||
|         toBlock: 'latest', | ||||
|         topics: [ null, this._api.util.asciiToHex('IMG') ], | ||||
|         topics: [ null, padRight(this._api.util.asciiToHex('IMG'), 32) ], | ||||
|         skipInitFetch: true | ||||
|       }, (error, logs) => { | ||||
|         if (error) { | ||||
|           return console.error('balances::attachToTokenMetaChange', 'failed to attach to tokenreg MetaChanged', error.toString(), error.stack); | ||||
|         } | ||||
| 
 | ||||
|         // In case multiple logs for same token
 | ||||
|         // in one block. Take the last value.
 | ||||
|         const tokens = logs | ||||
|           .filter((log) => log.type === 'mined') | ||||
|           .reduce((_tokens, log) => { | ||||
|             const id = log.params.id.value.toNumber(); | ||||
|             const image = log.params.value.value; | ||||
| 
 | ||||
|             const token = Object.values(this._tokens).find((c) => c.id === id); | ||||
|             const { address } = token; | ||||
| 
 | ||||
|             _tokens[address] = { address, id, image }; | ||||
|             return _tokens; | ||||
|           }, {}); | ||||
| 
 | ||||
|         Object | ||||
|           .values(tokens) | ||||
|           .forEach((token) => { | ||||
|             const { address, image } = token; | ||||
| 
 | ||||
|             if (this._images[address] !== image.toString()) { | ||||
|               this._store.dispatch(setAddressImage(address, image)); | ||||
|               this._images[address] = image.toString(); | ||||
|             } | ||||
|           }); | ||||
|         this.handleTokensLogs(logs); | ||||
|       }) | ||||
|       .then((tokenregMetaSubId) => { | ||||
|         this._tokenregMetaSubId = tokenregMetaSubId; | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         console.warn('balances::attachToTokenMetaChange', e); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   fetchTokenInfo (tokenreg, tokenId) { | ||||
|     return Promise | ||||
|       .all([ | ||||
|         tokenreg.instance.token.call({}, [tokenId]), | ||||
|         tokenreg.instance.meta.call({}, [tokenId, 'IMG']) | ||||
|       ]) | ||||
|       .then(([ tokenData, image ]) => { | ||||
|         const [ address, tag, format, name ] = tokenData; | ||||
|         const contract = this._api.newContract(abis.eip20, address); | ||||
| 
 | ||||
|         if (this._images[address] !== image.toString()) { | ||||
|           this._store.dispatch(setAddressImage(address, image)); | ||||
|           this._images[address] = image.toString(); | ||||
|         } | ||||
| 
 | ||||
|         const token = { | ||||
|           format: format.toString(), | ||||
|           id: tokenId, | ||||
| 
 | ||||
|           address, | ||||
|           tag, | ||||
|           name, | ||||
|           contract | ||||
|         }; | ||||
| 
 | ||||
|         this._tokens[address] = token; | ||||
| 
 | ||||
|         return token; | ||||
|       }) | ||||
|       .catch((e) => { | ||||
|         console.warn('balances::fetchTokenInfo', `couldn't fetch token #${tokenId}`, e); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * TODO?: txCount is only shown on an address page, so we | ||||
|    * might not need to fetch it for each address for each block, | ||||
|    * but only for one address when the user is on the account | ||||
|    * view. | ||||
|    */ | ||||
|   fetchAccountBalance (address) { | ||||
|     const _tokens = Object.values(this._tokens); | ||||
|     const tokensPromises = _tokens | ||||
|       .map((token) => { | ||||
|         return token.contract.instance.balanceOf.call({}, [ address ]); | ||||
|       }); | ||||
| 
 | ||||
|     return Promise | ||||
|       .all([ | ||||
|         this._api.eth.getTransactionCount(address), | ||||
|         this._api.eth.getBalance(address) | ||||
|       ].concat(tokensPromises)) | ||||
|       .then(([ txCount, ethBalance, ...tokensBalance ]) => { | ||||
|         const tokens = [] | ||||
|           .concat( | ||||
|             { token: ETH, value: ethBalance }, | ||||
|             _tokens | ||||
|               .map((token, index) => ({ | ||||
|                 token, | ||||
|                 value: tokensBalance[index] | ||||
|               })) | ||||
|           ); | ||||
| 
 | ||||
|         const balance = { txCount, tokens }; | ||||
|         return balance; | ||||
|       }); | ||||
|   handleTokensLogs (logs) { | ||||
|     const tokenIds = logs.map((log) => log.params.id.value.toNumber()); | ||||
|     this._store.dispatch(fetchTokens(tokenIds)); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -14,16 +14,354 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export function getBalances (balances) { | ||||
| import { range, uniq, isEqual } from 'lodash'; | ||||
| 
 | ||||
| import { hashToImageUrl } from './imagesReducer'; | ||||
| import { setAddressImage } from './imagesActions'; | ||||
| 
 | ||||
| import * as ABIS from '../../contracts/abi'; | ||||
| import imagesEthereum from '../../../assets/images/contracts/ethereum-black-64x64.png'; | ||||
| 
 | ||||
| const ETH = { | ||||
|   name: 'Ethereum', | ||||
|   tag: 'ETH', | ||||
|   image: imagesEthereum | ||||
| }; | ||||
| 
 | ||||
| export function setBalances (balances) { | ||||
|   return { | ||||
|     type: 'getBalances', | ||||
|     type: 'setBalances', | ||||
|     balances | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function getTokens (tokens) { | ||||
| export function setTokens (tokens) { | ||||
|   return { | ||||
|     type: 'getTokens', | ||||
|     type: 'setTokens', | ||||
|     tokens | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function setTokenReg (tokenreg) { | ||||
|   return { | ||||
|     type: 'setTokenReg', | ||||
|     tokenreg | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function setTokensFilter (tokensFilter) { | ||||
|   return { | ||||
|     type: 'setTokensFilter', | ||||
|     tokensFilter | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function setTokenImage (tokenAddress, image) { | ||||
|   return { | ||||
|     type: 'setTokenImage', | ||||
|     tokenAddress, image | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function loadTokens () { | ||||
|   return (dispatch, getState) => { | ||||
|     const { tokenreg } = getState().balances; | ||||
| 
 | ||||
|     return tokenreg.instance.tokenCount | ||||
|       .call() | ||||
|       .then((numTokens) => { | ||||
|         const tokenIds = range(numTokens.toNumber()); | ||||
|         dispatch(fetchTokens(tokenIds)); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('balances::loadTokens', error); | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function fetchTokens (_tokenIds) { | ||||
|   const tokenIds = uniq(_tokenIds || []); | ||||
|   return (dispatch, getState) => { | ||||
|     const { api, images, balances } = getState(); | ||||
|     const { tokenreg } = balances; | ||||
| 
 | ||||
|     return Promise | ||||
|       .all(tokenIds.map((id) => fetchTokenInfo(tokenreg, id, api))) | ||||
|       .then((tokens) => { | ||||
|         // dispatch only the changed images
 | ||||
|         tokens | ||||
|           .forEach((token) => { | ||||
|             const { image, address } = token; | ||||
| 
 | ||||
|             if (images[address] === image) { | ||||
|               return; | ||||
|             } | ||||
| 
 | ||||
|             dispatch(setTokenImage(address, image)); | ||||
|             dispatch(setAddressImage(address, image, true)); | ||||
|           }); | ||||
| 
 | ||||
|         dispatch(setTokens(tokens)); | ||||
|         dispatch(fetchBalances()); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('balances::fetchTokens', error); | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function fetchBalances (_addresses) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { api, personal } = getState(); | ||||
|     const { visibleAccounts } = personal; | ||||
| 
 | ||||
|     const addresses = uniq(_addresses || visibleAccounts || []); | ||||
| 
 | ||||
|     if (addresses.length === 0) { | ||||
|       return Promise.resolve(); | ||||
|     } | ||||
| 
 | ||||
|     const fullFetch = addresses.length === 1; | ||||
| 
 | ||||
|     return Promise | ||||
|       .all(addresses.map((addr) => fetchAccount(addr, api, fullFetch))) | ||||
|       .then((accountsBalances) => { | ||||
|         const balances = {}; | ||||
| 
 | ||||
|         addresses.forEach((addr, idx) => { | ||||
|           balances[addr] = accountsBalances[idx]; | ||||
|         }); | ||||
| 
 | ||||
|         dispatch(setBalances(balances)); | ||||
|         updateTokensFilter(addresses)(dispatch, getState); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('balances::fetchBalances', error); | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function updateTokensFilter (_addresses, _tokens) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { api, balances, personal } = getState(); | ||||
|     const { visibleAccounts } = personal; | ||||
|     const { tokensFilter } = balances; | ||||
| 
 | ||||
|     const addresses = uniq(_addresses || visibleAccounts || []).sort(); | ||||
|     const tokens = _tokens || Object.values(balances.tokens) || []; | ||||
|     const tokenAddresses = tokens.map((t) => t.address).sort(); | ||||
| 
 | ||||
|     if (tokensFilter.filterFromId || tokensFilter.filterToId) { | ||||
|       const sameTokens = isEqual(tokenAddresses, tokensFilter.tokenAddresses); | ||||
|       const sameAddresses = isEqual(addresses, tokensFilter.addresses); | ||||
| 
 | ||||
|       if (sameTokens && sameAddresses) { | ||||
|         return queryTokensFilter(tokensFilter)(dispatch, getState); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     let promise = Promise.resolve(); | ||||
| 
 | ||||
|     if (tokensFilter.filterFromId) { | ||||
|       promise = promise.then(() => api.eth.uninstallFilter(tokensFilter.filterFromId)); | ||||
|     } | ||||
| 
 | ||||
|     if (tokensFilter.filterToId) { | ||||
|       promise = promise.then(() => api.eth.uninstallFilter(tokensFilter.filterToId)); | ||||
|     } | ||||
| 
 | ||||
|     if (tokenAddresses.length === 0 || addresses.length === 0) { | ||||
|       return promise; | ||||
|     } | ||||
| 
 | ||||
|     const TRANSFER_SIGNATURE = api.util.sha3('Transfer(address,address,uint256)'); | ||||
|     const topicsFrom = [ TRANSFER_SIGNATURE, addresses, null ]; | ||||
|     const topicsTo = [ TRANSFER_SIGNATURE, null, addresses ]; | ||||
| 
 | ||||
|     const options = { | ||||
|       fromBlock: 0, | ||||
|       toBlock: 'pending', | ||||
|       address: tokenAddresses | ||||
|     }; | ||||
| 
 | ||||
|     const optionsFrom = { | ||||
|       ...options, | ||||
|       topics: topicsFrom | ||||
|     }; | ||||
| 
 | ||||
|     const optionsTo = { | ||||
|       ...options, | ||||
|       topics: topicsTo | ||||
|     }; | ||||
| 
 | ||||
|     const newFilters = Promise.all([ | ||||
|       api.eth.newFilter(optionsFrom), | ||||
|       api.eth.newFilter(optionsTo) | ||||
|     ]); | ||||
| 
 | ||||
|     promise | ||||
|       .then(() => newFilters) | ||||
|       .then(([ filterFromId, filterToId ]) => { | ||||
|         const nextTokensFilter = { | ||||
|           filterFromId, filterToId, | ||||
|           addresses, tokenAddresses | ||||
|         }; | ||||
| 
 | ||||
|         dispatch(setTokensFilter(nextTokensFilter)); | ||||
|         fetchTokensBalances(addresses, tokens)(dispatch, getState); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('balances::updateTokensFilter', error); | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function queryTokensFilter (tokensFilter) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { api, personal, balances } = getState(); | ||||
|     const { visibleAccounts } = personal; | ||||
|     const visibleAddresses = visibleAccounts.map((a) => a.toLowerCase()); | ||||
| 
 | ||||
|     Promise | ||||
|       .all([ | ||||
|         api.eth.getFilterChanges(tokensFilter.filterFromId), | ||||
|         api.eth.getFilterChanges(tokensFilter.filterToId) | ||||
|       ]) | ||||
|       .then(([ logsFrom, logsTo ]) => { | ||||
|         const addresses = []; | ||||
|         const tokenAddresses = []; | ||||
| 
 | ||||
|         logsFrom | ||||
|           .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 (toIdx > -1) { | ||||
|               addresses.push(visibleAccounts[toIdx]); | ||||
|             } | ||||
| 
 | ||||
|             tokenAddresses.push(tokenAddress); | ||||
|           }); | ||||
| 
 | ||||
|         if (addresses.length === 0) { | ||||
|           return; | ||||
|         } | ||||
| 
 | ||||
|         const tokens = balances.tokens.filter((t) => tokenAddresses.includes(t.address)); | ||||
| 
 | ||||
|         fetchTokensBalances(uniq(addresses), tokens)(dispatch, getState); | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function fetchTokensBalances (_addresses = null, _tokens = null) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { api, personal, balances } = getState(); | ||||
|     const { visibleAccounts } = personal; | ||||
| 
 | ||||
|     const addresses = _addresses || visibleAccounts; | ||||
|     const tokens = _tokens || Object.values(balances.tokens); | ||||
| 
 | ||||
|     if (addresses.length === 0) { | ||||
|       return Promise.resolve(); | ||||
|     } | ||||
| 
 | ||||
|     return Promise | ||||
|       .all(addresses.map((addr) => fetchTokensBalance(addr, tokens, api))) | ||||
|       .then((tokensBalances) => { | ||||
|         const balances = {}; | ||||
| 
 | ||||
|         addresses.forEach((addr, idx) => { | ||||
|           balances[addr] = tokensBalances[idx]; | ||||
|         }); | ||||
| 
 | ||||
|         dispatch(setBalances(balances)); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('balances::fetchTokensBalances', error); | ||||
|       }); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function fetchAccount (address, api, full = false) { | ||||
|   const promises = [ api.eth.getBalance(address) ]; | ||||
| 
 | ||||
|   if (full) { | ||||
|     promises.push(api.eth.getTransactionCount(address)); | ||||
|   } | ||||
| 
 | ||||
|   return Promise | ||||
|     .all(promises) | ||||
|     .then(([ ethBalance, txCount ]) => { | ||||
|       const tokens = [ { token: ETH, value: ethBalance } ]; | ||||
|       const balance = { tokens }; | ||||
| 
 | ||||
|       if (full) { | ||||
|         balance.txCount = txCount; | ||||
|       } | ||||
| 
 | ||||
|       return balance; | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.warn('balances::fetchAccountBalance', `couldn't fetch balance for account #${address}`, error); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchTokensBalance (address, _tokens, api) { | ||||
|   const tokensPromises = _tokens | ||||
|     .map((token) => { | ||||
|       return token.contract.instance.balanceOf.call({}, [ address ]); | ||||
|     }); | ||||
| 
 | ||||
|   return Promise | ||||
|     .all(tokensPromises) | ||||
|     .then((tokensBalance) => { | ||||
|       const tokens = _tokens | ||||
|         .map((token, index) => ({ | ||||
|           token, | ||||
|           value: tokensBalance[index] | ||||
|         })); | ||||
| 
 | ||||
|       const balance = { tokens }; | ||||
|       return balance; | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.warn('balances::fetchTokensBalance', `couldn't fetch tokens balance for account #${address}`, error); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function fetchTokenInfo (tokenreg, tokenId, api, dispatch) { | ||||
|   return Promise | ||||
|     .all([ | ||||
|       tokenreg.instance.token.call({}, [tokenId]), | ||||
|       tokenreg.instance.meta.call({}, [tokenId, 'IMG']) | ||||
|     ]) | ||||
|     .then(([ tokenData, image ]) => { | ||||
|       const [ address, tag, format, name ] = tokenData; | ||||
|       const contract = api.newContract(ABIS.eip20, address); | ||||
| 
 | ||||
|       const token = { | ||||
|         format: format.toString(), | ||||
|         id: tokenId, | ||||
|         image: hashToImageUrl(image), | ||||
|         address, | ||||
|         tag, | ||||
|         name, | ||||
|         contract | ||||
|       }; | ||||
| 
 | ||||
|       return token; | ||||
|     }) | ||||
|     .catch((error) => { | ||||
|       console.warn('balances::fetchTokenInfo', `couldn't fetch token #${tokenId}`, error); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| @ -15,22 +15,102 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { handleActions } from 'redux-actions'; | ||||
| import BigNumber from 'bignumber.js'; | ||||
| 
 | ||||
| const initialState = { | ||||
|   balances: {}, | ||||
|   tokens: {} | ||||
|   tokens: {}, | ||||
|   tokenreg: null, | ||||
|   tokensFilter: {} | ||||
| }; | ||||
| 
 | ||||
| export default handleActions({ | ||||
|   getBalances (state, action) { | ||||
|     const { balances } = action; | ||||
|   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 }); | ||||
|     }); | ||||
| 
 | ||||
|     return Object.assign({}, state, { balances }); | ||||
|   }, | ||||
| 
 | ||||
|   getTokens (state, action) { | ||||
|   setTokens (state, action) { | ||||
|     const { tokens } = action; | ||||
| 
 | ||||
|     if (Array.isArray(tokens)) { | ||||
|       const objTokens = tokens.reduce((_tokens, token) => { | ||||
|         _tokens[token.address] = token; | ||||
|         return _tokens; | ||||
|       }, {}); | ||||
| 
 | ||||
|       return Object.assign({}, state, { tokens: objTokens }); | ||||
|     } | ||||
| 
 | ||||
|     return Object.assign({}, state, { tokens }); | ||||
|   }, | ||||
| 
 | ||||
|   setTokenImage (state, action) { | ||||
|     const { tokenAddress, image } = action; | ||||
|     const { balances } = state; | ||||
|     const nextBalances = {}; | ||||
| 
 | ||||
|     Object.keys(balances).forEach((address) => { | ||||
|       const tokenIndex = balances[address].tokens.findIndex((t) => t.token.address === tokenAddress); | ||||
| 
 | ||||
|       if (tokenIndex === -1 || balances[address].tokens[tokenIndex].value.equals(0)) { | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const tokens = [].concat(balances[address].tokens); | ||||
|       tokens[tokenIndex].token = { | ||||
|         ...tokens[tokenIndex].token, | ||||
|         image | ||||
|       }; | ||||
| 
 | ||||
|       nextBalances[address] = { | ||||
|         ...balances[address], | ||||
|         tokens | ||||
|       }; | ||||
|     }); | ||||
| 
 | ||||
|     return Object.assign({}, state, { balance: { ...balances, nextBalances } }); | ||||
|   }, | ||||
| 
 | ||||
|   setTokenReg (state, action) { | ||||
|     const { tokenreg } = action; | ||||
|     return Object.assign({}, state, { tokenreg }); | ||||
|   }, | ||||
| 
 | ||||
|   setTokensFilter (state, action) { | ||||
|     const { tokensFilter } = action; | ||||
|     return Object.assign({}, state, { tokensFilter }); | ||||
|   } | ||||
| }, initialState); | ||||
|  | ||||
| @ -14,10 +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/>.
 | ||||
| 
 | ||||
| export function setAddressImage (address, hashArray) { | ||||
| export function setAddressImage (address, hashArray, converted = false) { | ||||
|   return { | ||||
|     type: 'setAddressImage', | ||||
|     address, | ||||
|     hashArray | ||||
|     hashArray, | ||||
|     converted | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -31,10 +31,12 @@ export function hashToImageUrl (hashArray) { | ||||
| 
 | ||||
| export default handleActions({ | ||||
|   setAddressImage (state, action) { | ||||
|     const { address, hashArray } = action; | ||||
|     const { address, hashArray, converted } = action; | ||||
| 
 | ||||
|     const image = converted ? hashArray : hashToImageUrl(hashArray); | ||||
| 
 | ||||
|     return Object.assign({}, state, { | ||||
|       [address]: hashToImageUrl(hashArray) | ||||
|       [address]: image | ||||
|     }); | ||||
|   } | ||||
| }, initialState); | ||||
|  | ||||
| @ -27,3 +27,4 @@ export signerReducer from './signerReducer'; | ||||
| export statusReducer from './statusReducer'; | ||||
| export blockchainReducer from './blockchainReducer'; | ||||
| export compilerReducer from './compilerReducer'; | ||||
| export snackbarReducer from './snackbarReducer'; | ||||
|  | ||||
| @ -14,9 +14,33 @@ | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| import { fetchBalances } from './balancesActions'; | ||||
| 
 | ||||
| export function personalAccountsInfo (accountsInfo) { | ||||
|   return { | ||||
|     type: 'personalAccountsInfo', | ||||
|     accountsInfo | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function _setVisibleAccounts (addresses) { | ||||
|   return { | ||||
|     type: 'setVisibleAccounts', | ||||
|     addresses | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function setVisibleAccounts (addresses) { | ||||
|   return (dispatch, getState) => { | ||||
|     const { visibleAccounts } = getState().personal; | ||||
| 
 | ||||
|     if (isEqual(addresses.sort(), visibleAccounts.sort())) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     dispatch(fetchBalances(addresses)); | ||||
|     dispatch(_setVisibleAccounts(addresses)); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import { handleActions } from 'redux-actions'; | ||||
| import { isEqual } from 'lodash'; | ||||
| 
 | ||||
| const initialState = { | ||||
|   accountsInfo: {}, | ||||
| @ -23,7 +24,8 @@ const initialState = { | ||||
|   contacts: {}, | ||||
|   hasContacts: false, | ||||
|   contracts: {}, | ||||
|   hasContracts: false | ||||
|   hasContracts: false, | ||||
|   visibleAccounts: [] | ||||
| }; | ||||
| 
 | ||||
| export default handleActions({ | ||||
| @ -55,5 +57,17 @@ export default handleActions({ | ||||
|       contracts, | ||||
|       hasContracts: Object.keys(contracts).length !== 0 | ||||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   setVisibleAccounts (state, action) { | ||||
|     const addresses = (action.addresses || []).sort(); | ||||
| 
 | ||||
|     if (isEqual(addresses, state.addresses)) { | ||||
|       return state; | ||||
|     } | ||||
| 
 | ||||
|     return Object.assign({}, state, { | ||||
|       visibleAccounts: addresses | ||||
|     }); | ||||
|   } | ||||
| }, initialState); | ||||
|  | ||||
							
								
								
									
										34
									
								
								js/src/redux/providers/snackbarActions.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								js/src/redux/providers/snackbarActions.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/>.
 | ||||
| 
 | ||||
| export function showSnackbar (message, cooldown) { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(openSnackbar(message, cooldown)); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function openSnackbar (message, cooldown) { | ||||
|   return { | ||||
|     type: 'openSnackbar', | ||||
|     message, cooldown | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function closeSnackbar () { | ||||
|   return { | ||||
|     type: 'closeSnackbar' | ||||
|   }; | ||||
| } | ||||
							
								
								
									
										44
									
								
								js/src/redux/providers/snackbarReducer.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								js/src/redux/providers/snackbarReducer.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,44 @@ | ||||
| // 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 { handleActions } from 'redux-actions'; | ||||
| 
 | ||||
| const initialState = { | ||||
|   open: false, | ||||
|   message: '', | ||||
|   cooldown: 1000 | ||||
| }; | ||||
| 
 | ||||
| export default handleActions({ | ||||
|   openSnackbar (state, action) { | ||||
|     const { message, cooldown } = action; | ||||
| 
 | ||||
|     return { | ||||
|       ...state, | ||||
|       open: true, | ||||
|       cooldown: cooldown || state.cooldown, | ||||
|       message | ||||
|     }; | ||||
|   }, | ||||
| 
 | ||||
|   closeSnackbar (state) { | ||||
|     return { | ||||
|       ...state, | ||||
|       open: false, | ||||
|       cooldown: initialState.cooldown | ||||
|     }; | ||||
|   } | ||||
| }, initialState); | ||||
| @ -30,6 +30,8 @@ export default class Status { | ||||
| 
 | ||||
|     this._pollPingTimeoutId = null; | ||||
|     this._longStatusTimeoutId = null; | ||||
| 
 | ||||
|     this._timestamp = Date.now(); | ||||
|   } | ||||
| 
 | ||||
|   start () { | ||||
| @ -131,10 +133,10 @@ export default class Status { | ||||
|       secureToken | ||||
|     }; | ||||
| 
 | ||||
|     const gotReconnected = !this._apiStatus.isConnected && apiStatus.isConnected; | ||||
|     const gotConnected = !this._apiStatus.isConnected && apiStatus.isConnected; | ||||
| 
 | ||||
|     if (gotReconnected) { | ||||
|       this._pollLongStatus(true); | ||||
|     if (gotConnected) { | ||||
|       this._pollLongStatus(); | ||||
|       this._store.dispatch(statusCollection({ isPingable: true })); | ||||
|     } | ||||
| 
 | ||||
| @ -156,20 +158,22 @@ export default class Status { | ||||
| 
 | ||||
|     const { refreshStatus } = this._store.getState().nodeStatus; | ||||
| 
 | ||||
|     const statusPromises = [ this._api.eth.syncing(), this._api.parity.netPeers() ]; | ||||
|     const statusPromises = [ this._api.eth.syncing() ]; | ||||
| 
 | ||||
|     if (refreshStatus) { | ||||
|       statusPromises.push(this._api.parity.netPeers()); | ||||
|       statusPromises.push(this._api.eth.hashrate()); | ||||
|     } | ||||
| 
 | ||||
|     Promise | ||||
|       .all(statusPromises) | ||||
|       .then(([ syncing, netPeers, ...statusResults ]) => { | ||||
|       .then(([ syncing, ...statusResults ]) => { | ||||
|         const status = statusResults.length === 0 | ||||
|           ? { syncing, netPeers } | ||||
|           ? { syncing } | ||||
|           : { | ||||
|             syncing, netPeers, | ||||
|             hashrate: statusResults[0] | ||||
|             syncing, | ||||
|             netPeers: statusResults[0], | ||||
|             hashrate: statusResults[1] | ||||
|           }; | ||||
| 
 | ||||
|         if (!isEqual(status, this._status)) { | ||||
| @ -223,7 +227,7 @@ export default class Status { | ||||
|    * fetched every 30s just in case, and whenever | ||||
|    * the client got reconnected. | ||||
|    */ | ||||
|   _pollLongStatus = (newConnection = false) => { | ||||
|   _pollLongStatus = () => { | ||||
|     if (!this._api.isConnected) { | ||||
|       return; | ||||
|     } | ||||
| @ -241,34 +245,33 @@ export default class Status { | ||||
| 
 | ||||
|     Promise | ||||
|       .all([ | ||||
|         this._api.parity.netPeers(), | ||||
|         this._api.web3.clientVersion(), | ||||
|         this._api.net.version(), | ||||
|         this._api.parity.defaultExtraData(), | ||||
|         this._api.parity.netChain(), | ||||
|         this._api.parity.netPort(), | ||||
|         this._api.parity.rpcSettings(), | ||||
|         newConnection ? Promise.resolve(null) : this._api.parity.enode() | ||||
|         this._api.parity.enode() | ||||
|       ]) | ||||
|       .then(([ | ||||
|         clientVersion, netVersion, defaultExtraData, netChain, netPort, rpcSettings, enode | ||||
|         netPeers, clientVersion, netVersion, defaultExtraData, netChain, netPort, rpcSettings, enode | ||||
|       ]) => { | ||||
|         const isTest = | ||||
|           netVersion === '2' || // morden
 | ||||
|           netVersion === '3'; // ropsten
 | ||||
| 
 | ||||
|         const longStatus = { | ||||
|           netPeers, | ||||
|           clientVersion, | ||||
|           defaultExtraData, | ||||
|           netChain, | ||||
|           netPort, | ||||
|           rpcSettings, | ||||
|           isTest | ||||
|           isTest, | ||||
|           enode | ||||
|         }; | ||||
| 
 | ||||
|         if (enode) { | ||||
|           longStatus.enode = enode; | ||||
|         } | ||||
| 
 | ||||
|         if (!isEqual(longStatus, this._longStatus)) { | ||||
|           this._store.dispatch(statusCollection(longStatus)); | ||||
|           this._longStatus = longStatus; | ||||
| @ -278,7 +281,7 @@ export default class Status { | ||||
|         console.error('_pollLongStatus', error); | ||||
|       }); | ||||
| 
 | ||||
|     nextTimeout(newConnection ? 5000 : 30000); | ||||
|     nextTimeout(60000); | ||||
|   } | ||||
| 
 | ||||
|   _pollLogs = () => { | ||||
|  | ||||
| @ -43,7 +43,7 @@ const initialState = { | ||||
|   isConnected: false, | ||||
|   isConnecting: false, | ||||
|   isPingable: false, | ||||
|   isTest: false, | ||||
|   isTest: undefined, | ||||
|   refreshStatus: false, | ||||
|   traceMode: undefined | ||||
| }; | ||||
|  | ||||
| @ -17,7 +17,7 @@ | ||||
| import { combineReducers } from 'redux'; | ||||
| import { routerReducer } from 'react-router-redux'; | ||||
| 
 | ||||
| import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer } from './providers'; | ||||
| import { apiReducer, balancesReducer, blockchainReducer, compilerReducer, imagesReducer, personalReducer, signerReducer, statusReducer as nodeStatusReducer, snackbarReducer } from './providers'; | ||||
| 
 | ||||
| import { errorReducer } from '../ui/Errors'; | ||||
| import { settingsReducer } from '../views/Settings'; | ||||
| @ -37,6 +37,7 @@ export default function () { | ||||
|     images: imagesReducer, | ||||
|     nodeStatus: nodeStatusReducer, | ||||
|     personal: personalReducer, | ||||
|     signer: signerReducer | ||||
|     signer: signerReducer, | ||||
|     snackbar: snackbarReducer | ||||
|   }); | ||||
| } | ||||
|  | ||||
| @ -77,6 +77,12 @@ export default class SecureApi extends Api { | ||||
|           return this | ||||
|             ._checkNodeUp() | ||||
|             .then((isNodeUp) => { | ||||
|               const { timestamp } = lastError; | ||||
| 
 | ||||
|               if ((Date.now() - timestamp) > 250) { | ||||
|                 return nextTick(); | ||||
|               } | ||||
| 
 | ||||
|               const nextToken = this._tokensToTry[0] || 'initial'; | ||||
|               const nextState = nextToken !== 'initial' ? 0 : 1; | ||||
| 
 | ||||
| @ -89,7 +95,7 @@ export default class SecureApi extends Api { | ||||
|                 this.updateToken(nextToken, nextState); | ||||
|               } | ||||
| 
 | ||||
|               nextTick(); | ||||
|               return nextTick(); | ||||
|             }); | ||||
|         } | ||||
|         break; | ||||
|  | ||||
| @ -44,7 +44,7 @@ class BlockStatus extends Component { | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (!syncing.warpChunksAmount.eq(syncing.warpChunksProcessed)) { | ||||
|     if (syncing.warpChunksAmount && syncing.warpChunksProcessed && !syncing.warpChunksAmount.eq(syncing.warpChunksProcessed)) { | ||||
|       return ( | ||||
|         <div className={ styles.syncStatus }> | ||||
|           { syncing.warpChunksProcessed.mul(100).div(syncing.warpChunksAmount).toFormat(2) }% warp restore | ||||
|  | ||||
| @ -15,19 +15,25 @@ | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { IconButton } from 'material-ui'; | ||||
| import Snackbar from 'material-ui/Snackbar'; | ||||
| import Clipboard from 'react-copy-to-clipboard'; | ||||
| import CopyIcon from 'material-ui/svg-icons/content/content-copy'; | ||||
| import Theme from '../Theme'; | ||||
| import { darkBlack } from 'material-ui/styles/colors'; | ||||
| 
 | ||||
| import { showSnackbar } from '../../redux/providers/snackbarActions'; | ||||
| 
 | ||||
| const { textColor, disabledTextColor } = Theme.flatButton; | ||||
| 
 | ||||
| import styles from './copyToClipboard.css'; | ||||
| 
 | ||||
| export default class CopyToClipboard extends Component { | ||||
| class CopyToClipboard extends Component { | ||||
|   static propTypes = { | ||||
|     showSnackbar: PropTypes.func.isRequired, | ||||
|     data: PropTypes.string.isRequired, | ||||
| 
 | ||||
|     onCopy: PropTypes.func, | ||||
|     size: PropTypes.number, // in px
 | ||||
|     cooldown: PropTypes.number // in ms
 | ||||
| @ -42,11 +48,12 @@ export default class CopyToClipboard extends Component { | ||||
| 
 | ||||
|   state = { | ||||
|     copied: false, | ||||
|     timeout: null | ||||
|     timeoutId: null | ||||
|   }; | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     const { timeoutId } = this.state; | ||||
| 
 | ||||
|     if (timeoutId) { | ||||
|       window.clearTimeout(timeoutId); | ||||
|     } | ||||
| @ -59,14 +66,6 @@ export default class CopyToClipboard extends Component { | ||||
|     return ( | ||||
|       <Clipboard onCopy={ this.onCopy } text={ data }> | ||||
|         <div className={ styles.wrapper }> | ||||
|           <Snackbar | ||||
|             open={ copied } | ||||
|             message={ | ||||
|               <div>copied <code className={ styles.data }>{ data }</code> to clipboard</div> | ||||
|             } | ||||
|             autoHideDuration={ 2000 } | ||||
|             bodyStyle={ { backgroundColor: darkBlack } } | ||||
|           /> | ||||
|           <IconButton | ||||
|             disableTouchRipple | ||||
|             style={ { width: size, height: size, padding: '0' } } | ||||
| @ -80,14 +79,28 @@ export default class CopyToClipboard extends Component { | ||||
|   } | ||||
| 
 | ||||
|   onCopy = () => { | ||||
|     const { cooldown, onCopy } = this.props; | ||||
|     const { data, onCopy, cooldown, showSnackbar } = this.props; | ||||
|     const message = (<div>copied <code className={ styles.data }>{ data }</code> to clipboard</div>); | ||||
| 
 | ||||
|     this.setState({ | ||||
|       copied: true, | ||||
|       timeout: setTimeout(() => { | ||||
|         this.setState({ copied: false, timeout: null }); | ||||
|       timeoutId: setTimeout(() => { | ||||
|         this.setState({ copied: false, timeoutId: null }); | ||||
|       }, cooldown) | ||||
|     }); | ||||
| 
 | ||||
|     showSnackbar(message, cooldown); | ||||
|     onCopy(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({ | ||||
|     showSnackbar | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   null, | ||||
|   mapDispatchToProps | ||||
| )(CopyToClipboard); | ||||
|  | ||||
| @ -28,20 +28,7 @@ export default class Header extends Component { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     account: PropTypes.object, | ||||
|     balance: PropTypes.object, | ||||
|     isTest: PropTypes.bool | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|     name: null | ||||
|   } | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.setName(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps () { | ||||
|     this.setName(); | ||||
|     balance: PropTypes.object | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
| @ -87,13 +74,13 @@ export default class Header extends Component { | ||||
|   } | ||||
| 
 | ||||
|   renderTxCount () { | ||||
|     const { isTest, balance } = this.props; | ||||
|     const { balance } = this.props; | ||||
| 
 | ||||
|     if (!balance) { | ||||
|       return null; | ||||
|     } | ||||
| 
 | ||||
|     const txCount = balance.txCount.sub(isTest ? 0x100000 : 0); | ||||
|     const { txCount } = balance; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.infoline }> | ||||
| @ -101,28 +88,4 @@ export default class Header extends Component { | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   onSubmitName = (name) => { | ||||
|     const { api } = this.context; | ||||
|     const { account } = this.props; | ||||
| 
 | ||||
|     this.setState({ name }, () => { | ||||
|       api.parity | ||||
|         .setAccountName(account.address, name) | ||||
|         .catch((error) => { | ||||
|           console.error(error); | ||||
|         }); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   setName () { | ||||
|     const { account } = this.props; | ||||
| 
 | ||||
|     if (account && account.name !== this.propName) { | ||||
|       this.propName = account.name; | ||||
|       this.setState({ | ||||
|         name: account.name | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -143,6 +143,12 @@ class Transactions extends Component { | ||||
|   getTransactions = (props) => { | ||||
|     const { isTest, address, traceMode } = props; | ||||
| 
 | ||||
|     // Don't fetch the transactions if we don't know in which
 | ||||
|     // network we are yet...
 | ||||
|     if (isTest === undefined) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     return this | ||||
|       .fetchTransactions(isTest, address, traceMode) | ||||
|       .then(transactions => { | ||||
|  | ||||
| @ -30,6 +30,7 @@ import shapeshiftBtn from '../../../assets/images/shapeshift-btn.png'; | ||||
| 
 | ||||
| import Header from './Header'; | ||||
| import Transactions from './Transactions'; | ||||
| import { setVisibleAccounts } from '../../redux/providers/personalActions'; | ||||
| 
 | ||||
| import VerificationStore from '../../modals/SMSVerification/store'; | ||||
| 
 | ||||
| @ -41,11 +42,12 @@ class Account extends Component { | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
|     images: PropTypes.object.isRequired, | ||||
| 
 | ||||
|     params: PropTypes.object, | ||||
|     accounts: PropTypes.object, | ||||
|     balances: PropTypes.object, | ||||
|     images: PropTypes.object.isRequired, | ||||
|     isTest: PropTypes.bool | ||||
|     balances: PropTypes.object | ||||
|   } | ||||
| 
 | ||||
|   propName = null | ||||
| @ -66,10 +68,30 @@ class Account extends Component { | ||||
| 
 | ||||
|     const verificationStore = new VerificationStore(api, address); | ||||
|     this.setState({ verificationStore }); | ||||
|     this.setVisibleAccounts(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevAddress = this.props.params.address; | ||||
|     const nextAddress = nextProps.params.address; | ||||
| 
 | ||||
|     if (prevAddress !== nextAddress) { | ||||
|       this.setVisibleAccounts(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     this.props.setVisibleAccounts([]); | ||||
|   } | ||||
| 
 | ||||
|   setVisibleAccounts (props = this.props) { | ||||
|     const { params, setVisibleAccounts } = props; | ||||
|     const addresses = [ params.address ]; | ||||
|     setVisibleAccounts(addresses); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { accounts, balances, isTest } = this.props; | ||||
|     const { accounts, balances } = this.props; | ||||
|     const { address } = this.props.params; | ||||
| 
 | ||||
|     const account = (accounts || {})[address]; | ||||
| @ -90,7 +112,6 @@ class Account extends Component { | ||||
|         { this.renderActionbar() } | ||||
|         <Page> | ||||
|           <Header | ||||
|             isTest={ isTest } | ||||
|             account={ account } | ||||
|             balance={ balance } /> | ||||
|           <Transactions | ||||
| @ -307,10 +328,8 @@ function mapStateToProps (state) { | ||||
|   const { accounts } = state.personal; | ||||
|   const { balances } = state.balances; | ||||
|   const { images } = state; | ||||
|   const { isTest } = state.nodeStatus; | ||||
| 
 | ||||
|   return { | ||||
|     isTest, | ||||
|     accounts, | ||||
|     balances, | ||||
|     images | ||||
| @ -318,7 +337,9 @@ function mapStateToProps (state) { | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
|   return bindActionCreators({ | ||||
|     setVisibleAccounts | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|  | ||||
| @ -117,14 +117,14 @@ export default class List extends Component { | ||||
|       if (balanceA && !balanceB) return -1; | ||||
|       if (!balanceA && balanceB) return 1; | ||||
| 
 | ||||
|       const ethA = balanceA.tokens | ||||
|         .find(token => token.token.tag.toLowerCase() === 'eth') | ||||
|         .value; | ||||
|       const ethB = balanceB.tokens | ||||
|         .find(token => token.token.tag.toLowerCase() === 'eth') | ||||
|         .value; | ||||
|       const ethA = balanceA.tokens.find(token => token.token.tag.toLowerCase() === 'eth'); | ||||
|       const ethB = balanceB.tokens.find(token => token.token.tag.toLowerCase() === 'eth'); | ||||
| 
 | ||||
|       return -1 * ethA.comparedTo(ethB); | ||||
|       if (!ethA && !ethB) return 0; | ||||
|       if (ethA && !ethB) return -1; | ||||
|       if (!ethA && ethB) return 1; | ||||
| 
 | ||||
|       return -1 * ethA.value.comparedTo(ethB.value); | ||||
|     } | ||||
| 
 | ||||
|     if (key === 'tags') { | ||||
|  | ||||
| @ -38,10 +38,6 @@ export default class Summary extends Component { | ||||
|     noLink: false | ||||
|   }; | ||||
| 
 | ||||
|   state = { | ||||
|     name: 'Unnamed' | ||||
|   }; | ||||
| 
 | ||||
|   shouldComponentUpdate (nextProps) { | ||||
|     const prev = { | ||||
|       link: this.props.link, name: this.props.name, | ||||
| @ -66,8 +62,8 @@ export default class Summary extends Component { | ||||
|       return true; | ||||
|     } | ||||
| 
 | ||||
|     const prevValues = prevTokens.map((t) => t.value.toNumber()); | ||||
|     const nextValues = nextTokens.map((t) => t.value.toNumber()); | ||||
|     const prevValues = prevTokens.map((t) => ({ value: t.value.toNumber(), image: t.token.image })); | ||||
|     const nextValues = nextTokens.map((t) => ({ value: t.value.toNumber(), image: t.token.image })); | ||||
| 
 | ||||
|     if (!isEqual(prevValues, nextValues)) { | ||||
|       return true; | ||||
|  | ||||
| @ -18,11 +18,12 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import { uniq } from 'lodash'; | ||||
| import { uniq, isEqual } from 'lodash'; | ||||
| 
 | ||||
| import List from './List'; | ||||
| import { CreateAccount } from '../../modals'; | ||||
| import { Actionbar, ActionbarExport, ActionbarSearch, ActionbarSort, Button, Page, Tooltip } from '../../ui'; | ||||
| import { setVisibleAccounts } from '../../redux/providers/personalActions'; | ||||
| 
 | ||||
| import styles from './accounts.css'; | ||||
| 
 | ||||
| @ -32,6 +33,8 @@ class Accounts extends Component { | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     accounts: PropTypes.object, | ||||
|     hasAccounts: PropTypes.bool, | ||||
|     balances: PropTypes.object | ||||
| @ -50,6 +53,27 @@ class Accounts extends Component { | ||||
|     window.setTimeout(() => { | ||||
|       this.setState({ show: true }); | ||||
|     }, 100); | ||||
| 
 | ||||
|     this.setVisibleAccounts(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevAddresses = Object.keys(this.props.accounts); | ||||
|     const nextAddresses = Object.keys(nextProps.accounts); | ||||
| 
 | ||||
|     if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) { | ||||
|       this.setVisibleAccounts(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     this.props.setVisibleAccounts([]); | ||||
|   } | ||||
| 
 | ||||
|   setVisibleAccounts (props = this.props) { | ||||
|     const { accounts, setVisibleAccounts } = props; | ||||
|     const addresses = Object.keys(accounts); | ||||
|     setVisibleAccounts(addresses); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
| @ -206,7 +230,9 @@ function mapStateToProps (state) { | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
|   return bindActionCreators({ | ||||
|     setVisibleAccounts | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|  | ||||
| @ -26,6 +26,7 @@ import { Actionbar, Button, Page } from '../../ui'; | ||||
| import Header from '../Account/Header'; | ||||
| import Transactions from '../Account/Transactions'; | ||||
| import Delete from './Delete'; | ||||
| import { setVisibleAccounts } from '../../redux/providers/personalActions'; | ||||
| 
 | ||||
| import styles from './address.css'; | ||||
| 
 | ||||
| @ -36,9 +37,10 @@ class Address extends Component { | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     contacts: PropTypes.object, | ||||
|     balances: PropTypes.object, | ||||
|     isTest: PropTypes.bool, | ||||
|     params: PropTypes.object | ||||
|   } | ||||
| 
 | ||||
| @ -47,8 +49,31 @@ class Address extends Component { | ||||
|     showEditDialog: false | ||||
|   } | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     this.setVisibleAccounts(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevAddress = this.props.params.address; | ||||
|     const nextAddress = nextProps.params.address; | ||||
| 
 | ||||
|     if (prevAddress !== nextAddress) { | ||||
|       this.setVisibleAccounts(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     this.props.setVisibleAccounts([]); | ||||
|   } | ||||
| 
 | ||||
|   setVisibleAccounts (props = this.props) { | ||||
|     const { params, setVisibleAccounts } = props; | ||||
|     const addresses = [ params.address ]; | ||||
|     setVisibleAccounts(addresses); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { contacts, balances, isTest } = this.props; | ||||
|     const { contacts, balances } = this.props; | ||||
|     const { address } = this.props.params; | ||||
|     const { showDeleteDialog } = this.state; | ||||
| 
 | ||||
| @ -70,7 +95,6 @@ class Address extends Component { | ||||
|           onClose={ this.closeDeleteDialog } /> | ||||
|         <Page> | ||||
|           <Header | ||||
|             isTest={ isTest } | ||||
|             account={ contact } | ||||
|             balance={ balance } /> | ||||
|           <Transactions | ||||
| @ -134,17 +158,17 @@ class Address extends Component { | ||||
| function mapStateToProps (state) { | ||||
|   const { contacts } = state.personal; | ||||
|   const { balances } = state.balances; | ||||
|   const { isTest } = state.nodeStatus; | ||||
| 
 | ||||
|   return { | ||||
|     isTest, | ||||
|     contacts, | ||||
|     balances | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
|   return bindActionCreators({ | ||||
|     setVisibleAccounts | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|  | ||||
| @ -18,12 +18,13 @@ import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import { uniq } from 'lodash'; | ||||
| 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 { setVisibleAccounts } from '../../redux/providers/personalActions'; | ||||
| 
 | ||||
| import styles from './addresses.css'; | ||||
| 
 | ||||
| @ -33,6 +34,8 @@ class Addresses extends Component { | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     balances: PropTypes.object, | ||||
|     contacts: PropTypes.object, | ||||
|     hasContacts: PropTypes.bool | ||||
| @ -45,6 +48,29 @@ class Addresses extends Component { | ||||
|     searchTokens: [] | ||||
|   } | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.setVisibleAccounts(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevAddresses = Object.keys(this.props.contacts); | ||||
|     const nextAddresses = Object.keys(nextProps.contacts); | ||||
| 
 | ||||
|     if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) { | ||||
|       this.setVisibleAccounts(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     this.props.setVisibleAccounts([]); | ||||
|   } | ||||
| 
 | ||||
|   setVisibleAccounts (props = this.props) { | ||||
|     const { contacts, setVisibleAccounts } = props; | ||||
|     const addresses = Object.keys(contacts); | ||||
|     setVisibleAccounts(addresses); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { balances, contacts, hasContacts } = this.props; | ||||
|     const { searchValues, sortOrder } = this.state; | ||||
| @ -231,7 +257,9 @@ function mapStateToProps (state) { | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
|   return bindActionCreators({ | ||||
|     setVisibleAccounts | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|  | ||||
							
								
								
									
										17
									
								
								js/src/views/Application/Snackbar/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								js/src/views/Application/Snackbar/index.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| export default from './snackbar'; | ||||
							
								
								
									
										68
									
								
								js/src/views/Application/Snackbar/snackbar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								js/src/views/Application/Snackbar/snackbar.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | ||||
| // Copyright 2015, 2016 Ethcore (UK) Ltd.
 | ||||
| // This file is part of Parity.
 | ||||
| 
 | ||||
| // Parity is free software: you can redistribute it and/or modify
 | ||||
| // it under the terms of the GNU General Public License as published by
 | ||||
| // the Free Software Foundation, either version 3 of the License, or
 | ||||
| // (at your option) any later version.
 | ||||
| 
 | ||||
| // Parity is distributed in the hope that it will be useful,
 | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | ||||
| // GNU General Public License for more details.
 | ||||
| 
 | ||||
| // You should have received a copy of the GNU General Public License
 | ||||
| // along with Parity.  If not, see <http://www.gnu.org/licenses/>.
 | ||||
| 
 | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| 
 | ||||
| import { Snackbar as SnackbarMUI } from 'material-ui'; | ||||
| import { darkBlack } from 'material-ui/styles/colors'; | ||||
| 
 | ||||
| import { closeSnackbar } from '../../../redux/providers/snackbarActions'; | ||||
| 
 | ||||
| class Snackbar extends Component { | ||||
|   static propTypes = { | ||||
|     closeSnackbar: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     open: PropTypes.bool, | ||||
|     cooldown: PropTypes.number, | ||||
|     message: PropTypes.any | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { open, message, cooldown } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <SnackbarMUI | ||||
|         open={ open } | ||||
|         message={ message } | ||||
|         autoHideDuration={ cooldown } | ||||
|         bodyStyle={ { backgroundColor: darkBlack } } | ||||
|         onRequestClose={ this.handleClose } | ||||
|       /> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   handleClose = () => { | ||||
|     this.props.closeSnackbar(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|   const { open, message, cooldown } = state.snackbar; | ||||
|   return { open, message, cooldown }; | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({ | ||||
|     closeSnackbar | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|   mapStateToProps, | ||||
|   mapDispatchToProps | ||||
| )(Snackbar); | ||||
| @ -17,21 +17,24 @@ | ||||
| import React, { Component, PropTypes } from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import { observer } from 'mobx-react'; | ||||
| 
 | ||||
| import Connection from '../Connection'; | ||||
| import ParityBar from '../ParityBar'; | ||||
| 
 | ||||
| import Snackbar from './Snackbar'; | ||||
| import Container from './Container'; | ||||
| import DappContainer from './DappContainer'; | ||||
| import FrameError from './FrameError'; | ||||
| 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; | ||||
| const showFirstRun = window.localStorage.getItem('showFirstRun') === '1'; | ||||
| 
 | ||||
| @observer | ||||
| class Application extends Component { | ||||
|   static contextTypes = { | ||||
|     api: PropTypes.object.isRequired, | ||||
| @ -46,13 +49,7 @@ class Application extends Component { | ||||
|     blockNumber: PropTypes.object | ||||
|   } | ||||
| 
 | ||||
|   state = { | ||||
|     showFirstRun: false | ||||
|   } | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.checkAccounts(); | ||||
|   } | ||||
|   store = new Store(this.context.api); | ||||
| 
 | ||||
|   render () { | ||||
|     const [root] = (window.location.hash || '').replace('#/', '').split('/'); | ||||
| @ -75,18 +72,18 @@ class Application extends Component { | ||||
| 
 | ||||
|   renderApp () { | ||||
|     const { children, pending, netChain, isTest, blockNumber } = this.props; | ||||
|     const { showFirstRun } = this.state; | ||||
| 
 | ||||
|     return ( | ||||
|       <Container | ||||
|         showFirstRun={ showFirstRun } | ||||
|         onCloseFirstRun={ this.onCloseFirstRun }> | ||||
|         showFirstRun={ this.store.firstrunVisible } | ||||
|         onCloseFirstRun={ this.store.closeFirstrun }> | ||||
|         <TabBar | ||||
|           netChain={ netChain } | ||||
|           isTest={ isTest } | ||||
|           pending={ pending } /> | ||||
|         { children } | ||||
|         { blockNumber ? (<Status />) : null } | ||||
|         <Snackbar /> | ||||
|       </Container> | ||||
|     ); | ||||
|   } | ||||
| @ -100,28 +97,6 @@ class Application extends Component { | ||||
|       </DappContainer> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   checkAccounts () { | ||||
|     const { api } = this.context; | ||||
| 
 | ||||
|     api.eth | ||||
|       .accounts() | ||||
|       .then((accounts) => { | ||||
|         this.setState({ | ||||
|           showFirstRun: showFirstRun || accounts.length === 0 | ||||
|         }); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.error('checkAccounts', error); | ||||
|       }); | ||||
|   } | ||||
| 
 | ||||
|   onCloseFirstRun = () => { | ||||
|     window.localStorage.setItem('showFirstRun', '0'); | ||||
|     this.setState({ | ||||
|       showFirstRun: false | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function mapStateToProps (state) { | ||||
|  | ||||
							
								
								
									
										51
									
								
								js/src/views/Application/store.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								js/src/views/Application/store.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| // 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 { action, observable } from 'mobx'; | ||||
| 
 | ||||
| const showFirstRun = window.localStorage.getItem('showFirstRun') !== '0'; | ||||
| 
 | ||||
| export default class Store { | ||||
|   @observable firstrunVisible = showFirstRun; | ||||
| 
 | ||||
|   constructor (api) { | ||||
|     this._api = api; | ||||
| 
 | ||||
|     this._checkAccounts(); | ||||
|   } | ||||
| 
 | ||||
|   @action closeFirstrun = () => { | ||||
|     this.toggleFirstrun(false); | ||||
|   } | ||||
| 
 | ||||
|   @action toggleFirstrun = (visible = false) => { | ||||
|     this.firstrunVisible = visible; | ||||
|     window.localStorage.setItem('showFirstRun', visible ? '1' : '0'); | ||||
|   } | ||||
| 
 | ||||
|   _checkAccounts () { | ||||
|     this._api.parity | ||||
|       .accountsInfo() | ||||
|       .then((info) => { | ||||
|         const accounts = Object.keys(info).filter((address) => info[address].uuid); | ||||
| 
 | ||||
|         this.toggleFirstrun(this.firstrunVisible || !accounts || !accounts.length); | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.error('checkAccounts', error); | ||||
|       }); | ||||
|   } | ||||
| } | ||||
| @ -24,6 +24,8 @@ import EyeIcon from 'material-ui/svg-icons/image/remove-red-eye'; | ||||
| import ContentClear from 'material-ui/svg-icons/content/clear'; | ||||
| 
 | ||||
| import { newError } from '../../redux/actions'; | ||||
| import { setVisibleAccounts } from '../../redux/providers/personalActions'; | ||||
| 
 | ||||
| import { EditMeta, ExecuteContract } from '../../modals'; | ||||
| import { Actionbar, Button, Page, Modal, Editor } from '../../ui'; | ||||
| 
 | ||||
| @ -41,6 +43,8 @@ class Contract extends Component { | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     accounts: PropTypes.object, | ||||
|     balances: PropTypes.object, | ||||
|     contracts: PropTypes.object, | ||||
| @ -68,21 +72,29 @@ class Contract extends Component { | ||||
| 
 | ||||
|     this.attachContract(this.props); | ||||
|     this.setBaseAccount(this.props); | ||||
|     this.setVisibleAccounts(); | ||||
| 
 | ||||
|     api | ||||
|       .subscribe('eth_blockNumber', this.queryContract) | ||||
|       .then(blockSubscriptionId => this.setState({ blockSubscriptionId })); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (newProps) { | ||||
|     const { accounts, contracts } = newProps; | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const { accounts, contracts } = nextProps; | ||||
| 
 | ||||
|     if (Object.keys(contracts).length !== Object.keys(this.props.contracts).length) { | ||||
|       this.attachContract(newProps); | ||||
|       this.attachContract(nextProps); | ||||
|     } | ||||
| 
 | ||||
|     if (Object.keys(accounts).length !== Object.keys(this.props.accounts).length) { | ||||
|       this.setBaseAccount(newProps); | ||||
|       this.setBaseAccount(nextProps); | ||||
|     } | ||||
| 
 | ||||
|     const prevAddress = this.props.params.address; | ||||
|     const nextAddress = nextProps.params.address; | ||||
| 
 | ||||
|     if (prevAddress !== nextAddress) { | ||||
|       this.setVisibleAccounts(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -92,6 +104,13 @@ class Contract extends Component { | ||||
| 
 | ||||
|     api.unsubscribe(blockSubscriptionId); | ||||
|     contract.unsubscribe(subscriptionId); | ||||
|     this.props.setVisibleAccounts([]); | ||||
|   } | ||||
| 
 | ||||
|   setVisibleAccounts (props = this.props) { | ||||
|     const { params, setVisibleAccounts } = props; | ||||
|     const addresses = [ params.address ]; | ||||
|     setVisibleAccounts(addresses); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
| @ -112,7 +131,6 @@ class Contract extends Component { | ||||
|         { this.renderExecuteDialog() } | ||||
|         <Page> | ||||
|           <Header | ||||
|             isTest={ isTest } | ||||
|             account={ account } | ||||
|             balance={ balance } /> | ||||
|           <Queries | ||||
| @ -430,7 +448,7 @@ function mapStateToProps (state) { | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({ newError }, dispatch); | ||||
|   return bindActionCreators({ newError, setVisibleAccounts }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|  | ||||
| @ -20,10 +20,11 @@ import { connect } from 'react-redux'; | ||||
| import { bindActionCreators } from 'redux'; | ||||
| import ContentAdd from 'material-ui/svg-icons/content/add'; | ||||
| import FileIcon from 'material-ui/svg-icons/action/description'; | ||||
| import { uniq } from 'lodash'; | ||||
| import { uniq, isEqual } from 'lodash'; | ||||
| 
 | ||||
| import { Actionbar, ActionbarSearch, ActionbarSort, Button, Page } from '../../ui'; | ||||
| import { AddContract, DeployContract } from '../../modals'; | ||||
| import { setVisibleAccounts } from '../../redux/providers/personalActions'; | ||||
| 
 | ||||
| import List from '../Accounts/List'; | ||||
| 
 | ||||
| @ -35,6 +36,8 @@ class Contracts extends Component { | ||||
|   } | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     setVisibleAccounts: PropTypes.func.isRequired, | ||||
| 
 | ||||
|     balances: PropTypes.object, | ||||
|     accounts: PropTypes.object, | ||||
|     contracts: PropTypes.object, | ||||
| @ -49,6 +52,29 @@ class Contracts extends Component { | ||||
|     searchTokens: [] | ||||
|   } | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|     this.setVisibleAccounts(); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|     const prevAddresses = Object.keys(this.props.contracts); | ||||
|     const nextAddresses = Object.keys(nextProps.contracts); | ||||
| 
 | ||||
|     if (prevAddresses.length !== nextAddresses.length || !isEqual(prevAddresses.sort(), nextAddresses.sort())) { | ||||
|       this.setVisibleAccounts(nextProps); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     this.props.setVisibleAccounts([]); | ||||
|   } | ||||
| 
 | ||||
|   setVisibleAccounts (props = this.props) { | ||||
|     const { contracts, setVisibleAccounts } = props; | ||||
|     const addresses = Object.keys(contracts); | ||||
|     setVisibleAccounts(addresses); | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { contracts, hasContracts, balances } = this.props; | ||||
|     const { searchValues, sortOrder } = this.state; | ||||
| @ -205,7 +231,9 @@ function mapStateToProps (state) { | ||||
| } | ||||
| 
 | ||||
| function mapDispatchToProps (dispatch) { | ||||
|   return bindActionCreators({}, dispatch); | ||||
|   return bindActionCreators({ | ||||
|     setVisibleAccounts | ||||
|   }, dispatch); | ||||
| } | ||||
| 
 | ||||
| export default connect( | ||||
|  | ||||
| @ -63,7 +63,7 @@ export default class Dapp extends Component { | ||||
|         className={ styles.frame } | ||||
|         frameBorder={ 0 } | ||||
|         name={ name } | ||||
|         sandbox='allow-same-origin allow-scripts' | ||||
|         sandbox='allow-forms allow-popups allow-same-origin allow-scripts' | ||||
|         scrolling='auto' | ||||
|         src={ src }> | ||||
|       </iframe> | ||||
|  | ||||
| @ -54,5 +54,15 @@ | ||||
|     "version": "1.0.0", | ||||
|     "visible": true, | ||||
|     "secure": true | ||||
|   }, | ||||
|   { | ||||
|     "id": "0x7bbc4f1a27628781b96213e781a1b8eec6982c1db8fac739af6e4c5a55862c03", | ||||
|     "url": "dappreg", | ||||
|     "name": "Dapp Registration", | ||||
|     "description": "Enables the registration and content management of dapps on the network", | ||||
|     "author": "Parity Team <admin@ethcore.io>", | ||||
|     "version": "1.0.0", | ||||
|     "visible": false, | ||||
|     "secure": true | ||||
|   } | ||||
| ] | ||||
|  | ||||
| @ -32,6 +32,8 @@ export default class DappsStore { | ||||
|   @observable modalOpen = false; | ||||
|   @observable externalOverlayVisible = true; | ||||
| 
 | ||||
|   _manifests = {}; | ||||
| 
 | ||||
|   constructor (api) { | ||||
|     this._api = api; | ||||
| 
 | ||||
| @ -249,12 +251,27 @@ export default class DappsStore { | ||||
|   } | ||||
| 
 | ||||
|   _fetchManifest (manifestHash) { | ||||
|     if (/^(0x)?0+/.test(manifestHash)) { | ||||
|       return Promise.resolve(null); | ||||
|     } | ||||
| 
 | ||||
|     if (this._manifests[manifestHash]) { | ||||
|       return Promise.resolve(this._manifests[manifestHash]); | ||||
|     } | ||||
| 
 | ||||
|     return fetch(`${this._getHost()}/api/content/${manifestHash}/`, { redirect: 'follow', mode: 'cors' }) | ||||
|       .then((response) => { | ||||
|         return response.ok | ||||
|           ? response.json() | ||||
|           : null; | ||||
|       }) | ||||
|       .then((manifest) => { | ||||
|         if (manifest) { | ||||
|           this._manifests[manifestHash] = manifest; | ||||
|         } | ||||
| 
 | ||||
|         return manifest; | ||||
|       }) | ||||
|       .catch((error) => { | ||||
|         console.warn('DappsStore:fetchManifest', error); | ||||
|         return null; | ||||
|  | ||||
| @ -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/>. | ||||
| */ | ||||
| 
 | ||||
| .bar, .expanded { | ||||
|   position: fixed; | ||||
|   bottom: 0; | ||||
| @ -42,8 +43,7 @@ | ||||
| 
 | ||||
| .expanded { | ||||
|   right: 16px; | ||||
|   width: 964px; | ||||
|   height: 300px; | ||||
|   max-height: 300px; | ||||
|   border-radius: 4px 4px 0 0; | ||||
|   overflow-y: auto; | ||||
|   display: flex; | ||||
|  | ||||
							
								
								
									
										24
									
								
								js/src/views/Signer/_layout.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								js/src/views/Signer/_layout.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| /* 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/>. | ||||
| */ | ||||
| 
 | ||||
| $pendingHeight: 190px; | ||||
| $finishedHeight: 120px; | ||||
| 
 | ||||
| $embedWidth: 920px; | ||||
| $statusWidth: 260px; | ||||
| 
 | ||||
| $accountPadding: 75px; | ||||
| @ -14,31 +14,35 @@ | ||||
| /* You should have received a copy of the GNU General Public License | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../../_layout.css'; | ||||
| 
 | ||||
| .container { | ||||
|   position: relative; | ||||
|   padding: 25px 0 15px; | ||||
|   display: flex; | ||||
|   padding: 1.5em 0 1em; | ||||
| } | ||||
| 
 | ||||
| .actions, .signDetails { | ||||
|   display: inline-block; | ||||
|   vertical-align: middle; | ||||
|   min-height: 120px; | ||||
|   min-height: $pendingHeight; | ||||
| } | ||||
| 
 | ||||
| .signDetails { | ||||
|   border-right: 1px solid #eee; | ||||
|   margin-right: 2rem; | ||||
|   /* TODO [todr] mess - just to align with transaction */ | ||||
|   width:  430px; | ||||
|   flex: 1; | ||||
| } | ||||
| 
 | ||||
| .address, .info { | ||||
|   box-sizing: border-box; | ||||
|   display: inline-block; | ||||
|   width: 50%; | ||||
| } | ||||
| 
 | ||||
| .address { | ||||
|   padding-right: $accountPadding; | ||||
| } | ||||
| 
 | ||||
| .info { | ||||
|   padding: 0 30px; | ||||
|   width: 250px; | ||||
|   color: #E53935; | ||||
|   vertical-align: top; | ||||
| } | ||||
| @ -63,7 +67,7 @@ | ||||
| 
 | ||||
| .actions { | ||||
|   display: inline-block; | ||||
|   min-height: 120px; | ||||
|   min-height: $finishedHeight; | ||||
| } | ||||
| 
 | ||||
| .signDetails img { | ||||
|  | ||||
| @ -15,31 +15,26 @@ | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../../_layout.css'; | ||||
| 
 | ||||
| .container { | ||||
|   padding: 25px 0 15px; | ||||
| } | ||||
|   display: flex; | ||||
|   padding: 1.5em 0 1em; | ||||
| 
 | ||||
| .mainContainer { | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .mainContainer > * { | ||||
|   & > * { | ||||
|     vertical-align: middle; | ||||
|   min-height: 120px; | ||||
|     min-height: $finishedHeight; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .statusContainer { | ||||
|   width: 220px; | ||||
|   padding: 0 40px 0 40px; | ||||
|   /*border-left: 1px solid #aaa;*/ | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   box-sizing: content-box; | ||||
|   box-sizing: border-box; | ||||
|   float: right; | ||||
|   padding: 0 1em; | ||||
|   flex: 0 0 $statusWidth; | ||||
| } | ||||
| 
 | ||||
| .transactionDetails { | ||||
|   padding-right: 321px; | ||||
|   width: 100%; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
|  | ||||
| @ -63,7 +63,6 @@ export default class TransactionFinished extends Component { | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ `${styles.container} ${className || ''}` }> | ||||
|         <div className={ styles.mainContainer }> | ||||
|         <TransactionMainDetails | ||||
|           { ...this.props } | ||||
|           { ...this.state } | ||||
| @ -80,7 +79,6 @@ export default class TransactionFinished extends Component { | ||||
|           { this.renderStatus() } | ||||
|         </div> | ||||
|       </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -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 '../../_layout.css'; | ||||
| 
 | ||||
| .transaction { | ||||
|   flex: 1; | ||||
| } | ||||
| 
 | ||||
| .transaction > * { | ||||
| @ -30,11 +34,11 @@ | ||||
| } | ||||
| 
 | ||||
| .from .account { | ||||
|   padding-right: 75px; | ||||
|   padding-right: $accountPadding; | ||||
| } | ||||
| 
 | ||||
| .to .account { | ||||
|   padding-left: 75px; | ||||
|   padding-left: $accountPadding; | ||||
| } | ||||
| 
 | ||||
| .from img, .to img { | ||||
|  | ||||
| @ -33,7 +33,6 @@ export default class TransactionMainDetails extends Component { | ||||
|     isTest: PropTypes.bool.isRequired, | ||||
|     to: PropTypes.string, // undefined if it's a contract
 | ||||
|     toBalance: PropTypes.object, // eth BigNumber - undefined if it's a contract or until it's fetched
 | ||||
|     className: PropTypes.string, | ||||
|     children: PropTypes.node | ||||
|   }; | ||||
| 
 | ||||
| @ -60,23 +59,15 @@ export default class TransactionMainDetails extends Component { | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { className, children } = this.props; | ||||
|     const { to } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ className }> | ||||
|         { this.renderTransfer() } | ||||
|         { this.renderContract() } | ||||
|         { children } | ||||
|       </div> | ||||
|     ); | ||||
|     return to | ||||
|       ? this.renderTransfer() | ||||
|       : this.renderContract(); | ||||
|   } | ||||
| 
 | ||||
|   renderTransfer () { | ||||
|     const { from, fromBalance, to, toBalance, isTest } = this.props; | ||||
| 
 | ||||
|     if (!to) { | ||||
|       return; | ||||
|     } | ||||
|     const { children, from, fromBalance, to, toBalance, isTest } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.transaction }> | ||||
| @ -101,16 +92,13 @@ export default class TransactionMainDetails extends Component { | ||||
|               isTest={ isTest } /> | ||||
|           </div> | ||||
|         </div> | ||||
|         { children } | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   renderContract () { | ||||
|     const { from, fromBalance, to, isTest } = this.props; | ||||
| 
 | ||||
|     if (to) { | ||||
|       return; | ||||
|     } | ||||
|     const { children, from, fromBalance, isTest } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ styles.transaction }> | ||||
| @ -134,6 +122,7 @@ export default class TransactionMainDetails extends Component { | ||||
|             Contract | ||||
|           </div> | ||||
|         </div> | ||||
|         { children } | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| @ -14,33 +14,14 @@ | ||||
| /* You should have received a copy of the GNU General Public License | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| 
 | ||||
| @import '../../_layout.css'; | ||||
| 
 | ||||
| .container { | ||||
|   padding: 25px 0 15px; | ||||
| } | ||||
|   display: flex; | ||||
|   padding: 1.5em 0 1em; | ||||
| 
 | ||||
| .transactionDetails { | ||||
|   padding-right: 321px; | ||||
|   width: 100%; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| 
 | ||||
| .mainContainer { | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .mainContainer:after { | ||||
|   clear: both; | ||||
| } | ||||
| 
 | ||||
| .mainContainer > * { | ||||
|   & > * { | ||||
|     vertical-align: middle; | ||||
|   min-height: 190px; | ||||
|   } | ||||
| 
 | ||||
| .inputs { | ||||
|   margin-right: 30px; | ||||
|   margin-left: 30px; | ||||
|   width: 180px; | ||||
|   position: relative; | ||||
|   top: -15px; /* due to material ui weird styling */ | ||||
| } | ||||
|  | ||||
| @ -70,7 +70,6 @@ export default class TransactionPending extends Component { | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ `${styles.container} ${className || ''}` }> | ||||
|         <div className={ styles.mainContainer }> | ||||
|         <TransactionMainDetails | ||||
|           { ...this.props } | ||||
|           { ...this.state } | ||||
| @ -93,7 +92,6 @@ export default class TransactionPending extends Component { | ||||
|           onReject={ this.onReject } | ||||
|         /> | ||||
|       </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -14,14 +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 '../../_layout.css'; | ||||
| 
 | ||||
| .container { | ||||
|   width: 220px; | ||||
|   padding: 20px 40px 0 40px; | ||||
|   /*border-left: 1px solid #aaa;*/ | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   right: 0; | ||||
|   box-sizing: content-box; | ||||
|   box-sizing: border-box; | ||||
|   padding: 1em 1em 0 1em; | ||||
|   flex: 0 0 $statusWidth; | ||||
| } | ||||
| 
 | ||||
| .rejectToggle { | ||||
|  | ||||
| @ -15,7 +15,7 @@ | ||||
| /* along with Parity.  If not, see <http://www.gnu.org/licenses/>. | ||||
| */ | ||||
| .confirmForm { | ||||
|   margin-top: -45px; | ||||
|   margin-top: -2em; | ||||
| } | ||||
| 
 | ||||
| .confirmButton { | ||||
|  | ||||
| @ -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/>. | ||||
| */ | ||||
| 
 | ||||
| /* the rejection button itself, once .reject has been pressed */ | ||||
| .rejectButton { | ||||
|   display: block !important; | ||||
|  | ||||
| @ -1,3 +1,24 @@ | ||||
| /* 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/>. | ||||
| */ | ||||
| 
 | ||||
| .container { | ||||
|   display: block; | ||||
| } | ||||
| 
 | ||||
| .iconsContainer { | ||||
|   display: block; | ||||
|   text-align: center; | ||||
| @ -67,4 +88,3 @@ | ||||
| .expandedContainer:empty { | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -27,7 +27,6 @@ import styles from './TransactionSecondaryDetails.css'; | ||||
| import * as tUtil from '../util/transaction'; | ||||
| 
 | ||||
| export default class TransactionSecondaryDetails extends Component { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     id: PropTypes.object.isRequired, | ||||
|     date: PropTypes.instanceOf(Date), | ||||
| @ -45,7 +44,7 @@ export default class TransactionSecondaryDetails extends Component { | ||||
|     const className = this.props.className || ''; | ||||
| 
 | ||||
|     return ( | ||||
|       <div className={ className }> | ||||
|       <div className={ `${styles.container} ${className}` }> | ||||
|         <div className={ styles.iconsContainer }> | ||||
|           { this.renderGasPrice() } | ||||
|           { this.renderData() } | ||||
|  | ||||
| @ -14,8 +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 '../../_layout.css'; | ||||
| 
 | ||||
| .signer { | ||||
|   width: 916px; | ||||
|   box-sizing: border-box; | ||||
|   padding: 0; | ||||
|   width: $embedWidth; | ||||
| } | ||||
| 
 | ||||
| .pending { | ||||
|  | ||||
| @ -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/>. | ||||
| */ | ||||
| 
 | ||||
| .request { | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -66,7 +66,7 @@ reseal_on_txs = "all" | ||||
| reseal_min_period = 4000 | ||||
| work_queue_size = 20 | ||||
| relay_set = "cheap" | ||||
| usd_per_tx = "0" | ||||
| usd_per_tx = "0.0025" | ||||
| usd_per_eth = "auto" | ||||
| price_update_period = "hourly" | ||||
| gas_floor_target = "4700000" | ||||
|  | ||||
| @ -190,7 +190,7 @@ usage! { | ||||
| 			or |c: &Config| otry!(c.mining).tx_time_limit.clone().map(Some), | ||||
| 		flag_relay_set: String = "cheap", | ||||
| 			or |c: &Config| otry!(c.mining).relay_set.clone(), | ||||
| 		flag_usd_per_tx: String = "0", | ||||
| 		flag_usd_per_tx: String = "0.0025", | ||||
| 			or |c: &Config| otry!(c.mining).usd_per_tx.clone(), | ||||
| 		flag_usd_per_eth: String = "auto", | ||||
| 			or |c: &Config| otry!(c.mining).usd_per_eth.clone(), | ||||
| @ -568,7 +568,7 @@ mod tests { | ||||
| 			flag_tx_gas_limit: Some("6283184".into()), | ||||
| 			flag_tx_time_limit: Some(100u64), | ||||
| 			flag_relay_set: "cheap".into(), | ||||
| 			flag_usd_per_tx: "0".into(), | ||||
| 			flag_usd_per_tx: "0.0025".into(), | ||||
| 			flag_usd_per_eth: "auto".into(), | ||||
| 			flag_price_update_period: "hourly".into(), | ||||
| 			flag_gas_floor_target: "4700000".into(), | ||||
|  | ||||
| @ -177,7 +177,7 @@ pub enum GasPricerConfig { | ||||
| impl Default for GasPricerConfig { | ||||
| 	fn default() -> Self { | ||||
| 		GasPricerConfig::Calibrated { | ||||
| 			usd_per_tx: 0f32, | ||||
| 			usd_per_tx: 0.0025f32, | ||||
| 			recalibration_period: Duration::from_secs(3600), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user