Ui 2 styling adjustments (#5534)

* Stateless components

* Adjust borders

* Stateless for status

* Externalise link colors

* css lint

* stateless

* Create ui/IconCache, replacing redux

* Update Signer buttons

* Requests background

* Adjust request styling

* Stateless components

* ParityBar background alignment
This commit is contained in:
Jaco Greeff 2017-05-02 17:50:44 +02:00 committed by GitHub
parent b57e8f6f0d
commit e7484d07aa
32 changed files with 369 additions and 403 deletions

View File

@ -17,7 +17,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import styles from './header.css'; import styles from './header.css';
import blocks from '../../../../assets/images/dapps/blocks-350.jpg'; import blocks from '~/../assets/images/dapps/blocks-350.jpg';
export default class Header extends Component { export default class Header extends Component {
static propTypes = { static propTypes = {

View File

@ -16,14 +16,12 @@
import { newError } from '~/ui/Errors/actions'; import { newError } from '~/ui/Errors/actions';
import { setAddressImage } from './providers/imagesActions';
import { openSnackbar, showSnackbar } from './providers/snackbarActions'; import { openSnackbar, showSnackbar } from './providers/snackbarActions';
import { toggleStatusRefresh } from './providers/statusActions'; import { toggleStatusRefresh } from './providers/statusActions';
import { toggleView } from './providers/settings/actions'; import { toggleView } from './providers/settings/actions';
export { export {
newError, newError,
setAddressImage,
openSnackbar, openSnackbar,
showSnackbar, showSnackbar,
toggleStatusRefresh, toggleStatusRefresh,

View File

@ -22,7 +22,6 @@ export Status from './status';
export apiReducer from './apiReducer'; export apiReducer from './apiReducer';
export balancesReducer from './balancesReducer'; export balancesReducer from './balancesReducer';
export workerReducer from './workerReducer'; export workerReducer from './workerReducer';
export imagesReducer from './imagesReducer';
export personalReducer from './personalReducer'; export personalReducer from './personalReducer';
export requestsReducer from './requestsReducer'; export requestsReducer from './requestsReducer';
export settingsReducer from './settings/reducers'; export settingsReducer from './settings/reducers';

View File

@ -18,10 +18,10 @@ import { uniq } from 'lodash';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { LOG_KEYS, getLogger } from '~/config'; import { LOG_KEYS, getLogger } from '~/config';
import { IconCache } from '~/ui';
import { fetchTokenIds, fetchTokenInfo } from '~/util/tokens'; import { fetchTokenIds, fetchTokenInfo } from '~/util/tokens';
import { updateTokensFilter } from './balancesActions'; import { updateTokensFilter } from './balancesActions';
import { setAddressImage } from './imagesActions';
const log = getLogger(LOG_KEYS.Balances); const log = getLogger(LOG_KEYS.Balances);
@ -54,8 +54,9 @@ export function fetchTokens (_tokenIndexes, options = {}) {
const tokenIndexes = uniq(_tokenIndexes || []); const tokenIndexes = uniq(_tokenIndexes || []);
return (dispatch, getState) => { return (dispatch, getState) => {
const { api, images } = getState(); const { api } = getState();
const { tokenReg } = Contracts.get(api); const { tokenReg } = Contracts.get(api);
const iconCache = IconCache.get();
return tokenReg.getInstance() return tokenReg.getInstance()
.then((tokenRegInstance) => { .then((tokenRegInstance) => {
@ -69,8 +70,8 @@ export function fetchTokens (_tokenIndexes, options = {}) {
const { id, image, address } = token; const { id, image, address } = token;
// dispatch only the changed images // dispatch only the changed images
if (images[address] !== image) { if (iconCache.images[address] !== image) {
dispatch(setAddressImage(address, image, true)); iconCache.add(address, image, true);
} }
tokens[id] = token; tokens[id] = token;

View File

@ -19,7 +19,7 @@ import { routerReducer } from 'react-router-redux';
import { import {
apiReducer, balancesReducer, apiReducer, balancesReducer,
workerReducer, imagesReducer, personalReducer, requestsReducer, workerReducer, personalReducer, requestsReducer,
settingsReducer, signerReducer, statusReducer as nodeStatusReducer, settingsReducer, signerReducer, statusReducer as nodeStatusReducer,
snackbarReducer, tokensReducer, walletReducer snackbarReducer, tokensReducer, walletReducer
} from './providers'; } from './providers';
@ -39,7 +39,6 @@ export default function () {
balances: balancesReducer, balances: balancesReducer,
certifications: certificationsReducer, certifications: certificationsReducer,
images: imagesReducer,
nodeStatus: nodeStatusReducer, nodeStatus: nodeStatusReducer,
personal: personalReducer, personal: personalReducer,
registry: registryReducer, registry: registryReducer,

View File

@ -14,10 +14,8 @@
// 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 { hashToImageUrl } from './providers/imagesReducer';
import { withError } from '~/ui/Errors/middleware'; import { withError } from '~/ui/Errors/middleware';
export { export {
hashToImageUrl,
withError withError
}; };

View File

@ -15,9 +15,9 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
$baseColor: 18; $baseColor: 255;
$baseOpacity: 0.95; $baseOpacity: 0.95;
$borderColor: rgba($baseColor, $baseColor, $baseColor, 0.25); $borderColor: rgba(0, 0, 0, 0.15);
.requests { .requests {
align-items: flex-end; align-items: flex-end;

View File

@ -14,13 +14,12 @@
// 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 React, { Component } from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import styles from '../firstRun.css'; import styles from '../firstRun.css';
export default class Completed extends Component { export default function Completed () {
render () {
return ( return (
<div className={ styles.completed }> <div className={ styles.completed }>
<p> <p>
@ -38,4 +37,3 @@ export default class Completed extends Component {
</div> </div>
); );
} }
}

View File

@ -14,21 +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 React, { Component, PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Checkbox } from 'material-ui'; import { Checkbox } from 'material-ui';
import styles from '../firstRun.css'; import styles from '../firstRun.css';
export default class TnC extends Component { export default function TnC ({ hasAccepted, onAccept }) {
static propTypes = {
hasAccepted: PropTypes.bool.isRequired,
onAccept: PropTypes.func.isRequired
}
render () {
const { hasAccepted, onAccept } = this.props;
return ( return (
<div className={ styles.tnc }> <div className={ styles.tnc }>
<h1>SECURITY WARNINGS</h1> <h1>SECURITY WARNINGS</h1>
@ -178,4 +170,8 @@ export default class TnC extends Component {
</div> </div>
); );
} }
}
TnC.propTypes = {
hasAccepted: PropTypes.bool.isRequired,
onAccept: PropTypes.func.isRequired
};

View File

@ -14,7 +14,7 @@
// 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 React, { Component } from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import imagesEthcore from '~/../assets/images/parity-logo-white.svg'; import imagesEthcore from '~/../assets/images/parity-logo-white.svg';
@ -28,8 +28,7 @@ const LOGO_STYLE = {
margin: '0 1.5em' margin: '0 1.5em'
}; };
export default class FirstRun extends Component { export default function FirstRun () {
render () {
return ( return (
<div className={ styles.welcome }> <div className={ styles.welcome }>
<img <img
@ -80,4 +79,3 @@ export default class FirstRun extends Component {
</div> </div>
); );
} }
}

View File

@ -51,7 +51,7 @@ $modalZ: 10001;
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
background: rgba(255, 255, 255, 0.5); background: rgba(0, 0, 0, 0.35);
z-index: $overlayZ; z-index: $overlayZ;
user-select: none; user-select: none;
} }

View File

@ -25,10 +25,7 @@
} }
.signerIcon { .signerIcon {
width: 24px;
height: 24px;
vertical-align: middle; vertical-align: middle;
margin-left: 12px;
} }
.passwordHint { .passwordHint {

View File

@ -15,13 +15,12 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import keycode from 'keycode'; import keycode from 'keycode';
import RaisedButton from 'material-ui/RaisedButton';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import { Form, Input, IdentityIcon, QrCode, QrScan } from '~/ui'; import { Button, Form, Input, IdentityIcon, QrCode, QrScan } from '~/ui';
import { generateTxQr, generateDataQr } from '~/util/qrscan'; import { generateTxQr, generateDataQr } from '~/util/qrscan';
import styles from './transactionPendingFormConfirm.css'; import styles from './transactionPendingFormConfirm.css';
@ -131,7 +130,7 @@ export default class TransactionPendingFormConfirm extends Component {
data-place='bottom' data-place='bottom'
data-tip data-tip
> >
<RaisedButton <Button
className={ styles.confirmButton } className={ styles.confirmButton }
disabled={ disabled || isSending || !isWalletOk } disabled={ disabled || isSending || !isWalletOk }
fullWidth fullWidth
@ -143,8 +142,7 @@ export default class TransactionPendingFormConfirm extends Component {
/> />
} }
label={ confirmText } label={ confirmText }
onTouchTap={ this.onConfirm } onClick={ this.onConfirm }
primary
/> />
</div> </div>
) )

View File

@ -17,7 +17,7 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import RaisedButton from 'material-ui/RaisedButton'; import { Button } from '~/ui';
import styles from './transactionPendingFormReject.css'; import styles from './transactionPendingFormReject.css';
@ -45,8 +45,8 @@ export default class TransactionPendingFormReject extends Component {
/> />
</strong> </strong>
</div> </div>
<RaisedButton <Button
onTouchTap={ onReject } onClick={ onReject }
className={ styles.rejectButton } className={ styles.rejectButton }
fullWidth fullWidth
label={ label={

View File

@ -19,18 +19,19 @@ import { Button as SemButton } from 'semantic-ui-react';
import { nodeOrStringProptype } from '~/util/proptypes'; import { nodeOrStringProptype } from '~/util/proptypes';
export default function Button ({ active, animated, basic, className, color, disabled, icon, label, onClick, primary, size, toggle }) { export default function Button ({ active, animated, basic, className, color, disabled, fullWidth, icon, label, onClick, primary, size, toggle }) {
return ( return (
<SemButton <SemButton
active={ active } active={ active }
animated={ animated } animated={ animated }
basic={ basic } basic={ basic }
className={ className } className={ className }
content={ label }
color={ color } color={ color }
disabled={ disabled } disabled={ disabled }
fluid={ fullWidth }
icon={ icon } icon={ icon }
content={ label } onTouchTap={ onClick }
onClick={ onClick }
primary={ primary } primary={ primary }
size={ size } size={ size }
toggle={ toggle } toggle={ toggle }
@ -46,6 +47,7 @@ Button.propTypes = {
className: PropTypes.string, className: PropTypes.string,
color: PropTypes.string, color: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
fullWidth: PropTypes.bool,
icon: PropTypes.node, icon: PropTypes.node,
label: nodeOrStringProptype(), label: nodeOrStringProptype(),
onClick: PropTypes.func, onClick: PropTypes.func,

View File

@ -17,9 +17,8 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { hashToImageUrl } from '~/redux/providers/imagesReducer';
import defaultIcon from '~/../assets/images/certifications/unknown.svg'; import defaultIcon from '~/../assets/images/certifications/unknown.svg';
import IconCache from '~/ui/IconCache';
import styles from './certifications.css'; import styles from './certifications.css';
@ -68,7 +67,7 @@ class Certifications extends Component {
className={ styles.icon } className={ styles.icon }
src={ src={
icon icon
? `${dappsUrl}${hashToImageUrl(icon)}` ? `${dappsUrl}${IconCache.hashToImage(icon)}`
: defaultIcon : defaultIcon
} }
/> />

View File

@ -15,6 +15,8 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
@import '../_colors.css';
.container { .container {
height: 100%; height: 100%;
position: relative; position: relative;
@ -41,7 +43,7 @@
} }
.titleLink { .titleLink {
color: rgb(0, 151, 167); color: $linkColor;
} }
.author { .author {

View File

@ -14,24 +14,12 @@
// 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 React, { Component, PropTypes } from 'react'; import React, { PropTypes } from 'react';
import styles from './dappIcon.css'; import styles from './dappIcon.css';
export default class DappIcon extends Component { export default function DappIcon ({ app, className, small }, context) {
static contextTypes = { const { dappsUrl } = context.api;
api: PropTypes.object.isRequired
};
static propTypes = {
app: PropTypes.object.isRequired,
className: PropTypes.string,
small: PropTypes.bool
};
render () {
const { dappsUrl } = this.context.api;
const { app, className, small } = this.props;
return ( return (
<img <img
@ -46,4 +34,13 @@ export default class DappIcon extends Component {
/> />
); );
} }
}
DappIcon.contextTypes = {
api: PropTypes.object.isRequired
};
DappIcon.propTypes = {
app: PropTypes.object.isRequired,
className: PropTypes.string,
small: PropTypes.bool
};

View File

@ -15,7 +15,9 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
@import '../_colors.css';
.link { .link {
color: rgb(0, 151, 167); color: $linkColor;
cursor: pointer; cursor: pointer;
} }

View File

@ -14,30 +14,41 @@
// 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 { handleActions } from 'redux-actions'; import { action, observable } from 'mobx';
import { bytesToHex } from '@parity/api/util/format'; import { bytesToHex } from '@parity/api/util/format';
const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000';
const API_PATH = '/api/content/';
const initialState = { let instance = null;
images: {}
};
export function hashToImageUrl (hashArray) { export default class IconCache {
const hash = hashArray ? bytesToHex(hashArray) : ZERO; @observable images = {};
return hash === ZERO ? null : `/api/content/${hash.substr(2)}`; @action add (address, imageOrHash, isImage = false) {
} this.images = Object.assign({}, this.images, {
[address]: isImage
export default handleActions({ ? imageOrHash
setAddressImage (state, action) { : IconCache.hashToImage(imageOrHash)
const { address, hashArray, converted } = action;
const image = converted ? hashArray : hashToImageUrl(hashArray);
return Object.assign({}, state, {
[address]: image
}); });
} }
}, initialState);
static hashToImage (_hash) {
const hash = _hash
? bytesToHex(_hash)
: ZERO;
return hash === ZERO
? null
: `${API_PATH}${hash.substr(2)}`;
}
static get (force = false) {
if (!instance || force) {
instance = new IconCache();
}
return instance;
}
}

View File

@ -14,11 +14,4 @@
// 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/>.
export function setAddressImage (address, hashArray, converted = false) { export default from './iconCache';
return {
type: 'setAddressImage',
address,
hashArray,
converted
};
}

View File

@ -52,7 +52,7 @@
.button { .button {
display: inline; display: inline;
width: 24px; width: 1em;
height: 24px; height: 1em;
margin: 6px 0.25em 0 12px; margin: 0 0.25em;
} }

View File

@ -15,16 +15,18 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { createIdentityImg } from '@parity/api/util/identity'; import { createIdentityImg } from '@parity/api/util/identity';
import { isNullAddress } from '~/util/validation'; import { isNullAddress } from '~/util/validation';
import { CancelIcon, ContractIcon } from '../Icons'; import IconCache from '~/ui/IconCache';
import { CancelIcon, ContractIcon } from '~/ui/Icons';
import styles from './identityIcon.css'; import styles from './identityIcon.css';
class IdentityIcon extends Component { const iconCache = IconCache.get();
export default class IdentityIcon extends Component {
static contextTypes = { static contextTypes = {
api: PropTypes.object.isRequired api: PropTypes.object.isRequired
} }
@ -35,7 +37,6 @@ class IdentityIcon extends Component {
center: PropTypes.bool, center: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
disabled: PropTypes.bool, disabled: PropTypes.bool,
images: PropTypes.object.isRequired,
inline: PropTypes.bool, inline: PropTypes.bool,
padded: PropTypes.bool, padded: PropTypes.bool,
tiny: PropTypes.bool tiny: PropTypes.bool
@ -46,26 +47,23 @@ class IdentityIcon extends Component {
} }
componentDidMount () { componentDidMount () {
this.updateIcon(this.props.address, this.props.images); this.updateIcon(this.props.address);
} }
componentWillReceiveProps (newProps) { componentWillReceiveProps (newProps) {
const sameAddress = newProps.address === this.props.address; if (newProps.address === this.props.address) {
const sameImages = Object.keys(newProps.images).length === Object.keys(this.props.images).length;
if (sameAddress && sameImages) {
return; return;
} }
this.updateIcon(newProps.address, newProps.images); this.updateIcon(newProps.address);
} }
updateIcon (_address, images) { updateIcon (_address) {
const { api } = this.context; const { api } = this.context;
const { button, inline, tiny } = this.props; const { button, inline, tiny } = this.props;
if (images[_address]) { if (iconCache[_address]) {
this.setState({ iconsrc: `${api.dappsUrl}${images[_address]}` }); this.setState({ iconsrc: `${api.dappsUrl}${iconCache[_address]}` });
return; return;
} }
@ -145,14 +143,3 @@ class IdentityIcon extends Component {
); );
} }
} }
function mapStateToProps (state) {
const { images } = state;
return { images };
}
export default connect(
mapStateToProps,
null
)(IdentityIcon);

View File

@ -16,15 +16,16 @@
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import React from 'react'; import React from 'react';
import sinon from 'sinon';
import IdentityIcon from './'; import IdentityIcon from './';
import IconCache from '../IconCache';
const ADDRESS0 = '0x0000000000000000000000000000000000000000'; const ADDRESS0 = '0x0000000000000000000000000000000000000000';
const ADDRESS1 = '0x0123456789012345678901234567890123456789'; const ADDRESS1 = '0x0123456789012345678901234567890123456789';
const ADDRESS2 = '0x9876543210987654321098765432109876543210'; const ADDRESS2 = '0x9876543210987654321098765432109876543210';
let component; let component;
let iconCache;
let instance; let instance;
function createApi () { function createApi () {
@ -33,20 +34,6 @@ function createApi () {
}; };
} }
function createRedux () {
return {
dispatch: sinon.stub(),
subscribe: sinon.stub(),
getState: () => {
return {
images: {
[ADDRESS2]: 'reduxImage'
}
};
}
};
}
function render (props = {}) { function render (props = {}) {
if (props && props.address === undefined) { if (props && props.address === undefined) {
props.address = ADDRESS1; props.address = ADDRESS1;
@ -54,12 +41,15 @@ function render (props = {}) {
component = shallow( component = shallow(
<IdentityIcon { ...props } />, <IdentityIcon { ...props } />,
{ context: { store: createRedux() } } { context: { api: createApi() } }
).find('IdentityIcon').shallow({ context: { api: createApi() } }); );
instance = component.instance(); instance = component.instance();
instance.componentDidMount(); instance.componentDidMount();
iconCache = IconCache.get(true);
iconCache.add(ADDRESS2, 'cachedImage');
return component; return component;
} }
@ -76,11 +66,11 @@ describe('ui/IdentityIcon', () => {
expect(img.props().src).to.equal('test-createIdentityImg'); expect(img.props().src).to.equal('test-createIdentityImg');
}); });
it('renders an <img> with redux source when available', () => { it('renders an <img> with cache source when available', () => {
const img = render({ address: ADDRESS2 }).find('img'); const img = render({ address: ADDRESS2 }).find('img');
expect(img).to.have.length(1); expect(img).to.have.length(1);
expect(img.props().src).to.equal('dappsUrl/reduxImage'); expect(img.props().src).to.equal('dappsUrl/cachedImage');
}); });
it('renders an <ContractIcon> with no address specified', () => { it('renders an <ContractIcon> with no address specified', () => {

View File

@ -14,7 +14,7 @@
// 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 React, { Component, PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -34,20 +34,7 @@ const defaultNameNull = (
/> />
); );
export class IdentityName extends Component { export function IdentityName ({ account, address, className, empty, name, shorten, unknown }) {
static propTypes = {
account: PropTypes.object,
address: PropTypes.string,
className: PropTypes.string,
empty: PropTypes.bool,
name: PropTypes.string,
shorten: PropTypes.bool,
unknown: PropTypes.bool
}
render () {
const { account, address, className, empty, name, shorten, unknown } = this.props;
if (!account && empty) { if (!account && empty) {
return null; return null;
} }
@ -72,7 +59,16 @@ export class IdentityName extends Component {
</span> </span>
); );
} }
}
IdentityName.propTypes = {
account: PropTypes.object,
address: PropTypes.string,
className: PropTypes.string,
empty: PropTypes.bool,
name: PropTypes.string,
shorten: PropTypes.bool,
unknown: PropTypes.bool
};
function mapStateToProps (state, props) { function mapStateToProps (state, props) {
const { address } = props; const { address } = props;

View File

@ -15,6 +15,8 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
@import '../_colors.css';
.item { .item {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
@ -69,7 +71,7 @@
filter: none; filter: none;
&::after { &::after {
background: rgb(0, 151, 167); background: $linkColor;
content: ''; content: '';
height: 4px; height: 4px;
left: 0; left: 0;

View File

@ -15,13 +15,15 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes } from 'react'; import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import unknownImage from '~/../assets/images/contracts/unknown-64x64.png'; import unknownImage from '~/../assets/images/contracts/unknown-64x64.png';
import IconCache from '~/ui/IconCache';
function TokenImage ({ image, token }, context) { const iconCache = IconCache.get();
export default function TokenImage ({ token }, context) {
const { api } = context; const { api } = context;
const imageurl = token.image || image; const imageurl = token.image || iconCache.images[token.address];
let imagesrc = unknownImage; let imagesrc = unknownImage;
if (imageurl) { if (imageurl) {
@ -45,24 +47,8 @@ TokenImage.contextTypes = {
}; };
TokenImage.propTypes = { TokenImage.propTypes = {
image: PropTypes.string,
token: PropTypes.shape({ token: PropTypes.shape({
image: PropTypes.string, image: PropTypes.string,
address: PropTypes.string address: PropTypes.string
}).isRequired }).isRequired
}; };
function mapStateToProps (iniState) {
const { images } = iniState;
return (_, props) => {
const { token } = props;
return { image: images[token.address] };
};
}
export default connect(
mapStateToProps,
null
)(TokenImage);

18
js/src/ui/_colors.css Normal file
View File

@ -0,0 +1,18 @@
/* 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/>.
*/
$linkColor: #4183c4;

View File

@ -36,6 +36,7 @@ export Features, { FEATURES, FeaturesStore } from './Features';
export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput, VaultSelect } from './Form'; export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput, VaultSelect } from './Form';
export GasPriceEditor from './GasPriceEditor'; export GasPriceEditor from './GasPriceEditor';
export GasPriceSelector from './GasPriceSelector'; export GasPriceSelector from './GasPriceSelector';
export IconCache from './IconCache';
export Icons from './Icons'; export Icons from './Icons';
export IdentityIcon from './IdentityIcon'; export IdentityIcon from './IdentityIcon';
export IdentityName from './IdentityName'; export IdentityName from './IdentityName';

View File

@ -20,10 +20,9 @@ import { pick, range, uniq } from 'lodash';
import { bytesToHex } from '@parity/api/util/format'; import { bytesToHex } from '@parity/api/util/format';
import Contracts from '~/contracts'; import Contracts from '~/contracts';
import { hashToImageUrl } from '~/redux/util';
import builtinJson from '~/config/dappsBuiltin.json'; import builtinJson from '~/config/dappsBuiltin.json';
import viewsJson from '~/config/dappsViews.json'; import viewsJson from '~/config/dappsViews.json';
import { IconCache } from '~/ui';
const builtinApps = [].concat( const builtinApps = [].concat(
viewsJson.map((app) => { viewsJson.map((app) => {
@ -105,7 +104,7 @@ export function fetchBuiltinApps (api) {
app.type = app.isView app.type = app.isView
? 'view' ? 'view'
: 'builtin'; : 'builtin';
app.image = hashToImageUrl(imageIds[index]); app.image = IconCache.hashToImage(imageIds[index]);
return app; return app;
}); });
}) })
@ -169,7 +168,7 @@ export function fetchRegistryApp (api, dappReg, appId) {
.then(([ imageId, contentId, manifestId ]) => { .then(([ imageId, contentId, manifestId ]) => {
const app = { const app = {
id: appId, id: appId,
image: hashToImageUrl(imageId), image: IconCache.hashToImage(imageId),
contentHash: bytesToHex(contentId).substr(2), contentHash: bytesToHex(contentId).substr(2),
manifestHash: bytesToHex(manifestId).substr(2), manifestHash: bytesToHex(manifestId).substr(2),
type: 'network', type: 'network',

View File

@ -20,7 +20,7 @@ import BigNumber from 'bignumber.js';
import { sha3 } from '@parity/api/util/sha3'; import { sha3 } from '@parity/api/util/sha3';
import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png'; import imagesEthereum from '~/../assets/images/contracts/ethereum-black-64x64.png';
import { hashToImageUrl } from '~/redux/util'; import { IconCache } from '~/ui';
const BALANCEOF_SIGNATURE = sha3('balanceOf(address)'); const BALANCEOF_SIGNATURE = sha3('balanceOf(address)');
const ADDRESS_PADDING = range(24).map(() => '0').join(''); const ADDRESS_PADDING = range(24).map(() => '0').join('');
@ -57,7 +57,7 @@ export function fetchTokenInfo (api, tokenregInstace, tokenIndex) {
const token = { const token = {
format: format.toString(), format: format.toString(),
index: tokenIndex, index: tokenIndex,
image: hashToImageUrl(image), image: IconCache.hashToImage(image),
id: sha3(address + tokenIndex).slice(0, 10), id: sha3(address + tokenIndex).slice(0, 10),
address, address,
name, name,

View File

@ -34,7 +34,6 @@ function createRedux () {
[ADDRESS]: {} [ADDRESS]: {}
} }
}, },
images: {},
nodeStatus: { nodeStatus: {
netVersion: '1', netVersion: '1',
traceMode: false traceMode: false