mirror of https://github.com/vector-im/riot-web
				
				
				
			Add MemberPresenceAvatar and control presence ourselves
Includes rudimentary support for custom statuses and user-controlled status. Some minor tweaks have also been made to better control how we advertise our presence. Signed-off-by: Travis Ralston <travpc@gmail.com>pull/21833/head
							parent
							
								
									d7c3fc9adb
								
							
						
					
					
						commit
						6cd07731c4
					
				|  | @ -93,6 +93,7 @@ class MatrixClientPeg { | |||
|         const opts = utils.deepCopy(this.opts); | ||||
|         // the react sdk doesn't work without this, so don't allow
 | ||||
|         opts.pendingEventOrdering = "detached"; | ||||
|         opts.disablePresence = true; // we do this manually
 | ||||
| 
 | ||||
|         try { | ||||
|             const promise = this.matrixClient.store.startup(); | ||||
|  |  | |||
|  | @ -56,13 +56,27 @@ class Presence { | |||
|         return this.state; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the current status message. | ||||
|      * @returns {String} the status message, may be null | ||||
|      */ | ||||
|     getStatusMessage() { | ||||
|         return this.statusMessage; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the presence state. | ||||
|      * If the state has changed, the Home Server will be notified. | ||||
|      * @param {string} newState the new presence state (see PRESENCE enum) | ||||
|      * @param {String} statusMessage an optional status message for the presence | ||||
|      * @param {boolean} maintain true to have this status maintained by this tracker | ||||
|      */ | ||||
|     setState(newState) { | ||||
|         if (newState === this.state) { | ||||
|     setState(newState, statusMessage=null, maintain=false) { | ||||
|         if (this.maintain) { | ||||
|             // Don't update presence if we're maintaining a particular status
 | ||||
|             return; | ||||
|         } | ||||
|         if (newState === this.state && statusMessage === this.statusMessage) { | ||||
|             return; | ||||
|         } | ||||
|         if (PRESENCE_STATES.indexOf(newState) === -1) { | ||||
|  | @ -72,21 +86,37 @@ class Presence { | |||
|             return; | ||||
|         } | ||||
|         const old_state = this.state; | ||||
|         const old_message = this.statusMessage; | ||||
|         this.state = newState; | ||||
|         this.statusMessage = statusMessage; | ||||
|         this.maintain = maintain; | ||||
| 
 | ||||
|         if (MatrixClientPeg.get().isGuest()) { | ||||
|             return; // don't try to set presence when a guest; it won't work.
 | ||||
|         } | ||||
| 
 | ||||
|         const updateContent = { | ||||
|             presence: this.state, | ||||
|             status_msg: this.statusMessage ? this.statusMessage : '', | ||||
|         }; | ||||
| 
 | ||||
|         const self = this; | ||||
|         MatrixClientPeg.get().setPresence(this.state).done(function() { | ||||
|         MatrixClientPeg.get().setPresence(updateContent).done(function() { | ||||
|             console.log("Presence: %s", newState); | ||||
| 
 | ||||
|             // We have to dispatch because the js-sdk is unreliable at telling us about our own presence
 | ||||
|             dis.dispatch({action: "self_presence_updated", statusInfo: updateContent}); | ||||
|         }, function(err) { | ||||
|             console.error("Failed to set presence: %s", err); | ||||
|             self.state = old_state; | ||||
|             self.statusMessage = old_message; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     stopMaintainingStatus() { | ||||
|         this.maintain = false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Callback called when the user made no action on the page for UNAVAILABLE_TIME ms. | ||||
|      * @private | ||||
|  | @ -95,7 +125,8 @@ class Presence { | |||
|         this.setState("unavailable"); | ||||
|     } | ||||
| 
 | ||||
|     _onUserActivity() { | ||||
|     _onUserActivity(payload) { | ||||
|         if (payload.action === "sync_state" || payload.action === "self_presence_updated") return; | ||||
|         this._resetTimer(); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,135 @@ | |||
| /* | ||||
|  Copyright 2017 Travis Ralston | ||||
| 
 | ||||
|  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'; | ||||
| 
 | ||||
| import React from "react"; | ||||
| import * as sdk from "../../../index"; | ||||
| import MatrixClientPeg from "../../../MatrixClientPeg"; | ||||
| import AccessibleButton from '../elements/AccessibleButton'; | ||||
| import Presence from "../../../Presence"; | ||||
| import dispatcher from "../../../dispatcher"; | ||||
| 
 | ||||
| module.exports = React.createClass({ | ||||
|     displayName: 'MemberPresenceAvatar', | ||||
| 
 | ||||
|     propTypes: { | ||||
|         member: React.PropTypes.object.isRequired, | ||||
|         width: React.PropTypes.number, | ||||
|         height: React.PropTypes.number, | ||||
|         resizeMethod: React.PropTypes.string, | ||||
|     }, | ||||
| 
 | ||||
|     getDefaultProps: function() { | ||||
|         return { | ||||
|             width: 40, | ||||
|             height: 40, | ||||
|             resizeMethod: 'crop', | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     getInitialState: function() { | ||||
|         const presenceState = this.props.member.user.presence; | ||||
|         return { | ||||
|             status: presenceState, | ||||
|         }; | ||||
|     }, | ||||
| 
 | ||||
|     componentWillMount: function() { | ||||
|         MatrixClientPeg.get().on("User.presence", this.onUserPresence); | ||||
|         this.dispatcherRef = dispatcher.register(this.onAction); | ||||
|     }, | ||||
| 
 | ||||
|     componentWillUnmount: function() { | ||||
|         if (MatrixClientPeg.get()) { | ||||
|             MatrixClientPeg.get().removeListener("User.presence", this.onUserPresence); | ||||
|         } | ||||
|         dispatcher.unregister(this.dispatcherRef); | ||||
|     }, | ||||
| 
 | ||||
|     onAction: function(payload) { | ||||
|         if (payload.action !== "self_presence_updated") return; | ||||
|         if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) return; | ||||
|         this.setState({ | ||||
|             status: payload.statusInfo.presence, | ||||
|             message: payload.statusInfo.status_msg, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     onUserPresence: function(event, user) { | ||||
|         if (user.userId !== MatrixClientPeg.get().getUserId()) return; | ||||
|         this.setState({ | ||||
|             status: user.presence, | ||||
|             message: user.presenceStatusMsg, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     onClick: function() { | ||||
|         if (Presence.getState() === "online") { | ||||
|             Presence.setState("unavailable", "This is a message", true); | ||||
|         } else { | ||||
|             Presence.stopMaintainingStatus(); | ||||
|         } | ||||
|         console.log("CLICK"); | ||||
| 
 | ||||
|         const presenceState = this.props.member.user.presence; | ||||
|         const presenceLastActiveAgo = this.props.member.user.lastActiveAgo; | ||||
|         const presenceLastTs = this.props.member.user.lastPresenceTs; | ||||
|         const presenceCurrentlyActive = this.props.member.user.currentlyActive; | ||||
|         const presenceMessage = this.props.member.user.presenceStatusMsg; | ||||
| 
 | ||||
|         console.log({ | ||||
|             presenceState, | ||||
|             presenceLastActiveAgo, | ||||
|             presenceLastTs, | ||||
|             presenceCurrentlyActive, | ||||
|             presenceMessage, | ||||
|         }); | ||||
|     }, | ||||
| 
 | ||||
|     render: function() { | ||||
|         const MemberAvatar = sdk.getComponent("avatars.MemberAvatar"); | ||||
| 
 | ||||
|         let onClickFn = null; | ||||
|         if (this.props.member.userId === MatrixClientPeg.get().getUserId()) { | ||||
|             onClickFn = this.onClick; | ||||
|         } | ||||
| 
 | ||||
|         const avatarNode = ( | ||||
|             <MemberAvatar member={this.props.member} width={this.props.width} height={this.props.height} | ||||
|                           resizeMethod={this.props.resizeMethod}/> | ||||
|         ); | ||||
|         const statusNode = ( | ||||
|             <span className={"mx_MemberPresenceAvatar_status mx_MemberPresenceAvatar_status_" + this.state.status}/> | ||||
|         ); | ||||
| 
 | ||||
|         let avatar = ( | ||||
|             <div className="mx_MemberPresenceAvatar"> | ||||
|                 {avatarNode} | ||||
|                 {statusNode} | ||||
|             </div> | ||||
|         ); | ||||
|         if (onClickFn) { | ||||
|             avatar = ( | ||||
|                 <AccessibleButton onClick={onClickFn} className="mx_MemberPresenceAvatar" element="div"> | ||||
|                     {avatarNode} | ||||
|                     {statusNode} | ||||
|                 </AccessibleButton> | ||||
|             ); | ||||
|         } | ||||
|         return avatar; | ||||
|     }, | ||||
| }); | ||||
|  | @ -238,7 +238,7 @@ export default class MessageComposer extends React.Component { | |||
|     render() { | ||||
|         const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId); | ||||
|         const uploadInputStyle = {display: 'none'}; | ||||
|         const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); | ||||
|         const MemberPresenceAvatar = sdk.getComponent('avatars.MemberPresenceAvatar'); | ||||
|         const TintableSvg = sdk.getComponent("elements.TintableSvg"); | ||||
|         const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); | ||||
| 
 | ||||
|  | @ -246,7 +246,7 @@ export default class MessageComposer extends React.Component { | |||
| 
 | ||||
|         controls.push( | ||||
|             <div key="controls_avatar" className="mx_MessageComposer_avatar"> | ||||
|                 <MemberAvatar member={me} width={24} height={24} /> | ||||
|                 <MemberPresenceAvatar member={me} width={24} height={24} /> | ||||
|             </div>, | ||||
|         ); | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 Travis Ralston
						Travis Ralston