Merge pull request #811 from matrix-org/luke/login-ui

First iteration on improving login UI
This commit is contained in:
Luke Barnard 2017-04-21 16:25:16 +01:00 committed by GitHub
commit 19d6d1ef4b
6 changed files with 244 additions and 142 deletions

View file

@ -25,6 +25,9 @@ import emojione from 'emojione';
import classNames from 'classnames'; import classNames from 'classnames';
emojione.imagePathSVG = 'emojione/svg/'; emojione.imagePathSVG = 'emojione/svg/';
// Store PNG path for displaying many flags at once (for increased performance over SVG)
emojione.imagePathPNG = 'emojione/png/';
// Use SVGs for emojis
emojione.imageType = 'svg'; emojione.imageType = 'svg';
const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi"); const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
@ -64,16 +67,23 @@ export function unicodeToImage(str) {
* emoji. * emoji.
* *
* @param alt {string} String to use for the image alt text * @param alt {string} String to use for the image alt text
* @param useSvg {boolean} Whether to use SVG image src. If False, PNG will be used.
* @param unicode {integer} One or more integers representing unicode characters * @param unicode {integer} One or more integers representing unicode characters
* @returns A img node with the corresponding emoji * @returns A img node with the corresponding emoji
*/ */
export function charactersToImageNode(alt, ...unicode) { export function charactersToImageNode(alt, useSvg, ...unicode) {
const fileName = unicode.map((u) => { const fileName = unicode.map((u) => {
return u.toString(16); return u.toString(16);
}).join('-'); }).join('-');
return <img alt={alt} src={`${emojione.imagePathSVG}${fileName}.svg${emojione.cacheBustParam}`}/>; const path = useSvg ? emojione.imagePathSVG : emojione.imagePathPNG;
const fileType = useSvg ? 'svg' : 'png';
return <img
alt={alt}
src={`${path}${fileName}.${fileType}${emojione.cacheBustParam}`}
/>;
} }
export function stripParagraphs(html: string): string { export function stripParagraphs(html: string): string {
const contentDiv = document.createElement('div'); const contentDiv = document.createElement('div');
contentDiv.innerHTML = html; contentDiv.innerHTML = html;

View file

@ -17,13 +17,11 @@ limitations under the License.
'use strict'; 'use strict';
var React = require('react'); import React from 'react';
var ReactDOM = require('react-dom'); import ReactDOM from 'react-dom';
var sdk = require('../../../index'); import url from 'url';
var Login = require("../../../Login"); import sdk from '../../../index';
var PasswordLogin = require("../../views/login/PasswordLogin"); import Login from '../../../Login';
var CasLogin = require("../../views/login/CasLogin");
var ServerConfig = require("../../views/login/ServerConfig");
/** /**
* A wire component which glues together login UI components and Login logic * A wire component which glues together login UI components and Login logic
@ -67,6 +65,7 @@ module.exports = React.createClass({
username: "", username: "",
phoneCountry: null, phoneCountry: null,
phoneNumber: "", phoneNumber: "",
currentFlow: "m.login.password",
}; };
}, },
@ -129,23 +128,19 @@ module.exports = React.createClass({
this.setState({ phoneNumber: phoneNumber }); this.setState({ phoneNumber: phoneNumber });
}, },
onHsUrlChanged: function(newHsUrl) { onServerConfigChange: function(config) {
var self = this; var self = this;
this.setState({ let newState = {
enteredHomeserverUrl: newHsUrl,
errorText: null, // reset err messages errorText: null, // reset err messages
}, function() { };
self._initLoginLogic(newHsUrl); if (config.hsUrl !== undefined) {
}); newState.enteredHomeserverUrl = config.hsUrl;
}, }
if (config.isUrl !== undefined) {
onIsUrlChanged: function(newIsUrl) { newState.enteredIdentityServerUrl = config.isUrl;
var self = this; }
this.setState({ this.setState(newState, function() {
enteredIdentityServerUrl: newIsUrl, self._initLoginLogic(config.hsUrl || null, config.isUrl);
errorText: null, // reset err messages
}, function() {
self._initLoginLogic(null, newIsUrl);
}); });
}, },
@ -161,25 +156,28 @@ module.exports = React.createClass({
}); });
this._loginLogic = loginLogic; this._loginLogic = loginLogic;
loginLogic.getFlows().then(function(flows) {
// old behaviour was to always use the first flow without presenting
// options. This works in most cases (we don't have a UI for multiple
// logins so let's skip that for now).
loginLogic.chooseFlow(0);
}, function(err) {
self._setStateFromError(err, false);
}).finally(function() {
self.setState({
busy: false
});
});
this.setState({ this.setState({
enteredHomeserverUrl: hsUrl, enteredHomeserverUrl: hsUrl,
enteredIdentityServerUrl: isUrl, enteredIdentityServerUrl: isUrl,
busy: true, busy: true,
loginIncorrect: false, loginIncorrect: false,
}); });
loginLogic.getFlows().then(function(flows) {
// old behaviour was to always use the first flow without presenting
// options. This works in most cases (we don't have a UI for multiple
// logins so let's skip that for now).
loginLogic.chooseFlow(0);
self.setState({
currentFlow: self._getCurrentFlowStep(),
});
}, function(err) {
self._setStateFromError(err, false);
}).finally(function() {
self.setState({
busy: false,
});
});
}, },
_getCurrentFlowStep: function() { _getCurrentFlowStep: function() {
@ -231,6 +229,13 @@ module.exports = React.createClass({
componentForStep: function(step) { componentForStep: function(step) {
switch (step) { switch (step) {
case 'm.login.password': case 'm.login.password':
const PasswordLogin = sdk.getComponent('login.PasswordLogin');
// HSs that are not matrix.org may not be configured to have their
// domain name === domain part.
let hsDomain = url.parse(this.state.enteredHomeserverUrl).hostname;
if (hsDomain !== 'matrix.org') {
hsDomain = null;
}
return ( return (
<PasswordLogin <PasswordLogin
onSubmit={this.onPasswordLogin} onSubmit={this.onPasswordLogin}
@ -242,9 +247,11 @@ module.exports = React.createClass({
onPhoneNumberChanged={this.onPhoneNumberChanged} onPhoneNumberChanged={this.onPhoneNumberChanged}
onForgotPasswordClick={this.props.onForgotPasswordClick} onForgotPasswordClick={this.props.onForgotPasswordClick}
loginIncorrect={this.state.loginIncorrect} loginIncorrect={this.state.loginIncorrect}
hsDomain={hsDomain}
/> />
); );
case 'm.login.cas': case 'm.login.cas':
const CasLogin = sdk.getComponent('login.CasLogin');
return ( return (
<CasLogin onSubmit={this.onCasLogin} /> <CasLogin onSubmit={this.onCasLogin} />
); );
@ -262,10 +269,11 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
var Loader = sdk.getComponent("elements.Spinner"); const Loader = sdk.getComponent("elements.Spinner");
var LoginHeader = sdk.getComponent("login.LoginHeader"); const LoginHeader = sdk.getComponent("login.LoginHeader");
var LoginFooter = sdk.getComponent("login.LoginFooter"); const LoginFooter = sdk.getComponent("login.LoginFooter");
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null; const ServerConfig = sdk.getComponent("login.ServerConfig");
const loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
var loginAsGuestJsx; var loginAsGuestJsx;
if (this.props.enableGuest) { if (this.props.enableGuest) {
@ -291,15 +299,14 @@ module.exports = React.createClass({
<h2>Sign in <h2>Sign in
{ loader } { loader }
</h2> </h2>
{ this.componentForStep(this._getCurrentFlowStep()) } { this.componentForStep(this.state.currentFlow) }
<ServerConfig ref="serverConfig" <ServerConfig ref="serverConfig"
withToggleButton={true} withToggleButton={true}
customHsUrl={this.props.customHsUrl} customHsUrl={this.props.customHsUrl}
customIsUrl={this.props.customIsUrl} customIsUrl={this.props.customIsUrl}
defaultHsUrl={this.props.defaultHsUrl} defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl} defaultIsUrl={this.props.defaultIsUrl}
onHsUrlChanged={this.onHsUrlChanged} onServerConfigChange={this.onServerConfigChange}
onIsUrlChanged={this.onIsUrlChanged}
delayTimeMs={1000}/> delayTimeMs={1000}/>
<div className="mx_Login_error"> <div className="mx_Login_error">
{ this.state.errorText } { this.state.errorText }

View file

@ -248,13 +248,10 @@ export default class Dropdown extends React.Component {
</MenuOption> </MenuOption>
); );
}); });
if (options.length === 0) {
if (!this.state.searchQuery) { return [<div className="mx_Dropdown_option">
options.push( No results
<div key="_searchprompt" className="mx_Dropdown_searchPrompt"> </div>];
Type to search...
</div>
);
} }
return options; return options;
} }
@ -267,16 +264,20 @@ export default class Dropdown extends React.Component {
let menu; let menu;
if (this.state.expanded) { if (this.state.expanded) {
currentValue = <input type="text" className="mx_Dropdown_option" if (this.props.searchEnabled) {
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress} currentValue = <input type="text" className="mx_Dropdown_option"
onKeyUp={this._onInputKeyUp} ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
onChange={this._onInputChange} onKeyUp={this._onInputKeyUp}
value={this.state.searchQuery} onChange={this._onInputChange}
/>; value={this.state.searchQuery}
/>;
}
menu = <div className="mx_Dropdown_menu" style={menuStyle}> menu = <div className="mx_Dropdown_menu" style={menuStyle}>
{this._getMenuOptions()} {this._getMenuOptions()}
</div>; </div>;
} else { }
if (!currentValue) {
const selectedChild = this.props.getShortOption ? const selectedChild = this.props.getShortOption ?
this.props.getShortOption(this.props.value) : this.props.getShortOption(this.props.value) :
this.childrenByKey[this.props.value]; this.childrenByKey[this.props.value];
@ -313,6 +314,7 @@ Dropdown.propTypes = {
onOptionChange: React.PropTypes.func.isRequired, onOptionChange: React.PropTypes.func.isRequired,
// Called when the value of the search field changes // Called when the value of the search field changes
onSearchChange: React.PropTypes.func, onSearchChange: React.PropTypes.func,
searchEnabled: React.PropTypes.bool,
// Function that, given the key of an option, returns // Function that, given the key of an option, returns
// a node representing that option to be displayed in the // a node representing that option to be displayed in the
// box itself as the currently-selected option (ie. as // box itself as the currently-selected option (ie. as

View file

@ -33,8 +33,6 @@ function countryMatchesSearchQuery(query, country) {
return false; return false;
} }
const MAX_DISPLAYED_ROWS = 2;
export default class CountryDropdown extends React.Component { export default class CountryDropdown extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -64,7 +62,7 @@ export default class CountryDropdown extends React.Component {
// Unicode Regional Indicator Symbol letter 'A' // Unicode Regional Indicator Symbol letter 'A'
const RIS_A = 0x1F1E6; const RIS_A = 0x1F1E6;
const ASCII_A = 65; const ASCII_A = 65;
return charactersToImageNode(iso2, return charactersToImageNode(iso2, true,
RIS_A + (iso2.charCodeAt(0) - ASCII_A), RIS_A + (iso2.charCodeAt(0) - ASCII_A),
RIS_A + (iso2.charCodeAt(1) - ASCII_A), RIS_A + (iso2.charCodeAt(1) - ASCII_A),
); );
@ -93,10 +91,6 @@ export default class CountryDropdown extends React.Component {
displayedCountries = COUNTRIES; displayedCountries = COUNTRIES;
} }
if (displayedCountries.length > MAX_DISPLAYED_ROWS) {
displayedCountries = displayedCountries.slice(0, MAX_DISPLAYED_ROWS);
}
const options = displayedCountries.map((country) => { const options = displayedCountries.map((country) => {
return <div key={country.iso2}> return <div key={country.iso2}>
{this._flagImgForIso2(country.iso2)} {this._flagImgForIso2(country.iso2)}
@ -111,7 +105,7 @@ export default class CountryDropdown extends React.Component {
return <Dropdown className={this.props.className} return <Dropdown className={this.props.className}
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange} onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
menuWidth={298} getShortOption={this._flagImgForIso2} menuWidth={298} getShortOption={this._flagImgForIso2}
value={value} value={value} searchEnabled={true}
> >
{options} {options}
</Dropdown> </Dropdown>

View file

@ -25,55 +25,49 @@ import {field_input_incorrect} from '../../../UiEffects';
/** /**
* A pure UI component which displays a username/password form. * A pure UI component which displays a username/password form.
*/ */
module.exports = React.createClass({displayName: 'PasswordLogin', class PasswordLogin extends React.Component {
propTypes: { static defaultProps = {
onSubmit: React.PropTypes.func.isRequired, // fn(username, password) onUsernameChanged: function() {},
onForgotPasswordClick: React.PropTypes.func, // fn() onPasswordChanged: function() {},
initialUsername: React.PropTypes.string, onPhoneCountryChanged: function() {},
initialPhoneCountry: React.PropTypes.string, onPhoneNumberChanged: function() {},
initialPhoneNumber: React.PropTypes.string, initialUsername: "",
initialPassword: React.PropTypes.string, initialPhoneCountry: "",
onUsernameChanged: React.PropTypes.func, initialPhoneNumber: "",
onPhoneCountryChanged: React.PropTypes.func, initialPassword: "",
onPhoneNumberChanged: React.PropTypes.func, loginIncorrect: false,
onPasswordChanged: React.PropTypes.func, hsDomain: "",
loginIncorrect: React.PropTypes.bool, }
},
getDefaultProps: function() { constructor(props) {
return { super(props);
onUsernameChanged: function() {}, this.state = {
onPasswordChanged: function() {},
onPhoneCountryChanged: function() {},
onPhoneNumberChanged: function() {},
initialUsername: "",
initialPhoneCountry: "",
initialPhoneNumber: "",
initialPassword: "",
loginIncorrect: false,
};
},
getInitialState: function() {
return {
username: this.props.initialUsername, username: this.props.initialUsername,
password: this.props.initialPassword, password: this.props.initialPassword,
phoneCountry: this.props.initialPhoneCountry, phoneCountry: this.props.initialPhoneCountry,
phoneNumber: this.props.initialPhoneNumber, phoneNumber: this.props.initialPhoneNumber,
loginType: PasswordLogin.LOGIN_FIELD_MXID,
}; };
},
componentWillMount: function() { this.onSubmitForm = this.onSubmitForm.bind(this);
this.onUsernameChanged = this.onUsernameChanged.bind(this);
this.onLoginTypeChange = this.onLoginTypeChange.bind(this);
this.onPhoneCountryChanged = this.onPhoneCountryChanged.bind(this);
this.onPhoneNumberChanged = this.onPhoneNumberChanged.bind(this);
this.onPasswordChanged = this.onPasswordChanged.bind(this);
}
componentWillMount() {
this._passwordField = null; this._passwordField = null;
}, }
componentWillReceiveProps: function(nextProps) { componentWillReceiveProps(nextProps) {
if (!this.props.loginIncorrect && nextProps.loginIncorrect) { if (!this.props.loginIncorrect && nextProps.loginIncorrect) {
field_input_incorrect(this._passwordField); field_input_incorrect(this._passwordField);
} }
}, }
onSubmitForm: function(ev) { onSubmitForm(ev) {
ev.preventDefault(); ev.preventDefault();
this.props.onSubmit( this.props.onSubmit(
this.state.username, this.state.username,
@ -81,29 +75,99 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
this.state.phoneNumber, this.state.phoneNumber,
this.state.password, this.state.password,
); );
}, }
onUsernameChanged: function(ev) { onUsernameChanged(ev) {
this.setState({username: ev.target.value}); this.setState({username: ev.target.value});
this.props.onUsernameChanged(ev.target.value); this.props.onUsernameChanged(ev.target.value);
}, }
onPhoneCountryChanged: function(country) { onLoginTypeChange(loginType) {
this.setState({
loginType: loginType,
username: "" // Reset because email and username use the same state
});
}
onPhoneCountryChanged(country) {
this.setState({phoneCountry: country}); this.setState({phoneCountry: country});
this.props.onPhoneCountryChanged(country); this.props.onPhoneCountryChanged(country);
}, }
onPhoneNumberChanged: function(ev) { onPhoneNumberChanged(ev) {
this.setState({phoneNumber: ev.target.value}); this.setState({phoneNumber: ev.target.value});
this.props.onPhoneNumberChanged(ev.target.value); this.props.onPhoneNumberChanged(ev.target.value);
}, }
onPasswordChanged: function(ev) { onPasswordChanged(ev) {
this.setState({password: ev.target.value}); this.setState({password: ev.target.value});
this.props.onPasswordChanged(ev.target.value); this.props.onPasswordChanged(ev.target.value);
}, }
render: function() { renderLoginField(loginType) {
switch(loginType) {
case PasswordLogin.LOGIN_FIELD_EMAIL:
return <input
className="mx_Login_field mx_Login_email"
key="email_input"
type="text"
name="username" // make it a little easier for browser's remember-password
onChange={this.onUsernameChanged}
placeholder="joe@example.com"
value={this.state.username}
autoFocus
/>;
case PasswordLogin.LOGIN_FIELD_MXID:
const mxidInputClasses = classNames({
"mx_Login_field": true,
"mx_Login_username": true,
"mx_Login_field_has_suffix": Boolean(this.props.hsDomain),
});
let suffix = null;
if (this.props.hsDomain) {
suffix = <div className="mx_Login_username_suffix">
:{this.props.hsDomain}
</div>;
}
return <div className="mx_Login_username_group">
<div className="mx_Login_username_prefix">@</div>
<input
className={mxidInputClasses}
key="username_input"
type="text"
name="username" // make it a little easier for browser's remember-password
onChange={this.onUsernameChanged}
placeholder="username"
value={this.state.username}
autoFocus
/>
{suffix}
</div>;
case PasswordLogin.LOGIN_FIELD_PHONE:
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown');
return <div className="mx_Login_phoneSection">
<CountryDropdown
className="mx_Login_phoneCountry"
ref="phone_country"
onOptionChange={this.onPhoneCountryChanged}
value={this.state.phoneCountry}
/>
<input
className="mx_Login_phoneNumberField mx_Login_field"
ref="phoneNumber"
key="phone_input"
type="text"
name="phoneNumber"
onChange={this.onPhoneNumberChanged}
placeholder="Mobile phone number"
value={this.state.phoneNumber}
autoFocus
/>
</div>;
}
}
render() {
var forgotPasswordJsx; var forgotPasswordJsx;
if (this.props.onForgotPasswordClick) { if (this.props.onForgotPasswordClick) {
@ -119,29 +183,25 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
error: this.props.loginIncorrect, error: this.props.loginIncorrect,
}); });
const CountryDropdown = sdk.getComponent('views.login.CountryDropdown'); const Dropdown = sdk.getComponent('elements.Dropdown');
const loginField = this.renderLoginField(this.state.loginType);
return ( return (
<div> <div>
<form onSubmit={this.onSubmitForm}> <form onSubmit={this.onSubmitForm}>
<input className="mx_Login_field mx_Login_username" type="text" <div className="mx_Login_type_container">
name="username" // make it a little easier for browser's remember-password <label className="mx_Login_type_label">I want to sign in with my</label>
value={this.state.username} onChange={this.onUsernameChanged} <Dropdown
placeholder="Email or user name" autoFocus /> className="mx_Login_type_dropdown"
or value={this.state.loginType}
<div className="mx_Login_phoneSection"> onOptionChange={this.onLoginTypeChange}>
<CountryDropdown ref="phone_country" onOptionChange={this.onPhoneCountryChanged} <span key={PasswordLogin.LOGIN_FIELD_MXID}>Matrix ID</span>
className="mx_Login_phoneCountry" <span key={PasswordLogin.LOGIN_FIELD_EMAIL}>Email Address</span>
value={this.state.phoneCountry} <span key={PasswordLogin.LOGIN_FIELD_PHONE}>Phone</span>
/> </Dropdown>
<input type="text" ref="phoneNumber"
onChange={this.onPhoneNumberChanged}
placeholder="Mobile phone number"
className="mx_Login_phoneNumberField mx_Login_field"
value={this.state.phoneNumber}
name="phoneNumber"
/>
</div> </div>
<br /> {loginField}
<input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password" <input className={pwFieldClass} ref={(e) => {this._passwordField = e;}} type="password"
name="password" name="password"
value={this.state.password} onChange={this.onPasswordChanged} value={this.state.password} onChange={this.onPasswordChanged}
@ -153,4 +213,25 @@ module.exports = React.createClass({displayName: 'PasswordLogin',
</div> </div>
); );
} }
}); }
PasswordLogin.LOGIN_FIELD_EMAIL = "login_field_email";
PasswordLogin.LOGIN_FIELD_MXID = "login_field_mxid";
PasswordLogin.LOGIN_FIELD_PHONE = "login_field_phone";
PasswordLogin.propTypes = {
onSubmit: React.PropTypes.func.isRequired, // fn(username, password)
onForgotPasswordClick: React.PropTypes.func, // fn()
initialUsername: React.PropTypes.string,
initialPhoneCountry: React.PropTypes.string,
initialPhoneNumber: React.PropTypes.string,
initialPassword: React.PropTypes.string,
onUsernameChanged: React.PropTypes.func,
onPhoneCountryChanged: React.PropTypes.func,
onPhoneNumberChanged: React.PropTypes.func,
onPasswordChanged: React.PropTypes.func,
loginIncorrect: React.PropTypes.bool,
hsDomain: React.PropTypes.string,
};
module.exports = PasswordLogin;

View file

@ -27,8 +27,7 @@ module.exports = React.createClass({
displayName: 'ServerConfig', displayName: 'ServerConfig',
propTypes: { propTypes: {
onHsUrlChanged: React.PropTypes.func, onServerConfigChange: React.PropTypes.func,
onIsUrlChanged: React.PropTypes.func,
// default URLs are defined in config.json (or the hardcoded defaults) // default URLs are defined in config.json (or the hardcoded defaults)
// they are used if the user has not overridden them with a custom URL. // they are used if the user has not overridden them with a custom URL.
@ -50,8 +49,7 @@ module.exports = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
onHsUrlChanged: function() {}, onServerConfigChange: function() {},
onIsUrlChanged: function() {},
customHsUrl: "", customHsUrl: "",
customIsUrl: "", customIsUrl: "",
withToggleButton: false, withToggleButton: false,
@ -75,7 +73,10 @@ module.exports = React.createClass({
this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() { this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() {
var hsUrl = this.state.hs_url.trim().replace(/\/$/, ""); var hsUrl = this.state.hs_url.trim().replace(/\/$/, "");
if (hsUrl === "") hsUrl = this.props.defaultHsUrl; if (hsUrl === "") hsUrl = this.props.defaultHsUrl;
this.props.onHsUrlChanged(hsUrl); this.props.onServerConfigChange({
hsUrl : this.state.hs_url,
isUrl : this.state.is_url,
});
}); });
}); });
}, },
@ -85,7 +86,10 @@ module.exports = React.createClass({
this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() { this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() {
var isUrl = this.state.is_url.trim().replace(/\/$/, ""); var isUrl = this.state.is_url.trim().replace(/\/$/, "");
if (isUrl === "") isUrl = this.props.defaultIsUrl; if (isUrl === "") isUrl = this.props.defaultIsUrl;
this.props.onIsUrlChanged(isUrl); this.props.onServerConfigChange({
hsUrl : this.state.hs_url,
isUrl : this.state.is_url,
});
}); });
}); });
}, },
@ -102,12 +106,16 @@ module.exports = React.createClass({
configVisible: visible configVisible: visible
}); });
if (!visible) { if (!visible) {
this.props.onHsUrlChanged(this.props.defaultHsUrl); this.props.onServerConfigChange({
this.props.onIsUrlChanged(this.props.defaultIsUrl); hsUrl : this.props.defaultHsUrl,
isUrl : this.props.defaultIsUrl,
});
} }
else { else {
this.props.onHsUrlChanged(this.state.hs_url); this.props.onServerConfigChange({
this.props.onIsUrlChanged(this.state.is_url); hsUrl : this.state.hs_url,
isUrl : this.state.is_url,
});
} }
}, },