Merge pull request #4870 from matrix-org/t3chguy/room-list/2
Room List v2 context menu interactions
This commit is contained in:
commit
c4bbdefa8d
4 changed files with 110 additions and 65 deletions
|
@ -116,6 +116,7 @@ export class ContextMenu extends React.Component {
|
||||||
this.props.onFinished();
|
this.props.onFinished();
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
const x = e.clientX;
|
const x = e.clientX;
|
||||||
const y = e.clientY;
|
const y = e.clientY;
|
||||||
|
|
||||||
|
@ -133,6 +134,12 @@ export class ContextMenu extends React.Component {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onContextMenuPreventBubbling = (e) => {
|
||||||
|
// stop propagation so that any context menu handlers don't leak out of this context menu
|
||||||
|
// but do not inhibit the default browser menu
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
_onMoveFocus = (element, up) => {
|
_onMoveFocus = (element, up) => {
|
||||||
let descending = false; // are we currently descending or ascending through the DOM tree?
|
let descending = false; // are we currently descending or ascending through the DOM tree?
|
||||||
|
|
||||||
|
@ -324,7 +331,7 @@ export class ContextMenu extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
|
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown} onContextMenu={this.onContextMenuPreventBubbling}>
|
||||||
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} role={this.props.managed ? "menu" : undefined}>
|
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} role={this.props.managed ? "menu" : undefined}>
|
||||||
{ chevron }
|
{ chevron }
|
||||||
{ props.children }
|
{ props.children }
|
||||||
|
@ -340,10 +347,18 @@ export class ContextMenu extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
||||||
export const ContextMenuButton = ({ label, isExpanded, children, ...props }) => {
|
export const ContextMenuButton = ({ label, isExpanded, children, onClick, onContextMenu, ...props }) => {
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
return (
|
return (
|
||||||
<AccessibleButton {...props} title={label} aria-label={label} aria-haspopup={true} aria-expanded={isExpanded}>
|
<AccessibleButton
|
||||||
|
{...props}
|
||||||
|
onClick={onClick}
|
||||||
|
onContextMenu={onContextMenu || onClick}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
|
aria-haspopup={true}
|
||||||
|
aria-expanded={isExpanded}
|
||||||
|
>
|
||||||
{ children }
|
{ children }
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
|
|
@ -42,8 +42,10 @@ interface IProps {
|
||||||
isMinimized: boolean;
|
isMinimized: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PartialDOMRect = Pick<DOMRect, "width" | "left" | "top" | "height">;
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
menuDisplayed: boolean;
|
contextMenuPosition: PartialDOMRect;
|
||||||
isDarkTheme: boolean;
|
isDarkTheme: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
menuDisplayed: false,
|
contextMenuPosition: null,
|
||||||
isDarkTheme: this.isUserOnDarkTheme(),
|
isDarkTheme: this.isUserOnDarkTheme(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -106,13 +108,25 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
private onOpenMenuClick = (ev: InputEvent) => {
|
private onOpenMenuClick = (ev: InputEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.setState({menuDisplayed: true});
|
const target = ev.target as HTMLButtonElement;
|
||||||
|
this.setState({contextMenuPosition: target.getBoundingClientRect()});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCloseMenu = (ev: InputEvent) => {
|
private onContextMenu = (ev: React.MouseEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.setState({menuDisplayed: false});
|
this.setState({
|
||||||
|
contextMenuPosition: {
|
||||||
|
left: ev.clientX,
|
||||||
|
top: ev.clientY,
|
||||||
|
width: 20,
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onCloseMenu = () => {
|
||||||
|
this.setState({contextMenuPosition: null});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSwitchThemeClick = () => {
|
private onSwitchThemeClick = () => {
|
||||||
|
@ -129,7 +143,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const payload: OpenToTabPayload = {action: Action.ViewUserSettings, initialTabId: tabId};
|
const payload: OpenToTabPayload = {action: Action.ViewUserSettings, initialTabId: tabId};
|
||||||
defaultDispatcher.dispatch(payload);
|
defaultDispatcher.dispatch(payload);
|
||||||
this.setState({menuDisplayed: false}); // also close the menu
|
this.setState({contextMenuPosition: null}); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
private onShowArchived = (ev: ButtonEvent) => {
|
private onShowArchived = (ev: ButtonEvent) => {
|
||||||
|
@ -145,7 +159,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
|
Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
|
||||||
this.setState({menuDisplayed: false}); // also close the menu
|
this.setState({contextMenuPosition: null}); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSignOutClick = (ev: ButtonEvent) => {
|
private onSignOutClick = (ev: ButtonEvent) => {
|
||||||
|
@ -153,7 +167,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
|
||||||
Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog);
|
Modal.createTrackedDialog('Logout from LeftPanel', '', LogoutDialog);
|
||||||
this.setState({menuDisplayed: false}); // also close the menu
|
this.setState({contextMenuPosition: null}); // also close the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
private onHomeClick = (ev: ButtonEvent) => {
|
private onHomeClick = (ev: ButtonEvent) => {
|
||||||
|
@ -164,7 +178,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderContextMenu = (): React.ReactNode => {
|
private renderContextMenu = (): React.ReactNode => {
|
||||||
if (!this.state.menuDisplayed) return null;
|
if (!this.state.contextMenuPosition) return null;
|
||||||
|
|
||||||
let hostingLink;
|
let hostingLink;
|
||||||
const signupLink = getHostingLink("user-context-menu");
|
const signupLink = getHostingLink("user-context-menu");
|
||||||
|
@ -198,13 +212,12 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const elementRect = this.buttonRef.current.getBoundingClientRect();
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
chevronFace="none"
|
chevronFace="none"
|
||||||
// -20 to overlap the context menu by just over the width of the `...` icon and make it look connected
|
// -20 to overlap the context menu by just over the width of the `...` icon and make it look connected
|
||||||
left={elementRect.width + elementRect.left - 20}
|
left={this.state.contextMenuPosition.width + this.state.contextMenuPosition.left - 20}
|
||||||
top={elementRect.top + elementRect.height}
|
top={this.state.contextMenuPosition.top + this.state.contextMenuPosition.height}
|
||||||
onFinished={this.onCloseMenu}
|
onFinished={this.onCloseMenu}
|
||||||
>
|
>
|
||||||
<div className="mx_IconizedContextMenu mx_UserMenu_contextMenu">
|
<div className="mx_IconizedContextMenu mx_UserMenu_contextMenu">
|
||||||
|
@ -290,7 +303,8 @@ export default class UserMenu extends React.Component<IProps, IState> {
|
||||||
onClick={this.onOpenMenuClick}
|
onClick={this.onOpenMenuClick}
|
||||||
inputRef={this.buttonRef}
|
inputRef={this.buttonRef}
|
||||||
label={_t("Account settings")}
|
label={_t("Account settings")}
|
||||||
isExpanded={this.state.menuDisplayed}
|
isExpanded={!!this.state.contextMenuPosition}
|
||||||
|
onContextMenu={this.onContextMenu}
|
||||||
>
|
>
|
||||||
<div className="mx_UserMenu_row">
|
<div className="mx_UserMenu_row">
|
||||||
<span className="mx_UserMenu_userAvatarContainer">
|
<span className="mx_UserMenu_userAvatarContainer">
|
||||||
|
|
|
@ -69,22 +69,21 @@ interface IProps {
|
||||||
// TODO: Account for https://github.com/vector-im/riot-web/issues/14179
|
// TODO: Account for https://github.com/vector-im/riot-web/issues/14179
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PartialDOMRect = Pick<DOMRect, "left" | "top" | "height">;
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
notificationState: ListNotificationState;
|
notificationState: ListNotificationState;
|
||||||
menuDisplayed: boolean;
|
contextMenuPosition: PartialDOMRect;
|
||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RoomSublist2 extends React.Component<IProps, IState> {
|
export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
private headerButton = createRef();
|
|
||||||
private menuButtonRef: React.RefObject<HTMLButtonElement> = createRef();
|
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
notificationState: new ListNotificationState(this.props.isInvite, this.props.tagId),
|
notificationState: new ListNotificationState(this.props.isInvite, this.props.tagId),
|
||||||
menuDisplayed: false,
|
contextMenuPosition: null,
|
||||||
isResizing: false,
|
isResizing: false,
|
||||||
};
|
};
|
||||||
this.state.notificationState.setRooms(this.props.rooms);
|
this.state.notificationState.setRooms(this.props.rooms);
|
||||||
|
@ -141,11 +140,24 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
private onOpenMenuClick = (ev: InputEvent) => {
|
private onOpenMenuClick = (ev: InputEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.setState({menuDisplayed: true});
|
const target = ev.target as HTMLButtonElement;
|
||||||
|
this.setState({contextMenuPosition: target.getBoundingClientRect()});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onContextMenu = (ev: React.MouseEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
|
this.setState({
|
||||||
|
contextMenuPosition: {
|
||||||
|
left: ev.clientX,
|
||||||
|
top: ev.clientY,
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCloseMenu = () => {
|
private onCloseMenu = () => {
|
||||||
this.setState({menuDisplayed: false});
|
this.setState({contextMenuPosition: null});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onUnreadFirstChanged = async () => {
|
private onUnreadFirstChanged = async () => {
|
||||||
|
@ -227,15 +239,14 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextMenu = null;
|
let contextMenu = null;
|
||||||
if (this.state.menuDisplayed) {
|
if (this.state.contextMenuPosition) {
|
||||||
const elementRect = this.menuButtonRef.current.getBoundingClientRect();
|
|
||||||
const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic;
|
const isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic;
|
||||||
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
const isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
chevronFace="none"
|
chevronFace="none"
|
||||||
left={elementRect.left}
|
left={this.state.contextMenuPosition.left}
|
||||||
top={elementRect.top + elementRect.height}
|
top={this.state.contextMenuPosition.top + this.state.contextMenuPosition.height}
|
||||||
onFinished={this.onCloseMenu}
|
onFinished={this.onCloseMenu}
|
||||||
>
|
>
|
||||||
<div className="mx_RoomSublist2_contextMenu">
|
<div className="mx_RoomSublist2_contextMenu">
|
||||||
|
@ -286,9 +297,8 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
<ContextMenuButton
|
<ContextMenuButton
|
||||||
className="mx_RoomSublist2_menuButton"
|
className="mx_RoomSublist2_menuButton"
|
||||||
onClick={this.onOpenMenuClick}
|
onClick={this.onOpenMenuClick}
|
||||||
inputRef={this.menuButtonRef}
|
|
||||||
label={_t("List options")}
|
label={_t("List options")}
|
||||||
isExpanded={this.state.menuDisplayed}
|
isExpanded={!!this.state.contextMenuPosition}
|
||||||
/>
|
/>
|
||||||
{contextMenu}
|
{contextMenu}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@ -297,7 +307,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
private renderHeader(): React.ReactElement {
|
private renderHeader(): React.ReactElement {
|
||||||
return (
|
return (
|
||||||
<RovingTabIndexWrapper inputRef={this.headerButton}>
|
<RovingTabIndexWrapper>
|
||||||
{({onFocus, isActive, ref}) => {
|
{({onFocus, isActive, ref}) => {
|
||||||
// TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180
|
// TODO: Use onFocus: https://github.com/vector-im/riot-web/issues/14180
|
||||||
const tabIndex = isActive ? 0 : -1;
|
const tabIndex = isActive ? 0 : -1;
|
||||||
|
@ -342,12 +352,14 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
<div className={classes}>
|
<div className={classes}>
|
||||||
<div className='mx_RoomSublist2_stickable'>
|
<div className='mx_RoomSublist2_stickable'>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
onFocus={onFocus}
|
||||||
inputRef={ref}
|
inputRef={ref}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
className={"mx_RoomSublist2_headerText"}
|
className={"mx_RoomSublist2_headerText"}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
aria-level={1}
|
aria-level={1}
|
||||||
onClick={this.onHeaderClick}
|
onClick={this.onHeaderClick}
|
||||||
|
onContextMenu={this.onContextMenu}
|
||||||
>
|
>
|
||||||
<span className={collapseClasses} />
|
<span className={collapseClasses} />
|
||||||
<span>{this.props.label}</span>
|
<span>{this.props.label}</span>
|
||||||
|
@ -372,7 +384,7 @@ export default class RoomSublist2 extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
'mx_RoomSublist2': true,
|
'mx_RoomSublist2': true,
|
||||||
'mx_RoomSublist2_hasMenuOpen': this.state.menuDisplayed,
|
'mx_RoomSublist2_hasMenuOpen': !!this.state.contextMenuPosition,
|
||||||
'mx_RoomSublist2_minimized': this.props.isMinimized,
|
'mx_RoomSublist2_minimized': this.props.isMinimized,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -60,18 +60,20 @@ interface IProps {
|
||||||
// TODO: Incoming call boxes: https://github.com/vector-im/riot-web/issues/14177
|
// TODO: Incoming call boxes: https://github.com/vector-im/riot-web/issues/14177
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PartialDOMRect = Pick<DOMRect, "left" | "bottom">;
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
hover: boolean;
|
hover: boolean;
|
||||||
notificationState: INotificationState;
|
notificationState: INotificationState;
|
||||||
selected: boolean;
|
selected: boolean;
|
||||||
notificationsMenuDisplayed: boolean;
|
notificationsMenuPosition: PartialDOMRect;
|
||||||
generalMenuDisplayed: boolean;
|
generalMenuPosition: PartialDOMRect;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextMenuBelow = (elementRect) => {
|
const contextMenuBelow = (elementRect: PartialDOMRect) => {
|
||||||
// align the context menu's icons with the icon which opened the context menu
|
// align the context menu's icons with the icon which opened the context menu
|
||||||
const left = elementRect.left + window.pageXOffset - 9;
|
const left = elementRect.left + window.pageXOffset - 9;
|
||||||
let top = elementRect.bottom + window.pageYOffset + 17;
|
const top = elementRect.bottom + window.pageYOffset + 17;
|
||||||
const chevronFace = "none";
|
const chevronFace = "none";
|
||||||
return {left, top, chevronFace};
|
return {left, top, chevronFace};
|
||||||
};
|
};
|
||||||
|
@ -103,9 +105,6 @@ const NotifOption: React.FC<INotifOptionProps> = ({active, onClick, iconClassNam
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class RoomTile2 extends React.Component<IProps, IState> {
|
export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
private notificationsMenuButtonRef: React.RefObject<HTMLButtonElement> = createRef();
|
|
||||||
private generalMenuButtonRef: React.RefObject<HTMLButtonElement> = createRef();
|
|
||||||
|
|
||||||
// TODO: a11y: https://github.com/vector-im/riot-web/issues/14180
|
// TODO: a11y: https://github.com/vector-im/riot-web/issues/14180
|
||||||
|
|
||||||
constructor(props: IProps) {
|
constructor(props: IProps) {
|
||||||
|
@ -115,8 +114,8 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
hover: false,
|
hover: false,
|
||||||
notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag),
|
notificationState: new TagSpecificNotificationState(this.props.room, this.props.tag),
|
||||||
selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId,
|
selected: ActiveRoomObserver.activeRoomId === this.props.room.roomId,
|
||||||
notificationsMenuDisplayed: false,
|
notificationsMenuPosition: null,
|
||||||
generalMenuDisplayed: false,
|
generalMenuPosition: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
ActiveRoomObserver.addListener(this.props.room.roomId, this.onActiveRoomUpdate);
|
||||||
|
@ -137,6 +136,8 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private onTileClick = (ev: React.KeyboardEvent) => {
|
private onTileClick = (ev: React.KeyboardEvent) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.stopPropagation();
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'view_room',
|
action: 'view_room',
|
||||||
// TODO: Support show_room_tile in new room list: https://github.com/vector-im/riot-web/issues/14233
|
// TODO: Support show_room_tile in new room list: https://github.com/vector-im/riot-web/issues/14233
|
||||||
|
@ -153,25 +154,34 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
private onNotificationsMenuOpenClick = (ev: InputEvent) => {
|
private onNotificationsMenuOpenClick = (ev: InputEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.setState({notificationsMenuDisplayed: true});
|
const target = ev.target as HTMLButtonElement;
|
||||||
|
this.setState({notificationsMenuPosition: target.getBoundingClientRect()});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCloseNotificationsMenu = (ev: InputEvent) => {
|
private onCloseNotificationsMenu = () => {
|
||||||
ev.preventDefault();
|
this.setState({notificationsMenuPosition: null});
|
||||||
ev.stopPropagation();
|
|
||||||
this.setState({notificationsMenuDisplayed: false});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onGeneralMenuOpenClick = (ev: InputEvent) => {
|
private onGeneralMenuOpenClick = (ev: InputEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.setState({generalMenuDisplayed: true});
|
const target = ev.target as HTMLButtonElement;
|
||||||
|
this.setState({generalMenuPosition: target.getBoundingClientRect()});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCloseGeneralMenu = (ev: InputEvent) => {
|
private onContextMenu = (ev: React.MouseEvent) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.setState({generalMenuDisplayed: false});
|
this.setState({
|
||||||
|
generalMenuPosition: {
|
||||||
|
left: ev.clientX,
|
||||||
|
bottom: ev.clientY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onCloseGeneralMenu = () => {
|
||||||
|
this.setState({generalMenuPosition: null});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onTagRoom = (ev: ButtonEvent, tagId: TagID) => {
|
private onTagRoom = (ev: ButtonEvent, tagId: TagID) => {
|
||||||
|
@ -190,7 +200,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
action: 'leave_room',
|
action: 'leave_room',
|
||||||
room_id: this.props.room.roomId,
|
room_id: this.props.room.roomId,
|
||||||
});
|
});
|
||||||
this.setState({generalMenuDisplayed: false}); // hide the menu
|
this.setState({generalMenuPosition: null}); // hide the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
private onOpenRoomSettings = (ev: ButtonEvent) => {
|
private onOpenRoomSettings = (ev: ButtonEvent) => {
|
||||||
|
@ -201,7 +211,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
action: 'open_room_settings',
|
action: 'open_room_settings',
|
||||||
room_id: this.props.room.roomId,
|
room_id: this.props.room.roomId,
|
||||||
});
|
});
|
||||||
this.setState({generalMenuDisplayed: false}); // hide the menu
|
this.setState({generalMenuPosition: null}); // hide the menu
|
||||||
};
|
};
|
||||||
|
|
||||||
private async saveNotifState(ev: ButtonEvent, newState: ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE) {
|
private async saveNotifState(ev: ButtonEvent, newState: ALL_MESSAGES_LOUD | ALL_MESSAGES | MENTIONS_ONLY | MUTE) {
|
||||||
|
@ -218,10 +228,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close the context menu
|
this.setState({notificationsMenuPosition: null}); // Close the context menu
|
||||||
this.setState({
|
|
||||||
notificationsMenuDisplayed: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES);
|
private onClickAllNotifs = ev => this.saveNotifState(ev, ALL_MESSAGES);
|
||||||
|
@ -238,10 +245,9 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
const state = getRoomNotifsState(this.props.room.roomId);
|
const state = getRoomNotifsState(this.props.room.roomId);
|
||||||
|
|
||||||
let contextMenu = null;
|
let contextMenu = null;
|
||||||
if (this.state.notificationsMenuDisplayed) {
|
if (this.state.notificationsMenuPosition) {
|
||||||
const elementRect = this.notificationsMenuButtonRef.current.getBoundingClientRect();
|
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<ContextMenu {...contextMenuBelow(elementRect)} onFinished={this.onCloseNotificationsMenu}>
|
<ContextMenu {...contextMenuBelow(this.state.notificationsMenuPosition)} onFinished={this.onCloseNotificationsMenu}>
|
||||||
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu">
|
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu">
|
||||||
<div className="mx_IconizedContextMenu_optionList">
|
<div className="mx_IconizedContextMenu_optionList">
|
||||||
<NotifOption
|
<NotifOption
|
||||||
|
@ -289,9 +295,8 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
<ContextMenuButton
|
<ContextMenuButton
|
||||||
className={classes}
|
className={classes}
|
||||||
onClick={this.onNotificationsMenuOpenClick}
|
onClick={this.onNotificationsMenuOpenClick}
|
||||||
inputRef={this.notificationsMenuButtonRef}
|
|
||||||
label={_t("Notification options")}
|
label={_t("Notification options")}
|
||||||
isExpanded={this.state.notificationsMenuDisplayed}
|
isExpanded={!!this.state.notificationsMenuPosition}
|
||||||
/>
|
/>
|
||||||
{contextMenu}
|
{contextMenu}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@ -307,10 +312,9 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let contextMenu = null;
|
let contextMenu = null;
|
||||||
if (this.state.generalMenuDisplayed) {
|
if (this.state.generalMenuPosition) {
|
||||||
const elementRect = this.generalMenuButtonRef.current.getBoundingClientRect();
|
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<ContextMenu {...contextMenuBelow(elementRect)} onFinished={this.onCloseGeneralMenu}>
|
<ContextMenu {...contextMenuBelow(this.state.generalMenuPosition)} onFinished={this.onCloseGeneralMenu}>
|
||||||
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu">
|
<div className="mx_IconizedContextMenu mx_IconizedContextMenu_compact mx_RoomTile2_contextMenu">
|
||||||
<div className="mx_IconizedContextMenu_optionList">
|
<div className="mx_IconizedContextMenu_optionList">
|
||||||
<AccessibleButton onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}>
|
<AccessibleButton onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}>
|
||||||
|
@ -338,9 +342,8 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
<ContextMenuButton
|
<ContextMenuButton
|
||||||
className="mx_RoomTile2_menuButton"
|
className="mx_RoomTile2_menuButton"
|
||||||
onClick={this.onGeneralMenuOpenClick}
|
onClick={this.onGeneralMenuOpenClick}
|
||||||
inputRef={this.generalMenuButtonRef}
|
|
||||||
label={_t("Room options")}
|
label={_t("Room options")}
|
||||||
isExpanded={this.state.generalMenuDisplayed}
|
isExpanded={!!this.state.generalMenuPosition}
|
||||||
/>
|
/>
|
||||||
{contextMenu}
|
{contextMenu}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
@ -354,7 +357,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
'mx_RoomTile2': true,
|
'mx_RoomTile2': true,
|
||||||
'mx_RoomTile2_selected': this.state.selected,
|
'mx_RoomTile2_selected': this.state.selected,
|
||||||
'mx_RoomTile2_hasMenuOpen': this.state.generalMenuDisplayed || this.state.notificationsMenuDisplayed,
|
'mx_RoomTile2_hasMenuOpen': !!(this.state.generalMenuPosition || this.state.notificationsMenuPosition),
|
||||||
'mx_RoomTile2_minimized': this.props.isMinimized,
|
'mx_RoomTile2_minimized': this.props.isMinimized,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -416,6 +419,7 @@ export default class RoomTile2 extends React.Component<IProps, IState> {
|
||||||
onMouseLeave={this.onTileMouseLeave}
|
onMouseLeave={this.onTileMouseLeave}
|
||||||
onClick={this.onTileClick}
|
onClick={this.onTileClick}
|
||||||
role="treeitem"
|
role="treeitem"
|
||||||
|
onContextMenu={this.onContextMenu}
|
||||||
>
|
>
|
||||||
<div className="mx_RoomTile2_avatarContainer">
|
<div className="mx_RoomTile2_avatarContainer">
|
||||||
<RoomAvatar room={this.props.room} width={avatarSize} height={avatarSize} />
|
<RoomAvatar room={this.props.room} width={avatarSize} height={avatarSize} />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue