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 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 {
static propTypes = {

View File

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

View File

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

View File

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

View File

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

View File

@ -14,10 +14,8 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { hashToImageUrl } from './providers/imagesReducer';
import { withError } from '~/ui/Errors/middleware';
export {
hashToImageUrl,
withError
};

View File

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

View File

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

View File

@ -14,21 +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 React, { Component, PropTypes } from 'react';
import React, { PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { Checkbox } from 'material-ui';
import styles from '../firstRun.css';
export default class TnC extends Component {
static propTypes = {
hasAccepted: PropTypes.bool.isRequired,
onAccept: PropTypes.func.isRequired
}
render () {
const { hasAccepted, onAccept } = this.props;
export default function TnC ({ hasAccepted, onAccept }) {
return (
<div className={ styles.tnc }>
<h1>SECURITY WARNINGS</h1>
@ -177,5 +169,9 @@ export default class TnC extends Component {
/>
</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
// 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 imagesEthcore from '~/../assets/images/parity-logo-white.svg';
@ -28,8 +28,7 @@ const LOGO_STYLE = {
margin: '0 1.5em'
};
export default class FirstRun extends Component {
render () {
export default function FirstRun () {
return (
<div className={ styles.welcome }>
<img
@ -79,5 +78,4 @@ export default class FirstRun extends Component {
</p>
</div>
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -19,18 +19,19 @@ import { Button as SemButton } from 'semantic-ui-react';
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 (
<SemButton
active={ active }
animated={ animated }
basic={ basic }
className={ className }
content={ label }
color={ color }
disabled={ disabled }
fluid={ fullWidth }
icon={ icon }
content={ label }
onClick={ onClick }
onTouchTap={ onClick }
primary={ primary }
size={ size }
toggle={ toggle }
@ -46,6 +47,7 @@ Button.propTypes = {
className: PropTypes.string,
color: PropTypes.string,
disabled: PropTypes.bool,
fullWidth: PropTypes.bool,
icon: PropTypes.node,
label: nodeOrStringProptype(),
onClick: PropTypes.func,

View File

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

View File

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

View File

@ -14,24 +14,12 @@
// 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 React, { PropTypes } from 'react';
import styles from './dappIcon.css';
export default class DappIcon extends Component {
static contextTypes = {
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;
export default function DappIcon ({ app, className, small }, context) {
const { dappsUrl } = context.api;
return (
<img
@ -45,5 +33,14 @@ 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/>.
*/
@import '../_colors.css';
.link {
color: rgb(0, 151, 167);
color: $linkColor;
cursor: pointer;
}

View File

@ -14,30 +14,41 @@
// 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';
import { action, observable } from 'mobx';
import { bytesToHex } from '@parity/api/util/format';
const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000';
const API_PATH = '/api/content/';
const initialState = {
images: {}
};
let instance = null;
export function hashToImageUrl (hashArray) {
const hash = hashArray ? bytesToHex(hashArray) : ZERO;
export default class IconCache {
@observable images = {};
return hash === ZERO ? null : `/api/content/${hash.substr(2)}`;
}
export default handleActions({
setAddressImage (state, action) {
const { address, hashArray, converted } = action;
const image = converted ? hashArray : hashToImageUrl(hashArray);
return Object.assign({}, state, {
[address]: image
@action add (address, imageOrHash, isImage = false) {
this.images = Object.assign({}, this.images, {
[address]: isImage
? imageOrHash
: IconCache.hashToImage(imageOrHash)
});
}
}, 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
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export function setAddressImage (address, hashArray, converted = false) {
return {
type: 'setAddressImage',
address,
hashArray,
converted
};
}
export default from './iconCache';

View File

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

View File

@ -15,16 +15,18 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { createIdentityImg } from '@parity/api/util/identity';
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';
class IdentityIcon extends Component {
const iconCache = IconCache.get();
export default class IdentityIcon extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
}
@ -35,7 +37,6 @@ class IdentityIcon extends Component {
center: PropTypes.bool,
className: PropTypes.string,
disabled: PropTypes.bool,
images: PropTypes.object.isRequired,
inline: PropTypes.bool,
padded: PropTypes.bool,
tiny: PropTypes.bool
@ -46,26 +47,23 @@ class IdentityIcon extends Component {
}
componentDidMount () {
this.updateIcon(this.props.address, this.props.images);
this.updateIcon(this.props.address);
}
componentWillReceiveProps (newProps) {
const sameAddress = newProps.address === this.props.address;
const sameImages = Object.keys(newProps.images).length === Object.keys(this.props.images).length;
if (sameAddress && sameImages) {
if (newProps.address === this.props.address) {
return;
}
this.updateIcon(newProps.address, newProps.images);
this.updateIcon(newProps.address);
}
updateIcon (_address, images) {
updateIcon (_address) {
const { api } = this.context;
const { button, inline, tiny } = this.props;
if (images[_address]) {
this.setState({ iconsrc: `${api.dappsUrl}${images[_address]}` });
if (iconCache[_address]) {
this.setState({ iconsrc: `${api.dappsUrl}${iconCache[_address]}` });
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 React from 'react';
import sinon from 'sinon';
import IdentityIcon from './';
import IconCache from '../IconCache';
const ADDRESS0 = '0x0000000000000000000000000000000000000000';
const ADDRESS1 = '0x0123456789012345678901234567890123456789';
const ADDRESS2 = '0x9876543210987654321098765432109876543210';
let component;
let iconCache;
let instance;
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 = {}) {
if (props && props.address === undefined) {
props.address = ADDRESS1;
@ -54,12 +41,15 @@ function render (props = {}) {
component = shallow(
<IdentityIcon { ...props } />,
{ context: { store: createRedux() } }
).find('IdentityIcon').shallow({ context: { api: createApi() } });
{ context: { api: createApi() } }
);
instance = component.instance();
instance.componentDidMount();
iconCache = IconCache.get(true);
iconCache.add(ADDRESS2, 'cachedImage');
return component;
}
@ -76,11 +66,11 @@ describe('ui/IdentityIcon', () => {
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');
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', () => {

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import React, { PropTypes } from 'react';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
@ -34,20 +34,7 @@ const defaultNameNull = (
/>
);
export class IdentityName extends Component {
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;
export function IdentityName ({ account, address, className, empty, name, shorten, unknown }) {
if (!account && empty) {
return null;
}
@ -71,9 +58,18 @@ export class IdentityName extends Component {
}
</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) {
const { address } = props;
const { personal, tokens } = state;

View File

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

View File

@ -15,13 +15,15 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
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 imageurl = token.image || image;
const imageurl = token.image || iconCache.images[token.address];
let imagesrc = unknownImage;
if (imageurl) {
@ -45,24 +47,8 @@ TokenImage.contextTypes = {
};
TokenImage.propTypes = {
image: PropTypes.string,
token: PropTypes.shape({
image: PropTypes.string,
address: PropTypes.string
}).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 GasPriceEditor from './GasPriceEditor';
export GasPriceSelector from './GasPriceSelector';
export IconCache from './IconCache';
export Icons from './Icons';
export IdentityIcon from './IdentityIcon';
export IdentityName from './IdentityName';

View File

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

View File

@ -20,7 +20,7 @@ import BigNumber from 'bignumber.js';
import { sha3 } from '@parity/api/util/sha3';
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 ADDRESS_PADDING = range(24).map(() => '0').join('');
@ -57,7 +57,7 @@ export function fetchTokenInfo (api, tokenregInstace, tokenIndex) {
const token = {
format: format.toString(),
index: tokenIndex,
image: hashToImageUrl(image),
image: IconCache.hashToImage(image),
id: sha3(address + tokenIndex).slice(0, 10),
address,
name,

View File

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