From 3847996b5b87f0aa7f470fc10b2a47fd80a8dc7e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 28 Nov 2019 20:26:09 +0000 Subject: [PATCH] transition Tooltips over to deprecated code Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/ContextualMenu.js | 232 +++---------------- src/components/views/dialogs/ShareDialog.js | 13 +- src/components/views/elements/AppTile.js | 8 +- src/components/views/elements/Tooltip.js | 5 +- src/components/views/messages/TextualBody.js | 13 +- 5 files changed, 39 insertions(+), 232 deletions(-) diff --git a/src/components/structures/ContextualMenu.js b/src/components/structures/ContextualMenu.js index d332f8f824..756ebec863 100644 --- a/src/components/structures/ContextualMenu.js +++ b/src/components/structures/ContextualMenu.js @@ -20,8 +20,7 @@ import React, {useRef, useState} from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import {focusCapturedRef} from "../../utils/Accessibility"; -import {Key, KeyCode} from "../../Keyboard"; +import {Key} from "../../Keyboard"; import sdk from "../../index"; import AccessibleButton from "../views/elements/AccessibleButton"; @@ -43,194 +42,6 @@ function getOrCreateContainer() { return container; } -export default class ContextualMenu extends React.Component { - propTypes: { - top: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - right: PropTypes.number, - menuWidth: PropTypes.number, - menuHeight: PropTypes.number, - chevronOffset: PropTypes.number, - chevronFace: PropTypes.string, // top, bottom, left, right or none - // Function to be called on menu close - onFinished: PropTypes.func, - menuPaddingTop: PropTypes.number, - menuPaddingRight: PropTypes.number, - menuPaddingBottom: PropTypes.number, - menuPaddingLeft: PropTypes.number, - zIndex: PropTypes.number, - - // If true, insert an invisible screen-sized element behind the - // menu that when clicked will close it. - hasBackground: PropTypes.bool, - - // The component to render as the context menu - elementClass: PropTypes.element.isRequired, - // on resize callback - windowResize: PropTypes.func, - // method to close menu - closeMenu: PropTypes.func.isRequired, - }; - - constructor() { - super(); - this.state = { - contextMenuRect: null, - }; - - this.onContextMenu = this.onContextMenu.bind(this); - this.collectContextMenuRect = this.collectContextMenuRect.bind(this); - } - - collectContextMenuRect(element) { - // We don't need to clean up when unmounting, so ignore - if (!element) return; - - // For screen readers to find the thing - focusCapturedRef(element); - - this.setState({ - contextMenuRect: element.getBoundingClientRect(), - }); - } - - onContextMenu(e) { - if (this.props.closeMenu) { - this.props.closeMenu(); - - e.preventDefault(); - const x = e.clientX; - const y = e.clientY; - - // XXX: This isn't pretty but the only way to allow opening a different context menu on right click whilst - // a context menu and its click-guard are up without completely rewriting how the context menus work. - setImmediate(() => { - const clickEvent = document.createEvent('MouseEvents'); - clickEvent.initMouseEvent( - 'contextmenu', true, true, window, 0, - 0, 0, x, y, false, false, - false, false, 0, null, - ); - document.elementFromPoint(x, y).dispatchEvent(clickEvent); - }); - } - } - - _onKeyDown = (ev) => { - if (ev.keyCode === KeyCode.ESCAPE) { - ev.stopPropagation(); - ev.preventDefault(); - this.props.closeMenu(); - } - }; - - render() { - const position = {}; - let chevronFace = null; - const props = this.props; - - 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 contextMenuRect = this.state.contextMenuRect || null; - const padding = 10; - - const chevronOffset = {}; - if (props.chevronFace) { - chevronFace = props.chevronFace; - } - const hasChevron = chevronFace && chevronFace !== "none"; - - if (chevronFace === 'top' || chevronFace === 'bottom') { - chevronOffset.left = props.chevronOffset; - } else { - const target = position.top; - - // By default, no adjustment is made - let adjusted = target; - - // If we know the dimensions of the context menu, adjust its position - // such that it does not leave the (padded) window. - if (contextMenuRect) { - adjusted = Math.min(position.top, document.body.clientHeight - contextMenuRect.height - padding); - } - - position.top = adjusted; - chevronOffset.top = Math.max(props.chevronOffset, props.chevronOffset + target - adjusted); - } - - const chevron = hasChevron ? -
: - undefined; - const className = 'mx_ContextualMenu_wrapper'; - - const menuClasses = classNames({ - 'mx_ContextualMenu': true, - 'mx_ContextualMenu_left': !hasChevron && position.left, - 'mx_ContextualMenu_right': !hasChevron && position.right, - 'mx_ContextualMenu_top': !hasChevron && position.top, - 'mx_ContextualMenu_bottom': !hasChevron && position.bottom, - 'mx_ContextualMenu_withChevron_left': chevronFace === 'left', - 'mx_ContextualMenu_withChevron_right': chevronFace === 'right', - 'mx_ContextualMenu_withChevron_top': chevronFace === 'top', - 'mx_ContextualMenu_withChevron_bottom': chevronFace === 'bottom', - }); - - const menuStyle = {}; - if (props.menuWidth) { - menuStyle.width = props.menuWidth; - } - - if (props.menuHeight) { - menuStyle.height = props.menuHeight; - } - - if (!isNaN(Number(props.menuPaddingTop))) { - menuStyle["paddingTop"] = props.menuPaddingTop; - } - if (!isNaN(Number(props.menuPaddingLeft))) { - menuStyle["paddingLeft"] = props.menuPaddingLeft; - } - if (!isNaN(Number(props.menuPaddingBottom))) { - menuStyle["paddingBottom"] = props.menuPaddingBottom; - } - if (!isNaN(Number(props.menuPaddingRight))) { - menuStyle["paddingRight"] = props.menuPaddingRight; - } - - const wrapperStyle = {}; - if (!isNaN(Number(props.zIndex))) { - menuStyle["zIndex"] = props.zIndex + 1; - wrapperStyle["zIndex"] = props.zIndex; - } - - const ElementClass = props.elementClass; - - // FIXME: If a menu uses getDefaultProps it clobbers the onFinished - // property set here so you can't close the menu from a button click! - return
-
- { chevron } - -
- { props.hasBackground &&
} -
; - } -} - const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]); // Generic ContextMenu Portal wrapper // all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1} @@ -396,7 +207,7 @@ export class ContextMenu extends React.Component { ev.preventDefault(); }; - render() { + renderMenu(hasBackground=this.props.hasBackground) { const position = {}; let chevronFace = null; const props = this.props; @@ -488,13 +299,13 @@ export class ContextMenu extends React.Component { } let background; - if (props.hasBackground) { + if (hasBackground) { background = (
); } - const menu = ( + return (
{ chevron } @@ -503,7 +314,10 @@ export class ContextMenu extends React.Component { { background }
); - return ReactDOM.createPortal(menu, getOrCreateContainer()); + } + + render() { + return ReactDOM.createPortal(this.renderMenu(), getOrCreateContainer()); } } @@ -589,8 +403,8 @@ MenuItemRadio.propTypes = { // Placement method for to position context menu to right of elementRect with chevronOffset export const toRightOf = (elementRect, chevronOffset=12) => { const left = elementRect.right + window.pageXOffset + 3; - let top = (elementRect.top + (elementRect.height / 2) + window.pageYOffset); - top = top - (chevronOffset + 8); // where 8 is half the height of the chevron + let top = elementRect.top + (elementRect.height / 2) + window.pageYOffset; + top -= chevronOffset + 8; // where 8 is half the height of the chevron return {left, top}; }; @@ -626,8 +440,15 @@ export const useContextMenu = () => { return [isOpen, _button, open, close, setIsOpen]; }; -export function createMenu(ElementClass, props, hasBackground=true) { - const closeMenu = function(...args) { +export default class LegacyContextMenu extends ContextMenu { + render() { + return this.renderMenu(false); + } +} + +// XXX: Deprecated, used only for dynamic Tooltips. Avoid using at all costs. +export function createMenu(ElementClass, props) { + const onFinished = function(...args) { ReactDOM.unmountComponentAtNode(getOrCreateContainer()); if (props && props.onFinished) { @@ -635,16 +456,15 @@ export function createMenu(ElementClass, props, hasBackground=true) { } }; - // We only reference closeMenu once per call to createMenu - const menu = ; + onFinished={onFinished} // eslint-disable-line react/jsx-no-bind + windowResize={onFinished} // eslint-disable-line react/jsx-no-bind + > + + ; ReactDOM.render(menu, getOrCreateContainer()); - return {close: closeMenu}; + return {close: onFinished}; } diff --git a/src/components/views/dialogs/ShareDialog.js b/src/components/views/dialogs/ShareDialog.js index 65d581c3bc..f34f2c0174 100644 --- a/src/components/views/dialogs/ShareDialog.js +++ b/src/components/views/dialogs/ShareDialog.js @@ -22,6 +22,7 @@ import { _t } from '../../../languageHandler'; import QRCode from 'qrcode-react'; import {RoomPermalinkCreator, makeGroupPermalink, makeUserPermalink} from "../../../utils/permalinks/Permalinks"; import * as ContextualMenu from "../../structures/ContextualMenu"; +import {toRightOf} from "../../structures/ContextualMenu"; const socials = [ { @@ -102,18 +103,12 @@ export default class ShareDialog extends React.Component { console.error('Failed to copy: ', err); } - const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const buttonRect = e.target.getBoundingClientRect(); - - // The window X and Y offsets are to adjust position when zoomed in to page - const x = buttonRect.right + window.pageXOffset; - const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; + const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const {close} = ContextualMenu.createMenu(GenericTextContextMenu, { - chevronOffset: 10, - left: x, - top: y, + ...toRightOf(buttonRect, 11), message: successful ? _t('Copied!') : _t('Failed to copy'), - }, false); + }); // Drop a reference to this close handler for componentWillUnmount this.closeCopiedTooltip = e.target.onmouseleave = close; } diff --git a/src/components/views/elements/AppTile.js b/src/components/views/elements/AppTile.js index b04de8a9c4..b2cf44a3d1 100644 --- a/src/components/views/elements/AppTile.js +++ b/src/components/views/elements/AppTile.js @@ -681,10 +681,10 @@ export default class AppTile extends React.Component { diff --git a/src/components/views/elements/Tooltip.js b/src/components/views/elements/Tooltip.js index 8ff3ce9bdb..fa5b53ccd0 100644 --- a/src/components/views/elements/Tooltip.js +++ b/src/components/views/elements/Tooltip.js @@ -129,9 +129,6 @@ module.exports = createReactClass({ render: function() { // Render a placeholder - return ( -
-
- ); + return
; }, }); diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index dab03cd537..d88f6511d8 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -33,6 +33,7 @@ import ReplyThread from "../elements/ReplyThread"; import {pillifyLinks} from '../../../utils/pillify'; import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import {isPermalinkHost} from "../../../utils/permalinks/Permalinks"; +import {toRightOf} from "../../structures/ContextualMenu"; module.exports = createReactClass({ displayName: 'TextualBody', @@ -272,18 +273,12 @@ module.exports = createReactClass({ const copyCode = button.parentNode.getElementsByTagName("code")[0]; const successful = this.copyToClipboard(copyCode.textContent); - const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const buttonRect = e.target.getBoundingClientRect(); - - // The window X and Y offsets are to adjust position when zoomed in to page - const x = buttonRect.right + window.pageXOffset; - const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; + const GenericTextContextMenu = sdk.getComponent('context_menus.GenericTextContextMenu'); const {close} = ContextualMenu.createMenu(GenericTextContextMenu, { - chevronOffset: 10, - left: x, - top: y, + ...toRightOf(buttonRect, 11), message: successful ? _t('Copied!') : _t('Failed to copy'), - }, false); + }); e.target.onmouseleave = close; };