diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js
index 5563eaea83..ac848030af 100644
--- a/src/components/structures/RoomView.js
+++ b/src/components/structures/RoomView.js
@@ -293,7 +293,7 @@ module.exports = React.createClass({
// no change
}
else {
- this.setState((state, props) => {
+ this.setState((state, props) => {
return {numUnreadMessages: state.numUnreadMessages + 1};
});
}
diff --git a/src/components/views/rooms/EntityTile.js b/src/components/views/rooms/EntityTile.js
index 47e3fd3350..acc424b098 100644
--- a/src/components/views/rooms/EntityTile.js
+++ b/src/components/views/rooms/EntityTile.js
@@ -37,7 +37,8 @@ module.exports = React.createClass({
avatarJsx: React.PropTypes.any, //
className: React.PropTypes.string,
presenceState: React.PropTypes.string,
- presenceActiveAgo: React.PropTypes.number,
+ presenceLastActiveAgo: React.PropTypes.number,
+ presenceLastTs: React.PropTypes.number,
presenceCurrentlyActive: React.PropTypes.bool,
showInviteButton: React.PropTypes.bool,
shouldComponentUpdate: React.PropTypes.func,
@@ -50,7 +51,8 @@ module.exports = React.createClass({
shouldComponentUpdate: function(nextProps, nextState) { return true; },
onClick: function() {},
presenceState: "offline",
- presenceActiveAgo: -1,
+ presenceLastActiveAgo: 0,
+ presenceLastTs: 0,
showInviteButton: false,
suppressOnHover: false
};
@@ -82,13 +84,16 @@ module.exports = React.createClass({
var nameEl;
if (this.state.hover && !this.props.suppressOnHover) {
+ var activeAgo = this.props.presenceLastActiveAgo ?
+ (Date.now() - (this.props.presenceLastTs - this.props.presenceLastActiveAgo)) : -1;
+
mainClassName += " mx_EntityTile_hover";
var PresenceLabel = sdk.getComponent("rooms.PresenceLabel");
nameEl = (
{ this.props.name }
-
diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js
index 0597d897db..8f392aac95 100644
--- a/src/components/views/rooms/MemberList.js
+++ b/src/components/views/rooms/MemberList.js
@@ -64,15 +64,19 @@ module.exports = React.createClass({
cli.on("RoomMember.name", this.onRoomMemberName);
cli.on("RoomState.events", this.onRoomStateEvent);
cli.on("Room", this.onRoom); // invites
+ cli.on("User.presence", this.onUserPresence);
+ // cli.on("Room.timeline", this.onRoomTimeline);
},
componentWillUnmount: function() {
- if (MatrixClientPeg.get()) {
- MatrixClientPeg.get().removeListener("Room", this.onRoom);
- MatrixClientPeg.get().removeListener("RoomState.members", this.onRoomStateMember);
- MatrixClientPeg.get().removeListener("RoomMember.name", this.onRoomMemberName);
- MatrixClientPeg.get().removeListener("User.presence", this.userPresenceFn);
- MatrixClientPeg.get().removeListener("RoomState.events", this.onRoomStateEvent);
+ var cli = MatrixClientPeg.get();
+ if (cli) {
+ cli.removeListener("RoomState.members", this.onRoomStateMember);
+ cli.removeListener("RoomMember.name", this.onRoomMemberName);
+ cli.removeListener("RoomState.events", this.onRoomStateEvent);
+ cli.removeListener("Room", this.onRoom);
+ cli.removeListener("User.presence", this.onUserPresence);
+ // cli.removeListener("Room.timeline", this.onRoomTimeline);
}
},
@@ -87,25 +91,45 @@ module.exports = React.createClass({
members: self.roomMembers()
});
}, 50);
+ },
+/*
+ onRoomTimeline: function(ev, room, toStartOfTimeline, removed, data) {
+ // ignore anything but real-time updates at the end of the room:
+ // updates from pagination will happen when the paginate completes.
+ if (toStartOfTimeline || !data || !data.liveEvent) return;
+
+ // treat any activity from a user as implicit presence to update the
+ // ordering of the list whenever someone says something.
+ // Except right now we're not tiebreaking "active now" users in this way
+ // so don't bother for now.
+ if (ev.getSender()) {
+ // console.log("implicit presence from " + ev.getSender());
+
+ var tile = this.refs[ev.getSender()];
+ if (tile) {
+ // work around a race where you might have a room member object
+ // before the user object exists. XXX: why does this ever happen?
+ var all_members = room.currentState.members;
+ var userId = ev.getSender();
+ if (all_members[userId].user === null) {
+ all_members[userId].user = MatrixClientPeg.get().getUser(userId);
+ }
+ this._updateList(); // reorder the membership list
+ }
+ }
+ },
+*/
+
+ onUserPresence(event, user) {
// Attach a SINGLE listener for global presence changes then locate the
// member tile and re-render it. This is more efficient than every tile
// evar attaching their own listener.
- function updateUserState(event, user) {
- // XXX: evil hack to track the age of this presence info.
- // this should be removed once syjs-28 is resolved in the JS SDK itself.
- user.lastPresenceTs = Date.now();
-
- var tile = self.refs[user.userId];
-
- if (tile) {
- self._updateList(); // reorder the membership list
- }
+ // console.log("explicit presence from " + user.userId);
+ var tile = this.refs[user.userId];
+ if (tile) {
+ this._updateList(); // reorder the membership list
}
- // FIXME: we should probably also reset 'lastActiveAgo' to zero whenever
- // we see a typing notif from a user, as we don't get presence updates for those.
- MatrixClientPeg.get().on("User.presence", updateUserState);
- this.userPresenceFn = updateUserState;
},
onRoom: function(room) {
@@ -133,6 +157,7 @@ module.exports = React.createClass({
},
_updateList: new rate_limited_func(function() {
+ // console.log("Updating memberlist");
this.memberDict = this.getMemberDict();
var self = this;
@@ -266,7 +291,6 @@ module.exports = React.createClass({
var all_members = room.currentState.members;
- // XXX: evil hack until SYJS-28 is fixed
Object.keys(all_members).map(function(userId) {
// work around a race where you might have a room member object
// before the user object exists. This may or may not cause
@@ -275,9 +299,8 @@ module.exports = React.createClass({
all_members[userId].user = MatrixClientPeg.get().getUser(userId);
}
- if (all_members[userId].user && !all_members[userId].user.lastPresenceTs) {
- all_members[userId].user.lastPresenceTs = Date.now();
- }
+ // XXX: this user may have no lastPresenceTs value!
+ // the right solution here is to fix the race rather than leave it as 0
});
return all_members;
@@ -288,7 +311,7 @@ module.exports = React.createClass({
var all_user_ids = Object.keys(all_members);
var ConferenceHandler = CallHandler.getConferenceHandler();
- if (this.memberSort) all_user_ids.sort(this.memberSort);
+ all_user_ids.sort(this.memberSort);
var to_display = [];
var count = 0;
@@ -325,27 +348,84 @@ module.exports = React.createClass({
});
},
- memberSort: function(userIdA, userIdB) {
- var userA = this.memberDict[userIdA].user;
- var userB = this.memberDict[userIdB].user;
-
- var presenceMap = {
- online: 3,
- unavailable: 2,
- offline: 1
- };
-
- var presenceOrdA = userA ? presenceMap[userA.presence] : 0;
- var presenceOrdB = userB ? presenceMap[userB.presence] : 0;
-
- if (presenceOrdA != presenceOrdB) {
- return presenceOrdB - presenceOrdA;
+ memberString: function(member) {
+ if (!member) {
+ return "(null)";
}
+ else {
+ return "(" + member.name + ", " + member.powerLevel + ", " + member.user.lastActiveAgo + ", " + member.user.currentlyActive + ")";
+ }
+ },
- var lastActiveTsA = userA && userA.lastActiveAgo ? userA.lastPresenceTs - userA.lastActiveAgo : 0;
- var lastActiveTsB = userB && userB.lastActiveAgo ? userB.lastPresenceTs - userB.lastActiveAgo : 0;
+ // returns negative if a comes before b,
+ // returns 0 if a and b are equivalent in ordering
+ // returns positive if a comes after b.
+ memberSort: function(userIdA, userIdB) {
+ // order by last active, with "active now" first.
+ // ...and then by power
+ // ...and then alphabetically.
+ // We could tiebreak instead by "last recently spoken in this room" if we wanted to.
- return lastActiveTsB - lastActiveTsA;
+ var memberA = this.memberDict[userIdA];
+ var memberB = this.memberDict[userIdB];
+ var userA = memberA.user;
+ var userB = memberB.user;
+
+ // if (!userA || !userB) {
+ // console.log("comparing " + memberA.name + " user=" + memberA.user + " with " + memberB.name + " user=" + memberB.user);
+ // }
+
+ if (!userA && !userB) return 0;
+ if (userA && !userB) return -1;
+ if (!userA && userB) return 1;
+
+ // console.log("comparing " + this.memberString(memberA) + " and " + this.memberString(memberB));
+
+ if (userA.currentlyActive && userB.currentlyActive) {
+ // console.log(memberA.name + " and " + memberB.name + " are both active");
+ if (memberA.powerLevel === memberB.powerLevel) {
+ // console.log(memberA + " and " + memberB + " have same power level");
+ if (memberA.name && memberB.name) {
+ // console.log("comparing names: " + memberA.name + " and " + memberB.name);
+ return memberA.name.localeCompare(memberB.name);
+ }
+ else {
+ return 0;
+ }
+ }
+ else {
+ // console.log("comparing power: " + memberA.powerLevel + " and " + memberB.powerLevel);
+ return memberB.powerLevel - memberA.powerLevel;
+ }
+ }
+
+ if (userA.currentlyActive && !userB.currentlyActive) return -1;
+ if (!userA.currentlyActive && userB.currentlyActive) return 1;
+
+ // For now, let's just order things by timestamp. It's really annoying
+ // that a user disappears from sight just because they temporarily go offline
+ /*
+ var presenceMap = {
+ online: 3,
+ unavailable: 2,
+ offline: 1
+ };
+
+ var presenceOrdA = userA ? presenceMap[userA.presence] : 0;
+ var presenceOrdB = userB ? presenceMap[userB.presence] : 0;
+
+ if (presenceOrdA != presenceOrdB) {
+ return presenceOrdB - presenceOrdA;
+ }
+ */
+
+ var lastActiveTsA = userA && userA.lastActiveTs ? userA.lastActiveTs : 0;
+ var lastActiveTsB = userB && userB.lastActiveTs ? userB.lastActiveTs : 0;
+
+ // console.log("comparing ts: " + lastActiveTsA + " and " + lastActiveTsB);
+
+ return lastActiveTsB - lastActiveTsA;
+ }
},
onSearchQueryChanged: function(input) {
diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js
index b19a215668..cf79394228 100644
--- a/src/components/views/rooms/MemberTile.js
+++ b/src/components/views/rooms/MemberTile.js
@@ -82,15 +82,13 @@ module.exports = React.createClass({
if (member.user) {
this.user_last_modified_time = member.user.getLastModifiedTime();
-
- // FIXME: make presence data update whenever User.presence changes...
- active = member.user.lastActiveAgo ?
- (Date.now() - (member.user.lastPresenceTs - member.user.lastActiveAgo)) : -1;
}
this.member_last_modified_time = member.getLastModifiedTime();
-
+
return (
-
diff --git a/src/components/views/rooms/PresenceLabel.js b/src/components/views/rooms/PresenceLabel.js
index ac0410c1f7..2ece4c771e 100644
--- a/src/components/views/rooms/PresenceLabel.js
+++ b/src/components/views/rooms/PresenceLabel.js
@@ -76,6 +76,8 @@ module.exports = React.createClass({
render: function() {
if (this.props.activeAgo >= 0) {
var ago = this.props.currentlyActive ? "now" : (this.getDuration(this.props.activeAgo) + " ago");
+ // var ago = this.getDuration(this.props.activeAgo) + " ago";
+ // if (this.props.currentlyActive) ago += " (now?)";
return (
{ this.getPrettyPresence(this.props.presenceState) } { ago }