Migrate all standard Context Menus over to new custom framework

This commit is contained in:
Michael Telatynski 2019-11-11 17:53:17 +00:00
parent af396fdf60
commit 1c4d89f2d7
13 changed files with 830 additions and 424 deletions

View file

@ -1,6 +1,7 @@
/*
Copyright 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,17 +16,146 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import React, {useState, useEffect, useRef} from 'react';
import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import Modal from '../../../Modal';
import { createMenu } from '../../structures/ContextualMenu';
import {ContextMenu} from '../../structures/ContextualMenu';
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
import {RoomContext} from "../../structures/RoomView";
const useContextMenu = () => {
const _button = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const open = () => {
setIsOpen(true);
};
const close = () => {
setIsOpen(false);
};
return [isOpen, _button, open, close, setIsOpen];
};
const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
const [menuDisplayed, _button, openMenu, closeMenu] = useContextMenu();
useEffect(() => {
onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]);
let contextMenu;
if (menuDisplayed) {
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
const tile = getTile && getTile();
const replyThread = getReplyThread && getReplyThread();
const onCryptoClick = () => {
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
{event: mxEvent},
);
};
let e2eInfoCallback = null;
if (mxEvent.isEncrypted()) {
e2eInfoCallback = onCryptoClick;
}
const menuOptions = {
chevronFace: "none",
};
const buttonRect = _button.current.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const buttonRight = buttonRect.right + window.pageXOffset;
const buttonBottom = buttonRect.bottom + window.pageYOffset;
const buttonTop = buttonRect.top + window.pageYOffset;
// Align the right edge of the menu to the right edge of the button
menuOptions.right = window.innerWidth - buttonRight;
// Align the menu vertically on whichever side of the button has more
// space available.
if (buttonBottom < window.innerHeight / 2) {
menuOptions.top = buttonBottom;
} else {
menuOptions.bottom = window.innerHeight - buttonTop;
}
contextMenu = <ContextMenu props={menuOptions} onFinished={closeMenu}>
<MessageContextMenu
mxEvent={mxEvent}
permalinkCreator={permalinkCreator}
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
collapseReplyThread={replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined}
e2eInfoCallback={e2eInfoCallback}
onFinished={closeMenu}
/>
</ContextMenu>;
}
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <React.Fragment>
<AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
title={_t("Options")}
onClick={openMenu}
aria-haspopup={true}
aria-expanded={menuDisplayed}
inputRef={_button}
/>
{ contextMenu }
</React.Fragment>;
};
const ReactButton = ({mxEvent, reactions}) => {
const [menuDisplayed, _button, openMenu, closeMenu] = useContextMenu();
let contextMenu;
if (menuDisplayed) {
const menuOptions = {
chevronFace: "none",
};
const buttonRect = _button.current.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const buttonRight = buttonRect.right + window.pageXOffset;
const buttonBottom = buttonRect.bottom + window.pageYOffset;
const buttonTop = buttonRect.top + window.pageYOffset;
// Align the right edge of the menu to the right edge of the button
menuOptions.right = window.innerWidth - buttonRight;
// Align the menu vertically on whichever side of the button has more
// space available.
if (buttonBottom < window.innerHeight / 2) {
menuOptions.top = buttonBottom;
} else {
menuOptions.bottom = window.innerHeight - buttonTop;
}
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
contextMenu = <ContextMenu props={menuOptions} onFinished={closeMenu}>
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
</ContextMenu>;
}
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <React.Fragment>
<AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
title={_t("React")}
onClick={openMenu}
aria-haspopup={true}
aria-expanded={menuDisplayed}
inputRef={_button}
/>
{ contextMenu }
</React.Fragment>;
};
export default class MessageActionBar extends React.PureComponent {
static propTypes = {
mxEvent: PropTypes.object.isRequired,
@ -62,14 +192,6 @@ export default class MessageActionBar extends React.PureComponent {
this.props.onFocusChange(focused);
};
onCryptoClick = () => {
const event = this.props.mxEvent;
Modal.createTrackedDialogAsync('Encrypted Event Dialog', '',
import('../../../async-components/views/dialogs/EncryptedEventDialog'),
{event},
);
};
onReplyClick = (ev) => {
dis.dispatch({
action: 'reply_to_event',
@ -84,71 +206,6 @@ export default class MessageActionBar extends React.PureComponent {
});
};
getMenuOptions = (ev) => {
const menuOptions = {};
const buttonRect = ev.target.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page
const buttonRight = buttonRect.right + window.pageXOffset;
const buttonBottom = buttonRect.bottom + window.pageYOffset;
const buttonTop = buttonRect.top + window.pageYOffset;
// Align the right edge of the menu to the right edge of the button
menuOptions.right = window.innerWidth - buttonRight;
// Align the menu vertically on whichever side of the button has more
// space available.
if (buttonBottom < window.innerHeight / 2) {
menuOptions.top = buttonBottom;
} else {
menuOptions.bottom = window.innerHeight - buttonTop;
}
return menuOptions;
};
onReactClick = (ev) => {
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
const menuOptions = {
...this.getMenuOptions(ev),
mxEvent: this.props.mxEvent,
reactions: this.props.reactions,
chevronFace: "none",
onFinished: () => this.onFocusChange(false),
};
createMenu(ReactionPicker, menuOptions);
this.onFocusChange(true);
};
onOptionsClick = (ev) => {
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
const { getTile, getReplyThread } = this.props;
const tile = getTile && getTile();
const replyThread = getReplyThread && getReplyThread();
let e2eInfoCallback = null;
if (this.props.mxEvent.isEncrypted()) {
e2eInfoCallback = () => this.onCryptoClick();
}
const menuOptions = {
...this.getMenuOptions(ev),
mxEvent: this.props.mxEvent,
chevronFace: "none",
permalinkCreator: this.props.permalinkCreator,
eventTileOps: tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined,
collapseReplyThread: replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined,
e2eInfoCallback: e2eInfoCallback,
onFinished: () => {
this.onFocusChange(false);
},
};
createMenu(MessageContextMenu, menuOptions);
this.onFocusChange(true);
};
render() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
@ -158,11 +215,7 @@ export default class MessageActionBar extends React.PureComponent {
if (isContentActionable(this.props.mxEvent)) {
if (this.context.room.canReact) {
reactButton = <AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
title={_t("React")}
onClick={this.onReactClick}
/>;
reactButton = <ReactButton mxEvent={this.props.mxEvent} reactions={this.props.reactions} />;
}
if (this.context.room.canReply) {
replyButton = <AccessibleButton
@ -185,11 +238,12 @@ export default class MessageActionBar extends React.PureComponent {
{reactButton}
{replyButton}
{editButton}
<AccessibleButton
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
title={_t("Options")}
onClick={this.onOptionsClick}
aria-haspopup={true}
<OptionsButton
mxEvent={this.props.mxEvent}
getReplyThread={this.props.getReplyThread}
getTile={this.props.getTile}
permalinkCreator={this.props.permalinkCreator}
onFocusChange={this.props.onFocusChange}
/>
</div>;
}