diff --git a/skins/base/views/atoms/EditableText.js b/skins/base/views/atoms/EditableText.js index 07bdc911d7..a450874468 100644 --- a/skins/base/views/atoms/EditableText.js +++ b/skins/base/views/atoms/EditableText.js @@ -44,7 +44,7 @@ module.exports = React.createClass({ onFinish: function(ev) { if (ev.target.value) { - this.setValue(ev.target.value); + this.setValue(ev.target.value, ev.key === "Enter"); } else { this.cancelEdit(); } diff --git a/skins/base/views/molecules/MemberInfo.js b/skins/base/views/molecules/MemberInfo.js index 19e0dc4644..d8559dbbb2 100644 --- a/skins/base/views/molecules/MemberInfo.js +++ b/skins/base/views/molecules/MemberInfo.js @@ -19,11 +19,33 @@ limitations under the License. var React = require('react'); var MatrixClientPeg = require("../../../../src/MatrixClientPeg"); -//var MemberInfoController = require("../../../../src/controllers/molecules/MemberInfo"); +var MemberInfoController = require("../../../../src/controllers/molecules/MemberInfo"); module.exports = React.createClass({ displayName: 'MemberInfo', - //mixins: [MemberInfoController], + mixins: [MemberInfoController], + + getDuration: function(time) { + if (!time) return; + var t = parseInt(time / 1000); + var s = t % 60; + var m = parseInt(t / 60) % 60; + var h = parseInt(t / (60 * 60)) % 24; + var d = parseInt(t / (60 * 60 * 24)); + if (t < 60) { + if (t < 0) { + return "0s"; + } + return s + "s"; + } + if (t < 60 * 60) { + return m + "m"; + } + if (t < 24 * 60 * 60) { + return h + "h"; + } + return d + "d "; + }, render: function() { var power; @@ -31,6 +53,10 @@ module.exports = React.createClass({ var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png"; power = ; } + var activeAgo = "unknown"; + if (this.state.active >= 0) { + activeAgo = this.getDuration(this.state.active); + } return (
@@ -41,9 +67,9 @@ module.exports = React.createClass({ width="128" height="128" alt=""/>
{this.props.member.userId}
-
Presence: {this.props.member.presence}
-
Last active: {this.props.member.last_active_ago}
-
Start chat
+
Presence: {this.state.presence}
+
Last active: {activeAgo}
+
Start chat
); } diff --git a/skins/base/views/organisms/MemberList.js b/skins/base/views/organisms/MemberList.js index 590ebb5555..9283a928ea 100644 --- a/skins/base/views/organisms/MemberList.js +++ b/skins/base/views/organisms/MemberList.js @@ -23,6 +23,7 @@ var MemberListController = require("../../../../src/controllers/organisms/Member var ComponentBroker = require('../../../../src/ComponentBroker'); var MemberTile = ComponentBroker.get("molecules/MemberTile"); +var EditableText = ComponentBroker.get("atoms/EditableText"); module.exports = React.createClass({ @@ -39,6 +40,31 @@ module.exports = React.createClass({ }); }, + onPopulateInvite: function(inputText, shouldSubmit) { + // reset back to placeholder + this.refs.invite.setValue("Invite", false, true); + if (!shouldSubmit) { + return; // enter key wasn't pressed + } + this.onInvite(inputText); + }, + + inviteTile: function() { + if (this.state.inviting) { + return ( +
+ ); + } + return ( +
+
+
+ +
+
+ ); + }, + render: function() { return (
@@ -49,10 +75,7 @@ module.exports = React.createClass({

Members

{this.makeMemberTiles()} -
-
-
Invite
-
+ {this.inviteTile()}
diff --git a/src/TextForEvent.js b/src/TextForEvent.js index e302b647de..c8f2f71b11 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -2,7 +2,7 @@ function textForMemberEvent(ev) { // XXX: SYJS-16 var senderName = ev.sender ? ev.sender.name : ev.getSender(); - var targetName = ev.target ? ev.target.name : ev.getContent().state_key; + var targetName = ev.target ? ev.target.name : ev.getStateKey(); var reason = ev.getContent().reason ? ( " Reason: " + ev.getContent().reason ) : ""; diff --git a/src/controllers/atoms/EditableText.js b/src/controllers/atoms/EditableText.js index 4d9eb01016..15ee58a7f3 100644 --- a/src/controllers/atoms/EditableText.js +++ b/src/controllers/atoms/EditableText.js @@ -55,11 +55,16 @@ module.exports = { return this.state.value; }, - setValue: function(val) { + setValue: function(val, shouldSubmit, suppressListener) { + var self = this; this.setState({ value: val, phase: this.Phases.Display, - }, this.onValueChanged); + }, function() { + if (!suppressListener) { + self.onValueChanged(shouldSubmit); + } + }); }, edit: function() { @@ -74,7 +79,7 @@ module.exports = { }); }, - onValueChanged: function() { - this.props.onValueChanged(this.state.value); + onValueChanged: function(shouldSubmit) { + this.props.onValueChanged(this.state.value, shouldSubmit); }, }; diff --git a/src/controllers/molecules/MemberInfo.js b/src/controllers/molecules/MemberInfo.js new file mode 100644 index 0000000000..96f8bb8ca8 --- /dev/null +++ b/src/controllers/molecules/MemberInfo.js @@ -0,0 +1,115 @@ +/* +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. +*/ + +/* + * State vars: + * 'presence' : string (online|offline|unavailable etc) + * 'active' : number (ms ago; can be -1) + */ + +'use strict'; +var MatrixClientPeg = require("../../MatrixClientPeg"); +var dis = require("../../dispatcher"); + +module.exports = { + componentDidMount: function() { + var self = this; + function updateUserState(event, user) { + if (!self.props.member) { return; } + + if (user.userId === self.props.member.userId) { + self.setState({ + presence: user.presence, + active: user.lastActiveAgo + }); + } + } + MatrixClientPeg.get().on("User.presence", updateUserState); + this.userPresenceFn = updateUserState; + + if (this.props.member) { + var usr = MatrixClientPeg.get().getUser(this.props.member.userId); + if (!usr) { + return; + } + this.setState({ + presence: usr.presence, + active: usr.lastActiveAgo + }); + } + }, + + componentWillUnmount: function() { + MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn); + }, + + onChatClick: function() { + // check if there are any existing rooms with just us and them (1:1) + // If so, just view that room. If not, create a private room with them. + var rooms = MatrixClientPeg.get().getRooms(); + var userIds = [ + this.props.member.userId, + MatrixClientPeg.get().credentials.userId + ]; + var existingRoomId = null; + for (var i = 0; i < rooms.length; i++) { + var members = rooms[i].getJoinedMembers(); + if (members.length === 2) { + var hasTargetUsers = true; + for (var j = 0; j < members.length; j++) { + if (userIds.indexOf(members[j].userId) === -1) { + hasTargetUsers = false; + break; + } + } + if (hasTargetUsers) { + existingRoomId = rooms[i].roomId; + break; + } + } + } + + if (existingRoomId) { + dis.dispatch({ + action: 'view_room', + room_id: existingRoomId + }); + } + else { + MatrixClientPeg.get().createRoom({ + invite: [this.props.member.userId], + preset: "private_chat" + }).done(function(res) { + dis.dispatch({ + action: 'view_room', + room_id: res.room_id + }); + }, function(err) { + console.error( + "Failed to create room: %s", JSON.stringify(err) + ); + }); + } + }, + + getInitialState: function() { + return { + presence: "offline", + active: -1 + } + } +}; + diff --git a/src/controllers/organisms/MemberList.js b/src/controllers/organisms/MemberList.js index fb5c002812..6021d0fc9a 100644 --- a/src/controllers/organisms/MemberList.js +++ b/src/controllers/organisms/MemberList.js @@ -61,6 +61,32 @@ module.exports = { }); }, + onInvite: function(inputText) { + var self = this; + // sanity check the input + inputText = inputText.trim(); // react requires es5-shim so we know trim() exists + if (inputText[0] !== '@' || inputText.indexOf(":") === -1) { + console.error("Bad user ID to invite: %s", inputText); + return; + } + self.setState({ + inviting: true + }); + console.log("Invite %s to %s", inputText, this.props.roomId); + MatrixClientPeg.get().invite(this.props.roomId, inputText).done( + function(res) { + console.log("Invited"); + self.setState({ + inviting: false + }); + }, function(err) { + console.error("Failed to invite: %s", JSON.stringify(err)); + self.setState({ + inviting: false + }); + }); + }, + roomMembers: function(limit) { if (!this.props.roomId) return {}; var cli = MatrixClientPeg.get();