Backporting to beta (#4175)

* verification: check if server is running (#4140)

* verification: check if server is running

See also ethcore/email-verification#67c6466 and ethcore/sms-verification#a585e42.

* verification: show in the UI if server is running

* verification: code style , more i18n

* fix i18n key

* Optimized hash lookups (#4144)

* Optimize hash comparison

* Use libc

* Ropsten fork detection (#4163)

* Stop flickering + added loader in AddressSelector (#4149)

* Stop UI flickering + added loader to AddressSelector #4103

* PR Grumbles

* Add a password strength component (#4153)

* Added new PasswordStrength Component

* Added tests

* PR Grumbles

* icarus -> update, increase web timeout. (#4165)

* icarus -> update, increase web timeout.

* Fix estimate gas

* Fix token images // Error in Contract Queries (#4169)

* Fix dapps not loading (#4170)

* Add secure to dappsreg

* Remove trailing slash // fix dapps
This commit is contained in:
Arkadiy Paronyan
2017-01-16 13:41:37 +01:00
committed by Gav Wood
parent 5b30a61158
commit 65594b8865
36 changed files with 589 additions and 112 deletions

View File

@@ -27,6 +27,7 @@
transition: transform ease-out 0.1s;
transform: scale(1);
overflow: hidden;
&.copied {
animation-duration: 0.25s;

View File

@@ -62,11 +62,15 @@ class Balance extends Component {
value = api.util.fromWei(balance.value).toFormat(3);
}
let imagesrc = token.image;
if (!imagesrc) {
imagesrc = images[token.address]
? `${api.dappsUrl}${images[token.address]}`
: unknownImage;
const imageurl = token.image || images[token.address];
let imagesrc = unknownImage;
if (imageurl) {
const host = /^(\/)?api/.test(imageurl)
? api.dappsUrl
: '';
imagesrc = `${host}${imageurl}`;
}
return (

View File

@@ -15,6 +15,22 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.outerInput {
display: flex;
flex-direction: row;
position: relative;
.input {
flex: 1;
}
.loader {
position: absolute;
bottom: 1rem;
right: 9rem;
}
}
.input {
box-sizing: border-box;
appearance: textfield;
@@ -58,13 +74,13 @@
}
.label {
margin: 1rem 2.5rem 0.25em;
margin: 1rem 0.5rem 0.25em;
color: rgba(255, 255, 255, 0.498039);
}
.underline {
position: relative;
margin: 0 9rem 0 2.5rem;
margin: 0 0.5rem 0 0.5rem;
}
.empty {
@@ -78,7 +94,7 @@
.input {
font-size: 1.5em;
padding: 0 9rem 0.5em 2.5rem;
padding: 0 9rem 0.5em 0.5rem;
display: block;
padding-right: 6rem;
@@ -92,7 +108,7 @@
flex-direction: row;
justify-content: flex-start;
margin: 2rem 2rem 0;
margin: 2rem 0 0;
> * {
flex: 1;
@@ -107,8 +123,11 @@
.title {
text-transform: uppercase;
font-size: 1.5em;
font-color: white;
h3 {
margin: 0;
}
}
.cards {

View File

@@ -25,6 +25,7 @@ import TextFieldUnderline from 'material-ui/TextField/TextFieldUnderline';
import AccountCard from '~/ui/AccountCard';
import InputAddress from '~/ui/Form/InputAddress';
import Loading from '~/ui/Loading';
import Portal from '~/ui/Portal';
import { nodeOrStringProptype } from '~/util/proptypes';
import { validateAddress } from '~/util/validation';
@@ -130,7 +131,7 @@ class AddressSelect extends Component {
const input = (
<InputAddress
accountsInfo={ accountsInfo }
allowCopy={ allowCopy }
allowCopy={ (disabled || readOnly) && allowCopy ? allowCopy : false }
className={ className }
disabled={ disabled || readOnly }
error={ error }
@@ -182,17 +183,18 @@ class AddressSelect extends Component {
<label className={ styles.label } htmlFor={ id }>
{ label }
</label>
<input
id={ id }
className={ styles.input }
placeholder={ ilHint }
onBlur={ this.handleInputBlur }
onFocus={ this.handleInputFocus }
onChange={ this.handleChange }
ref={ this.setInputRef }
/>
<div className={ styles.outerInput }>
<input
id={ id }
className={ styles.input }
placeholder={ ilHint }
onBlur={ this.handleInputBlur }
onFocus={ this.handleInputFocus }
onChange={ this.handleChange }
ref={ this.setInputRef }
/>
{ this.renderLoader() }
</div>
<div className={ styles.underline }>
<TextFieldUnderline
@@ -210,6 +212,19 @@ class AddressSelect extends Component {
);
}
renderLoader () {
if (!this.store.loading) {
return null;
}
return (
<Loading
className={ styles.loader }
size={ 0.5 }
/>
);
}
renderCurrentInput () {
const { inputValue } = this.state;
@@ -304,7 +319,9 @@ class AddressSelect extends Component {
return (
<div className={ styles.category } key={ `${key}_${index}` }>
<div className={ styles.title }>{ label }</div>
<div className={ styles.title }>
<h3>{ label }</h3>
</div>
{ content }
</div>
);

View File

@@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React from 'react';
import { observable, action } from 'mobx';
import { observable, action, transaction } from 'mobx';
import { flatMap, uniqBy } from 'lodash';
import { FormattedMessage } from 'react-intl';
@@ -26,6 +26,7 @@ const ZERO = /^(0x)?0*$/;
export default class AddressSelectStore {
@observable loading = false;
@observable values = [];
@observable registryValues = [];
@@ -224,21 +225,28 @@ export default class AddressSelectStore {
};
});
// Registries Lookup
this.registryValues = [];
// Clear the previous results after 50ms
// if still fetching
const timeoutId = setTimeout(() => {
transaction(() => {
this.registryValues = [];
this.loading = true;
});
}, 50);
const lookups = this.regLookups.map((regLookup) => regLookup(value));
Promise
// Registries Lookup
return Promise
.all(lookups)
.then((results) => {
return results
.filter((result) => result && !ZERO.test(result.address));
})
.then((results) => {
results = uniqBy(results, (result) => result.address);
clearTimeout(timeoutId);
this.registryValues = results
const registryValues = uniqBy(results, (result) => result.address)
.map((result) => {
const lowercaseAddress = result.address.toLowerCase();
@@ -253,6 +261,11 @@ export default class AddressSelectStore {
return result;
});
transaction(() => {
this.loading = false;
this.registryValues = registryValues;
});
});
}

View File

@@ -76,7 +76,7 @@ class InputAddress extends Component {
const props = {};
if (!readOnly && !disabled) {
if (!disabled) {
props.focused = focused;
}

View File

@@ -0,0 +1,17 @@
// Copyright 2015, 2016 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 './passwordStrength';

View File

@@ -0,0 +1,31 @@
/* Copyright 2015, 2016 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/>.
*/
.strength {
margin-top: 1.25em;
}
.feedback {
font-size: 0.75em;
}
.label {
user-select: none;
line-height: 18px;
font-size: 12px;
color: rgba(255, 255, 255, 0.498039);
}

View File

@@ -0,0 +1,125 @@
// Copyright 2015, 2016 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 { debounce } from 'lodash';
import { LinearProgress } from 'material-ui';
import { FormattedMessage } from 'react-intl';
import zxcvbn from 'zxcvbn';
import styles from './passwordStrength.css';
const BAR_STYLE = {
borderRadius: 1,
height: 7,
marginTop: '0.5em'
};
export default class PasswordStrength extends Component {
static propTypes = {
input: PropTypes.string.isRequired
};
state = {
strength: null
};
constructor (props) {
super(props);
this.updateStrength = debounce(this._updateStrength, 50, { leading: true });
}
componentWillMount () {
this.updateStrength(this.props.input);
}
componentWillReceiveProps (nextProps) {
if (nextProps.input !== this.props.input) {
this.updateStrength(nextProps.input);
}
}
_updateStrength (input = '') {
const strength = zxcvbn(input);
this.setState({ strength });
}
render () {
const { strength } = this.state;
if (!strength) {
return null;
}
const { score, feedback } = strength;
// Score is between 0 and 4
const value = score * 100 / 5 + 20;
const color = this.getStrengthBarColor(score);
return (
<div className={ styles.strength }>
<label className={ styles.label }>
<FormattedMessage
id='ui.passwordStrength.label'
defaultMessage='password strength'
/>
</label>
<LinearProgress
color={ color }
mode='determinate'
style={ BAR_STYLE }
value={ value }
/>
<div className={ styles.feedback }>
{ this.renderFeedback(feedback) }
</div>
</div>
);
}
// Note that the suggestions are in english, thus it wouldn't
// make sense to add translations to surrounding words
renderFeedback (feedback = {}) {
const { suggestions = [] } = feedback;
return (
<div>
<p>
{ suggestions.join(' ') }
</p>
</div>
);
}
getStrengthBarColor (score) {
switch (score) {
case 4:
case 3:
return 'lightgreen';
case 2:
return 'yellow';
case 1:
return 'orange';
default:
return 'red';
}
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2015, 2016 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 { shallow } from 'enzyme';
import React from 'react';
import PasswordStrength from './passwordStrength';
const INPUT_A = 'l33t_test';
const INPUT_B = 'Fu£dk;s$%kdlaOe9)_';
const INPUT_NULL = '';
function render (props) {
return shallow(
<PasswordStrength { ...props } />
).shallow();
}
describe('ui/Form/PasswordStrength', () => {
describe('rendering', () => {
it('renders', () => {
expect(render({ input: INPUT_A })).to.be.ok;
});
it('renders a linear progress', () => {
expect(render({ input: INPUT_A }).find('LinearProgress')).to.be.ok;
});
describe('compute strength', () => {
it('has low score with empty input', () => {
expect(
render({ input: INPUT_NULL }).find('LinearProgress').props().value
).to.equal(20);
});
it('has medium score', () => {
expect(
render({ input: INPUT_A }).find('LinearProgress').props().value
).to.equal(60);
});
it('has high score', () => {
expect(
render({ input: INPUT_B }).find('LinearProgress').props().value
).to.equal(100);
});
});
});
});

View File

@@ -71,7 +71,9 @@ export default class TypedInput extends Component {
const { isEth, value } = this.props;
if (typeof isEth === 'boolean' && value) {
const ethValue = isEth ? fromWei(value) : value;
// Remove formatting commas
const sanitizedValue = typeof value === 'string' ? value.replace(/,/g, '') : value;
const ethValue = isEth ? fromWei(sanitizedValue) : value;
this.setState({ isEth, ethValue });
}
}
@@ -393,7 +395,9 @@ export default class TypedInput extends Component {
return this.setState({ isEth: !isEth });
}
const value = isEth ? toWei(ethValue) : fromWei(ethValue);
// Remove formatting commas
const sanitizedValue = typeof ethValue === 'string' ? ethValue.replace(/,/g, '') : ethValue;
const value = isEth ? toWei(sanitizedValue) : fromWei(sanitizedValue);
this.setState({ isEth: !isEth, ethValue: value }, () => {
this.onEthValueChange(null, value);
});

View File

@@ -21,15 +21,22 @@ import styles from './loading.css';
export default class Loading extends Component {
static propTypes = {
className: PropTypes.string,
size: PropTypes.number
};
static defaultProps = {
className: '',
size: 2
};
render () {
const size = (this.props.size || 2) * 60;
const { className, size } = this.props;
const computedSize = size * 60;
return (
<div className={ styles.loading }>
<CircularProgress size={ size } />
<div className={ [ styles.loading, className ].join(' ') }>
<CircularProgress size={ computedSize } />
</div>
);
}

View File

@@ -68,6 +68,9 @@ $top: 20vh;
opacity: 0;
z-index: -10;
padding: 1em;
box-sizing: border-box;
* {
min-width: 0;
}
@@ -83,6 +86,7 @@ $top: 20vh;
top: 0.5rem;
right: 1rem;
font-size: 4em;
z-index: 100;
transition-property: opacity;
transition-duration: 0.25s;

View File

@@ -43,6 +43,7 @@ import Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal';
import muiTheme from './Theme';
import Page from './Page';
import ParityBackground from './ParityBackground';
import PasswordStrength from './Form/PasswordStrength';
import ShortenedHash from './ShortenedHash';
import SignerIcon from './SignerIcon';
import Tags from './Tags';
@@ -91,6 +92,7 @@ export {
muiTheme,
Page,
ParityBackground,
PasswordStrength,
RadioButtons,
ShortenedHash,
Select,