From c0e5d1d13b7d086e0202a33739d53675928cefa1 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 13 Jan 2016 15:55:28 +0000 Subject: [PATCH 1/2] Factor out presence text. Do prep work for displaying 3pid invites on memberlist. Factored out presence to PresenceLabel. --- src/component-index.js | 1 + src/components/views/rooms/MemberList.js | 30 +++++++- src/components/views/rooms/MemberTile.js | 51 +++---------- src/components/views/rooms/PresenceLabel.js | 84 +++++++++++++++++++++ 4 files changed, 125 insertions(+), 41 deletions(-) create mode 100644 src/components/views/rooms/PresenceLabel.js diff --git a/src/component-index.js b/src/component-index.js index 9fe15adfc6..7ae15ba12c 100644 --- a/src/component-index.js +++ b/src/component-index.js @@ -64,6 +64,7 @@ module.exports.components['views.rooms.MemberInfo'] = require('./components/view module.exports.components['views.rooms.MemberList'] = require('./components/views/rooms/MemberList'); module.exports.components['views.rooms.MemberTile'] = require('./components/views/rooms/MemberTile'); module.exports.components['views.rooms.MessageComposer'] = require('./components/views/rooms/MessageComposer'); +module.exports.components['views.rooms.PresenceLabel'] = require('./components/views/rooms/PresenceLabel'); module.exports.components['views.rooms.RoomHeader'] = require('./components/views/rooms/RoomHeader'); module.exports.components['views.rooms.RoomList'] = require('./components/views/rooms/RoomList'); module.exports.components['views.rooms.RoomPreviewBar'] = require('./components/views/rooms/RoomPreviewBar'); diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index b0e2baa3d3..f4d017733d 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -229,7 +229,8 @@ module.exports = React.createClass({ var MemberTile = sdk.getComponent("rooms.MemberTile"); var self = this; - return self.state.members.filter(function(userId) { + + var memberList = self.state.members.filter(function(userId) { var m = self.memberDict[userId]; return m.membership == membership; }).map(function(userId) { @@ -238,6 +239,33 @@ module.exports = React.createClass({ ); }); + + if (membership === "invite") { + // include 3pid invites (m.room.third_party_invite) state events. + // The HS may have already converted these into m.room.member invites so + // we shouldn't add them if the 3pid invite state key (token) is in the + // member invite (content.third_party_invite.signed.token) + var room = MatrixClientPeg.get().getRoom(this.props.roomId); + if (room) { + room.currentState.getStateEvents("m.room.third_party_invite").forEach( + function(e) { + // discard all invites which have a m.room.member event since we've + // already added them. + var memberEvent = room.currentState.getInviteForThreePidToken(e.getStateKey()); + if (memberEvent) { + console.log("Found match => %s", memberEvent.getStateKey()); + return; + } + console.log("Display match => "); + /* + memberList.push( + + ) */ + }) + } + } + + return memberList; }, onPopulateInvite: function(e) { diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index 32cc619f13..0a32441a00 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -26,6 +26,11 @@ var Modal = require("../../../Modal"); module.exports = React.createClass({ displayName: 'MemberTile', + propTypes: { + member: React.PropTypes.any.isRequired, // RoomMember + onFinished: React.PropTypes.func + }, + getInitialState: function() { return {}; }, @@ -71,37 +76,6 @@ module.exports = React.createClass({ }); }, - 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 "; - }, - - getPrettyPresence: function(user) { - if (!user) return "Unknown"; - var presence = user.presence; - if (presence === "online") return "Online"; - if (presence === "unavailable") return "Idle"; // XXX: is this actually right? - if (presence === "offline") return "Offline"; - return "Unknown"; - }, - getPowerLabel: function() { var label = this.props.member.userId; if (this.state.isTargetMod) { @@ -111,6 +85,8 @@ module.exports = React.createClass({ }, render: function() { + var PresenceLabel = sdk.getComponent("rooms.PresenceLabel"); + this.member_last_modified_time = this.props.member.getLastModifiedTime(); if (this.props.member.user) { this.user_last_modified_time = this.props.member.user.getLastModifiedTime(); @@ -144,22 +120,17 @@ module.exports = React.createClass({ var nameEl; if (this.state.hover) { - var presence; // FIXME: make presence data update whenever User.presence changes... var active = this.props.member.user ? ((Date.now() - (this.props.member.user.lastPresenceTs - this.props.member.user.lastActiveAgo)) || -1) : -1; - if (active >= 0) { - presence =
{ this.getPrettyPresence(this.props.member.user) } { this.getDuration(active) } ago
; - } - else { - presence =
{ this.getPrettyPresence(this.props.member.user) }
; - } - nameEl = + nameEl = (
{ name }
- { presence } +
+ ); } else { nameEl = diff --git a/src/components/views/rooms/PresenceLabel.js b/src/components/views/rooms/PresenceLabel.js new file mode 100644 index 0000000000..4ecad5b3df --- /dev/null +++ b/src/components/views/rooms/PresenceLabel.js @@ -0,0 +1,84 @@ +/* +Copyright 2015, 2016 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('../../../MatrixClientPeg'); +var sdk = require('../../../index'); + +module.exports = React.createClass({ + displayName: 'PresenceLabel', + + propTypes: { + activeAgo: React.PropTypes.number, + presenceState: React.PropTypes.string + }, + + getDefaultProps: function() { + return { + ago: -1, + presenceState: null + }; + }, + + 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 "; + }, + + getPrettyPresence: function(presence) { + if (presence === "online") return "Online"; + if (presence === "unavailable") return "Idle"; // XXX: is this actually right? + if (presence === "offline") return "Offline"; + return "Unknown"; + }, + + render: function() { + if (this.props.activeAgo >= 0) { + return ( +
+ { this.getPrettyPresence(this.props.presenceState) } { this.getDuration(this.props.activeAgo) } ago +
+ ); + } + else { + return ( +
+ { this.getPrettyPresence(this.props.presenceState) } +
+ ); + } + } +}); From 8c9352c484ef8e367dce0837686c492bb61acc51 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 13 Jan 2016 16:55:28 +0000 Subject: [PATCH 2/2] Make MemberAvatar and MemberTile work without RoomMember objects --- src/components/views/avatars/MemberAvatar.js | 71 ++++++++------- src/components/views/rooms/MemberList.js | 9 +- src/components/views/rooms/MemberTile.js | 96 +++++++++++--------- 3 files changed, 98 insertions(+), 78 deletions(-) diff --git a/src/components/views/avatars/MemberAvatar.js b/src/components/views/avatars/MemberAvatar.js index 21c717aac5..f209006b1c 100644 --- a/src/components/views/avatars/MemberAvatar.js +++ b/src/components/views/avatars/MemberAvatar.js @@ -24,10 +24,16 @@ module.exports = React.createClass({ displayName: 'MemberAvatar', propTypes: { - member: React.PropTypes.object.isRequired, + member: React.PropTypes.object, width: React.PropTypes.number, height: React.PropTypes.number, resizeMethod: React.PropTypes.string, + /** + * The custom display name to use for this member. This can serve as a + * drop in replacement for RoomMember objects, or as a clobber name on + * an existing RoomMember. Used for 3pid invites. + */ + customDisplayName: React.PropTypes.string }, getDefaultProps: function() { @@ -38,64 +44,68 @@ module.exports = React.createClass({ } }, + getInitialState: function() { + var defaultImageUrl = Avatar.defaultAvatarUrlForString( + this.props.customDisplayName || this.props.member.userId + ) + return { + imageUrl: this._getMemberImageUrl() || defaultImageUrl, + defaultImageUrl: defaultImageUrl + }; + }, + componentWillReceiveProps: function(nextProps) { this.refreshUrl(); }, - defaultAvatarUrl: function(member, width, height, resizeMethod) { - return Avatar.defaultAvatarUrlForString(member.userId); - }, - onError: function(ev) { // don't tightloop if the browser can't load a data url - if (ev.target.src == this.defaultAvatarUrl(this.props.member)) { + if (ev.target.src == this.state.defaultImageUrl) { return; } this.setState({ - imageUrl: this.defaultAvatarUrl(this.props.member) + imageUrl: this.state.defaultImageUrl }); }, - _computeUrl: function() { + _getMemberImageUrl: function() { + if (!this.props.member) { return null; } + return Avatar.avatarUrlForMember(this.props.member, this.props.width, this.props.height, this.props.resizeMethod); }, + _getInitialLetter: function() { + var name = this.props.customDisplayName || this.props.member.name; + var initial = name[0]; + if (initial === '@' && name[1]) { + initial = name[1]; + } + return initial.toUpperCase(); + }, + refreshUrl: function() { - var newUrl = this._computeUrl(); + var newUrl = this._getMemberImageUrl(); if (newUrl != this.currentUrl) { this.currentUrl = newUrl; this.setState({imageUrl: newUrl}); } }, - getInitialState: function() { - return { - imageUrl: this._computeUrl() - }; - }, - - - /////////////// - render: function() { - // XXX: recalculates default avatar url constantly - if (this.state.imageUrl === this.defaultAvatarUrl(this.props.member)) { - var initial; - if (this.props.member.name[0]) - initial = this.props.member.name[0].toUpperCase(); - if (initial === '@' && this.props.member.name[1]) - initial = this.props.member.name[1].toUpperCase(); - + var name = this.props.customDisplayName || this.props.member.name; + + if (this.state.imageUrl === this.state.defaultImageUrl) { + var initialLetter = this._getInitialLetter(); return ( - { initialLetter } + ); @@ -104,9 +114,8 @@ module.exports = React.createClass({ + title={name} + {...this.props} /> ); } }); diff --git a/src/components/views/rooms/MemberList.js b/src/components/views/rooms/MemberList.js index f4d017733d..eac5466e88 100644 --- a/src/components/views/rooms/MemberList.js +++ b/src/components/views/rooms/MemberList.js @@ -15,6 +15,7 @@ limitations under the License. */ var React = require('react'); var classNames = require('classnames'); +var Matrix = require("matrix-js-sdk"); var MatrixClientPeg = require("../../../MatrixClientPeg"); var Modal = require("../../../Modal"); var sdk = require('../../../index'); @@ -253,14 +254,12 @@ module.exports = React.createClass({ // already added them. var memberEvent = room.currentState.getInviteForThreePidToken(e.getStateKey()); if (memberEvent) { - console.log("Found match => %s", memberEvent.getStateKey()); return; } - console.log("Display match => "); - /* memberList.push( - - ) */ + + ) }) } } diff --git a/src/components/views/rooms/MemberTile.js b/src/components/views/rooms/MemberTile.js index 0a32441a00..4752c4d539 100644 --- a/src/components/views/rooms/MemberTile.js +++ b/src/components/views/rooms/MemberTile.js @@ -27,24 +27,18 @@ module.exports = React.createClass({ displayName: 'MemberTile', propTypes: { - member: React.PropTypes.any.isRequired, // RoomMember - onFinished: React.PropTypes.func + member: React.PropTypes.any, // RoomMember + onFinished: React.PropTypes.func, + customDisplayName: React.PropTypes.string // for 3pid invites }, getInitialState: function() { return {}; }, - onLeaveClick: function() { - dis.dispatch({ - action: 'leave_room', - room_id: this.props.member.roomId, - }); - this.props.onFinished(); - }, - shouldComponentUpdate: function(nextProps, nextState) { if (this.state.hover !== nextState.hover) return true; + if (!this.props.member) { return false; } // e.g. 3pid members if ( this.member_last_modified_time === undefined || this.member_last_modified_time < nextProps.member.getLastModifiedTime() @@ -70,13 +64,25 @@ module.exports = React.createClass({ }, onClick: function(e) { + if (!this.props.member) { return; } // e.g. 3pid members + dis.dispatch({ action: 'view_user', member: this.props.member, }); }, + _getDisplayName: function() { + if (this.props.customDisplayName) { + return this.props.customDisplayName; + } + return this.props.member.name; + }, + getPowerLabel: function() { + if (!this.props.member) { + return this._getDisplayName(); + } var label = this.props.member.userId; if (this.state.isTargetMod) { label += " - Mod (" + this.props.member.powerLevelNorm + "%)"; @@ -85,68 +91,74 @@ module.exports = React.createClass({ }, render: function() { - var PresenceLabel = sdk.getComponent("rooms.PresenceLabel"); - - this.member_last_modified_time = this.props.member.getLastModifiedTime(); - if (this.props.member.user) { - this.user_last_modified_time = this.props.member.user.getLastModifiedTime(); - } - - var isMyUser = MatrixClientPeg.get().credentials.userId == this.props.member.userId; - - var power; - // if (this.props.member && this.props.member.powerLevelNorm > 0) { - // var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png"; - // power = ; - // } + var member = this.props.member; + var isMyUser = false; + var name = this._getDisplayName(); + var active = -1; var presenceClass = "mx_MemberTile_offline"; - var mainClassName = "mx_MemberTile "; - if (this.props.member.user) { - if (this.props.member.user.presence === "online") { - presenceClass = "mx_MemberTile_online"; - } - else if (this.props.member.user.presence === "unavailable") { - presenceClass = "mx_MemberTile_unavailable"; + + if (member) { + if (member.user) { + this.user_last_modified_time = member.user.getLastModifiedTime(); + + // FIXME: make presence data update whenever User.presence changes... + active = ( + (Date.now() - (member.user.lastPresenceTs - member.user.lastActiveAgo)) || -1 + ); + + if (member.user.presence === "online") { + presenceClass = "mx_MemberTile_online"; + } + else if (member.user.presence === "unavailable") { + presenceClass = "mx_MemberTile_unavailable"; + } } + this.member_last_modified_time = member.getLastModifiedTime(); + isMyUser = MatrixClientPeg.get().credentials.userId == member.userId; + + // if (this.props.member && this.props.member.powerLevelNorm > 0) { + // var img = "img/p/p" + Math.floor(20 * this.props.member.powerLevelNorm / 100) + ".png"; + // power = ; + // } } + + + var mainClassName = "mx_MemberTile "; mainClassName += presenceClass; if (this.state.hover) { mainClassName += " mx_MemberTile_hover"; } - var name = this.props.member.name; - // if (isMyUser) name += " (me)"; // this does nothing other than introduce line wrapping and pain - //var leave = isMyUser ? : null; - var nameEl; if (this.state.hover) { - // FIXME: make presence data update whenever User.presence changes... - var active = this.props.member.user ? ((Date.now() - (this.props.member.user.lastPresenceTs - this.props.member.user.lastActiveAgo)) || -1) : -1; - + var presenceState = (member && member.user) ? member.user.presence : null; + var PresenceLabel = sdk.getComponent("rooms.PresenceLabel"); nameEl = (
{ name }
+ presenceState={presenceState} />
); } else { - nameEl = + nameEl = (
{ name }
+ ); } var MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); + return (
- - { power } +
{ nameEl }