Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #811 from matrix-org/luke/login-ui
Browse files Browse the repository at this point in the history
First iteration on improving login UI
  • Loading branch information
lukebarnard1 authored Apr 21, 2017
2 parents b0288eb + 9c4c706 commit 19d6d1e
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 137 deletions.
14 changes: 12 additions & 2 deletions src/HtmlUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import emojione from 'emojione';
import classNames from 'classnames';

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';

const EMOJI_REGEX = new RegExp(emojione.unicodeRegexp+"+", "gi");
Expand Down Expand Up @@ -64,16 +67,23 @@ export function unicodeToImage(str) {
* emoji.
*
* @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
* @returns A img node with the corresponding emoji
*/
export function charactersToImageNode(alt, ...unicode) {
export function charactersToImageNode(alt, useSvg, ...unicode) {
const fileName = unicode.map((u) => {
return u.toString(16);
}).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 {
const contentDiv = document.createElement('div');
contentDiv.innerHTML = html;
Expand Down
81 changes: 44 additions & 37 deletions src/components/structures/login/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ limitations under the License.

'use strict';

var React = require('react');
var ReactDOM = require('react-dom');
var sdk = require('../../../index');
var Login = require("../../../Login");
var PasswordLogin = require("../../views/login/PasswordLogin");
var CasLogin = require("../../views/login/CasLogin");
var ServerConfig = require("../../views/login/ServerConfig");
import React from 'react';
import ReactDOM from 'react-dom';
import url from 'url';
import sdk from '../../../index';
import Login from '../../../Login';

/**
* A wire component which glues together login UI components and Login logic
Expand Down Expand Up @@ -67,6 +65,7 @@ module.exports = React.createClass({
username: "",
phoneCountry: null,
phoneNumber: "",
currentFlow: "m.login.password",
};
},

Expand Down Expand Up @@ -129,23 +128,19 @@ module.exports = React.createClass({
this.setState({ phoneNumber: phoneNumber });
},

onHsUrlChanged: function(newHsUrl) {
onServerConfigChange: function(config) {
var self = this;
this.setState({
enteredHomeserverUrl: newHsUrl,
errorText: null, // reset err messages
}, function() {
self._initLoginLogic(newHsUrl);
});
},

onIsUrlChanged: function(newIsUrl) {
var self = this;
this.setState({
enteredIdentityServerUrl: newIsUrl,
let newState = {
errorText: null, // reset err messages
}, function() {
self._initLoginLogic(null, newIsUrl);
};
if (config.hsUrl !== undefined) {
newState.enteredHomeserverUrl = config.hsUrl;
}
if (config.isUrl !== undefined) {
newState.enteredIdentityServerUrl = config.isUrl;
}
this.setState(newState, function() {
self._initLoginLogic(config.hsUrl || null, config.isUrl);
});
},

Expand All @@ -161,25 +156,28 @@ module.exports = React.createClass({
});
this._loginLogic = loginLogic;

this.setState({
enteredHomeserverUrl: hsUrl,
enteredIdentityServerUrl: isUrl,
busy: true,
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
busy: false,
});
});

this.setState({
enteredHomeserverUrl: hsUrl,
enteredIdentityServerUrl: isUrl,
busy: true,
loginIncorrect: false,
});
},

_getCurrentFlowStep: function() {
Expand Down Expand Up @@ -231,6 +229,13 @@ module.exports = React.createClass({
componentForStep: function(step) {
switch (step) {
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 (
<PasswordLogin
onSubmit={this.onPasswordLogin}
Expand All @@ -242,9 +247,11 @@ module.exports = React.createClass({
onPhoneNumberChanged={this.onPhoneNumberChanged}
onForgotPasswordClick={this.props.onForgotPasswordClick}
loginIncorrect={this.state.loginIncorrect}
hsDomain={hsDomain}
/>
);
case 'm.login.cas':
const CasLogin = sdk.getComponent('login.CasLogin');
return (
<CasLogin onSubmit={this.onCasLogin} />
);
Expand All @@ -262,10 +269,11 @@ module.exports = React.createClass({
},

render: function() {
var Loader = sdk.getComponent("elements.Spinner");
var LoginHeader = sdk.getComponent("login.LoginHeader");
var LoginFooter = sdk.getComponent("login.LoginFooter");
var loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;
const Loader = sdk.getComponent("elements.Spinner");
const LoginHeader = sdk.getComponent("login.LoginHeader");
const LoginFooter = sdk.getComponent("login.LoginFooter");
const ServerConfig = sdk.getComponent("login.ServerConfig");
const loader = this.state.busy ? <div className="mx_Login_loader"><Loader /></div> : null;

var loginAsGuestJsx;
if (this.props.enableGuest) {
Expand All @@ -291,15 +299,14 @@ module.exports = React.createClass({
<h2>Sign in
{ loader }
</h2>
{ this.componentForStep(this._getCurrentFlowStep()) }
{ this.componentForStep(this.state.currentFlow) }
<ServerConfig ref="serverConfig"
withToggleButton={true}
customHsUrl={this.props.customHsUrl}
customIsUrl={this.props.customIsUrl}
defaultHsUrl={this.props.defaultHsUrl}
defaultIsUrl={this.props.defaultIsUrl}
onHsUrlChanged={this.onHsUrlChanged}
onIsUrlChanged={this.onIsUrlChanged}
onServerConfigChange={this.onServerConfigChange}
delayTimeMs={1000}/>
<div className="mx_Login_error">
{ this.state.errorText }
Expand Down
30 changes: 16 additions & 14 deletions src/components/views/elements/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,13 +248,10 @@ export default class Dropdown extends React.Component {
</MenuOption>
);
});

if (!this.state.searchQuery) {
options.push(
<div key="_searchprompt" className="mx_Dropdown_searchPrompt">
Type to search...
</div>
);
if (options.length === 0) {
return [<div className="mx_Dropdown_option">
No results
</div>];
}
return options;
}
Expand All @@ -267,16 +264,20 @@ export default class Dropdown extends React.Component {

let menu;
if (this.state.expanded) {
currentValue = <input type="text" className="mx_Dropdown_option"
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
onKeyUp={this._onInputKeyUp}
onChange={this._onInputChange}
value={this.state.searchQuery}
/>;
if (this.props.searchEnabled) {
currentValue = <input type="text" className="mx_Dropdown_option"
ref={this._collectInputTextBox} onKeyPress={this._onInputKeyPress}
onKeyUp={this._onInputKeyUp}
onChange={this._onInputChange}
value={this.state.searchQuery}
/>;
}
menu = <div className="mx_Dropdown_menu" style={menuStyle}>
{this._getMenuOptions()}
</div>;
} else {
}

if (!currentValue) {
const selectedChild = this.props.getShortOption ?
this.props.getShortOption(this.props.value) :
this.childrenByKey[this.props.value];
Expand Down Expand Up @@ -313,6 +314,7 @@ Dropdown.propTypes = {
onOptionChange: React.PropTypes.func.isRequired,
// Called when the value of the search field changes
onSearchChange: React.PropTypes.func,
searchEnabled: React.PropTypes.bool,
// Function that, given the key of an option, returns
// a node representing that option to be displayed in the
// box itself as the currently-selected option (ie. as
Expand Down
10 changes: 2 additions & 8 deletions src/components/views/login/CountryDropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ function countryMatchesSearchQuery(query, country) {
return false;
}

const MAX_DISPLAYED_ROWS = 2;

export default class CountryDropdown extends React.Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -64,7 +62,7 @@ export default class CountryDropdown extends React.Component {
// Unicode Regional Indicator Symbol letter 'A'
const RIS_A = 0x1F1E6;
const ASCII_A = 65;
return charactersToImageNode(iso2,
return charactersToImageNode(iso2, true,
RIS_A + (iso2.charCodeAt(0) - ASCII_A),
RIS_A + (iso2.charCodeAt(1) - ASCII_A),
);
Expand Down Expand Up @@ -93,10 +91,6 @@ export default class CountryDropdown extends React.Component {
displayedCountries = COUNTRIES;
}

if (displayedCountries.length > MAX_DISPLAYED_ROWS) {
displayedCountries = displayedCountries.slice(0, MAX_DISPLAYED_ROWS);
}

const options = displayedCountries.map((country) => {
return <div key={country.iso2}>
{this._flagImgForIso2(country.iso2)}
Expand All @@ -111,7 +105,7 @@ export default class CountryDropdown extends React.Component {
return <Dropdown className={this.props.className}
onOptionChange={this.props.onOptionChange} onSearchChange={this._onSearchChange}
menuWidth={298} getShortOption={this._flagImgForIso2}
value={value}
value={value} searchEnabled={true}
>
{options}
</Dropdown>
Expand Down
Loading

0 comments on commit 19d6d1e

Please sign in to comment.