Merge remote-tracking branch 'origin/develop' into develop
commit
247df92f17
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,10 @@ const FEATURES = [
|
||||||
id: 'feature_pinning',
|
id: 'feature_pinning',
|
||||||
name: _td("Message Pinning"),
|
name: _td("Message Pinning"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'feature_presence_management',
|
||||||
|
name: _td("Presence Management"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -33,6 +33,7 @@ module.exports = {
|
||||||
menuHeight: React.PropTypes.number,
|
menuHeight: React.PropTypes.number,
|
||||||
chevronOffset: React.PropTypes.number,
|
chevronOffset: React.PropTypes.number,
|
||||||
menuColour: React.PropTypes.string,
|
menuColour: React.PropTypes.string,
|
||||||
|
chevronFace: React.PropTypes.string, // top, bottom, left, right
|
||||||
},
|
},
|
||||||
|
|
||||||
getOrCreateContainer: function() {
|
getOrCreateContainer: function() {
|
||||||
|
@ -58,12 +59,30 @@ module.exports = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const position = {
|
const position = {};
|
||||||
top: props.top,
|
let chevronFace = null;
|
||||||
};
|
|
||||||
|
if (props.top) {
|
||||||
|
position.top = props.top;
|
||||||
|
} else {
|
||||||
|
position.bottom = props.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.left) {
|
||||||
|
position.left = props.left;
|
||||||
|
chevronFace = 'left';
|
||||||
|
} else {
|
||||||
|
position.right = props.right;
|
||||||
|
chevronFace = 'right';
|
||||||
|
}
|
||||||
|
|
||||||
const chevronOffset = {};
|
const chevronOffset = {};
|
||||||
if (props.chevronOffset) {
|
if (props.chevronFace) {
|
||||||
|
chevronFace = props.chevronFace;
|
||||||
|
}
|
||||||
|
if (chevronFace === 'top' || chevronFace === 'bottom') {
|
||||||
|
chevronOffset.left = props.chevronOffset;
|
||||||
|
} else {
|
||||||
chevronOffset.top = props.chevronOffset;
|
chevronOffset.top = props.chevronOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,28 +93,27 @@ module.exports = {
|
||||||
.mx_ContextualMenu_chevron_left:after {
|
.mx_ContextualMenu_chevron_left:after {
|
||||||
border-right-color: ${props.menuColour};
|
border-right-color: ${props.menuColour};
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ContextualMenu_chevron_right:after {
|
.mx_ContextualMenu_chevron_right:after {
|
||||||
border-left-color: ${props.menuColour};
|
border-left-color: ${props.menuColour};
|
||||||
}
|
}
|
||||||
|
.mx_ContextualMenu_chevron_top:after {
|
||||||
|
border-left-color: ${props.menuColour};
|
||||||
|
}
|
||||||
|
.mx_ContextualMenu_chevron_bottom:after {
|
||||||
|
border-left-color: ${props.menuColour};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let chevron = null;
|
const chevron = <div style={chevronOffset} className={"mx_ContextualMenu_chevron_" + chevronFace}></div>;
|
||||||
if (props.left) {
|
|
||||||
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_left"></div>;
|
|
||||||
position.left = props.left;
|
|
||||||
} else {
|
|
||||||
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_right"></div>;
|
|
||||||
position.right = props.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = 'mx_ContextualMenu_wrapper';
|
const className = 'mx_ContextualMenu_wrapper';
|
||||||
|
|
||||||
const menuClasses = classNames({
|
const menuClasses = classNames({
|
||||||
'mx_ContextualMenu': true,
|
'mx_ContextualMenu': true,
|
||||||
'mx_ContextualMenu_left': props.left,
|
'mx_ContextualMenu_left': chevronFace === 'left',
|
||||||
'mx_ContextualMenu_right': !props.left,
|
'mx_ContextualMenu_right': chevronFace === 'right',
|
||||||
|
'mx_ContextualMenu_top': chevronFace === 'top',
|
||||||
|
'mx_ContextualMenu_bottom': chevronFace === 'bottom',
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuStyle = {};
|
const menuStyle = {};
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
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";
|
||||||
|
import UserSettingsStore from "../../../UserSettingsStore";
|
||||||
|
import * as ContextualMenu from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
|
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;
|
||||||
|
const presenceMessage = this.props.member.user.presenceStatusMsg;
|
||||||
|
return {
|
||||||
|
status: presenceState,
|
||||||
|
message: presenceMessage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onStatusChange: function(newStatus) {
|
||||||
|
Presence.stopMaintainingStatus();
|
||||||
|
if (newStatus === "online") {
|
||||||
|
Presence.setState(newStatus);
|
||||||
|
} else Presence.setState(newStatus, null, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick: function(e) {
|
||||||
|
const PresenceContextMenu = sdk.getComponent('context_menus.PresenceContextMenu');
|
||||||
|
const elementRect = e.target.getBoundingClientRect();
|
||||||
|
|
||||||
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
|
const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3;
|
||||||
|
const chevronOffset = 12;
|
||||||
|
let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
||||||
|
y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
|
||||||
|
|
||||||
|
ContextualMenu.createMenu(PresenceContextMenu, {
|
||||||
|
chevronOffset: chevronOffset,
|
||||||
|
chevronFace: 'bottom',
|
||||||
|
left: x,
|
||||||
|
top: y,
|
||||||
|
menuWidth: 125,
|
||||||
|
currentStatus: this.state.status,
|
||||||
|
onChange: this.onStatusChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
// 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;
|
||||||
|
},
|
||||||
|
|
||||||
|
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} />
|
||||||
|
);
|
||||||
|
let statusNode = (
|
||||||
|
<span className={"mx_MemberPresenceAvatar_status mx_MemberPresenceAvatar_status_" + this.state.status} />
|
||||||
|
);
|
||||||
|
|
||||||
|
// LABS: Disable presence management functions for now
|
||||||
|
if (!UserSettingsStore.isFeatureEnabled("feature_presence_management")) {
|
||||||
|
statusNode = null;
|
||||||
|
onClickFn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,7 @@
|
||||||
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
|
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
|
||||||
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
||||||
"Message Pinning": "Message Pinning",
|
"Message Pinning": "Message Pinning",
|
||||||
|
"Presence Management": "Presence Management",
|
||||||
"%(displayName)s is typing": "%(displayName)s is typing",
|
"%(displayName)s is typing": "%(displayName)s is typing",
|
||||||
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
||||||
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
|
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
|
||||||
|
|
Loading…
Reference in New Issue