Merge pull request #3715 from matrix-org/t3chguy/fix_roomlist_context_menu_regression

Fix remaining context menu regressions
This commit is contained in:
Michael Telatynski 2019-12-11 09:36:45 +00:00 committed by GitHub
commit cb50f5ff5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 45 deletions

View file

@ -419,7 +419,7 @@ export const toRightOf = (elementRect, chevronOffset=12) => {
const left = elementRect.right + window.pageXOffset + 3; const left = elementRect.right + window.pageXOffset + 3;
let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset; let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
top -= chevronOffset + 8; // where 8 is half the height of the chevron top -= chevronOffset + 8; // where 8 is half the height of the chevron
return {left, top}; return {left, top, chevronOffset};
}; };
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect // Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect

View file

@ -108,7 +108,7 @@ export default class ShareDialog extends React.Component {
const buttonRect = e.target.getBoundingClientRect(); const buttonRect = e.target.getBoundingClientRect();
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextMenu.createMenu(GenericTextContextMenu, { const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 11), ...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'), message: successful ? _t('Copied!') : _t('Failed to copy'),
}); });
// Drop a reference to this close handler for componentWillUnmount // Drop a reference to this close handler for componentWillUnmount

View file

@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {createRef} from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class'; import createReactClass from 'create-react-class';
import { MatrixClient } from 'matrix-js-sdk'; import { MatrixClient } from 'matrix-js-sdk';
@ -27,6 +27,7 @@ import classNames from 'classnames';
import MatrixClientPeg from "../../../MatrixClientPeg"; import MatrixClientPeg from "../../../MatrixClientPeg";
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu"; import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextMenu";
// XXX this class copies a lot from RoomTile.js
export default createReactClass({ export default createReactClass({
displayName: 'GroupInviteTile', displayName: 'GroupInviteTile',
@ -47,10 +48,6 @@ export default createReactClass({
}); });
}, },
componentDidMount: function() {
this._contextMenuButton = createRef();
},
onClick: function(e) { onClick: function(e) {
dis.dispatch({ dis.dispatch({
action: 'view_group', action: 'view_group',
@ -74,16 +71,12 @@ export default createReactClass({
}); });
}, },
openMenu: function(e) { _showContextMenu: function(boundingClientRect) {
// Only allow non-guests to access the context menu // Only allow non-guests to access the context menu
if (MatrixClientPeg.get().isGuest()) return; if (MatrixClientPeg.get().isGuest()) return;
// Prevent the GroupInviteTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
const state = { const state = {
menuDisplayed: true, contextMenuPosition: boundingClientRect,
}; };
// If the badge is clicked, then no longer show tooltip // If the badge is clicked, then no longer show tooltip
@ -94,9 +87,28 @@ export default createReactClass({
this.setState(state); this.setState(state);
}, },
onContextMenuButtonClick: function(e) {
// Prevent the RoomTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
this._showContextMenu(e.target.getBoundingClientRect());
},
onContextMenu: function(e) {
// Prevent the native context menu
e.preventDefault();
this._showContextMenu({
right: e.clientX,
top: e.clientY,
height: 0,
});
},
closeMenu: function() { closeMenu: function() {
this.setState({ this.setState({
menuDisplayed: false, contextMenuPosition: null,
}); });
}, },
@ -110,15 +122,16 @@ export default createReactClass({
const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />; const av = <BaseAvatar name={groupName} width={24} height={24} url={httpAvatarUrl} />;
const isMenuDisplayed = Boolean(this.state.contextMenuPosition);
const nameClasses = classNames('mx_RoomTile_name mx_RoomTile_invite mx_RoomTile_badgeShown', { const nameClasses = classNames('mx_RoomTile_name mx_RoomTile_invite mx_RoomTile_badgeShown', {
'mx_RoomTile_badgeShown': this.state.badgeHover || this.state.menuDisplayed, 'mx_RoomTile_badgeShown': this.state.badgeHover || isMenuDisplayed,
}); });
const label = <div title={this.props.group.groupId} className={nameClasses} dir="auto"> const label = <div title={this.props.group.groupId} className={nameClasses} dir="auto">
{ groupName } { groupName }
</div>; </div>;
const badgeEllipsis = this.state.badgeHover || this.state.menuDisplayed; const badgeEllipsis = this.state.badgeHover || isMenuDisplayed;
const badgeClasses = classNames('mx_RoomTile_badge mx_RoomTile_highlight', { const badgeClasses = classNames('mx_RoomTile_badge mx_RoomTile_highlight', {
'mx_RoomTile_badgeButton': badgeEllipsis, 'mx_RoomTile_badgeButton': badgeEllipsis,
}); });
@ -127,10 +140,9 @@ export default createReactClass({
const badge = ( const badge = (
<ContextMenuButton <ContextMenuButton
className={badgeClasses} className={badgeClasses}
inputRef={this._contextMenuButton} onClick={this.onContextMenuButtonClick}
onClick={this.openMenu}
label={_t("Options")} label={_t("Options")}
isExpanded={this.state.menuDisplayed} isExpanded={isMenuDisplayed}
> >
{ badgeContent } { badgeContent }
</ContextMenuButton> </ContextMenuButton>
@ -143,17 +155,16 @@ export default createReactClass({
} }
const classes = classNames('mx_RoomTile mx_RoomTile_highlight', { const classes = classNames('mx_RoomTile mx_RoomTile_highlight', {
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed, 'mx_RoomTile_menuDisplayed': isMenuDisplayed,
'mx_RoomTile_selected': this.state.selected, 'mx_RoomTile_selected': this.state.selected,
'mx_GroupInviteTile': true, 'mx_GroupInviteTile': true,
}); });
let contextMenu; let contextMenu;
if (this.state.menuDisplayed) { if (isMenuDisplayed) {
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
const GroupInviteTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu'); const GroupInviteTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu');
contextMenu = ( contextMenu = (
<ContextMenu {...toRightOf(elementRect)} onFinished={this.closeMenu}> <ContextMenu {...toRightOf(this.state.contextMenuPosition)} onFinished={this.closeMenu}>
<GroupInviteTileContextMenu group={this.props.group} onFinished={this.closeMenu} /> <GroupInviteTileContextMenu group={this.props.group} onFinished={this.closeMenu} />
</ContextMenu> </ContextMenu>
); );

View file

@ -280,7 +280,7 @@ module.exports = createReactClass({
const buttonRect = e.target.getBoundingClientRect(); const buttonRect = e.target.getBoundingClientRect();
const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu');
const {close} = ContextMenu.createMenu(GenericTextContextMenu, { const {close} = ContextMenu.createMenu(GenericTextContextMenu, {
...toRightOf(buttonRect, 11), ...toRightOf(buttonRect, 2),
message: successful ? _t('Copied!') : _t('Failed to copy'), message: successful ? _t('Copied!') : _t('Failed to copy'),
}); });
e.target.onmouseleave = close; e.target.onmouseleave = close;

View file

@ -17,7 +17,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, {createRef} from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import createReactClass from 'create-react-class'; import createReactClass from 'create-react-class';
import classNames from 'classnames'; import classNames from 'classnames';
@ -59,7 +59,7 @@ module.exports = createReactClass({
return ({ return ({
hover: false, hover: false,
badgeHover: false, badgeHover: false,
menuDisplayed: false, contextMenuPosition: null, // DOM bounding box, null if non-shown
roomName: this.props.room.name, roomName: this.props.room.name,
notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId), notifState: RoomNotifs.getRoomNotifsState(this.props.room.roomId),
notificationCount: this.props.room.getUnreadNotificationCount(), notificationCount: this.props.room.getUnreadNotificationCount(),
@ -145,8 +145,6 @@ module.exports = createReactClass({
}, },
componentDidMount: function() { componentDidMount: function() {
this._contextMenuButton = createRef();
const cli = MatrixClientPeg.get(); const cli = MatrixClientPeg.get();
cli.on("accountData", this.onAccountData); cli.on("accountData", this.onAccountData);
cli.on("Room.name", this.onRoomName); cli.on("Room.name", this.onRoomName);
@ -241,16 +239,12 @@ module.exports = createReactClass({
this.setState( { badgeHover: false } ); this.setState( { badgeHover: false } );
}, },
openMenu: function(e) { _showContextMenu: function(boundingClientRect) {
// Only allow non-guests to access the context menu // Only allow non-guests to access the context menu
if (MatrixClientPeg.get().isGuest()) return; if (MatrixClientPeg.get().isGuest()) return;
// Prevent the RoomTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
const state = { const state = {
menuDisplayed: true, contextMenuPosition: boundingClientRect,
}; };
// If the badge is clicked, then no longer show tooltip // If the badge is clicked, then no longer show tooltip
@ -261,9 +255,28 @@ module.exports = createReactClass({
this.setState(state); this.setState(state);
}, },
onContextMenuButtonClick: function(e) {
// Prevent the RoomTile onClick event firing as well
e.stopPropagation();
e.preventDefault();
this._showContextMenu(e.target.getBoundingClientRect());
},
onContextMenu: function(e) {
// Prevent the native context menu
e.preventDefault();
this._showContextMenu({
right: e.clientX,
top: e.clientY,
height: 0,
});
},
closeMenu: function() { closeMenu: function() {
this.setState({ this.setState({
menuDisplayed: false, contextMenuPosition: null,
}); });
this.props.refreshSubList(); this.props.refreshSubList();
}, },
@ -282,6 +295,8 @@ module.exports = createReactClass({
subtext = this.state.statusMessage; subtext = this.state.statusMessage;
} }
const isMenuDisplayed = Boolean(this.state.contextMenuPosition);
const classes = classNames({ const classes = classNames({
'mx_RoomTile': true, 'mx_RoomTile': true,
'mx_RoomTile_selected': this.state.selected, 'mx_RoomTile_selected': this.state.selected,
@ -289,7 +304,7 @@ module.exports = createReactClass({
'mx_RoomTile_unreadNotify': notifBadges, 'mx_RoomTile_unreadNotify': notifBadges,
'mx_RoomTile_highlight': mentionBadges, 'mx_RoomTile_highlight': mentionBadges,
'mx_RoomTile_invited': isInvite, 'mx_RoomTile_invited': isInvite,
'mx_RoomTile_menuDisplayed': this.state.menuDisplayed, 'mx_RoomTile_menuDisplayed': isMenuDisplayed,
'mx_RoomTile_noBadges': !badges, 'mx_RoomTile_noBadges': !badges,
'mx_RoomTile_transparent': this.props.transparent, 'mx_RoomTile_transparent': this.props.transparent,
'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed, 'mx_RoomTile_hasSubtext': subtext && !this.props.collapsed,
@ -301,7 +316,7 @@ module.exports = createReactClass({
const badgeClasses = classNames({ const badgeClasses = classNames({
'mx_RoomTile_badge': true, 'mx_RoomTile_badge': true,
'mx_RoomTile_badgeButton': this.state.badgeHover || this.state.menuDisplayed, 'mx_RoomTile_badgeButton': this.state.badgeHover || isMenuDisplayed,
}); });
let name = this.state.roomName; let name = this.state.roomName;
@ -323,7 +338,7 @@ module.exports = createReactClass({
const nameClasses = classNames({ const nameClasses = classNames({
'mx_RoomTile_name': true, 'mx_RoomTile_name': true,
'mx_RoomTile_invite': this.props.isInvite, 'mx_RoomTile_invite': this.props.isInvite,
'mx_RoomTile_badgeShown': badges || this.state.badgeHover || this.state.menuDisplayed, 'mx_RoomTile_badgeShown': badges || this.state.badgeHover || isMenuDisplayed,
}); });
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null; subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
@ -346,10 +361,9 @@ module.exports = createReactClass({
contextMenuButton = ( contextMenuButton = (
<ContextMenuButton <ContextMenuButton
className="mx_RoomTile_menuButton" className="mx_RoomTile_menuButton"
inputRef={this._contextMenuButton}
label={_t("Options")} label={_t("Options")}
isExpanded={this.state.menuDisplayed} isExpanded={isMenuDisplayed}
onClick={this.openMenu} /> onClick={this.onContextMenuButtonClick} />
); );
} }
@ -382,11 +396,10 @@ module.exports = createReactClass({
} }
let contextMenu; let contextMenu;
if (this.state.menuDisplayed && this._contextMenuButton.current) { if (isMenuDisplayed) {
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu'); const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
contextMenu = ( contextMenu = (
<ContextMenu {...toRightOf(elementRect)} onFinished={this.closeMenu}> <ContextMenu {...toRightOf(this.state.contextMenuPosition)} onFinished={this.closeMenu}>
<RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} /> <RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} />
</ContextMenu> </ContextMenu>
); );
@ -399,7 +412,7 @@ module.exports = createReactClass({
onClick={this.onClick} onClick={this.onClick}
onMouseEnter={this.onMouseEnter} onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave} onMouseLeave={this.onMouseLeave}
onContextMenu={this.openMenu} onContextMenu={this.onContextMenu}
aria-label={ariaLabel} aria-label={ariaLabel}
aria-selected={this.state.selected} aria-selected={this.state.selected}
role="treeitem" role="treeitem"