diff --git a/examples/trivial/fonts b/examples/trivial/fonts new file mode 120000 index 00000000..27f04cad --- /dev/null +++ b/examples/trivial/fonts @@ -0,0 +1 @@ +../../skins/base/fonts/ \ No newline at end of file diff --git a/examples/trivial/index.js b/examples/trivial/index.js index 2be90549..b601cfce 100644 --- a/examples/trivial/index.js +++ b/examples/trivial/index.js @@ -40,9 +40,17 @@ function routeUrl(location) { } } window.matrixChat.showScreen('register', params); + } else { + window.matrixChat.showScreen(location.hash.substring(2)); } } +function onHashChange(ev) { + routeUrl(window.location); +} + +window.addEventListener('hashchange', onHashChange); + var loaded = false; window.onload = function() { diff --git a/skins/base/css/molecules/MemberInfo.css b/skins/base/css/molecules/MemberInfo.css index db41c51e..144212d7 100644 --- a/skins/base/css/molecules/MemberInfo.css +++ b/skins/base/css/molecules/MemberInfo.css @@ -55,6 +55,8 @@ limitations under the License. .mx_MemberInfo_field { padding: 6px; + overflow: hidden; + text-overflow: ellipsis; } .mx_MemberInfo_button { diff --git a/skins/base/css/molecules/MemberTile.css b/skins/base/css/molecules/MemberTile.css index 2f735f62..d47768f3 100644 --- a/skins/base/css/molecules/MemberTile.css +++ b/skins/base/css/molecules/MemberTile.css @@ -17,6 +17,7 @@ limitations under the License. .mx_MemberTile { cursor: pointer; display: table-row; + height: 49px; } .mx_MemberTile_avatar { @@ -36,6 +37,24 @@ limitations under the License. background-color: #dbdbdb; } +.mx_MemberTile_inviteEditing .mx_MemberTile_avatar { + display: none; +} + +.mx_MemberTile_inviteEditing .mx_MemberTile_name { + position: absolute; + width: 200px; +} + +.mx_MemberTile_inviteEditing .mx_MemberTile_name input { + border-radius: 3px; + border: 1px solid #c7c7c7; + font-weight: 300; + font-size: 14px; + padding: 9px; + margin-top: 6px; +} + .mx_MemberTile_power { z-index: 10; position: absolute; @@ -52,10 +71,14 @@ limitations under the License. text-overflow: ellipsis; } -.mx_MemberTile_unavailable { +.mx_MemberTile_unavailable .mx_MemberTile_avatar, +.mx_MemberTile_unavailable .mx_MemberTile_name +{ opacity: 0.75; } -.mx_MemberTile_offline { +.mx_MemberTile_offline .mx_MemberTile_avatar, +.mx_MemberTile_offline .mx_MemberTile_name +{ opacity: 0.5; } \ No newline at end of file diff --git a/skins/base/css/molecules/RoomHeader.css b/skins/base/css/molecules/RoomHeader.css index c107e181..67743877 100644 --- a/skins/base/css/molecules/RoomHeader.css +++ b/skins/base/css/molecules/RoomHeader.css @@ -43,6 +43,26 @@ limitations under the License. width: 100%; } +.mx_RoomHeader_hangupButton { + height: 48px; + margin-top: 18px; + background-color: #80cef4; + border-radius: 48px; + margin-right: 8px; + color: #fff; + line-height: 48px; + text-align: center; + + -webkit-box-ordinal-group: 2; + -moz-box-ordinal-group: 2; + -ms-flex-order: 2; + -webkit-order: 2; + order: 2; + + -webkit-flex: 0 0 90px; + flex: 0 0 90px; +} + .mx_RoomHeader_rightRow { height: 48px; margin-top: 18px; @@ -50,11 +70,11 @@ limitations under the License. border-radius: 48px; border: 1px solid #a9dbf4; - -webkit-box-ordinal-group: 2; - -moz-box-ordinal-group: 2; - -ms-flex-order: 2; - -webkit-order: 2; - order: 2; + -webkit-box-ordinal-group: 3; + -moz-box-ordinal-group: 3; + -ms-flex-order: 3; + -webkit-order: 3; + order: 3; -webkit-flex: 0 0 200px; flex: 0 0 200px; @@ -66,6 +86,15 @@ limitations under the License. vertical-align: middle; } +.mx_RoomHeader_simpleHeader { + line-height: 88px; + color: #80cef4; + font-weight: 400; + font-size: 20px; + overflow: scroll; + text-overflow: ellipsis; +} + .mx_RoomHeader_name { vertical-align: middle; height: 28px; diff --git a/skins/base/css/organisms/LeftPanel.css b/skins/base/css/organisms/LeftPanel.css index ac10942d..bfc43026 100644 --- a/skins/base/css/organisms/LeftPanel.css +++ b/skins/base/css/organisms/LeftPanel.css @@ -43,7 +43,7 @@ limitations under the License. overflow-y: scroll; } -.mx_LeftPanel .mx_DirectoryMenu { +.mx_LeftPanel .mx_BottomLeftMenu { -webkit-box-ordinal-group: 3; -moz-box-ordinal-group: 3; -ms-flex-order: 3; @@ -56,15 +56,15 @@ limitations under the License. border-top: 1px solid #f3f8fa; } -.mx_LeftPanel .mx_DirectoryMenu .mx_RoomTile { +.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile { color: #378bb4; } -.mx_LeftPanel .mx_DirectoryMenu .mx_RoomTile_avatar { +.mx_LeftPanel .mx_BottomLeftMenu .mx_RoomTile_avatar { padding-left: 14px; } -.mx_LeftPanel .mx_DirectoryMenu .mx_DirectoryMenu_options { +.mx_LeftPanel .mx_BottomLeftMenu .mx_BottomLeftMenu_options { margin-top: 12px; width: 100%; } \ No newline at end of file diff --git a/skins/base/css/organisms/RoomDirectory.css b/skins/base/css/organisms/RoomDirectory.css new file mode 100644 index 00000000..9ca7811f --- /dev/null +++ b/skins/base/css/organisms/RoomDirectory.css @@ -0,0 +1,40 @@ +/* +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. +*/ + +.mx_RoomDirectory { + max-width: 720px; + margin: auto; +} + +.mx_RoomDirectory_input { + margin: auto; + border-radius: 3px; + border: 1px solid #c7c7c7; + font-weight: 300; + font-size: 14px; + padding: 9px; + margin-top: 12px; + margin-bottom: 12px; +} + +.mx_RoomDirectory_table { + width: 100%; +} + +.mx_RoomDirectory_table td, +.mx_RoomDirectory_table th, { + padding: 6px; +} \ No newline at end of file diff --git a/skins/base/css/organisms/RoomView.css b/skins/base/css/organisms/RoomView.css index 88f52aa6..77893741 100644 --- a/skins/base/css/organisms/RoomView.css +++ b/skins/base/css/organisms/RoomView.css @@ -105,6 +105,20 @@ limitations under the License. border-top: 1px solid #a8dbf3; } +.mx_RoomView_typingBar { + margin-top: 17px; + margin-left: 56px; + color: #818794; +} + +.mx_RoomView_typingBar img { + padding-left: 12px; + padding-right: 12px; + margin-left: -64px; + margin-top: -7px; + float: left; +} + .mx_RoomView .mx_MessageComposer { -webkit-box-ordinal-group: 5; -moz-box-ordinal-group: 5; diff --git a/skins/base/img/typing.png b/skins/base/img/typing.png new file mode 100644 index 00000000..066a0ce8 Binary files /dev/null and b/skins/base/img/typing.png differ diff --git a/skins/base/views/atoms/EditableText.js b/skins/base/views/atoms/EditableText.js index a4508744..38aa5c8d 100644 --- a/skins/base/views/atoms/EditableText.js +++ b/skins/base/views/atoms/EditableText.js @@ -33,6 +33,7 @@ module.exports = React.createClass({ }, onClickDiv: function() { + console.log("onClickDiv triggered"); this.setState({ phase: this.Phases.Edit, }) @@ -57,12 +58,12 @@ module.exports = React.createClass({ if (this.state.value) { editable_el = <div ref="display_div" onClick={this.onClickDiv}>{this.state.value}</div>; } else { - editable_el = <div ref="display_div" onClick={this.onClickDiv}><i>{this.props.placeHolder}</i></div>; + editable_el = <div ref="display_div" onClick={this.onClickDiv}>{this.props.label}</div>; } } else if (this.state.phase == this.Phases.Edit) { editable_el = ( <div> - <input type="text" defaultValue={this.state.value} onKeyUp={this.onKeyUp} onFocus={this.onFocus} onBlur={this.onFinish} autoFocus/> + <input type="text" defaultValue={this.state.value} onKeyUp={this.onKeyUp} onFocus={this.onFocus} onBlur={this.onFinish} placeholder={this.props.placeHolder} autoFocus/> </div> ); } diff --git a/skins/base/views/molecules/DirectoryMenu.js b/skins/base/views/molecules/BottomLeftMenu.js similarity index 83% rename from skins/base/views/molecules/DirectoryMenu.js rename to skins/base/views/molecules/BottomLeftMenu.js index 8ffb4180..d2340d7b 100644 --- a/skins/base/views/molecules/DirectoryMenu.js +++ b/skins/base/views/molecules/BottomLeftMenu.js @@ -21,34 +21,34 @@ var classNames = require('classnames'); var dis = require("../../../../src/dispatcher"); -//var DirectoryMenuController = require("../../../../src/controllers/molecules/DirectoryMenuController"); - var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); module.exports = React.createClass({ - displayName: 'DirectoryMenu', - // mixins: [DirectoryMenuController], + displayName: 'BottomLeftMenu', - // FIXME: should these onClicks be in the controller instead? onSettingsClick: function() { dis.dispatch({action: 'view_user_settings'}); }, + onRoomDirectoryClick: function() { + dis.dispatch({action: 'view_room_directory'}); + }, + onCreateRoomClick: function() { dis.dispatch({action: 'view_create_room'}); }, render: function() { return ( - <div className="mx_DirectoryMenu"> - <div className="mx_DirectoryMenu_options"> + <div className="mx_BottomLeftMenu"> + <div className="mx_BottomLeftMenu_options"> <div className="mx_RoomTile" onClick={this.onCreateRoomClick}> <div className="mx_RoomTile_avatar"> <img src="img/create-big.png" width="42" height="42"/> </div> <div className="mx_RoomTile_name">Create new room</div> </div> - <div className="mx_RoomTile"> + <div className="mx_RoomTile" onClick={this.onRoomDirectoryClick}> <div className="mx_RoomTile_avatar"> <img src="img/directory-big.png" width="42" height="42"/> </div> diff --git a/skins/base/views/molecules/MemberTile.js b/skins/base/views/molecules/MemberTile.js index 1286173d..8dd3f00a 100644 --- a/skins/base/views/molecules/MemberTile.js +++ b/skins/base/views/molecules/MemberTile.js @@ -59,8 +59,7 @@ module.exports = React.createClass({ mainClassName += presenceClass; return ( - <div className={mainClassName} onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave } - > + <div className={mainClassName} onMouseEnter={ this.mouseEnter } onMouseLeave={ this.mouseLeave }> <div className="mx_MemberTile_avatar"> <img className="mx_MemberTile_avatarImg" src={ this.props.member ? MatrixClientPeg.get().getAvatarUrlForMember(this.props.member, 40, 40, "crop") : null } diff --git a/skins/base/views/molecules/RoomHeader.js b/skins/base/views/molecules/RoomHeader.js index 6d01c6a4..fdf4d7e5 100644 --- a/skins/base/views/molecules/RoomHeader.js +++ b/skins/base/views/molecules/RoomHeader.js @@ -39,46 +39,55 @@ module.exports = React.createClass({ render: function() { - var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); + var header; + if (this.props.simpleHeader) { + header = + <div className="mx_RoomHeader_wrapper"> + <div className="mx_RoomHeader_simpleHeader"> + { this.props.simpleHeader } + </div> + </div> + } + else { + var topic = this.props.room.currentState.getStateEvents('m.room.topic', ''); - var callButtons; - if (this.state) { - switch (this.state.call_state) { - case "ringback": - case "connected": - callButtons = ( - <div className="mx_RoomHeader_button" onClick={this.onHangupClick}> - BYEBYE - </div> - ); - break; + var callButtons; + if (this.state) { + switch (this.state.call_state) { + case "ringback": + case "connected": + callButtons = ( + <div className="mx_RoomHeader_hangupButton" onClick={this.onHangupClick}> + End call + </div> + ); + break; + } } - } - var name = null; - var topic_el = null; - var save_button = null; - var settings_button = null; - if (this.props.editing) { - name = <input type="text" defaultValue={this.props.room.name} ref="name_edit"/>; - // if (topic) topic_el = <div className="mx_RoomHeader_topic"><textarea>{ topic.getContent().topic }</textarea></div> - save_button = ( - <div className="mx_RoomHeader_button"onClick={this.props.onSaveClick}> - Save - </div> - ); - } else { - name = <EditableText initialValue={this.props.room.name} onValueChanged={this.onNameChange} />; - if (topic) topic_el = <div className="mx_RoomHeader_topic">{ topic.getContent().topic }</div>; - settings_button = ( - <div className="mx_RoomHeader_button" onClick={this.props.onSettingsClick}> - <img src="img/settings.png" width="32" height="32"/> - </div> - ); - } + var name = null; + var topic_el = null; + var save_button = null; + var settings_button = null; + if (this.props.editing) { + name = <input type="text" defaultValue={this.props.room.name} ref="name_edit"/>; + // if (topic) topic_el = <div className="mx_RoomHeader_topic"><textarea>{ topic.getContent().topic }</textarea></div> + save_button = ( + <div className="mx_RoomHeader_button"onClick={this.props.onSaveClick}> + Save + </div> + ); + } else { + name = <EditableText initialValue={this.props.room.name} onValueChanged={this.onNameChange} />; + if (topic) topic_el = <div className="mx_RoomHeader_topic">{ topic.getContent().topic }</div>; + settings_button = ( + <div className="mx_RoomHeader_button" onClick={this.props.onSettingsClick}> + <img src="img/settings.png" width="32" height="32"/> + </div> + ); + } - return ( - <div className="mx_RoomHeader"> + header = <div className="mx_RoomHeader_wrapper"> <div className="mx_RoomHeader_leftRow"> <div className="mx_RoomHeader_avatar"> @@ -91,13 +100,13 @@ module.exports = React.createClass({ { topic_el } </div> </div> + {callButtons} <div className="mx_RoomHeader_rightRow"> { save_button } { settings_button } <div className="mx_RoomHeader_button"> <img src="img/search.png" width="32" height="32"/> </div> - {callButtons} <div className="mx_RoomHeader_button" onClick={this.onVideoClick}> <img src="img/video.png" width="32" height="32"/> </div> @@ -106,6 +115,11 @@ module.exports = React.createClass({ </div> </div> </div> + } + + return ( + <div className="mx_RoomHeader"> + { header } </div> ); }, diff --git a/skins/base/views/organisms/LeftPanel.js b/skins/base/views/organisms/LeftPanel.js index 252a5d06..16575910 100644 --- a/skins/base/views/organisms/LeftPanel.js +++ b/skins/base/views/organisms/LeftPanel.js @@ -20,7 +20,7 @@ var React = require('react'); var ComponentBroker = require('../../../../src/ComponentBroker'); var RoomList = ComponentBroker.get('organisms/RoomList'); -var DirectoryMenu = ComponentBroker.get('molecules/DirectoryMenu'); +var BottomLeftMenu = ComponentBroker.get('molecules/BottomLeftMenu'); var IncomingCallBox = ComponentBroker.get('molecules/voip/IncomingCallBox'); var RoomCreate = ComponentBroker.get('molecules/RoomCreate'); @@ -33,7 +33,7 @@ module.exports = React.createClass({ <img className="mx_LeftPanel_hideButton" src="img/hide.png" width="32" height="32" alt="<"/> <IncomingCallBox /> <RoomList selectedRoom={this.props.selectedRoom} /> - <DirectoryMenu /> + <BottomLeftMenu /> </div> ); } diff --git a/skins/base/views/organisms/MemberList.js b/skins/base/views/organisms/MemberList.js index 3e586e56..dfeecb03 100644 --- a/skins/base/views/organisms/MemberList.js +++ b/skins/base/views/organisms/MemberList.js @@ -17,6 +17,7 @@ limitations under the License. 'use strict'; var React = require('react'); +var classNames = require('classnames'); var MemberListController = require("../../../../src/controllers/organisms/MemberList"); @@ -30,6 +31,10 @@ module.exports = React.createClass({ displayName: 'MemberList', mixins: [MemberListController], + getInitialState: function() { + return { editing: false }; + }, + // FIXME: combine this more nicely with the MemberInfo positioning stuff... onMemberListScroll: function(ev) { if (this.refs.memberListScroll) { @@ -55,23 +60,40 @@ module.exports = React.createClass({ onPopulateInvite: function(inputText, shouldSubmit) { // reset back to placeholder this.refs.invite.setValue("Invite", false, true); + this.setState({ editing: false }); if (!shouldSubmit) { return; // enter key wasn't pressed } this.onInvite(inputText); }, + onClickInvite: function(ev) { + this.setState({ editing: true }); + this.refs.invite.onClickDiv(); + console.log("forcing update on memberlist after having clicked invite"); + ev.stopPropagation(); + ev.preventDefault(); + }, + inviteTile: function() { - if (this.state.inviting) { - return ( - <div></div> - ); - } + // if (this.state.inviting) { + // return ( + // <div></div> + // ); + // } + + var classes = classNames({ + mx_MemberTile: true, + mx_MemberTile_inviteEditing: this.state.editing, + }); + + console.log("rendering inviteTile, with phase as " + (this.refs.invite ? this.refs.invite.state.phase : "unknown")); + return ( - <div className="mx_MemberTile"> + <div className={ classes } onClick={ this.onClickInvite } > <div className="mx_MemberTile_avatar"><img src="img/create-big.png" width="40" height="40" alt=""/></div> <div className="mx_MemberTile_name"> - <EditableText ref="invite" placeHolder="Invite" onValueChanged={this.onPopulateInvite}/> + <EditableText ref="invite" label="Invite" placeHolder="@user:domain.com" initialValue="" onValueChanged={this.onPopulateInvite}/> </div> </div> ); diff --git a/skins/base/views/organisms/RightPanel.js b/skins/base/views/organisms/RightPanel.js index e1634adc..5b6477a0 100644 --- a/skins/base/views/organisms/RightPanel.js +++ b/skins/base/views/organisms/RightPanel.js @@ -24,20 +24,53 @@ var MemberList = ComponentBroker.get('organisms/MemberList'); module.exports = React.createClass({ displayName: 'RightPanel', + Phase : { + Blank: 'Blank', + None: 'None', + MemberList: 'MemberList', + FileList: 'FileList', + }, + + getInitialState: function() { + return { + phase : this.Phase.None + } + }, + + onMemberListButtonClick: function() { + if (this.state.phase == this.Phase.None) { + this.setState({ phase: this.Phase.MemberList }); + } + else { + this.setState({ phase: this.Phase.None }); + } + }, + render: function() { - return ( - <div className="mx_RightPanel"> - <div className="mx_RightPanel_header"> + var buttonGroup; + var panel; + if (this.props.roomId) { + buttonGroup = <div className="mx_RightPanel_headerButtonGroup"> <div className="mx_RightPanel_headerButton"> <img src="img/file.png" width="32" height="32" alt="Files"/> </div> - <div className="mx_RightPanel_headerButton"> + <div className="mx_RightPanel_headerButton" onClick={ this.onMemberListButtonClick }> <img src="img/members.png" width="32" height="32" alt="Members"/> </div> - </div> + </div>; + + if (this.state.phase == this.Phase.MemberList) { + panel = <MemberList roomId={this.props.roomId} key={this.props.roomId} /> + } + } + + return ( + <div className="mx_RightPanel"> + <div className="mx_RightPanel_header"> + { buttonGroup } </div> - <MemberList roomId={this.props.roomId} key={this.props.roomId} /> + { panel } </div> ); } diff --git a/skins/base/views/organisms/RoomDirectory.js b/skins/base/views/organisms/RoomDirectory.js new file mode 100644 index 00000000..7ffb4977 --- /dev/null +++ b/skins/base/views/organisms/RoomDirectory.js @@ -0,0 +1,130 @@ +/* +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 MatrixClientPeg = require("../../../../src/MatrixClientPeg"); +var Modal = require("../../../../src/Modal"); +var ComponentBroker = require('../../../../src/ComponentBroker'); +var ErrorDialog = ComponentBroker.get("organisms/ErrorDialog"); +var RoomHeader = ComponentBroker.get('molecules/RoomHeader'); +var dis = require("../../../../src/dispatcher"); + + +module.exports = React.createClass({ + displayName: 'RoomDirectory', + + getInitialState: function() { + return { + publicRooms: [], + roomAlias: '', + } + }, + + componentDidMount: function() { + var self = this; + MatrixClientPeg.get().publicRooms(function (err, data) { + if (err) { + console.error("Failed to get publicRooms: %s", JSON.stringify(err)); + Modal.createDialog(ErrorDialog, { + title: "Failed to get public room list", + description: err.message + }); + } + else { + self.setState({ + publicRooms: data.chunk + }); + self.forceUpdate(); + } + }); + }, + + joinRoom: function(roomId) { + // XXX: check that JS SDK suppresses duplicate attempts to join the same room + MatrixClientPeg.get().joinRoom(roomId).done(function() { + dis.dispatch({ + action: 'view_room', + room_id: roomId + }); + }, function(err) { + console.error("Failed to join room: %s", JSON.stringify(err)); + Modal.createDialog(ErrorDialog, { + title: "Failed to join room", + description: err.message + }); + }); + }, + + getRows: function(filter) { + if (!this.state.publicRooms) return []; + + var rooms = this.state.publicRooms.filter(function(a) { + // FIXME: if incrementally typing, keep narrowing down the search set + return (a.aliases[0].search(filter) >= 0); + }).sort(function(a,b) { + return a.num_joined_members > b.num_joined_members; + }); + var rows = []; + var self = this; + for (var i = 0; i < rooms.length; i++) { + var name = rooms[i].name; + if (!name) { + if (rooms[i].aliases[0]) name = rooms[i].aliases[0] + } + else { + if (rooms[i].aliases[0]) name += " (" + rooms[i].aliases[0] + ")"; + } + rows.unshift( + <tr key={ rooms[i].room_id } onClick={ function() { self.joinRoom(rooms[i].room_id); } }> + <td><img src={ MatrixClientPeg.get().getAvatarUrlForRoom(rooms[i].room_id, 40, 40, "crop") } width="40" height="40" alt=""/> { name }</td> + <td>{ rooms[i].topic }</td> + <td style={ {'text-align' : 'center'} }>{ rooms[i].num_joined_members }</td> + </tr> + ); + } + return rows; + }, + + onKeyUp: function(ev) { + this.forceUpdate(); + this.setState({ roomAlias : this.refs.roomAlias.getDOMNode().value }) + if (ev.key == "Enter") { + this.joinRoom(this.refs.roomAlias.getDOMNode().value); + } + if (ev.key == "Down") { + + } + }, + + render: function() { + return ( + <div className="mx_RoomDirectory"> + <RoomHeader simpleHeader="Public Rooms" /> + <div className="mx_RoomDirectory_list"> + <input ref="roomAlias" placeholder="Join a room (e.g. #foo:domain.com)" className="mx_RoomDirectory_input" size="64" onKeyUp={ this.onKeyUp }/> + <table className="mx_RoomDirectory_table"> + <tr><th>Room</th><th>Topic</th><th>Users</th></tr> + { this.getRows(this.state.roomAlias) } + </table> + </div> + </div> + ); + } +}); + diff --git a/skins/base/views/organisms/RoomView.js b/skins/base/views/organisms/RoomView.js index 56699165..4f19b9b3 100644 --- a/skins/base/views/organisms/RoomView.js +++ b/skins/base/views/organisms/RoomView.js @@ -166,6 +166,7 @@ module.exports = React.createClass({ if (typingString) { statusBar = ( <div className="mx_RoomView_typingBar"> + <img src="img/typing.png" width="40" height="40" alt=""/> {typingString} </div> ); diff --git a/skins/base/views/organisms/UserSettings.js b/skins/base/views/organisms/UserSettings.js index 9afa3c03..7a57ec76 100644 --- a/skins/base/views/organisms/UserSettings.js +++ b/skins/base/views/organisms/UserSettings.js @@ -73,7 +73,7 @@ module.exports = React.createClass({ </div> <div className="mx_UserSettings_DisplayName"> - <EditableText ref="displayname" initialValue={this.state.displayName} placeHolder="Click to set display name." onValueChanged={this.changeDisplayname}/> + <EditableText ref="displayname" initialValue={this.state.displayName} label="Click to set display name." onValueChanged={this.changeDisplayname}/> <div className="mx_UserSettings_DisplayName_Edit" onClick={this.editDisplayName}>Edit</div> </div> diff --git a/skins/base/views/pages/MatrixChat.js b/skins/base/views/pages/MatrixChat.js index 85a29264..73af5082 100644 --- a/skins/base/views/pages/MatrixChat.js +++ b/skins/base/views/pages/MatrixChat.js @@ -26,6 +26,7 @@ var Login = ComponentBroker.get('templates/Login'); var UserSettings = ComponentBroker.get('organisms/UserSettings'); var Register = ComponentBroker.get('templates/Register'); var CreateRoom = ComponentBroker.get('organisms/CreateRoom'); +var RoomDirectory = ComponentBroker.get('organisms/RoomDirectory'); var MatrixChatController = require("../../../../src/controllers/pages/MatrixChat"); @@ -59,9 +60,15 @@ module.exports = React.createClass({ break; case this.PageTypes.UserSettings: page_element = <UserSettings /> + right_panel = <RightPanel/> break; case this.PageTypes.CreateRoom: page_element = <CreateRoom onRoomCreated={this.onRoomCreated}/> + right_panel = <RightPanel/> + break; + case this.PageTypes.RoomDirectory: + page_element = <RoomDirectory /> + right_panel = <RightPanel/> break; } diff --git a/src/ComponentBroker.js b/src/ComponentBroker.js index a9ccebd1..6e5d1e11 100644 --- a/src/ComponentBroker.js +++ b/src/ComponentBroker.js @@ -97,9 +97,10 @@ require('../skins/base/views/molecules/RoomSettings'); require('../skins/base/views/organisms/LeftPanel'); require('../skins/base/views/organisms/RightPanel'); require('../skins/base/views/organisms/LogoutPrompt'); +require('../skins/base/views/organisms/RoomDirectory'); require('../skins/base/views/molecules/RoomCreate'); require('../skins/base/views/molecules/RoomDropTarget'); -require('../skins/base/views/molecules/DirectoryMenu'); +require('../skins/base/views/molecules/BottomLeftMenu'); require('../skins/base/views/molecules/DateSeparator'); require('../skins/base/views/atoms/voip/VideoFeed'); require('../skins/base/views/molecules/voip/VideoView'); diff --git a/src/controllers/atoms/EditableText.js b/src/controllers/atoms/EditableText.js index 15ee58a7..5ea4ce8c 100644 --- a/src/controllers/atoms/EditableText.js +++ b/src/controllers/atoms/EditableText.js @@ -22,6 +22,7 @@ module.exports = { propTypes: { onValueChanged: React.PropTypes.func, initialValue: React.PropTypes.string, + label: React.PropTypes.string, placeHolder: React.PropTypes.string, }, @@ -34,7 +35,8 @@ module.exports = { return { onValueChanged: function() {}, initialValue: '', - placeHolder: 'Click to set', + label: 'Click to set', + placeholder: '', }; }, @@ -77,6 +79,7 @@ module.exports = { this.setState({ phase: this.Phases.Display, }); + this.onValueChanged(false); }, onValueChanged: function(shouldSubmit) { diff --git a/src/controllers/molecules/MessageComposer.js b/src/controllers/molecules/MessageComposer.js index a9de008d..2ece5636 100644 --- a/src/controllers/molecules/MessageComposer.js +++ b/src/controllers/molecules/MessageComposer.js @@ -200,7 +200,7 @@ module.exports = { }, function(err) { console.error("Command failure: %s", err); Modal.createDialog(ErrorDialog, { - title: "Server Error", + title: "Server error", description: err.message }); }); @@ -208,7 +208,7 @@ module.exports = { else if (cmd.error) { console.error(cmd.error); Modal.createDialog(ErrorDialog, { - title: "Command Error", + title: "Command error", description: cmd.error }); } diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js index 06952700..29d4f20a 100644 --- a/src/controllers/organisms/MemberList.js +++ b/src/controllers/organisms/MemberList.js @@ -101,7 +101,7 @@ module.exports = { }, function(err) { console.error("Failed to invite: %s", JSON.stringify(err)); Modal.createDialog(ErrorDialog, { - title: "Invite Server Error", + title: "Server error whilst inviting", description: err.message }); self.setState({ diff --git a/src/controllers/pages/MatrixChat.js b/src/controllers/pages/MatrixChat.js index 1e38a215..30135a2b 100644 --- a/src/controllers/pages/MatrixChat.js +++ b/src/controllers/pages/MatrixChat.js @@ -33,6 +33,7 @@ module.exports = { RoomView: "room_view", UserSettings: "user_settings", CreateRoom: "create_room", + RoomDirectory: "room_directory", }, AuxPanel: { @@ -43,7 +44,7 @@ module.exports = { return { logged_in: !!(MatrixClientPeg.get() && MatrixClientPeg.get().credentials), ready: false, - page_type: this.PageTypes.RoomView, + page_type: MatrixClientPeg.get().getRooms().length ? this.PageTypes.RoomView : this.PageTypes.RoomDirectory, aux_panel: null, }; }, @@ -127,6 +128,7 @@ module.exports = { currentRoom: payload.room_id, page_type: this.PageTypes.RoomView, }); + this.notifyNewScreen('room/'+payload.room_id); break; case 'view_prev_room': roomIndexDelta = -1; @@ -156,6 +158,11 @@ module.exports = { page_type: this.PageTypes.CreateRoom, }); break; + case 'view_room_directory': + this.setState({ + page_type: this.PageTypes.RoomDirectory, + }); + break; } }, @@ -172,13 +179,18 @@ module.exports = { var cli = MatrixClientPeg.get(); var self = this; cli.on('syncComplete', function() { - var firstRoom = null; - if (cli.getRooms() && cli.getRooms().length) { - firstRoom = RoomListSorter.mostRecentActivityFirst( - cli.getRooms() - )[0].roomId; + if (!self.state.currentRoom) { + var firstRoom = null; + if (cli.getRooms() && cli.getRooms().length) { + firstRoom = RoomListSorter.mostRecentActivityFirst( + cli.getRooms() + )[0].roomId; + } + self.setState({ready: true, currentRoom: firstRoom}); + self.notifyNewScreen('room/'+firstRoom); + } else { + self.setState({ready: true}); } - self.setState({ready: true, currentRoom: firstRoom}); dis.dispatch({action: 'focus_composer'}); }); cli.on('Call.incoming', function(call) { @@ -222,6 +234,12 @@ module.exports = { action: 'start_login', params: params }); + } else if (screen.indexOf('room/') == 0) { + var roomId = screen.split('/')[1]; + dis.dispatch({ + action: 'view_room', + room_id: roomId + }); } },