From ce3dab3c5be05cd5be6513cc7f740ba7d89fd4c8 Mon Sep 17 00:00:00 2001 From: Erik Johnston <erikj@matrix.org> Date: Thu, 16 Jul 2015 17:42:33 +0100 Subject: [PATCH 1/9] Make room name editable --- skins/base/views/molecules/RoomHeader.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/skins/base/views/molecules/RoomHeader.js b/skins/base/views/molecules/RoomHeader.js index 62f24484..33d43302 100644 --- a/skins/base/views/molecules/RoomHeader.js +++ b/skins/base/views/molecules/RoomHeader.js @@ -17,14 +17,20 @@ limitations under the License. 'use strict'; var React = require('react'); +var ComponentBroker = require('../../../../src/ComponentBroker'); var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); var RoomHeaderController = require("../../../../src/controllers/molecules/RoomHeader"); +var EditableText = ComponentBroker.get("atoms/EditableText"); module.exports = React.createClass({ displayName: 'RoomHeader', mixins: [RoomHeaderController], + onNameChange: function(new_name) { + MatrixClientPeg.get().setRoomName(this.props.room.roomId, new_name); + }, + render: function() { var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); @@ -52,7 +58,9 @@ module.exports = React.createClass({ <img src={ MatrixClientPeg.get().getAvatarUrlForRoom(this.props.room, 48, 48, "crop") } width="48" height="48"/> </div> <div className="mx_RoomHeader_info"> - <div className="mx_RoomHeader_name">{ this.props.room.name }</div> + <div className="mx_RoomHeader_name"> + <EditableText initalValue={this.props.room.name} onValueChanged={this.onNameChange} /> + </div> { topic } </div> </div> @@ -76,4 +84,3 @@ module.exports = React.createClass({ ); }, }); - From cfbef0177ee706a226d4eda9b7d4bc5a095100cb Mon Sep 17 00:00:00 2001 From: David Baker <dave@matrix.org> Date: Thu, 16 Jul 2015 17:51:21 +0100 Subject: [PATCH 2/9] Fix custom server or registration & do some of new login UI --- skins/base/views/templates/Login.js | 63 +++++++++++++++++++++++--- skins/base/views/templates/Register.js | 13 ++++-- src/controllers/templates/Login.js | 3 +- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/skins/base/views/templates/Login.js b/skins/base/views/templates/Login.js index f71e3070..ba75e5d1 100644 --- a/skins/base/views/templates/Login.js +++ b/skins/base/views/templates/Login.js @@ -28,15 +28,44 @@ var LoginController = require("../../../../src/controllers/templates/Login"); var ServerConfig = ComponentBroker.get("molecules/ServerConfig"); module.exports = React.createClass({ + DEFAULT_HS_URL: 'https://matrix.org', + DEFAULT_IS_URL: 'https://matrix.org', + displayName: 'Login', mixins: [LoginController], + getInitialState: function() { + return { + serverConfigVisible: false + }; + }, + + componentWillMount: function() { + this.onHSChosen(); + this.customHsUrl = this.DEFAULT_HS_URL; + this.customIsUrl = this.DEFAULT_IS_URL; + }, + getHsUrl: function() { - return this.refs.serverConfig.getHsUrl(); + if (this.state.serverConfigVisible) { + return this.refs.serverConfig.getHsUrl(); + } else { + return this.DEFAULT_HS_URL; + } }, getIsUrl: function() { - return this.refs.serverConfig.getIsUrl(); + if (this.state.serverConfigVisible) { + return this.refs.serverConfig.getIsUrl(); + } else { + return this.DEFAULT_IS_URL; + } + }, + + onServerConfigVisibleChange: function(ev) { + this.setState({ + serverConfigVisible: ev.target.checked + }); }, /** @@ -49,15 +78,35 @@ module.exports = React.createClass({ }; }, + onHsUrlChanged: function() { + this.customHsUrl = this.getHsUrl(); + this.customIsUrl = this.getIsUrl(); + if (this.updateHsTimeout) { + clearTimeout(this.updateHsTimeout); + } + /*var self = this; + this.updateHsTimeout = setTimeout(function() { + self.onHSChosen(); + }, 500);*/ + }, + componentForStep: function(step) { switch (step) { case 'choose_hs': + var serverConfigStyle = {}; + if (!this.state.serverConfigVisible) { + serverConfigStyle.display = 'none'; + } return ( <div> - <form onSubmit={this.onHSChosen}> - <ServerConfig ref="serverConfig" /> - <input type="submit" value="Continue" /> - </form> + <input type="checkbox" value={this.state.serverConfigVisible} onChange={this.onServerConfigVisibleChange} /> + Use custom server options (advanced) + <div style={serverConfigStyle}> + <ServerConfig ref="serverConfig" + defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl} + onHsUrlChanged={this.onHsUrlChanged} + /> + </div> </div> ); // XXX: clearly these should be separate organisms @@ -67,6 +116,7 @@ module.exports = React.createClass({ <form onSubmit={this.onUserPassEntered}> <input ref="user" type="text" placeholder="username" /><br /> <input ref="pass" type="password" placeholder="password" /><br /> + {this.componentForStep('choose_hs')} <input type="submit" value="Log in" /> </form> </div> @@ -94,7 +144,6 @@ module.exports = React.createClass({ render: function() { return ( <div className="mx_Login"> - <ProgressBar value={this.state.currentStep} max={this.state.totalSteps} /> {this.loginContent()} </div> ); diff --git a/skins/base/views/templates/Register.js b/skins/base/views/templates/Register.js index 930228b8..23367378 100644 --- a/skins/base/views/templates/Register.js +++ b/skins/base/views/templates/Register.js @@ -39,6 +39,11 @@ module.exports = React.createClass({ }; }, + componentWillMount: function() { + this.customHsUrl = this.DEFAULT_HS_URL; + this.customIsUrl = this.DEFAULT_IS_URL; + }, + getRegFormVals: function() { return { email: this.refs.email.getDOMNode().value, @@ -50,7 +55,7 @@ module.exports = React.createClass({ getHsUrl: function() { if (this.state.serverConfigVisible) { - return this.refs.serverConfig.getHsUrl(); + return this.customHsUrl; } else { return this.DEFAULT_HS_URL; } @@ -58,7 +63,7 @@ module.exports = React.createClass({ getIsUrl: function() { if (this.state.serverConfigVisible) { - return this.refs.serverConfig.getIsUrl(); + return this.customIsUrl; } else { return this.DEFAULT_IS_URL; } @@ -82,6 +87,8 @@ module.exports = React.createClass({ }, onServerUrlChanged: function(newUrl) { + this.customHsUrl = this.refs.serverConfig.getHsUrl(); + this.customIsUrl = this.refs.serverConfig.getIsUrl(); this.forceUpdate(); }, @@ -104,7 +111,7 @@ module.exports = React.createClass({ Use custom server options (advanced) <div style={serverConfigStyle}> <ServerConfig ref="serverConfig" - defaultHsUrl={this.default_hs_url} defaultIsUrl={this.DEFAULT_IS_URL} + defaultHsUrl={this.customHsUrl} defaultIsUrl={this.customIsUrl} onHsUrlChanged={this.onServerUrlChanged} onIsUrlChanged={this.onServerUrlChanged} /> </div> <br /> diff --git a/src/controllers/templates/Login.js b/src/controllers/templates/Login.js index 07ace740..3d25a910 100644 --- a/src/controllers/templates/Login.js +++ b/src/controllers/templates/Login.js @@ -38,8 +38,7 @@ module.exports = { this.setState({ step: step, errorText: '', busy: false }); }, - onHSChosen: function(ev) { - ev.preventDefault(); + onHSChosen: function() { MatrixClientPeg.replaceUsingUrls( this.getHsUrl(), this.getIsUrl() From d08c47a3284b00fe04a7f6bcc0580e3208d4728d Mon Sep 17 00:00:00 2001 From: David Baker <dave@matrix.org> Date: Thu, 16 Jul 2015 21:45:59 +0100 Subject: [PATCH 3/9] Fix npe --- src/controllers/organisms/MemberList.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js index a511816d..1213db9b 100644 --- a/src/controllers/organisms/MemberList.js +++ b/src/controllers/organisms/MemberList.js @@ -62,6 +62,7 @@ module.exports = { }, roomMembers: function(limit) { + if (!this.props.roomId) return {}; var cli = MatrixClientPeg.get(); var all_members = cli.getRoom(this.props.roomId).currentState.members; var all_user_ids = Object.keys(all_members); From 8ccce4d7027ff1919d1d126024c29d1ed1151410 Mon Sep 17 00:00:00 2001 From: David Baker <dave@matrix.org> Date: Thu, 16 Jul 2015 21:46:39 +0100 Subject: [PATCH 4/9] Make new login style work --- skins/base/views/templates/Login.js | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/skins/base/views/templates/Login.js b/skins/base/views/templates/Login.js index ba75e5d1..0fd64ce5 100644 --- a/skins/base/views/templates/Login.js +++ b/skins/base/views/templates/Login.js @@ -19,6 +19,7 @@ limitations under the License. var React = require('react'); var ComponentBroker = require("../../../../src/ComponentBroker"); +var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); var ProgressBar = ComponentBroker.get("molecules/ProgressBar"); var Loader = require("react-loader"); @@ -48,7 +49,7 @@ module.exports = React.createClass({ getHsUrl: function() { if (this.state.serverConfigVisible) { - return this.refs.serverConfig.getHsUrl(); + return this.customHsUrl; } else { return this.DEFAULT_HS_URL; } @@ -56,7 +57,7 @@ module.exports = React.createClass({ getIsUrl: function() { if (this.state.serverConfigVisible) { - return this.refs.serverConfig.getIsUrl(); + return this.customIsUrl; } else { return this.DEFAULT_IS_URL; } @@ -65,7 +66,7 @@ module.exports = React.createClass({ onServerConfigVisibleChange: function(ev) { this.setState({ serverConfigVisible: ev.target.checked - }); + }, this.onHsUrlChanged); }, /** @@ -79,12 +80,23 @@ module.exports = React.createClass({ }, onHsUrlChanged: function() { - this.customHsUrl = this.getHsUrl(); - this.customIsUrl = this.getIsUrl(); - if (this.updateHsTimeout) { + this.customHsUrl = this.refs.serverConfig.getHsUrl(); + this.customIsUrl = this.refs.serverConfig.getIsUrl(); + 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; + var self = this; this.updateHsTimeout = setTimeout(function() { self.onHSChosen(); }, 500);*/ From 0f39ec580f01b62cc7685bfd18411a0b20071803 Mon Sep 17 00:00:00 2001 From: David Baker <dave@matrix.org> Date: Thu, 16 Jul 2015 22:06:00 +0100 Subject: [PATCH 5/9] Slightly improve error messages --- src/controllers/templates/Register.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controllers/templates/Register.js b/src/controllers/templates/Register.js index 664b36b3..89a3872d 100644 --- a/src/controllers/templates/Register.js +++ b/src/controllers/templates/Register.js @@ -337,6 +337,14 @@ module.exports = { }); } else if (error.httpStatus == 401) { newState.errorText = "Authorisation failed!"; + } else if (error.httpStatus >= 400 && error.httpStatus < 500) { + newState.errorText = "Registration failed!"; + } else if (error.httpStatus >= 500 && error.httpStatus < 600) { + newState.errorText = "Server error during registration!"; + } else if (error.name == "M_MISSING_PARAM") { + // The HS hasn't remembered the login params from + // the first try when the login email was sent. + newState.errorText = "This home server does not support resuming registration."; } self.setState(newState); } From 28dcfb2f12fb8cc539ccfef800cb52bdccc39099 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson <matthew@matrix.org> Date: Thu, 16 Jul 2015 22:39:10 +0100 Subject: [PATCH 6/9] make it work on ff --- skins/base/css/molecules/RoomHeader.css | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skins/base/css/molecules/RoomHeader.css b/skins/base/css/molecules/RoomHeader.css index d0e86ad9..dbb7910f 100644 --- a/skins/base/css/molecules/RoomHeader.css +++ b/skins/base/css/molecules/RoomHeader.css @@ -27,11 +27,10 @@ limitations under the License. display: -moz-box; display: -ms-flexbox; display: -webkit-flex; - display: flex; + display: flex; } .mx_RoomHeader_leftRow { - display: table-row; height: 48px; -webkit-box-ordinal-group: 1; @@ -44,7 +43,6 @@ limitations under the License. } .mx_RoomHeader_rightRow { - display: table-row; height: 48px; background-color: #fff; border-radius: 48px; @@ -88,6 +86,7 @@ limitations under the License. .mx_RoomHeader_avatar { display: table-cell; + width: 48px; height: 50px; vertical-align: middle; } From a2ca5f28476194d2185b7abfba46683c53b85312 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson <matthew@matrix.org> Date: Thu, 16 Jul 2015 22:39:38 +0100 Subject: [PATCH 7/9] improve badges and room tile layout --- skins/base/css/molecules/RoomTile.css | 17 ++++++++++++++--- skins/base/views/molecules/RoomTile.js | 9 ++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/skins/base/css/molecules/RoomTile.css b/skins/base/css/molecules/RoomTile.css index 6e19395e..b258ea33 100644 --- a/skins/base/css/molecules/RoomTile.css +++ b/skins/base/css/molecules/RoomTile.css @@ -29,8 +29,8 @@ limitations under the License. padding-top: 3px; padding-bottom: 3px; vertical-align: middle; - width: 32px; - height: 32px; + width: 40px; + height: 40px; } .mx_RoomTile_avatar img { @@ -38,6 +38,12 @@ limitations under the License. background-color: #dbdbdb; } +.mx_RoomTile_nameBadge { + display: table; + width: 100%; + height: 50px; +} + .mx_RoomTile_name { display: table-cell; vertical-align: middle; @@ -45,8 +51,13 @@ limitations under the License. text-overflow: ellipsis; } +.mx_RoomTile_badgeCell { + display: table-cell; + vertical-align: middle; + width: 26px; +} + .mx_RoomTile_badge { - float: right; background-color: #80cef4; color: #fff; border-radius: 26px; diff --git a/skins/base/views/molecules/RoomTile.js b/skins/base/views/molecules/RoomTile.js index 00d16cf0..44709577 100644 --- a/skins/base/views/molecules/RoomTile.js +++ b/skins/base/views/molecules/RoomTile.js @@ -43,10 +43,17 @@ module.exports = React.createClass({ else if (this.props.unread) { badge = <div className="mx_RoomTile_badge">1</div>; } + var nameCell; + if (badge) { + nameCell = <div className="mx_RoomTile_nameBadge"><div className="mx_RoomTile_name">{name}</div><div className="mx_RoomTile_badgeCell">{badge}</div></div>; + } + else { + nameCell = <div className="mx_RoomTile_name">{name}</div>; + } return ( <div className={classes} onClick={this.onClick}> <div className="mx_RoomTile_avatar"><img src={ MatrixClientPeg.get().getAvatarUrlForRoom(this.props.room, 40, 40, "crop") } width="40" height="40"/></div> - <div className="mx_RoomTile_name">{name}{ badge }</div> + { nameCell } </div> ); } From 1a95148dae48945a3b3639c1f1b1447b27825f45 Mon Sep 17 00:00:00 2001 From: Matthew Hodgson <matthew@matrix.org> Date: Fri, 17 Jul 2015 00:12:36 +0100 Subject: [PATCH 8/9] basic date separator support --- skins/base/css/common.css | 1 + skins/base/css/organisms/LeftPanel.css | 1 - skins/base/css/organisms/RoomView.css | 9 +++++- skins/base/views/molecules/MRoomMemberTile.js | 4 ++- src/ComponentBroker.js | 1 + src/controllers/organisms/RoomView.js | 29 ++++++++++++++----- 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/skins/base/css/common.css b/skins/base/css/common.css index 4c2e7c0b..aa10e409 100644 --- a/skins/base/css/common.css +++ b/skins/base/css/common.css @@ -28,6 +28,7 @@ div.error { } h2 { + color: #80cef4; font-weight: 400; font-size: 20px; margin-top: 16px; diff --git a/skins/base/css/organisms/LeftPanel.css b/skins/base/css/organisms/LeftPanel.css index 70a4b6f7..bfe26f11 100644 --- a/skins/base/css/organisms/LeftPanel.css +++ b/skins/base/css/organisms/LeftPanel.css @@ -44,7 +44,6 @@ limitations under the License. padding-left: 16px; padding-right: 16px; - /* background-color: #0ff; */ height: 100%; overflow-y: scroll; } diff --git a/skins/base/css/organisms/RoomView.css b/skins/base/css/organisms/RoomView.css index bc1a1692..e1f3ea6b 100644 --- a/skins/base/css/organisms/RoomView.css +++ b/skins/base/css/organisms/RoomView.css @@ -64,7 +64,6 @@ limitations under the License. width: 100%; height: 100%; margin-bottom: 60px; - /* background-color: #ff0; */ overflow-y: scroll; } @@ -78,6 +77,14 @@ limitations under the License. width: 100%; } +.mx_RoomView_MessageList h2 { + clear: both; + margin-top: 32px; + margin-bottom: 8px; + padding-bottom: 6px; + border-bottom: 1px solid #a8dbf3; +} + .mx_RoomView_invitePrompt { } diff --git a/skins/base/views/molecules/MRoomMemberTile.js b/skins/base/views/molecules/MRoomMemberTile.js index 92b6b119..996b6f9c 100644 --- a/skins/base/views/molecules/MRoomMemberTile.js +++ b/skins/base/views/molecules/MRoomMemberTile.js @@ -45,12 +45,14 @@ module.exports = React.createClass({ render: function() { // XXX: for now, just cheekily borrow the css from message tile... + var timestamp = this.props.last ? <MessageTimestamp ts={this.props.mxEvent.getTs()} /> : null; + return ( <div className="mx_MessageTile"> <div className="mx_MessageTile_avatar"> <img src={ this.props.mxEvent.target ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.mxEvent.target, 40, 40, "crop") : null } width="40" height="40"/> </div> - <MessageTimestamp ts={this.props.mxEvent.getTs()} /> + { timestamp } <span className="mx_SenderProfile"></span> <span className="mx_MessageTile_content"> {this.getMemberEventText()} diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index 431f4766..0f606dd3 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -98,6 +98,7 @@ require('../skins/base/views/organisms/RightPanel'); require('../skins/base/views/molecules/RoomCreate'); require('../skins/base/views/molecules/RoomDropTarget'); require('../skins/base/views/molecules/DirectoryMenu'); +require('../skins/base/views/molecules/DateSeparator'); require('../skins/base/views/atoms/voip/VideoFeed'); require('../skins/base/views/molecules/voip/VideoView'); require('../skins/base/views/molecules/voip/CallView'); diff --git a/src/controllers/organisms/RoomView.js b/src/controllers/organisms/RoomView.js index 9078d558..f1a26ee4 100644 --- a/src/controllers/organisms/RoomView.js +++ b/src/controllers/organisms/RoomView.js @@ -36,6 +36,8 @@ var tileTypes = { 'm.call.hangup': ComponentBroker.get('molecules/voip/MCallHangupTile') }; +var DateSeparator = ComponentBroker.get('molecules/DateSeparator'); + module.exports = { getInitialState: function() { return { @@ -231,22 +233,33 @@ module.exports = { var TileType = tileTypes[mxEv.getType()]; var continuation = false; var last = false; + var dateSeparator = null; if (i == this.state.room.timeline.length - 1) { last = true; } - if (i > 0 && - count < this.state.messageCap - 1 && - this.state.room.timeline[i].sender && - this.state.room.timeline[i - 1].sender && - this.state.room.timeline[i].sender.userId === + if (i > 0 && count < this.state.messageCap - 1) { + if (this.state.room.timeline[i].sender && + this.state.room.timeline[i - 1].sender && + this.state.room.timeline[i].sender.userId === this.state.room.timeline[i - 1].sender.userId) - { - continuation = true; - } + { + continuation = true; + } + + var ts0 = this.state.room.timeline[i - 1].getTs(); + var ts1 = this.state.room.timeline[i].getTs(); + if (new Date(ts0).toDateString() !== new Date(ts1).toDateString()) { + dateSeparator = <DateSeparator key={ts1} ts={ts1}/>; + continuation = false; + } + } if (!TileType) continue; ret.unshift( <TileType key={mxEv.getId()} mxEvent={mxEv} continuation={continuation} last={last}/> ); + if (dateSeparator) { + ret.unshift(dateSeparator); + } ++count; } return ret; From 891ba401142bb25408e9d467b84295c51a94528b Mon Sep 17 00:00:00 2001 From: Matthew Hodgson <matthew@matrix.org> Date: Fri, 17 Jul 2015 00:12:42 +0100 Subject: [PATCH 9/9] basic date separator support --- skins/base/views/molecules/DateSeparator.js | 56 +++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 skins/base/views/molecules/DateSeparator.js diff --git a/skins/base/views/molecules/DateSeparator.js b/skins/base/views/molecules/DateSeparator.js new file mode 100644 index 00000000..061ce66d --- /dev/null +++ b/skins/base/views/molecules/DateSeparator.js @@ -0,0 +1,56 @@ +/* +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 days = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" +]; + +module.exports = React.createClass({ + displayName: 'DateSeparator', + render: function() { + var date = new Date(this.props.ts); + var today = new Date(); + var yesterday = new Date(); + yesterday.setDate(today.getDate() - 1); + var label; + if (date.toDateString() === today.toDateString()) { + label = "Today"; + } + else if (date.toDateString() === yesterday.toDateString()) { + label = "Yesterday"; + } + else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { + label = days[date.getDay()]; + } + else { + label = date.toDateString(); + } + + return ( + <h2>{ label }</h2> + ); + } +});