+ );
+ }
+
renderCurrentInput () {
const { inputValue } = this.state;
@@ -304,7 +319,9 @@ class AddressSelect extends Component {
return (
-
{ label }
+
+
{ label }
+
{ content }
);
diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js
index e04f8b55d92..5025564a07e 100644
--- a/js/src/ui/Form/AddressSelect/addressSelectStore.js
+++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js
@@ -15,7 +15,7 @@
// along with Parity. If not, see
.
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;
+ });
});
}
diff --git a/js/src/ui/Form/InputAddress/inputAddress.js b/js/src/ui/Form/InputAddress/inputAddress.js
index 3cdac2a2e21..4673634c835 100644
--- a/js/src/ui/Form/InputAddress/inputAddress.js
+++ b/js/src/ui/Form/InputAddress/inputAddress.js
@@ -76,7 +76,7 @@ class InputAddress extends Component {
const props = {};
- if (!readOnly && !disabled) {
+ if (!disabled) {
props.focused = focused;
}
diff --git a/js/src/ui/Form/PasswordStrength/index.js b/js/src/ui/Form/PasswordStrength/index.js
new file mode 100644
index 00000000000..54e3c3d3e88
--- /dev/null
+++ b/js/src/ui/Form/PasswordStrength/index.js
@@ -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
.
+
+export default from './passwordStrength';
diff --git a/js/src/ui/Form/PasswordStrength/passwordStrength.css b/js/src/ui/Form/PasswordStrength/passwordStrength.css
new file mode 100644
index 00000000000..6512f3afe2d
--- /dev/null
+++ b/js/src/ui/Form/PasswordStrength/passwordStrength.css
@@ -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
.
+*/
+
+.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);
+}
diff --git a/js/src/ui/Form/PasswordStrength/passwordStrength.js b/js/src/ui/Form/PasswordStrength/passwordStrength.js
new file mode 100644
index 00000000000..6bfe681b941
--- /dev/null
+++ b/js/src/ui/Form/PasswordStrength/passwordStrength.js
@@ -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
.
+
+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 (
+
+
+
+
+ { this.renderFeedback(feedback) }
+
+
+ );
+ }
+
+ // 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 (
+
+
+ { suggestions.join(' ') }
+
+
+ );
+ }
+
+ getStrengthBarColor (score) {
+ switch (score) {
+ case 4:
+ case 3:
+ return 'lightgreen';
+
+ case 2:
+ return 'yellow';
+
+ case 1:
+ return 'orange';
+
+ default:
+ return 'red';
+ }
+ }
+}
diff --git a/js/src/ui/Form/PasswordStrength/passwordStrength.spec.js b/js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
new file mode 100644
index 00000000000..ac616a87b9b
--- /dev/null
+++ b/js/src/ui/Form/PasswordStrength/passwordStrength.spec.js
@@ -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
.
+
+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(
+
+ ).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);
+ });
+ });
+ });
+});
diff --git a/js/src/ui/Form/TypedInput/typedInput.js b/js/src/ui/Form/TypedInput/typedInput.js
index 3b19bde0846..dcdd599bbf5 100644
--- a/js/src/ui/Form/TypedInput/typedInput.js
+++ b/js/src/ui/Form/TypedInput/typedInput.js
@@ -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);
});
diff --git a/js/src/ui/Loading/loading.js b/js/src/ui/Loading/loading.js
index 507c84bf9eb..6e8faaf8ee9 100644
--- a/js/src/ui/Loading/loading.js
+++ b/js/src/ui/Loading/loading.js
@@ -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 (
-
-
+
+
);
}
diff --git a/js/src/ui/Portal/portal.css b/js/src/ui/Portal/portal.css
index 65c3b010340..37c57b712d2 100644
--- a/js/src/ui/Portal/portal.css
+++ b/js/src/ui/Portal/portal.css
@@ -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;
diff --git a/js/src/ui/index.js b/js/src/ui/index.js
index da61cc9b926..79a7c7f7a90 100644
--- a/js/src/ui/index.js
+++ b/js/src/ui/index.js
@@ -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,
diff --git a/js/webpack/app.js b/js/webpack/app.js
index 0db813fbfe7..3375ff178d3 100644
--- a/js/webpack/app.js
+++ b/js/webpack/app.js
@@ -43,7 +43,7 @@ module.exports = {
index: './index.js'
}),
output: {
- publicPath: '/',
+ // publicPath: '/',
path: path.join(__dirname, '../', DEST),
filename: '[name].[hash:10].js'
},
diff --git a/util/bigint/Cargo.toml b/util/bigint/Cargo.toml
index 5fe02865cf5..5d47713fcf0 100644
--- a/util/bigint/Cargo.toml
+++ b/util/bigint/Cargo.toml
@@ -12,6 +12,7 @@ bigint = "1.0"
rustc-serialize = "0.3"
heapsize = "0.3"
rand = "0.3.12"
+libc = "0.2"
[features]
x64asm_arithmetic=[]
diff --git a/util/bigint/src/hash.rs b/util/bigint/src/hash.rs
index fb9fbbd7271..34eff396273 100644
--- a/util/bigint/src/hash.rs
+++ b/util/bigint/src/hash.rs
@@ -26,6 +26,7 @@ use rand::Rng;
use rand::os::OsRng;
use rustc_serialize::hex::{FromHex, FromHexError};
use bigint::{Uint, U256};
+use libc::{c_void, memcmp};
/// Trait for a fixed-size byte array to be used as the output of hash functions.
pub trait FixedHash: Sized {
@@ -214,25 +215,16 @@ macro_rules! impl_hash {
impl PartialEq for $from {
fn eq(&self, other: &Self) -> bool {
- for i in 0..$size {
- if self.0[i] != other.0[i] {
- return false;
- }
- }
- true
+ unsafe { memcmp(self.0.as_ptr() as *const c_void, other.0.as_ptr() as *const c_void, $size) == 0 }
}
}
impl Ord for $from {
fn cmp(&self, other: &Self) -> Ordering {
- for i in 0..$size {
- if self.0[i] > other.0[i] {
- return Ordering::Greater;
- } else if self.0[i] < other.0[i] {
- return Ordering::Less;
- }
- }
- Ordering::Equal
+ let r = unsafe { memcmp(self.0.as_ptr() as *const c_void, other.0.as_ptr() as *const c_void, $size) };
+ if r < 0 { return Ordering::Less }
+ if r > 0 { return Ordering::Greater }
+ return Ordering::Equal;
}
}
diff --git a/util/bigint/src/lib.rs b/util/bigint/src/lib.rs
index e394ba7fb0f..28ab5b10e1e 100644
--- a/util/bigint/src/lib.rs
+++ b/util/bigint/src/lib.rs
@@ -21,6 +21,7 @@
extern crate rand;
extern crate rustc_serialize;
extern crate bigint;
+extern crate libc;
#[macro_use] extern crate heapsize;
pub mod hash;