From 05eda88ea264e73d6669b0efd20c6d19ae7428b8 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Thu, 12 Nov 2015 11:57:33 +0000 Subject: [PATCH] Split out logic/UI for logging in - Add 'PasswordLogin' UI component - Add 'LoginPage' wire component which, along with Signup from react SDK, replaces the 'Login' page. - Move UI code (state/props) from ServerConfig which was lobotomoised in the React SDK. Unfinished. --- src/skins/vector/skindex.js | 2 + .../vector/views/molecules/ServerConfig.js | 133 +++++++++++-- .../vector/views/organisms/PasswordLogin.js | 64 +++++++ src/skins/vector/views/pages/LoginPage.js | 176 ++++++++++++++++++ src/skins/vector/views/pages/MatrixChat.js | 7 +- 5 files changed, 367 insertions(+), 15 deletions(-) create mode 100644 src/skins/vector/views/organisms/PasswordLogin.js create mode 100644 src/skins/vector/views/pages/LoginPage.js diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js index cf279c87..e05d3e65 100644 --- a/src/skins/vector/skindex.js +++ b/src/skins/vector/skindex.js @@ -71,6 +71,7 @@ skin['molecules.voip.CallView'] = require('./views/molecules/voip/CallView'); skin['molecules.voip.IncomingCallBox'] = require('./views/molecules/voip/IncomingCallBox'); skin['molecules.voip.VideoView'] = require('./views/molecules/voip/VideoView'); skin['organisms.CasLogin'] = require('./views/organisms/CasLogin'); +skin['organisms.PasswordLogin'] = require('./views/organisms/PasswordLogin'); skin['organisms.CreateRoom'] = require('./views/organisms/CreateRoom'); skin['organisms.ErrorDialog'] = require('./views/organisms/ErrorDialog'); skin['organisms.LeftPanel'] = require('./views/organisms/LeftPanel'); @@ -87,6 +88,7 @@ skin['organisms.UserSettings'] = require('./views/organisms/UserSettings'); skin['organisms.ViewSource'] = require('./views/organisms/ViewSource'); skin['pages.CompatibilityPage'] = require('./views/pages/CompatibilityPage'); skin['pages.MatrixChat'] = require('./views/pages/MatrixChat'); +skin['pages.LoginPage'] = require('./views/pages/LoginPage'); skin['templates.Login'] = require('./views/templates/Login'); skin['templates.Register'] = require('./views/templates/Register'); diff --git a/src/skins/vector/views/molecules/ServerConfig.js b/src/skins/vector/views/molecules/ServerConfig.js index d6947727..fcc8bfc5 100644 --- a/src/skins/vector/views/molecules/ServerConfig.js +++ b/src/skins/vector/views/molecules/ServerConfig.js @@ -20,37 +20,142 @@ var React = require('react'); var Modal = require('matrix-react-sdk/lib/Modal'); var sdk = require('matrix-react-sdk') -var ServerConfigController = require('matrix-react-sdk/lib/controllers/molecules/ServerConfig') - +/** + * A pure UI component which displays the HS and IS to use. + */ module.exports = React.createClass({ displayName: 'ServerConfig', - mixins: [ServerConfigController], + + propTypes: { + onHsUrlChanged: React.PropTypes.func, + onIsUrlChanged: React.PropTypes.func, + defaultHsUrl: React.PropTypes.string, + defaultIsUrl: React.PropTypes.string, + withToggleButton: React.PropTypes.bool, + delayTimeMs: React.PropTypes.number // time to wait before invoking onChanged + }, + + getDefaultProps: function() { + return { + onHsUrlChanged: function() {}, + onIsUrlChanged: function() {}, + withToggleButton: false, + delayTimeMs: 0 + }; + }, + + getInitialState: function() { + return { + hs_url: this.props.defaultHsUrl, + is_url: this.props.defaultIsUrl, + original_hs_url: this.props.defaultHsUrl, + original_is_url: this.props.defaultIsUrl, + // no toggle button = show, toggle button = hide + configVisible: !this.props.withToggleButton + } + }, + + onHomeserverChanged: function(ev) { + this.setState({hs_url: ev.target.value}, function() { + this._hsTimeoutId = this._waitThenInvoke(this._hsTimeoutId, function() { + this.props.onHsUrlChanged(this.state.hs_url); + }); + }); + }, + + onIdentityServerChanged: function(ev) { + this.setState({is_url: ev.target.value}, function() { + this._isTimeoutId = this._waitThenInvoke(this._isTimeoutId, function() { + this.props.onIsUrlChanged(this.state.is_url); + }); + }); + }, + + _waitThenInvoke: function(existingTimeoutId, fn) { + if (existingTimeoutId) { + clearTimeout(existingTimeoutId); + } + return setTimeout(fn.bind(this), this.props.delayTimeMs); + }, + + getHsUrl: function() { + return this.state.hs_url; + }, + + getIsUrl: function() { + return this.state.is_url; + }, + + onServerConfigVisibleChange: function(ev) { + this.setState({ + configVisible: ev.target.checked + }); + }, showHelpPopup: function() { var ErrorDialog = sdk.getComponent('organisms.ErrorDialog'); Modal.createDialog(ErrorDialog, { title: 'Custom Server Options', description: - You can use the custom server options to log into other Matrix servers by specifying a different Home server URL.
- This allows you to use Vector with an existing Matrix account on a different Home server.
+ You can use the custom server options to log into other Matrix + servers by specifying a different Home server URL.
- You can also set a custom Identity server but this will affect people's ability to find you - if you use a server in a group other than the main Matrix.org group. + This allows you to use Vector with an existing Matrix account on + a different Home server. +
+
+ You can also set a custom Identity server but this will affect + people's ability to find you if you use a server in a group other + than the main Matrix.org group.
, button: "Dismiss", - focus: true, + focus: true }); }, render: function() { + var serverConfigStyle = {}; + serverConfigStyle.display = this.state.configVisible ? 'block' : 'none'; + + var toggleButton; + if (this.props.withToggleButton) { + toggleButton = ( +
+ + +
+ ); + } + return ( -
- - - - - What does this mean? +
+ {toggleButton} +
+
+ + + + + + What does this mean? + +
+
); } }); diff --git a/src/skins/vector/views/organisms/PasswordLogin.js b/src/skins/vector/views/organisms/PasswordLogin.js new file mode 100644 index 00000000..ff277ced --- /dev/null +++ b/src/skins/vector/views/organisms/PasswordLogin.js @@ -0,0 +1,64 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +var React = require('react'); +var ReactDOM = require('react-dom'); + +/** + * A pure UI component which displays a username/password form. + */ +module.exports = React.createClass({displayName: 'PasswordLogin', + propTypes: { + onSubmit: React.PropTypes.func.isRequired // fn(username, password) + }, + + getInitialState: function() { + return { + username: "", + password: "" + }; + }, + + onSubmitForm: function() { + this.props.onSubmit(this.state.username, this.state.password); + }, + + onUsernameChanged: function(ev) { + this.setState({username: ev.target.value}); + }, + + onPasswordChanged: function(ev) { + this.setState({password: ev.target.value}); + }, + + render: function() { + return ( +
+
+ +
+ +
+ +
+
+ ); + } +}); \ No newline at end of file diff --git a/src/skins/vector/views/pages/LoginPage.js b/src/skins/vector/views/pages/LoginPage.js new file mode 100644 index 00000000..7b4f636f --- /dev/null +++ b/src/skins/vector/views/pages/LoginPage.js @@ -0,0 +1,176 @@ +/* +Copyright 2015 OpenMarket Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +'use strict'; + +var React = require('react'); +var ReactDOM = require('react-dom'); +var sdk = require('matrix-react-sdk'); +var Signup = require("matrix-react-sdk/lib/Signup"); + +/** + * A wire component which glues together login UI components and Signup logic + */ +module.exports = React.createClass({displayName: 'LoginPage', + propTypes: { + onLoggedIn: React.PropTypes.func.isRequired, + homeserverUrl: React.PropTypes.string, + identityServerUrl: React.PropTypes.string, + // login shouldn't know or care how registration is done. + onRegisterClick: React.PropTypes.func.isRequired + }, + + getDefaultProps: function() { + return { + homeserverUrl: 'https://matrix.org/', + identityServerUrl: 'https://matrix.org/' + }; + }, + + getInitialState: function() { + return { + busy: false, + errorText: null, + enteredHomeserverUrl: this.props.homeserverUrl, + enteredIdentityServerUrl: this.props.identityServerUrl + }; + }, + + componentWillMount: function() { + this._initLoginLogic(); + }, + + onPasswordLogin: function(username, password) { + // TODO + console.log("onPasswordLogin %s %s", username, password); + }, + + onHsUrlChanged: function(newHsUrl) { + console.log("onHsUrlChanged %s", newHsUrl); + this._initLoginLogic(newHsUrl); + }, + + _initLoginLogic: function(hsUrl, isUrl) { + var self = this; + hsUrl = hsUrl || this.state.enteredHomeserverUrl; + isUrl = isUrl || this.state.enteredIdentityServerUrl; + + var loginLogic = new Signup.Login(hsUrl, isUrl); + 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._setErrorTextFromError(err); + }).finally(function() { + self.setState({ + busy: false + }); + }); + + this.setState({ + enteredHomeserverUrl: hsUrl, + enteredIdentityServerUrl: isUrl, + busy: true, + errorText: null // reset err messages + }); + }, + + _getCurrentFlowStep: function() { + return this._loginLogic ? this._loginLogic.getCurrentFlowStep() : null + }, + + _setErrorTextFromError: function(err) { + var errCode = err.errcode; + if (!errCode && err.httpStatus) { + errCode = "HTTP " + err.httpStatus; + } + this.setState({ + errorText: ( + "Error: Problem communicating with the given homeserver " + + (errCode ? "(" + errCode + ")" : "") + ) + }); + }, + + componentForStep: function(step) { + switch (step) { + case 'm.login.password': + var PasswordLogin = sdk.getComponent('organisms.PasswordLogin'); + return ( + + ); + case 'm.login.cas': + var CasLogin = sdk.getComponent('organisms.CasLogin'); + return ( + + ); + default: + if (!step) { + return; + } + return ( +
+ Sorry, this homeserver is using a login which is not + recognised by Vector ({step}) +
+ ); + } + }, + + render: function() { + var Loader = sdk.getComponent("atoms.Spinner"); + var loader = this.state.busy ?
: null; + var ServerConfig = sdk.getComponent("molecules.ServerConfig"); + + return ( +
+
+
+ vector +
+
+

Sign in

+ {this.componentForStep(this._getCurrentFlowStep())} + +
+ { loader } + {this.state.errorText} +
+ + Create a new account + +
+
+ blog  ·   + twitter  ·   + github  ·   + powered by Matrix +
+
+
+
+ ); + } +}); diff --git a/src/skins/vector/views/pages/MatrixChat.js b/src/skins/vector/views/pages/MatrixChat.js index 0553c25a..0fea1f36 100644 --- a/src/skins/vector/views/pages/MatrixChat.js +++ b/src/skins/vector/views/pages/MatrixChat.js @@ -89,6 +89,10 @@ module.exports = React.createClass({ }); }, + onRegisterClick: function() { + this.showScreen("register"); + }, + render: function() { var LeftPanel = sdk.getComponent('organisms.LeftPanel'); var RoomView = sdk.getComponent('organisms.RoomView'); @@ -164,8 +168,9 @@ module.exports = React.createClass({ /> ); } else { + var LoginPage = sdk.getComponent("pages.LoginPage"); return ( - + ); } }