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>
This commit is contained in:
parent
d7c3fc9adb
commit
6cd07731c4
4 changed files with 173 additions and 6 deletions
|
@ -93,6 +93,7 @@ class MatrixClientPeg {
|
||||||
const opts = utils.deepCopy(this.opts);
|
const opts = utils.deepCopy(this.opts);
|
||||||
// the react sdk doesn't work without this, so don't allow
|
// the react sdk doesn't work without this, so don't allow
|
||||||
opts.pendingEventOrdering = "detached";
|
opts.pendingEventOrdering = "detached";
|
||||||
|
opts.disablePresence = true; // we do this manually
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const promise = this.matrixClient.store.startup();
|
const promise = this.matrixClient.store.startup();
|
||||||
|
|
|
@ -56,13 +56,27 @@ class Presence {
|
||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current status message.
|
||||||
|
* @returns {String} the status message, may be null
|
||||||
|
*/
|
||||||
|
getStatusMessage() {
|
||||||
|
return this.statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the presence state.
|
* Set the presence state.
|
||||||
* If the state has changed, the Home Server will be notified.
|
* If the state has changed, the Home Server will be notified.
|
||||||
* @param {string} newState the new presence state (see PRESENCE enum)
|
* @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) {
|
setState(newState, statusMessage=null, maintain=false) {
|
||||||
if (newState === this.state) {
|
if (this.maintain) {
|
||||||
|
// Don't update presence if we're maintaining a particular status
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newState === this.state && statusMessage === this.statusMessage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PRESENCE_STATES.indexOf(newState) === -1) {
|
if (PRESENCE_STATES.indexOf(newState) === -1) {
|
||||||
|
@ -72,21 +86,37 @@ class Presence {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const old_state = this.state;
|
const old_state = this.state;
|
||||||
|
const old_message = this.statusMessage;
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
|
this.statusMessage = statusMessage;
|
||||||
|
this.maintain = maintain;
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return; // don't try to set presence when a guest; it won't work.
|
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;
|
const self = this;
|
||||||
MatrixClientPeg.get().setPresence(this.state).done(function() {
|
MatrixClientPeg.get().setPresence(updateContent).done(function() {
|
||||||
console.log("Presence: %s", newState);
|
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) {
|
}, function(err) {
|
||||||
console.error("Failed to set presence: %s", err);
|
console.error("Failed to set presence: %s", err);
|
||||||
self.state = old_state;
|
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.
|
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
|
||||||
* @private
|
* @private
|
||||||
|
@ -95,7 +125,8 @@ class Presence {
|
||||||
this.setState("unavailable");
|
this.setState("unavailable");
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUserActivity() {
|
_onUserActivity(payload) {
|
||||||
|
if (payload.action === "sync_state" || payload.action === "self_presence_updated") return;
|
||||||
this._resetTimer();
|
this._resetTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
135
src/components/views/avatars/MemberPresenceAvatar.js
Normal file
135
src/components/views/avatars/MemberPresenceAvatar.js
Normal file
|
@ -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() {
|
render() {
|
||||||
const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
const uploadInputStyle = {display: 'none'};
|
const uploadInputStyle = {display: 'none'};
|
||||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
const MemberPresenceAvatar = sdk.getComponent('avatars.MemberPresenceAvatar');
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ export default class MessageComposer extends React.Component {
|
||||||
|
|
||||||
controls.push(
|
controls.push(
|
||||||
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
||||||
<MemberAvatar member={me} width={24} height={24} />
|
<MemberPresenceAvatar member={me} width={24} height={24} />
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue