Merge pull request #369 from matrix-org/wmwragg/mention-state-menu

Wmwragg/mention state menu
This commit is contained in:
Matthew Hodgson 2016-08-03 15:22:39 +01:00 committed by GitHub
commit f95a11a9bf
7 changed files with 158 additions and 51 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -25,6 +25,7 @@ limitations under the License.
*/
module.exports.components = {};
module.exports.components['structures.ContextualMenu'] = require('./components/structures/ContextualMenu');
module.exports.components['structures.CreateRoom'] = require('./components/structures/CreateRoom');
module.exports.components['structures.MatrixChat'] = require('./components/structures/MatrixChat');
module.exports.components['structures.MessagePanel'] = require('./components/structures/MessagePanel');

View file

@ -17,6 +17,7 @@ limitations under the License.
'use strict';
var classNames = require('classnames');
var React = require('react');
var ReactDOM = require('react-dom');
@ -27,6 +28,12 @@ var ReactDOM = require('react-dom');
module.exports = {
ContextualMenuContainerId: "mx_ContextualMenu_Container",
propTypes: {
menuWidth: React.PropTypes.number,
menuHeight: React.PropTypes.number,
chevronOffset: React.PropTypes.number,
},
getOrCreateContainer: function() {
var container = document.getElementById(this.ContextualMenuContainerId);
@ -45,29 +52,50 @@ module.exports = {
var closeMenu = function() {
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
if (props && props.onFinished) props.onFinished.apply(null, arguments);
if (props && props.onFinished) {
props.onFinished.apply(null, arguments);
}
};
var position = {
top: props.top - 20,
top: props.top,
};
var chevronOffset = {
top: props.chevronOffset,
}
var chevron = null;
if (props.left) {
chevron = <img className="mx_ContextualMenu_chevron_left" src="img/chevron-left.png" width="9" height="16" />
position.left = props.left + 8;
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_left"></div>
position.left = props.left;
} else {
chevron = <img className="mx_ContextualMenu_chevron_right" src="img/chevron-right.png" width="9" height="16" />
position.right = props.right + 8;
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_right"></div>
position.right = props.right;
}
var className = 'mx_ContextualMenu_wrapper';
var menuClasses = classNames({
'mx_ContextualMenu': true,
'mx_ContextualMenu_left': props.left,
'mx_ContextualMenu_right': !props.left,
});
var menuSize = {};
if (props.menuWidth) {
menuSize.width = props.menuWidth;
}
if (props.menuHeight) {
menuSize.height = props.menuHeight;
}
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
// property set here so you can't close the menu from a button click!
var menu = (
<div className={className}>
<div className="mx_ContextualMenu" style={position}>
<div className={className} style={position}>
<div className={menuClasses} style={menuSize}>
{chevron}
<Element {...props} onFinished={closeMenu}/>
</div>

View file

@ -20,7 +20,7 @@ var Favico = require('favico.js');
var MatrixClientPeg = require("../../MatrixClientPeg");
var Notifier = require("../../Notifier");
var ContextualMenu = require("../../ContextualMenu");
var ContextualMenu = require("./ContextualMenu");
var RoomListSorter = require("../../RoomListSorter");
var UserActivity = require("../../UserActivity");
var Presence = require("../../Presence");

View file

@ -23,7 +23,7 @@ var sdk = require('../../../index');
var MatrixClientPeg = require('../../../MatrixClientPeg')
var TextForEvent = require('../../../TextForEvent');
var ContextualMenu = require('../../../ContextualMenu');
var ContextualMenu = require('../../structures/ContextualMenu');
var dispatcher = require("../../../dispatcher");
var ObjectUtils = require('../../../ObjectUtils');
@ -249,12 +249,15 @@ module.exports = React.createClass({
},
onEditClicked: function(e) {
var MessageContextMenu = sdk.getComponent('rooms.MessageContextMenu');
var MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
var buttonRect = e.target.getBoundingClientRect()
var x = buttonRect.right;
var y = buttonRect.top + (e.target.height / 2);
// The window X and Y offsets are to adjust position when zoomed in to page
var x = buttonRect.right + window.pageXOffset;
var y = (buttonRect.top + (e.target.height / 2) + window.pageYOffset) - 19;
var self = this;
ContextualMenu.createMenu(MessageContextMenu, {
chevronOffset: 10,
mxEvent: this.props.mxEvent,
left: x,
top: y,

View file

@ -268,9 +268,11 @@ module.exports = React.createClass({
},
_repositionTooltip: function(e) {
if (this.tooltip && this.tooltip.parentElement) {
// We access the parent of the parent, as the tooltip is inside a container
// Needs refactoring into a better multipurpose tooltip
if (this.tooltip && this.tooltip.parentElement && this.tooltip.parentElement.parentElement) {
var scroll = ReactDOM.findDOMNode(this);
this.tooltip.style.top = (70 + scroll.parentElement.offsetTop + this.tooltip.parentElement.offsetTop - this._getScrollNode().scrollTop) + "px";
this.tooltip.style.top = (3 + scroll.parentElement.offsetTop + this.tooltip.parentElement.parentElement.offsetTop - this._getScrollNode().scrollTop) + "px";
}
},

View file

@ -21,6 +21,7 @@ var classNames = require('classnames');
var dis = require("../../../dispatcher");
var MatrixClientPeg = require('../../../MatrixClientPeg');
var sdk = require('../../../index');
var ContextualMenu = require('../../structures/ContextualMenu');
import {emojifyText} from '../../../HtmlUtils';
module.exports = React.createClass({
@ -43,16 +44,48 @@ module.exports = React.createClass({
},
getInitialState: function() {
var areNotifsMuted = false;
var cli = MatrixClientPeg.get();
if (!cli.isGuest()) {
var roomPushRule = cli.getRoomPushRule("global", this.props.room.roomId);
if (roomPushRule) {
if (0 <= roomPushRule.actions.indexOf("dont_notify")) {
areNotifsMuted = true;
}
}
}
return({
hover : false,
badgeHover : false,
menu: false,
areNotifsMuted: areNotifsMuted,
});
},
onAction: function(payload) {
switch (payload.action) {
case 'notification_change':
// Is the notification about this room?
if (payload.roomId === this.props.room.roomId) {
this.setState( { areNotifsMuted : payload.isMuted });
}
break;
}
},
componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction);
},
componentWillUnmount: function() {
dis.unregister(this.dispatcherRef);
},
onClick: function() {
dis.dispatch({
action: 'view_room',
room_id: this.props.room.roomId
room_id: this.props.room.roomId,
});
},
@ -65,13 +98,47 @@ module.exports = React.createClass({
},
badgeOnMouseEnter: function() {
this.setState( { badgeHover : true } );
// Only allow none guests to access the context menu
// and only change it if it needs to change
if (!MatrixClientPeg.get().isGuest() && !this.state.badgeHover) {
this.setState( { badgeHover : true } );
}
},
badgeOnMouseLeave: function() {
this.setState( { badgeHover : false } );
},
onBadgeClicked: function(e) {
// Only allow none guests to access the context menu
if (!MatrixClientPeg.get().isGuest()) {
// If the badge is clicked, then no longer show tooltip
if (this.props.collapsed) {
this.setState({ hover: false });
}
var Menu = sdk.getComponent('context_menus.NotificationStateContextMenu');
var elementRect = e.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
var x = elementRect.right + window.pageXOffset + 3;
var y = (elementRect.top + (elementRect.height / 2) + window.pageYOffset) - 53;
var self = this;
ContextualMenu.createMenu(Menu, {
menuWidth: 188,
menuHeight: 126,
chevronOffset: 45,
left: x,
top: y,
room: this.props.room,
onFinished: function() {
self.setState({ menu: false });
}
});
this.setState({ menu: true });
}
},
render: function() {
var myUserId = MatrixClientPeg.get().credentials.userId;
var me = this.props.room.currentState.members[myUserId];
@ -84,60 +151,63 @@ module.exports = React.createClass({
'mx_RoomTile_selected': this.props.selected,
'mx_RoomTile_unread': this.props.unread,
'mx_RoomTile_unreadNotify': notificationCount > 0,
'mx_RoomTile_read': !(this.props.highlight || notificationCount > 0),
'mx_RoomTile_highlight': this.props.highlight,
'mx_RoomTile_invited': (me && me.membership == 'invite'),
'mx_RoomTile_menu': this.state.menu,
});
var avatarClasses = classNames({
'mx_RoomTile_avatar': true,
'mx_RoomTile_mute': this.state.areNotifsMuted,
});
var badgeClasses = classNames({
'mx_RoomTile_badge': true,
'mx_RoomTile_badgeButton': this.state.badgeHover || this.state.menu,
'mx_RoomTile_badgeMute': this.state.areNotifsMuted,
});
// XXX: We should never display raw room IDs, but sometimes the
// room name js sdk gives is undefined (cannot repro this -- k)
var name = this.props.room.name || this.props.room.roomId;
name = name.replace(":", ":\u200b"); // add a zero-width space to allow linewrapping after the colon
var badge;
var badgeContent;
var badgeClasses;
if (this.state.badgeHover) {
if (this.state.badgeHover || this.state.menu) {
badgeContent = "\u00B7\u00B7\u00B7";
} else if (this.props.highlight || notificationCount > 0) {
badgeContent = notificationCount ? notificationCount : '!';
var limitedCount = (notificationCount > 99) ? '99+' : notificationCount;
badgeContent = notificationCount ? limitedCount : '!';
} else {
badgeContent = '\u200B';
}
if (this.props.highlight || notificationCount > 0) {
badgeClasses = "mx_RoomTile_badge";
if (this.state.areNotifsMuted && !(this.state.badgeHover || this.state.menu)) {
badge = <div className={ badgeClasses } onClick={this.onBadgeClicked} onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}><img className="mx_RoomTile_badgeIcon" src="img/icon-context-mute.svg" width="16" height="12" /></div>;
} else {
badgeClasses = "mx_RoomTile_badge mx_RoomTile_badge_no_unread";
badge = <div className={ badgeClasses } onClick={this.onBadgeClicked} onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}>{ badgeContent }</div>;
}
badge = <div className={ badgeClasses } onMouseEnter={this.badgeOnMouseEnter} onMouseLeave={this.badgeOnMouseLeave}>{ badgeContent }</div>;
/*
if (this.props.highlight) {
badge = <div className="mx_RoomTile_badge">!</div>;
}
else if (this.props.unread) {
badge = <div className="mx_RoomTile_badge">1</div>;
}
var nameCell;
if (badge) {
nameCell = <div className="mx_RoomTile_nameBadge"><div className="mx_RoomTile_name">{name}</div><div className="mx_RoomTile_badgeCell">{badge}</div></div>;
}
else {
nameCell = <div className="mx_RoomTile_name">{name}</div>;
}
*/
var label;
var tooltip;
if (!this.props.collapsed) {
var className = 'mx_RoomTile_name' + (this.props.isInvite ? ' mx_RoomTile_invite' : '');
var nameClasses = classNames({
'mx_RoomTile_name': true,
'mx_RoomTile_invite': this.props.isInvite,
'mx_RoomTile_mute': this.state.areNotifsMuted,
'mx_RoomTile_badgeShown': this.props.highlight || notificationCount > 0 || this.state.badgeHover || this.state.menu || this.state.areNotifsMuted,
});
let nameHTML = emojifyText(name);
if (this.props.selected) {
name = <span dangerouslySetInnerHTML={nameHTML}></span>;
label = <div className={ className }>{ name }</div>;
let nameSelected = <span dangerouslySetInnerHTML={nameHTML}></span>;
label = <div title={ name } onClick={this.onClick} className={ nameClasses }>{ nameSelected }</div>;
} else {
label = <div className={ className } dangerouslySetInnerHTML={nameHTML}></div>;
label = <div title={ name } onClick={this.onClick} className={ nameClasses } dangerouslySetInnerHTML={nameHTML}></div>;
}
}
else if (this.state.hover) {
@ -160,13 +230,16 @@ module.exports = React.createClass({
var connectDropTarget = this.props.connectDropTarget;
return connectDragSource(connectDropTarget(
<div className={classes} onClick={this.onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div className="mx_RoomTile_avatar">
<RoomAvatar room={this.props.room} width={24} height={24} />
<div className={classes} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<div className={avatarClasses}>
<RoomAvatar onClick={this.onClick} room={this.props.room} width={24} height={24} />
</div>
<div className="mx_RoomTile_nameContainer">
{ label }
{ badge }
</div>
{ label }
{ badge }
{ incomingCallBox }
{ tooltip }
</div>
));
}