diff --git a/src/skins/vector/skindex.js b/src/skins/vector/skindex.js index cf279c87..84f26499 100644 --- a/src/skins/vector/skindex.js +++ b/src/skins/vector/skindex.js @@ -70,7 +70,6 @@ skin['molecules.UserSelector'] = require('./views/molecules/UserSelector'); 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.CreateRoom'] = require('./views/organisms/CreateRoom'); skin['organisms.ErrorDialog'] = require('./views/organisms/ErrorDialog'); skin['organisms.LeftPanel'] = require('./views/organisms/LeftPanel'); @@ -87,7 +86,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['templates.Login'] = require('./views/templates/Login'); +skin['pages.Login'] = require('./views/pages/Login'); skin['templates.Register'] = require('./views/templates/Register'); module.exports = skin; \ No newline at end of file diff --git a/src/skins/vector/views/molecules/ServerConfig.js b/src/skins/vector/views/molecules/ServerConfig.js index d6947727..8f1eab38 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/CasLogin.js b/src/skins/vector/views/organisms/CasLogin.js deleted file mode 100644 index ad9dbed9..00000000 --- a/src/skins/vector/views/organisms/CasLogin.js +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 CasLoginController = require('matrix-react-sdk/lib/controllers/organisms/CasLogin'); - -module.exports = React.createClass({ - displayName: 'CasLogin', - mixins: [CasLoginController], - - render: function() { - return ( -
- -
- ); - }, - -}); diff --git a/src/skins/vector/views/pages/Login.js b/src/skins/vector/views/pages/Login.js new file mode 100644 index 00000000..b8f0e43e --- /dev/null +++ b/src/skins/vector/views/pages/Login.js @@ -0,0 +1,199 @@ +/* +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"); +var PasswordLogin = require("matrix-react-sdk/lib/components/PasswordLogin"); +var CasLogin = require("matrix-react-sdk/lib/components/CasLogin"); + +/** + * A wire component which glues together login UI components and Signup logic + */ +module.exports = React.createClass({displayName: 'Login', + 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://vector.im' + }; + }, + + getInitialState: function() { + return { + busy: false, + errorText: null, + enteredHomeserverUrl: this.props.homeserverUrl, + enteredIdentityServerUrl: this.props.identityServerUrl + }; + }, + + componentWillMount: function() { + this._initLoginLogic(); + }, + + onPasswordLogin: function(username, password) { + var self = this; + self.setState({ + busy: true + }); + + this._loginLogic.loginViaPassword(username, password).then(function(data) { + self.props.onLoggedIn(data); + }, function(error) { + self._setErrorTextFromError(error); + }).finally(function() { + self.setState({ + busy: false + }); + }); + }, + + onHsUrlChanged: function(newHsUrl) { + this._initLoginLogic(newHsUrl); + }, + + onIsUrlChanged: function(newIsUrl) { + this._initLoginLogic(null, newIsUrl); + }, + + _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) { + if (err.friendlyText) { + this.setState({ + errorText: err.friendlyText + }); + return; + } + + 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': + return ( + + ); + case 'm.login.cas': + 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..f8708f0e 100644 --- a/src/skins/vector/views/pages/MatrixChat.js +++ b/src/skins/vector/views/pages/MatrixChat.js @@ -89,11 +89,14 @@ module.exports = React.createClass({ }); }, + onRegisterClick: function() { + this.showScreen("register"); + }, + render: function() { var LeftPanel = sdk.getComponent('organisms.LeftPanel'); var RoomView = sdk.getComponent('organisms.RoomView'); var RightPanel = sdk.getComponent('organisms.RightPanel'); - var Login = sdk.getComponent('templates.Login'); var UserSettings = sdk.getComponent('organisms.UserSettings'); var Register = sdk.getComponent('templates.Register'); var CreateRoom = sdk.getComponent('organisms.CreateRoom'); @@ -164,8 +167,9 @@ module.exports = React.createClass({ /> ); } else { + var Login = sdk.getComponent("pages.Login"); return ( - + ); } } diff --git a/src/skins/vector/views/templates/Login.js b/src/skins/vector/views/templates/Login.js deleted file mode 100644 index 8bd9334e..00000000 --- a/src/skins/vector/views/templates/Login.js +++ /dev/null @@ -1,219 +0,0 @@ -/* -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 MatrixClientPeg = require('matrix-react-sdk/lib/MatrixClientPeg'); - -var LoginController = require('matrix-react-sdk/lib/controllers/templates/Login') - -var config = require('../../../../../config.json'); - -module.exports = React.createClass({ - displayName: 'Login', - mixins: [LoginController], - - getInitialState: function() { - // TODO: factor out all localstorage stuff into its own home. - // This is common to Login, Register and MatrixClientPeg - var localStorage = window.localStorage; - var hs_url, is_url; - if (localStorage) { - hs_url = localStorage.getItem("mx_hs_url"); - is_url = localStorage.getItem("mx_is_url"); - } - - return { - customHsUrl: hs_url || config.default_hs_url, - customIsUrl: is_url || config.default_is_url, - serverConfigVisible: (hs_url && hs_url !== config.default_hs_url || - is_url && is_url !== config.default_is_url) - }; - }, - - componentDidMount: function() { - this.onHSChosen(); - }, - - componentDidUpdate: function() { - if (!this.state.focusFired && this.refs.user) { - this.refs.user.focus(); - this.setState({ focusFired: true }); - } - }, - - getHsUrl: function() { - if (this.state.serverConfigVisible) { - return this.state.customHsUrl; - } else { - return config.default_hs_url; - } - }, - - getIsUrl: function() { - if (this.state.serverConfigVisible) { - return this.state.customIsUrl; - } else { - return config.default_is_url; - } - }, - - onServerConfigVisibleChange: function(ev) { - this.setState({ - serverConfigVisible: ev.target.checked - }, this.onHSChosen); - }, - - /** - * Gets the form field values for the current login stage - */ - getFormVals: function() { - return { - 'username': this.refs.user.value.trim(), - 'password': this.refs.pass.value.trim() - }; - }, - - onHsUrlChanged: function() { - var newHsUrl = this.refs.serverConfig.getHsUrl().trim(); - var newIsUrl = this.refs.serverConfig.getIsUrl().trim(); - - if (newHsUrl == this.state.customHsUrl && - newIsUrl == this.state.customIsUrl) - { - return; - } - else { - this.setState({ - customHsUrl: newHsUrl, - customIsUrl: newIsUrl, - }); - } - - // XXX: why are we replacing the MatrixClientPeg here when we're about - // to do it again 1s later in the setTimeout to onHSChosen? -- matthew - // Commenting it out for now to see what breaks. - /* - MatrixClientPeg.replaceUsingUrls( - this.getHsUrl(), - this.getIsUrl() - ); - this.setState({ - hs_url: this.getHsUrl(), - is_url: this.getIsUrl() - }); - */ - - // XXX: HSes do not have to offer password auth, so we - // need to update and maybe show a different component - // when a new HS is entered. - if (this.updateHsTimeout) { - clearTimeout(this.updateHsTimeout); - } - var self = this; - this.updateHsTimeout = setTimeout(function() { - self.onHSChosen(); - }, 1000); - }, - - componentForStep: function(step) { - switch (step) { - case 'choose_hs': - case 'fetch_stages': - var serverConfigStyle = {}; - serverConfigStyle.display = this.state.serverConfigVisible ? 'block' : 'none'; - var ServerConfig = sdk.getComponent("molecules.ServerConfig"); - - return ( -
- - -
- -
-
- ); - // XXX: clearly these should be separate organisms - case 'stage_m.login.password': - return ( -
-
-
-
- { this.componentForStep('choose_hs') } - -
-
- ); - case 'stage_m.login.cas': - var CasLogin = sdk.getComponent('organisms.CasLogin'); - return ( - - ); - } - }, - - onUsernameChanged: function(ev) { - this.setState({username: ev.target.value}); - }, - - onPasswordChanged: function(ev) { - this.setState({password: ev.target.value}); - }, - - loginContent: function() { - var Loader = sdk.getComponent("atoms.Spinner"); - var loader = this.state.busy ?
: null; - return ( -
-

Sign in

- {this.componentForStep(this.state.step)} -
- { loader } - {this.state.errorText} -
- Create a new account -
-
- blog  ·   - twitter  ·   - github  ·   - powered by Matrix -
-
- ); - }, - - render: function() { - return ( -
-
-
- vector -
- {this.loginContent()} -
-
- ); - } -});