summaryrefslogtreecommitdiffhomepage
path: root/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/components')
-rw-r--r--app/components/AccountInput.js184
-rw-r--r--app/components/Login.js21
2 files changed, 190 insertions, 15 deletions
diff --git a/app/components/AccountInput.js b/app/components/AccountInput.js
new file mode 100644
index 0000000000..56097f5e74
--- /dev/null
+++ b/app/components/AccountInput.js
@@ -0,0 +1,184 @@
+import React, { Component, PropTypes } from 'react';
+import { formatAccount } from '../lib/formatters';
+
+export default class AccountInput extends Component {
+
+ static propTypes = {
+ value: PropTypes.string,
+ onEnter: PropTypes.func,
+ onChange: PropTypes.func
+ }
+
+ constructor(props) {
+ super(props);
+
+ this.state = { value: '', selectionRange: [0,0] };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const nextVal = (nextProps.value || '').replace(/[^0-9]/g, '');
+ if(nextVal !== this.state.value) {
+ const len = nextVal.length;
+ this.setState({ value: nextVal, selectionRange: [len, len] });
+ }
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ return (this.props.value !== nextProps.value ||
+ this.props.onEnter !== nextProps.onEnter ||
+ this.props.onChange !== nextProps.onChange ||
+ this.state.value !== nextState.value ||
+ this.state.selectionRange[0] !== nextState.selectionRange[0] ||
+ this.state.selectionRange[1] !== nextState.selectionRange[1]);
+ }
+
+ insert(val, insert, selRange) {
+ const head = val.slice(0, selRange[0]);
+ const tail = val.slice(selRange[1], val.length);
+ const newVal = head + insert + tail;
+ const selectionOffset = head.length + insert.length;
+
+ return { value: newVal, selectionRange: [selectionOffset, selectionOffset] };
+ }
+
+ remove(val, selRange) {
+ let newVal, selectionOffset;
+
+ if(selRange[0] === selRange[1]) {
+ const head = val.slice(0, selRange[0] - 1);
+ const tail = val.slice(selRange[0], val.length);
+ newVal = head + tail;
+ selectionOffset = head.length;
+ } else {
+ const head = val.slice(0, selRange[0]);
+ const tail = val.slice(selRange[1], val.length);
+ newVal = head + tail;
+ selectionOffset = head.length;
+ }
+
+ return { value: newVal, selectionRange: [selectionOffset, selectionOffset] };
+ }
+
+ toInternalSelectionRange(val, domRange) {
+ const countSpaces = (val) => {
+ return (val.match(/\s/g) || []).length;
+ };
+
+ const fmt = formatAccount(val || '');
+ let start = domRange[0];
+ let end = domRange[1];
+ const spacesBefore = countSpaces(fmt.slice(0, start));
+ const spacesWithin = countSpaces(fmt.slice(start, end));
+ const finalStart = start - spacesBefore;
+ const finalEnd = end - (spacesBefore + spacesWithin);
+
+ console.log('toInternalSelectionRange: spacesBefore: ' + spacesBefore + ', spacesWithin: ' + spacesWithin +
+ "\n<" + fmt.slice(0, start) + ">\n<" + fmt.slice(start, end) + ">" + "\nfinalStart: " + finalStart + "\nfinalEnd: " + finalEnd);
+
+
+ return [ finalStart, finalEnd ];
+ }
+
+ toDomSelection(val, selRange) {
+ const countSpaces = (val, untilIndex) => {
+ if(val.length > 12) { return 0; }
+ return Math.floor(untilIndex / 4); // groups of 4 digits
+ };
+
+ let start = selRange[0];
+ let end = selRange[1];
+ const startSpaces = countSpaces(val, start);
+ const endSpaces = countSpaces(val, end);
+
+ console.log('toDomSelection: [' + start + ', ' + end + '] startSpaces: ' + startSpaces + ', endSpaces: ' + endSpaces);
+
+ start += startSpaces;
+ end += startSpaces + (endSpaces - startSpaces);
+
+ return [ start, end ];
+ }
+
+ onKeyDown(e) {
+ console.log('Entered ' + e.key);
+
+ const { value, selectionRange } = this.state;
+ let result;
+
+ // arrows.
+ if(e.which >= 37 && e.which <= 40) {
+ return;
+ }
+
+ // prevent native keyboard input management
+ e.preventDefault();
+
+ if(e.which === 8) { // backspace
+ result = this.remove(value, selectionRange);
+ } else if(/[0-9]/.test(e.key)) { // digits
+ result = this.insert(value, e.key, selectionRange);
+ } else { // any other keys
+ return;
+ }
+
+ this.setState(result, () => {
+ if(this.props.onChange) {
+ this.props.onChange(result.value);
+ }
+ });
+ }
+
+ onSelect(e) {
+ const ref = this._input;
+ let start = ref.selectionStart;
+ let end = ref.selectionEnd;
+
+ console.log('onSelect: ' + start + ', ' + end);
+
+ const { value, selectionRange: curRange } = this.state;
+ const selRange = this.toInternalSelectionRange(value, [start, end]);
+ // if(selRange[0] !== curRange[0] || selRange[1] !== curRange[1]) {
+ this.setState({ selectionRange: selRange });
+ // }
+ }
+
+ onKeyUp(e) {
+ if(e.which === 13 && this.props.onEnter) {
+ this.props.onEnter();
+ }
+ console.log('onKeyUp: ', e);
+ }
+
+ onRef(ref) {
+ this._input = ref;
+ if(ref) {
+ const { value, selectionRange } = this.state;
+ const domRange = this.toDomSelection(value, selectionRange);
+ // if(ref.selectionStart !== domRange[0] || ref.selectionEnd !== domRange[1]) {
+ ref.setSelectionRange(domRange[0], domRange[1]);
+ // }
+ }
+ }
+
+ render() {
+ const displayString = formatAccount(this.state.value || '');
+ const props = Object.assign({}, this.props);
+
+ // exclude built-in props
+ for(let key of Object.keys(AccountInput.propTypes)) {
+ if(props.hasOwnProperty(key)) {
+ delete props[key];
+ }
+ }
+
+ return (
+ <input type="text"
+ value={ displayString }
+ onSelect={ ::this.onSelect }
+ onKeyUp={ ::this.onKeyUp }
+ onKeyDown={ ::this.onKeyDown }
+ ref={ ::this.onRef }
+ { ...props } />
+ );
+ }
+
+} \ No newline at end of file
diff --git a/app/components/Login.js b/app/components/Login.js
index 43cccf3f69..5dbce441c8 100644
--- a/app/components/Login.js
+++ b/app/components/Login.js
@@ -1,8 +1,8 @@
import React, { Component, PropTypes } from 'react';
import { If, Then } from 'react-if';
import { Layout, Container, Header } from './Layout';
-import { formatAccount } from '../lib/formatters';
import { LoginState } from '../enums';
+import AccountInput from './AccountInput';
export default class Login extends Component {
static propTypes = {
@@ -39,24 +39,16 @@ export default class Login extends Component {
this.props.onExternalLink('createAccount');
}
- onInputChange(e) {
- const val = e.target.value.replace(/[^0-9]/g, '');
-
+ onInputChange(val) {
+ console.log('VAL: ' + val);
// notify delegate on first change after login failure
if(this.state.notifyOnFirstChangeAfterFailure) {
this.setState({ notifyOnFirstChangeAfterFailure: false });
this.props.onFirstChangeAfterFailure();
}
-
this.props.onChange(val);
}
- onInputKeyUp(e) {
- if(e.which === 13) {
- this.onLogin();
- }
- }
-
formTitle(s) {
switch(s) {
case LoginState.connecting: return 'Logging in...';
@@ -117,7 +109,6 @@ export default class Login extends Component {
const { account, status, error } = this.props.user;
const title = this.formTitle(status);
const subtitle = this.formSubtitle(status, error);
- const displayAccount = formatAccount(account || '');
const isConnecting = status === LoginState.connecting;
const isFailed = status === LoginState.failed;
const isLoggedIn = status === LoginState.ok;
@@ -169,12 +160,12 @@ export default class Login extends Component {
<div className={ 'login-form__fields' + (isLoggedIn ? ' login-form__fields--invisible' : '') }>
<div className="login-form__subtitle">{ subtitle }</div>
<div className={ inputWrapClass }>
- <input className="login-form__input-field"
+ <AccountInput className="login-form__input-field"
type="text"
placeholder="e.g 0000 0000 0000"
onChange={ ::this.onInputChange }
- onKeyUp={ ::this.onInputKeyUp }
- value={ displayAccount }
+ onEnter={ ::this.onLogin }
+ value={ account }
disabled={ isConnecting }
autoFocus={ true }
ref={ autoFocusRef } />