Add new Componennt for Token Images (#4496) (#4498)

This commit is contained in:
Nicolas Gotchac 2017-02-09 17:41:27 +01:00 committed by Jaco Greeff
parent 48eac51c8a
commit 71f84067d9
11 changed files with 225 additions and 176 deletions

View File

@ -14,15 +14,13 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Checkbox, MenuItem } from 'material-ui'; import { Checkbox } from 'material-ui';
import { isEqual } from 'lodash';
import Form, { Input, InputAddressSelect, AddressSelect, Select } from '~/ui/Form'; import Form, { Input, InputAddressSelect, AddressSelect } from '~/ui/Form';
import { nullableProptype } from '~/util/proptypes'; import { nullableProptype } from '~/util/proptypes';
import imageUnknown from '../../../../assets/images/contracts/unknown-64x64.png'; import TokenSelect from './tokenSelect';
import styles from '../transfer.css'; import styles from '../transfer.css';
const CHECK_STYLE = { const CHECK_STYLE = {
@ -31,110 +29,12 @@ const CHECK_STYLE = {
left: '1em' left: '1em'
}; };
class TokenSelect extends Component {
static contextTypes = {
api: PropTypes.object
}
static propTypes = {
onChange: PropTypes.func.isRequired,
balance: PropTypes.object.isRequired,
images: PropTypes.object.isRequired,
tag: PropTypes.string.isRequired
};
componentWillMount () {
this.computeTokens();
}
componentWillReceiveProps (nextProps) {
const prevTokens = this.props.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`);
const nextTokens = nextProps.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`);
if (!isEqual(prevTokens, nextTokens)) {
this.computeTokens(nextProps);
}
}
computeTokens (props = this.props) {
const { api } = this.context;
const { balance, images } = this.props;
const items = balance.tokens
.filter((token, index) => !index || token.value.gt(0))
.map((balance, index) => {
const token = balance.token;
const isEth = index === 0;
let imagesrc = token.image;
if (!imagesrc) {
imagesrc =
images[token.address]
? `${api.dappsUrl}${images[token.address]}`
: imageUnknown;
}
let value = 0;
if (isEth) {
value = api.util.fromWei(balance.value).toFormat(3);
} else {
const format = balance.token.format || 1;
const decimals = format === 1 ? 0 : Math.min(3, Math.floor(format / 10));
value = new BigNumber(balance.value).div(format).toFormat(decimals);
}
const label = (
<div className={ styles.token }>
<img src={ imagesrc } />
<div className={ styles.tokenname }>
{ token.name }
</div>
<div className={ styles.tokenbalance }>
{ value }<small> { token.tag }</small>
</div>
</div>
);
return (
<MenuItem
key={ `${index}_${token.tag}` }
value={ token.tag }
label={ label }
>
{ label }
</MenuItem>
);
});
this.setState({ items });
}
render () {
const { tag, onChange } = this.props;
const { items } = this.state;
return (
<Select
className={ styles.tokenSelect }
label='type of token transfer'
hint='type of token to transfer'
value={ tag }
onChange={ onChange }
>
{ items }
</Select>
);
}
}
export default class Details extends Component { export default class Details extends Component {
static propTypes = { static propTypes = {
address: PropTypes.string, address: PropTypes.string,
balance: PropTypes.object, balance: PropTypes.object,
all: PropTypes.bool, all: PropTypes.bool,
extras: PropTypes.bool, extras: PropTypes.bool,
images: PropTypes.object.isRequired,
sender: PropTypes.string, sender: PropTypes.string,
senderError: PropTypes.string, senderError: PropTypes.string,
sendersBalances: PropTypes.object, sendersBalances: PropTypes.object,
@ -249,12 +149,11 @@ export default class Details extends Component {
} }
renderTokenSelect () { renderTokenSelect () {
const { balance, images, tag } = this.props; const { balance, tag } = this.props;
return ( return (
<TokenSelect <TokenSelect
balance={ balance } balance={ balance }
images={ images }
tag={ tag } tag={ tag }
onChange={ this.onChangeToken } onChange={ this.onChangeToken }
/> />

View File

@ -0,0 +1,114 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { MenuItem } from 'material-ui';
import { isEqual } from 'lodash';
import { Select } from '~/ui/Form';
import TokenImage from '~/ui/TokenImage';
import styles from '../transfer.css';
export default class TokenSelect extends Component {
static contextTypes = {
api: PropTypes.object
};
static propTypes = {
onChange: PropTypes.func.isRequired,
balance: PropTypes.object.isRequired,
tag: PropTypes.string.isRequired
};
componentWillMount () {
this.computeTokens();
}
componentWillReceiveProps (nextProps) {
const prevTokens = this.props.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`);
const nextTokens = nextProps.balance.tokens.map((t) => `${t.token.tag}_${t.value.toNumber()}`);
if (!isEqual(prevTokens, nextTokens)) {
this.computeTokens(nextProps);
}
}
computeTokens (props = this.props) {
const { api } = this.context;
const { balance } = this.props;
const items = balance.tokens
.filter((token, index) => !index || token.value.gt(0))
.map((balance, index) => {
const token = balance.token;
const isEth = index === 0;
let value = 0;
if (isEth) {
value = api.util.fromWei(balance.value).toFormat(3);
} else {
const format = balance.token.format || 1;
const decimals = format === 1 ? 0 : Math.min(3, Math.floor(format / 10));
value = new BigNumber(balance.value).div(format).toFormat(decimals);
}
const label = (
<div className={ styles.token }>
<TokenImage token={ token } />
<div className={ styles.tokenname }>
{ token.name }
</div>
<div className={ styles.tokenbalance }>
{ value }<small> { token.tag }</small>
</div>
</div>
);
return (
<MenuItem
key={ `${index}_${token.tag}` }
value={ token.tag }
label={ label }
>
{ label }
</MenuItem>
);
});
this.setState({ items });
}
render () {
const { tag, onChange } = this.props;
const { items } = this.state;
return (
<Select
className={ styles.tokenSelect }
label='type of token transfer'
hint='type of token to transfer'
value={ tag }
onChange={ onChange }
>
{ items }
</Select>
);
}
}

View File

@ -44,7 +44,6 @@ class Transfer extends Component {
static propTypes = { static propTypes = {
newError: PropTypes.func.isRequired, newError: PropTypes.func.isRequired,
gasLimit: PropTypes.object.isRequired, gasLimit: PropTypes.object.isRequired,
images: PropTypes.object.isRequired,
senders: nullableProptype(PropTypes.object), senders: nullableProptype(PropTypes.object),
sendersBalances: nullableProptype(PropTypes.object), sendersBalances: nullableProptype(PropTypes.object),
@ -174,7 +173,7 @@ class Transfer extends Component {
} }
renderDetailsPage () { renderDetailsPage () {
const { account, balance, images, senders } = this.props; const { account, balance, senders } = this.props;
const { recipient, recipientError, sender, senderError, sendersBalances } = this.store; const { recipient, recipientError, sender, senderError, sendersBalances } = this.store;
const { valueAll, extras, tag, total, totalError, value, valueError } = this.store; const { valueAll, extras, tag, total, totalError, value, valueError } = this.store;
@ -184,7 +183,6 @@ class Transfer extends Component {
all={ valueAll } all={ valueAll }
balance={ balance } balance={ balance }
extras={ extras } extras={ extras }
images={ images }
onChange={ this.store.onUpdateDetails } onChange={ this.store.onUpdateDetails }
recipient={ recipient } recipient={ recipient }
recipientError={ recipientError } recipientError={ recipientError }

View File

@ -65,7 +65,7 @@ describe('ui/AccountCard', () => {
let balance; let balance;
beforeEach(() => { beforeEach(() => {
balance = component.find('Connect(Balance)'); balance = component.find('Balance');
}); });
it('renders the balance', () => { it('renders the balance', () => {

View File

@ -17,13 +17,12 @@
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import unknownImage from '~/../assets/images/contracts/unknown-64x64.png'; import TokenImage from '~/ui/TokenImage';
import styles from './balance.css'; import styles from './balance.css';
class Balance extends Component { export default class Balance extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object api: PropTypes.object
}; };
@ -31,7 +30,6 @@ class Balance extends Component {
static propTypes = { static propTypes = {
balance: PropTypes.object, balance: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
images: PropTypes.object.isRequired,
showOnlyEth: PropTypes.bool, showOnlyEth: PropTypes.bool,
showZeroValues: PropTypes.bool showZeroValues: PropTypes.bool
}; };
@ -43,7 +41,7 @@ class Balance extends Component {
render () { render () {
const { api } = this.context; const { api } = this.context;
const { balance, className, images, showZeroValues, showOnlyEth } = this.props; const { balance, className, showZeroValues, showOnlyEth } = this.props;
if (!balance || !balance.tokens) { if (!balance || !balance.tokens) {
return null; return null;
@ -79,26 +77,12 @@ class Balance extends Component {
value = api.util.fromWei(balance.value).toFormat(3); value = api.util.fromWei(balance.value).toFormat(3);
} }
const imageurl = token.image || images[token.address];
let imagesrc = unknownImage;
if (imageurl) {
const host = /^(\/)?api/.test(imageurl)
? api.dappsUrl
: '';
imagesrc = `${host}${imageurl}`;
}
return ( return (
<div <div
className={ styles.balance } className={ styles.balance }
key={ `${index}_${token.tag}` } key={ `${index}_${token.tag}` }
> >
<img <TokenImage token={ token } />
src={ imagesrc }
alt={ token.name }
/>
<div className={ styles.balanceValue }> <div className={ styles.balanceValue }>
<span title={ value }> { value } </span> <span title={ value }> { value } </span>
</div> </div>
@ -125,14 +109,3 @@ class Balance extends Component {
); );
} }
} }
function mapStateToProps (state) {
const { images } = state;
return { images };
}
export default connect(
mapStateToProps,
null
)(Balance);

View File

@ -16,7 +16,6 @@
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import React from 'react'; import React from 'react';
import sinon from 'sinon';
import apiutil from '~/api/util'; import apiutil from '~/api/util';
@ -32,7 +31,6 @@ const BALANCE = {
let api; let api;
let component; let component;
let store;
function createApi () { function createApi () {
api = { api = {
@ -43,36 +41,22 @@ function createApi () {
return api; return api;
} }
function createStore () {
store = {
dispatch: sinon.stub(),
subscribe: sinon.stub(),
getState: () => {
return {
images: {}
};
}
};
return store;
}
function render (props = {}) { function render (props = {}) {
if (!props.balance) { if (!props.balance) {
props.balance = BALANCE; props.balance = BALANCE;
} }
const api = createApi();
component = shallow( component = shallow(
<Balance <Balance
className='testClass' className='testClass'
{ ...props } { ...props }
/>, />,
{ {
context: { context: { api }
store: createStore()
}
} }
).find('Balance').shallow({ context: { api: createApi() } }); );
return component; return component;
} }
@ -91,18 +75,18 @@ describe('ui/Balance', () => {
}); });
it('renders all the non-zero balances', () => { it('renders all the non-zero balances', () => {
expect(component.find('img')).to.have.length(2); expect(component.find('Connect(TokenImage)')).to.have.length(2);
}); });
describe('render specifiers', () => { describe('render specifiers', () => {
it('renders only the single token with showOnlyEth', () => { it('renders only the single token with showOnlyEth', () => {
render({ showOnlyEth: true }); render({ showOnlyEth: true });
expect(component.find('img')).to.have.length(1); expect(component.find('Connect(TokenImage)')).to.have.length(1);
}); });
it('renders all the tokens with showZeroValues', () => { it('renders all the tokens with showZeroValues', () => {
render({ showZeroValues: true }); render({ showZeroValues: true });
expect(component.find('img')).to.have.length(3); expect(component.find('Connect(TokenImage)')).to.have.length(3);
}); });
it('shows ETH with zero value with showOnlyEth & showZeroValues', () => { it('shows ETH with zero value with showOnlyEth & showZeroValues', () => {
@ -116,7 +100,7 @@ describe('ui/Balance', () => {
] ]
} }
}); });
expect(component.find('img')).to.have.length(1); expect(component.find('Connect(TokenImage)')).to.have.length(1);
}); });
}); });
}); });

View File

@ -0,0 +1,17 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './tokenImage';

View File

@ -0,0 +1,72 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import unknownImage from '~/../assets/images/contracts/unknown-64x64.png';
class TokenImage extends Component {
static contextTypes = {
api: PropTypes.object
};
static propTypes = {
image: PropTypes.string,
token: PropTypes.shape({
image: PropTypes.string,
address: PropTypes.string
}).isRequired
};
render () {
const { api } = this.context;
const { image, token } = this.props;
const imageurl = token.image || image;
let imagesrc = unknownImage;
if (imageurl) {
const host = /^(\/)?api/.test(imageurl)
? api.dappsUrl
: '';
imagesrc = `${host}${imageurl}`;
}
return (
<img
src={ imagesrc }
alt={ token.name }
/>
);
}
}
function mapStateToProps (iniState) {
const { images } = iniState;
return (_, props) => {
const { token } = props;
return { image: images[token.address] };
};
}
export default connect(
mapStateToProps,
null
)(TokenImage);

View File

@ -73,7 +73,7 @@ describe('views/Account/Header', () => {
beforeEach(() => { beforeEach(() => {
render({ balance: { balance: 'testing' } }); render({ balance: { balance: 'testing' } });
balance = component.find('Connect(Balance)'); balance = component.find('Balance');
}); });
it('renders', () => { it('renders', () => {

View File

@ -37,7 +37,6 @@ class Account extends Component {
static propTypes = { static propTypes = {
fetchCertifiers: PropTypes.func.isRequired, fetchCertifiers: PropTypes.func.isRequired,
fetchCertifications: PropTypes.func.isRequired, fetchCertifications: PropTypes.func.isRequired,
images: PropTypes.object.isRequired,
setVisibleAccounts: PropTypes.func.isRequired, setVisibleAccounts: PropTypes.func.isRequired,
accounts: PropTypes.object, accounts: PropTypes.object,
@ -257,14 +256,13 @@ class Account extends Component {
return null; return null;
} }
const { balances, images } = this.props; const { balances } = this.props;
return ( return (
<Transfer <Transfer
account={ account } account={ account }
balance={ balance } balance={ balance }
balances={ balances } balances={ balances }
images={ images }
onClose={ this.store.toggleTransferDialog } onClose={ this.store.toggleTransferDialog }
/> />
); );
@ -289,12 +287,10 @@ class Account extends Component {
function mapStateToProps (state) { function mapStateToProps (state) {
const { accounts } = state.personal; const { accounts } = state.personal;
const { balances } = state.balances; const { balances } = state.balances;
const { images } = state;
return { return {
accounts, accounts,
balances, balances
images
}; };
} }

View File

@ -66,7 +66,6 @@ class Wallet extends Component {
static propTypes = { static propTypes = {
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,
balance: nullableProptype(PropTypes.object.isRequired), balance: nullableProptype(PropTypes.object.isRequired),
images: PropTypes.object.isRequired,
isTest: PropTypes.bool.isRequired, isTest: PropTypes.bool.isRequired,
owned: PropTypes.bool.isRequired, owned: PropTypes.bool.isRequired,
setVisibleAccounts: PropTypes.func.isRequired, setVisibleAccounts: PropTypes.func.isRequired,
@ -315,13 +314,12 @@ class Wallet extends Component {
return null; return null;
} }
const { walletAccount, balance, images } = this.props; const { walletAccount, balance } = this.props;
return ( return (
<Transfer <Transfer
account={ walletAccount } account={ walletAccount }
balance={ balance } balance={ balance }
images={ images }
onClose={ this.onTransferClose } onClose={ this.onTransferClose }
/> />
); );
@ -365,7 +363,6 @@ function mapStateToProps (_, initProps) {
const { isTest } = state.nodeStatus; const { isTest } = state.nodeStatus;
const { accountsInfo = {}, accounts = {} } = state.personal; const { accountsInfo = {}, accounts = {} } = state.personal;
const { balances } = state.balances; const { balances } = state.balances;
const { images } = state;
const walletAccount = accounts[address] || accountsInfo[address] || null; const walletAccount = accounts[address] || accountsInfo[address] || null;
if (walletAccount) { if (walletAccount) {
@ -379,7 +376,6 @@ function mapStateToProps (_, initProps) {
return { return {
address, address,
balance, balance,
images,
isTest, isTest,
owned, owned,
wallet, wallet,