You've already forked matrix-react-sdk
mirror of
https://github.com/matrix-org/matrix-react-sdk.git
synced 2025-07-28 15:22:05 +03:00
Consolidate all except tooltips
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
@ -16,13 +16,14 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, {useRef, useState} from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {focusCapturedRef} from "../../utils/Accessibility";
|
import {focusCapturedRef} from "../../utils/Accessibility";
|
||||||
import {Key, KeyCode} from "../../Keyboard";
|
import {Key, KeyCode} from "../../Keyboard";
|
||||||
import sdk from "../../index";
|
import sdk from "../../index";
|
||||||
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
|
|
||||||
// Shamelessly ripped off Modal.js. There's probably a better way
|
// Shamelessly ripped off Modal.js. There's probably a better way
|
||||||
// of doing reusable widgets like dialog boxes & menus where we go and
|
// of doing reusable widgets like dialog boxes & menus where we go and
|
||||||
@ -222,7 +223,7 @@ export default class ContextualMenu extends React.Component {
|
|||||||
return <div className={className} style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
|
return <div className={className} style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
|
||||||
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} tabIndex={0}>
|
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} tabIndex={0}>
|
||||||
{ chevron }
|
{ chevron }
|
||||||
<ElementClass {...props} onFinished={props.closeMenu} onResize={props.windowResize} />
|
<ElementClass {...props} onFinished={props.closeMenu} onResize={props.onFinished} />
|
||||||
</div>
|
</div>
|
||||||
{ props.hasBackground && <div className="mx_ContextualMenu_background" style={wrapperStyle}
|
{ props.hasBackground && <div className="mx_ContextualMenu_background" style={wrapperStyle}
|
||||||
onClick={props.closeMenu} onContextMenu={this.onContextMenu} /> }
|
onClick={props.closeMenu} onContextMenu={this.onContextMenu} /> }
|
||||||
@ -231,8 +232,10 @@ export default class ContextualMenu extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]);
|
const ARIA_MENU_ITEM_ROLES = new Set(["menuitem", "menuitemcheckbox", "menuitemradio"]);
|
||||||
|
// Generic ContextMenu Portal wrapper
|
||||||
class ContextualMenu2 extends React.Component {
|
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
||||||
|
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
||||||
|
export class ContextMenu extends React.Component {
|
||||||
propTypes: {
|
propTypes: {
|
||||||
top: PropTypes.number,
|
top: PropTypes.number,
|
||||||
bottom: PropTypes.number,
|
bottom: PropTypes.number,
|
||||||
@ -243,7 +246,7 @@ class ContextualMenu2 extends React.Component {
|
|||||||
chevronOffset: PropTypes.number,
|
chevronOffset: PropTypes.number,
|
||||||
chevronFace: PropTypes.string, // top, bottom, left, right or none
|
chevronFace: PropTypes.string, // top, bottom, left, right or none
|
||||||
// Function to be called on menu close
|
// Function to be called on menu close
|
||||||
onFinished: PropTypes.func,
|
onFinished: PropTypes.func.isRequired,
|
||||||
menuPaddingTop: PropTypes.number,
|
menuPaddingTop: PropTypes.number,
|
||||||
menuPaddingRight: PropTypes.number,
|
menuPaddingRight: PropTypes.number,
|
||||||
menuPaddingBottom: PropTypes.number,
|
menuPaddingBottom: PropTypes.number,
|
||||||
@ -258,10 +261,14 @@ class ContextualMenu2 extends React.Component {
|
|||||||
windowResize: PropTypes.func,
|
windowResize: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
hasBackground: true,
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.state = {
|
this.state = {
|
||||||
contextMenuRect: null,
|
contextMenuElem: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// persist what had focus when we got initialized so we can return it after
|
// persist what had focus when we got initialized so we can return it after
|
||||||
@ -277,19 +284,22 @@ class ContextualMenu2 extends React.Component {
|
|||||||
// We don't need to clean up when unmounting, so ignore
|
// We don't need to clean up when unmounting, so ignore
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
|
|
||||||
const first = element.querySelector('[role^="menuitem"]');
|
let first = element.querySelector('[role^="menuitem"]');
|
||||||
|
if (!first) {
|
||||||
|
first = element.querySelector('[tab-index]');
|
||||||
|
}
|
||||||
if (first) {
|
if (first) {
|
||||||
first.focus();
|
first.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
contextMenuRect: element.getBoundingClientRect(),
|
contextMenuElem: element,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onContextMenu = (e) => {
|
onContextMenu = (e) => {
|
||||||
if (this.props.closeMenu) {
|
if (this.props.onFinished) {
|
||||||
this.props.closeMenu();
|
this.props.onFinished();
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const x = e.clientX;
|
const x = e.clientX;
|
||||||
@ -347,13 +357,25 @@ class ContextualMenu2 extends React.Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_onKeyDown = (ev) => {
|
_onMoveFocusHomeEnd = (element, up) => {
|
||||||
let handled = true;
|
let results = element.querySelectorAll('[role^="menuitem"]');
|
||||||
|
if (!results) {
|
||||||
|
results = element.querySelectorAll('[tab-index]');
|
||||||
|
}
|
||||||
|
if (results && results.length) {
|
||||||
|
if (up) {
|
||||||
|
results[0].focus();
|
||||||
|
} else {
|
||||||
|
results[results.length - 1].focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_onKeyDown = (ev) => {
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
case Key.TAB:
|
case Key.TAB:
|
||||||
case Key.ESCAPE:
|
case Key.ESCAPE:
|
||||||
this.props.closeMenu();
|
this.props.onFinished();
|
||||||
break;
|
break;
|
||||||
case Key.ARROW_UP:
|
case Key.ARROW_UP:
|
||||||
this._onMoveFocus(ev.target, true);
|
this._onMoveFocus(ev.target, true);
|
||||||
@ -361,14 +383,17 @@ class ContextualMenu2 extends React.Component {
|
|||||||
case Key.ARROW_DOWN:
|
case Key.ARROW_DOWN:
|
||||||
this._onMoveFocus(ev.target, false);
|
this._onMoveFocus(ev.target, false);
|
||||||
break;
|
break;
|
||||||
default:
|
case Key.HOME:
|
||||||
handled = false;
|
this._onMoveFocusHomeEnd(this.state.contextMenuElem, true);
|
||||||
|
break;
|
||||||
|
case Key.END:
|
||||||
|
this._onMoveFocusHomeEnd(this.state.contextMenuElem, false);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handled) {
|
// consume all other keys in context menu
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -390,7 +415,7 @@ class ContextualMenu2 extends React.Component {
|
|||||||
chevronFace = 'right';
|
chevronFace = 'right';
|
||||||
}
|
}
|
||||||
|
|
||||||
const contextMenuRect = this.state.contextMenuRect || null;
|
const contextMenuRect = this.state.contextMenuElem ? this.state.contextMenuElem.getBoundingClientRect() : null;
|
||||||
const padding = 10;
|
const padding = 10;
|
||||||
|
|
||||||
const chevronOffset = {};
|
const chevronOffset = {};
|
||||||
@ -465,37 +490,36 @@ class ContextualMenu2 extends React.Component {
|
|||||||
let background;
|
let background;
|
||||||
if (props.hasBackground) {
|
if (props.hasBackground) {
|
||||||
background = (
|
background = (
|
||||||
<div className="mx_ContextualMenu_background" style={wrapperStyle} onClick={props.closeMenu} onContextMenu={this.onContextMenu} />
|
<div className="mx_ContextualMenu_background" style={wrapperStyle} onClick={props.onFinished} onContextMenu={this.onContextMenu} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const menu = (
|
||||||
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
|
<div className="mx_ContextualMenu_wrapper" style={{...position, ...wrapperStyle}} onKeyDown={this._onKeyDown}>
|
||||||
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect}>
|
<div className={menuClasses} style={menuStyle} ref={this.collectContextMenuRect} role="menu">
|
||||||
{ chevron }
|
{ chevron }
|
||||||
{ props.children }
|
{ props.children }
|
||||||
</div>
|
</div>
|
||||||
{ background }
|
{ background }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
return ReactDOM.createPortal(menu, getOrCreateContainer());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic ContextMenu Portal wrapper
|
// Semantic component for representing the AccessibleButton which launches a <ContextMenu />
|
||||||
// all options inside the menu should be of role=menuitem/menuitemcheckbox/menuitemradiobutton and have tabIndex={-1}
|
export const ContextMenuButton = ({ label, isExpanded, children, ...props }) => {
|
||||||
// this will allow the ContextMenu to manage its own focus using arrow keys as per the ARIA guidelines.
|
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
||||||
|
return (
|
||||||
export const ContextMenu = ({children, onFinished, props, hasBackground=true}) => {
|
<AccessibleButton {...props} title={label} aria-label={label} aria-haspopup={true} aria-expanded={isExpanded}>
|
||||||
const menu = <ContextualMenu2
|
{ children }
|
||||||
{...props}
|
</AccessibleButton>
|
||||||
hasBackground={hasBackground}
|
);
|
||||||
closeMenu={onFinished}
|
};
|
||||||
windowResize={onFinished}
|
ContextMenuButton.propTypes = {
|
||||||
>
|
...AccessibleButton.propTypes,
|
||||||
{ children }
|
label: PropTypes.string.isRequired,
|
||||||
</ContextualMenu2>;
|
isExpanded: PropTypes.bool.isRequired, // whether or not the context menu is currently open
|
||||||
|
|
||||||
return ReactDOM.createPortal(menu, getOrCreateContainer());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Semantic component for representing a role=menuitem
|
// Semantic component for representing a role=menuitem
|
||||||
@ -508,6 +532,7 @@ export const MenuItem = ({children, label, ...props}) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
MenuItem.propTypes = {
|
MenuItem.propTypes = {
|
||||||
|
...AccessibleButton.propTypes,
|
||||||
label: PropTypes.string, // optional
|
label: PropTypes.string, // optional
|
||||||
className: PropTypes.string, // optional
|
className: PropTypes.string, // optional
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
@ -520,6 +545,7 @@ export const MenuGroup = ({children, label, ...props}) => {
|
|||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
MenuGroup.propTypes = {
|
MenuGroup.propTypes = {
|
||||||
|
...AccessibleButton.propTypes,
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
className: PropTypes.string, // optional
|
className: PropTypes.string, // optional
|
||||||
};
|
};
|
||||||
@ -534,6 +560,7 @@ export const MenuItemCheckbox = ({children, label, active=false, disabled=false,
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
MenuItemCheckbox.propTypes = {
|
MenuItemCheckbox.propTypes = {
|
||||||
|
...AccessibleButton.propTypes,
|
||||||
label: PropTypes.string, // optional
|
label: PropTypes.string, // optional
|
||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
disabled: PropTypes.bool, // optional
|
disabled: PropTypes.bool, // optional
|
||||||
@ -551,6 +578,7 @@ export const MenuItemRadio = ({children, label, active=false, disabled=false, ..
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
MenuItemRadio.propTypes = {
|
MenuItemRadio.propTypes = {
|
||||||
|
...AccessibleButton.propTypes,
|
||||||
label: PropTypes.string, // optional
|
label: PropTypes.string, // optional
|
||||||
active: PropTypes.bool.isRequired,
|
active: PropTypes.bool.isRequired,
|
||||||
disabled: PropTypes.bool, // optional
|
disabled: PropTypes.bool, // optional
|
||||||
@ -566,6 +594,38 @@ export const toRightOf = (elementRect, chevronOffset=12) => {
|
|||||||
return {left, top};
|
return {left, top};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Placement method for <ContextMenu /> to position context menu right-aligned and flowing to the left of elementRect
|
||||||
|
export const aboveLeft = (elementRect, chevronFace="none") => {
|
||||||
|
const menuOptions = { chevronFace };
|
||||||
|
|
||||||
|
const buttonRight = elementRect.right + window.pageXOffset;
|
||||||
|
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
||||||
|
const buttonTop = elementRect.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;
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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];
|
||||||
|
};
|
||||||
|
|
||||||
export function createMenu(ElementClass, props, hasBackground=true) {
|
export function createMenu(ElementClass, props, hasBackground=true) {
|
||||||
const closeMenu = function(...args) {
|
const closeMenu = function(...args) {
|
||||||
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
|
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
|
||||||
|
@ -23,7 +23,7 @@ import MatrixClientPeg from '../../MatrixClientPeg';
|
|||||||
import Avatar from '../../Avatar';
|
import Avatar from '../../Avatar';
|
||||||
import { _t } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import dis from "../../dispatcher";
|
import dis from "../../dispatcher";
|
||||||
import {ContextMenu} from "./ContextualMenu";
|
import {ContextMenu, ContextMenuButton} from "./ContextualMenu";
|
||||||
import sdk from "../../index";
|
import sdk from "../../index";
|
||||||
|
|
||||||
const AVATAR_SIZE = 28;
|
const AVATAR_SIZE = 28;
|
||||||
@ -119,29 +119,26 @@ export default class TopLeftMenuButton extends React.Component {
|
|||||||
let contextMenu;
|
let contextMenu;
|
||||||
if (this.state.menuDisplayed) {
|
if (this.state.menuDisplayed) {
|
||||||
const elementRect = this._buttonRef.getBoundingClientRect();
|
const elementRect = this._buttonRef.getBoundingClientRect();
|
||||||
const x = elementRect.left;
|
|
||||||
const y = elementRect.top + elementRect.height;
|
|
||||||
|
|
||||||
const props = {
|
contextMenu = (
|
||||||
chevronFace: "none",
|
<ContextMenu
|
||||||
left: x,
|
chevronFace="none"
|
||||||
top: y,
|
left={elementRect.left}
|
||||||
};
|
top={elementRect.top + elementRect.height}
|
||||||
|
onFinished={this.closeMenu}
|
||||||
contextMenu = <ContextMenu props={props} onFinished={this.closeMenu}>
|
>
|
||||||
<TopLeftMenu displayName={name} userId={cli} onFinished={this.closeMenu} />
|
<TopLeftMenu displayName={name} userId={cli} onFinished={this.closeMenu} />
|
||||||
</ContextMenu>;
|
</ContextMenu>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<AccessibleButton
|
<ContextMenuButton
|
||||||
className="mx_TopLeftMenuButton"
|
className="mx_TopLeftMenuButton"
|
||||||
onClick={this.openMenu}
|
onClick={this.openMenu}
|
||||||
inputRef={(r) => this._buttonRef = r}
|
inputRef={(r) => this._buttonRef = r}
|
||||||
aria-label={_t("Your profile")}
|
label={_t("Your profile")}
|
||||||
aria-haspopup={true}
|
isExpanded={this.state.menuDisplayed}
|
||||||
aria-expanded={this.state.menuDisplayed}
|
|
||||||
>
|
>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
idName={MatrixClientPeg.get().getUserId()}
|
idName={MatrixClientPeg.get().getUserId()}
|
||||||
@ -153,7 +150,7 @@ export default class TopLeftMenuButton extends React.Component {
|
|||||||
/>
|
/>
|
||||||
{ nameElement }
|
{ nameElement }
|
||||||
{ chevronElement }
|
{ chevronElement }
|
||||||
</AccessibleButton>
|
</ContextMenuButton>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
@ -17,12 +17,12 @@ limitations under the License.
|
|||||||
import React, {createRef} from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import {_t} from "../../../languageHandler";
|
||||||
import MemberAvatar from '../avatars/MemberAvatar';
|
import MemberAvatar from '../avatars/MemberAvatar';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu";
|
import StatusMessageContextMenu from "../context_menus/StatusMessageContextMenu";
|
||||||
import SettingsStore from "../../../settings/SettingsStore";
|
import SettingsStore from "../../../settings/SettingsStore";
|
||||||
import {ContextMenu} from "../../structures/ContextualMenu";
|
import {ContextMenu, ContextMenuButton} from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
export default class MemberStatusMessageAvatar extends React.Component {
|
export default class MemberStatusMessageAvatar extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -118,29 +118,33 @@ export default class MemberStatusMessageAvatar extends React.Component {
|
|||||||
if (this.state.menuDisplayed) {
|
if (this.state.menuDisplayed) {
|
||||||
const elementRect = this._button.current.getBoundingClientRect();
|
const elementRect = this._button.current.getBoundingClientRect();
|
||||||
|
|
||||||
const x = (elementRect.left + window.pageXOffset);
|
|
||||||
const chevronWidth = 16; // See .mx_ContextualMenu_chevron_bottom
|
const chevronWidth = 16; // See .mx_ContextualMenu_chevron_bottom
|
||||||
const chevronOffset = (elementRect.width - chevronWidth) / 2;
|
|
||||||
const chevronMargin = 1; // Add some spacing away from target
|
const chevronMargin = 1; // Add some spacing away from target
|
||||||
const y = elementRect.top + window.pageYOffset - chevronMargin;
|
|
||||||
|
|
||||||
const props = {
|
contextMenu = (
|
||||||
chevronOffset: chevronOffset,
|
<ContextMenu
|
||||||
chevronFace: 'bottom',
|
chevronOffset={(elementRect.width - chevronWidth) / 2}
|
||||||
left: x,
|
chevronFace="bottom"
|
||||||
top: y,
|
left={elementRect.left + window.pageXOffset}
|
||||||
menuWidth: 226,
|
top={elementRect.top + window.pageYOffset - chevronMargin}
|
||||||
};
|
menuWidth={226}
|
||||||
|
onFinished={this.closeMenu}
|
||||||
contextMenu = <ContextMenu props={props} onFinished={this.closeMenu}>
|
>
|
||||||
<StatusMessageContextMenu user={this.props.member.user} onFinished={this.closeMenu} />
|
<StatusMessageContextMenu user={this.props.member.user} onFinished={this.closeMenu} />
|
||||||
</ContextMenu>;
|
</ContextMenu>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<AccessibleButton className={classes} inputRef={this._button} onClick={this.openMenu}>
|
<ContextMenuButton
|
||||||
|
className={classes}
|
||||||
|
inputRef={this._button}
|
||||||
|
onClick={this.openMenu}
|
||||||
|
isExpanded={this.state.menuDisplayed}
|
||||||
|
label={_t("User Status")}
|
||||||
|
>
|
||||||
{avatar}
|
{avatar}
|
||||||
</AccessibleButton>
|
</ContextMenuButton>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
@ -16,8 +16,8 @@ limitations under the License.
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sdk from '../../../index';
|
|
||||||
import {_t} from '../../../languageHandler';
|
import {_t} from '../../../languageHandler';
|
||||||
|
import {MenuItem} from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
export default class WidgetContextMenu extends React.Component {
|
export default class WidgetContextMenu extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -71,50 +71,45 @@ export default class WidgetContextMenu extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AccessibleButton = sdk.getComponent("views.elements.AccessibleButton");
|
|
||||||
|
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
if (this.props.onEditClicked) {
|
if (this.props.onEditClicked) {
|
||||||
options.push(
|
options.push(
|
||||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onEditClicked} key='edit'>
|
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onEditClicked} key='edit'>
|
||||||
{_t("Edit")}
|
{_t("Edit")}
|
||||||
</AccessibleButton>,
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.onReloadClicked) {
|
if (this.props.onReloadClicked) {
|
||||||
options.push(
|
options.push(
|
||||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked}
|
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onReloadClicked} key='reload'>
|
||||||
key='reload'>
|
|
||||||
{_t("Reload")}
|
{_t("Reload")}
|
||||||
</AccessibleButton>,
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.onSnapshotClicked) {
|
if (this.props.onSnapshotClicked) {
|
||||||
options.push(
|
options.push(
|
||||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onSnapshotClicked}
|
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onSnapshotClicked} key='snap'>
|
||||||
key='snap'>
|
|
||||||
{_t("Take picture")}
|
{_t("Take picture")}
|
||||||
</AccessibleButton>,
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.onDeleteClicked) {
|
if (this.props.onDeleteClicked) {
|
||||||
options.push(
|
options.push(
|
||||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onDeleteClicked}
|
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onDeleteClicked} key='delete'>
|
||||||
key='delete'>
|
|
||||||
{_t("Remove for everyone")}
|
{_t("Remove for everyone")}
|
||||||
</AccessibleButton>,
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push this last so it appears last. It's always present.
|
// Push this last so it appears last. It's always present.
|
||||||
options.push(
|
options.push(
|
||||||
<AccessibleButton className='mx_WidgetContextMenu_option' onClick={this.onRevokeClicked} key='revoke'>
|
<MenuItem className='mx_WidgetContextMenu_option' onClick={this.onRevokeClicked} key='revoke'>
|
||||||
{_t("Remove for me")}
|
{_t("Remove for me")}
|
||||||
</AccessibleButton>,
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Put separators between the options
|
// Put separators between the options
|
||||||
|
@ -18,7 +18,7 @@ limitations under the License.
|
|||||||
|
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import qs from 'querystring';
|
import qs from 'querystring';
|
||||||
import React from 'react';
|
import React, {createRef} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import WidgetMessaging from '../../../WidgetMessaging';
|
import WidgetMessaging from '../../../WidgetMessaging';
|
||||||
@ -35,7 +35,7 @@ import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
|
||||||
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
|
||||||
import {createMenu} from "../../structures/ContextualMenu";
|
import {aboveLeft, ContextMenu, ContextMenuButton} from "../../structures/ContextualMenu";
|
||||||
import PersistedElement from "./PersistedElement";
|
import PersistedElement from "./PersistedElement";
|
||||||
|
|
||||||
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
|
||||||
@ -62,6 +62,8 @@ export default class AppTile extends React.Component {
|
|||||||
this._revokeWidgetPermission = this._revokeWidgetPermission.bind(this);
|
this._revokeWidgetPermission = this._revokeWidgetPermission.bind(this);
|
||||||
this._onPopoutWidgetClick = this._onPopoutWidgetClick.bind(this);
|
this._onPopoutWidgetClick = this._onPopoutWidgetClick.bind(this);
|
||||||
this._onReloadWidgetClick = this._onReloadWidgetClick.bind(this);
|
this._onReloadWidgetClick = this._onReloadWidgetClick.bind(this);
|
||||||
|
|
||||||
|
this._contextMenuButton = createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -89,6 +91,7 @@ export default class AppTile extends React.Component {
|
|||||||
error: null,
|
error: null,
|
||||||
deleting: false,
|
deleting: false,
|
||||||
widgetPageTitle: newProps.widgetPageTitle,
|
widgetPageTitle: newProps.widgetPageTitle,
|
||||||
|
menuDisplayed: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,45 +558,12 @@ export default class AppTile extends React.Component {
|
|||||||
this.refs.appFrame.src = this.refs.appFrame.src;
|
this.refs.appFrame.src = this.refs.appFrame.src;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getMenuOptions(ev) {
|
_onContextMenuClick = () => {
|
||||||
// TODO: This block of code gets copy/pasted a lot. We should make that happen less.
|
this.setState({ menuDisplayed: true });
|
||||||
const menuOptions = {};
|
};
|
||||||
const buttonRect = ev.target.getBoundingClientRect();
|
|
||||||
// The window X and Y offsets are to adjust position when zoomed in to page
|
|
||||||
const buttonLeft = buttonRect.left + window.pageXOffset;
|
|
||||||
const buttonTop = buttonRect.top + window.pageYOffset;
|
|
||||||
// Align the right edge of the menu to the left edge of the button
|
|
||||||
menuOptions.right = window.innerWidth - buttonLeft;
|
|
||||||
// Align the menu vertically on whichever side of the button has more
|
|
||||||
// space available.
|
|
||||||
if (buttonTop < window.innerHeight / 2) {
|
|
||||||
menuOptions.top = buttonTop;
|
|
||||||
} else {
|
|
||||||
menuOptions.bottom = window.innerHeight - buttonTop;
|
|
||||||
}
|
|
||||||
return menuOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onContextMenuClick = (ev) => {
|
_closeContextMenu = () => {
|
||||||
const WidgetContextMenu = sdk.getComponent('views.context_menus.WidgetContextMenu');
|
this.setState({ menuDisplayed: false });
|
||||||
const menuOptions = {
|
|
||||||
...this._getMenuOptions(ev),
|
|
||||||
|
|
||||||
// A revoke handler is always required
|
|
||||||
onRevokeClicked: this._onRevokeClicked,
|
|
||||||
};
|
|
||||||
|
|
||||||
const canUserModify = this._canUserModify();
|
|
||||||
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
|
||||||
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
|
||||||
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
|
||||||
|
|
||||||
if (showEditButton) menuOptions.onEditClicked = this._onEditClick;
|
|
||||||
if (showDeleteButton) menuOptions.onDeleteClicked = this._onDeleteClick;
|
|
||||||
if (showPictureSnapshotButton) menuOptions.onSnapshotClicked = this._onSnapshotClick;
|
|
||||||
if (this.props.showReload) menuOptions.onReloadClicked = this._onReloadWidgetClick;
|
|
||||||
|
|
||||||
createMenu(WidgetContextMenu, menuOptions);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -601,7 +571,7 @@ export default class AppTile extends React.Component {
|
|||||||
|
|
||||||
// Don't render widget if it is in the process of being deleted
|
// Don't render widget if it is in the process of being deleted
|
||||||
if (this.state.deleting) {
|
if (this.state.deleting) {
|
||||||
return <div></div>;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
|
// Note that there is advice saying allow-scripts shouldn't be used with allow-same-origin
|
||||||
@ -697,7 +667,31 @@ export default class AppTile extends React.Component {
|
|||||||
mx_AppTileMenuBar_expanded: this.props.show,
|
mx_AppTileMenuBar_expanded: this.props.show,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
let contextMenu;
|
||||||
|
if (this.state.menuDisplayed) {
|
||||||
|
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
const canUserModify = this._canUserModify();
|
||||||
|
const showEditButton = Boolean(this._scalarClient && canUserModify);
|
||||||
|
const showDeleteButton = (this.props.showDelete === undefined || this.props.showDelete) && canUserModify;
|
||||||
|
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||||
|
|
||||||
|
const WidgetContextMenu = sdk.getComponent('views.context_menus.WidgetContextMenu');
|
||||||
|
contextMenu = (
|
||||||
|
<ContextMenu {...aboveLeft(elementRect, null)} onFinished={this._closeContextMenu}>
|
||||||
|
<WidgetContextMenu
|
||||||
|
onRevokeClicked={this._onRevokeClicked}
|
||||||
|
onEditClicked={showEditButton && this._onEditClick}
|
||||||
|
onDeleteClicked={showDeleteButton && this._onDeleteClick}
|
||||||
|
onSnapshotClicked={showPictureSnapshotButton && this._onSnapshotClick}
|
||||||
|
onReloadClicked={this.props.showReload && this._onReloadWidgetClick}
|
||||||
|
onFinished={this._closeContextMenu}
|
||||||
|
/>
|
||||||
|
</ContextMenu>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <React.Fragment>
|
||||||
<div className={appTileClass} id={this.props.id}>
|
<div className={appTileClass} id={this.props.id}>
|
||||||
{ this.props.showMenubar &&
|
{ this.props.showMenubar &&
|
||||||
<div ref="menu_bar" className={menuBarClasses} onClick={this.onClickMenuBar}>
|
<div ref="menu_bar" className={menuBarClasses} onClick={this.onClickMenuBar}>
|
||||||
@ -725,20 +719,24 @@ export default class AppTile extends React.Component {
|
|||||||
onClick={this._onPopoutWidgetClick}
|
onClick={this._onPopoutWidgetClick}
|
||||||
/> }
|
/> }
|
||||||
{ /* Context menu */ }
|
{ /* Context menu */ }
|
||||||
{ <AccessibleButton
|
{ <ContextMenuButton
|
||||||
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
className="mx_AppTileMenuBar_iconButton mx_AppTileMenuBar_iconButton_menu"
|
||||||
title={_t('More options')}
|
label={_t('More options')}
|
||||||
|
isExpanded={this.state.menuDisplayed}
|
||||||
|
inputRef={this._contextMenuButton}
|
||||||
onClick={this._onContextMenuClick}
|
onClick={this._onContextMenuClick}
|
||||||
/> }
|
/> }
|
||||||
</span>
|
</span>
|
||||||
</div> }
|
</div> }
|
||||||
{ appTileBody }
|
{ appTileBody }
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
|
{ contextMenu }
|
||||||
|
</React.Fragment>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AppTile.displayName ='AppTile';
|
AppTile.displayName = 'AppTile';
|
||||||
|
|
||||||
AppTile.propTypes = {
|
AppTile.propTypes = {
|
||||||
id: PropTypes.string.isRequired,
|
id: PropTypes.string.isRequired,
|
||||||
|
@ -23,13 +23,14 @@ import classNames from 'classnames';
|
|||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
|
import {_t} from '../../../languageHandler';
|
||||||
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
import { isOnlyCtrlOrCmdIgnoreShiftKeyEvent } from '../../../Keyboard';
|
||||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
|
|
||||||
import FlairStore from '../../../stores/FlairStore';
|
import FlairStore from '../../../stores/FlairStore';
|
||||||
import GroupStore from '../../../stores/GroupStore';
|
import GroupStore from '../../../stores/GroupStore';
|
||||||
import TagOrderStore from '../../../stores/TagOrderStore';
|
import TagOrderStore from '../../../stores/TagOrderStore';
|
||||||
import {ContextMenu, toRightOf} from "../../structures/ContextualMenu";
|
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
// A class for a child of TagPanel (possibly wrapped in a DNDTagTile) that represents
|
||||||
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
// a thing to click on for the user to filter the visible rooms in the RoomList to:
|
||||||
@ -139,7 +140,6 @@ export default createReactClass({
|
|||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
const Tooltip = sdk.getComponent('elements.Tooltip');
|
const Tooltip = sdk.getComponent('elements.Tooltip');
|
||||||
const profile = this.state.profile || {};
|
const profile = this.state.profile || {};
|
||||||
const name = profile.name || this.props.tag;
|
const name = profile.name || this.props.tag;
|
||||||
@ -178,14 +178,20 @@ export default createReactClass({
|
|||||||
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
||||||
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
|
const TagTileContextMenu = sdk.getComponent('context_menus.TagTileContextMenu');
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<ContextMenu props={toRightOf(elementRect)} onFinished={this.closeMenu}>
|
<ContextMenu {...toRightOf(elementRect)} onFinished={this.closeMenu}>
|
||||||
<TagTileContextMenu tag={this.props.tag} onFinished={this.closeMenu} />
|
<TagTileContextMenu tag={this.props.tag} onFinished={this.closeMenu} />
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<AccessibleButton className={className} onClick={this.onClick} onContextMenu={this.openMenu}>
|
<ContextMenuButton
|
||||||
|
className={className}
|
||||||
|
onClick={this.onClick}
|
||||||
|
onContextMenu={this.openMenu}
|
||||||
|
label={_t("Options")}
|
||||||
|
isExpanded={this.state.menuDisplayed}
|
||||||
|
>
|
||||||
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
<div className="mx_TagTile_avatar" onMouseOver={this.onMouseOver} onMouseOut={this.onMouseOut}>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
name={name}
|
name={name}
|
||||||
@ -198,7 +204,7 @@ export default createReactClass({
|
|||||||
{ contextButton }
|
{ contextButton }
|
||||||
{ badgeElement }
|
{ badgeElement }
|
||||||
</div>
|
</div>
|
||||||
</AccessibleButton>
|
</ContextMenuButton>
|
||||||
|
|
||||||
{ contextMenu }
|
{ contextMenu }
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
|
@ -22,9 +22,10 @@ import createReactClass from 'create-react-class';
|
|||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
|
import {_t} from '../../../languageHandler';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import MatrixClientPeg from "../../../MatrixClientPeg";
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
import {ContextMenu, toRightOf} from "../../structures/ContextualMenu";
|
import {ContextMenu, ContextMenuButton, toRightOf} from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
export default createReactClass({
|
export default createReactClass({
|
||||||
displayName: 'GroupInviteTile',
|
displayName: 'GroupInviteTile',
|
||||||
@ -124,9 +125,15 @@ export default createReactClass({
|
|||||||
|
|
||||||
const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
|
const badgeContent = badgeEllipsis ? '\u00B7\u00B7\u00B7' : '!';
|
||||||
const badge = (
|
const badge = (
|
||||||
<AccessibleButton className={badgeClasses} inputRef={this._contextMenuButton} onClick={this.openMenu}>
|
<ContextMenuButton
|
||||||
|
className={badgeClasses}
|
||||||
|
inputRef={this._contextMenuButton}
|
||||||
|
onClick={this.openMenu}
|
||||||
|
label={_t("Options")}
|
||||||
|
isExpanded={this.state.menuDisplayed}
|
||||||
|
>
|
||||||
{ badgeContent }
|
{ badgeContent }
|
||||||
</AccessibleButton>
|
</ContextMenuButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
let tooltip;
|
let tooltip;
|
||||||
@ -146,7 +153,7 @@ export default createReactClass({
|
|||||||
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
||||||
const GroupInviteTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu');
|
const GroupInviteTileContextMenu = sdk.getComponent('context_menus.GroupInviteTileContextMenu');
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<ContextMenu props={toRightOf(elementRect)} onFinished={this.closeMenu}>
|
<ContextMenu {...toRightOf(elementRect)} onFinished={this.closeMenu}>
|
||||||
<GroupInviteTileContextMenu group={this.props.group} onFinished={this.closeMenu} />
|
<GroupInviteTileContextMenu group={this.props.group} onFinished={this.closeMenu} />
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
|
@ -16,50 +16,17 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useState, useEffect, useRef} from 'react';
|
import React, {useEffect} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import dis from '../../../dispatcher';
|
import dis from '../../../dispatcher';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import {ContextMenu} from '../../structures/ContextualMenu';
|
import {aboveLeft, ContextMenu, ContextMenuButton, useContextMenu} from '../../structures/ContextualMenu';
|
||||||
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
|
||||||
import {RoomContext} from "../../structures/RoomView";
|
import {RoomContext} from "../../structures/RoomView";
|
||||||
|
|
||||||
const contextMenuProps = (elementRect) => {
|
|
||||||
const menuOptions = {
|
|
||||||
chevronFace: "none",
|
|
||||||
};
|
|
||||||
|
|
||||||
const buttonRight = elementRect.right + window.pageXOffset;
|
|
||||||
const buttonBottom = elementRect.bottom + window.pageYOffset;
|
|
||||||
const buttonTop = elementRect.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;
|
|
||||||
};
|
|
||||||
|
|
||||||
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 OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange}) => {
|
||||||
const [menuDisplayed, _button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, _button, openMenu, closeMenu] = useContextMenu();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -86,7 +53,7 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
|
|||||||
}
|
}
|
||||||
|
|
||||||
const buttonRect = _button.current.getBoundingClientRect();
|
const buttonRect = _button.current.getBoundingClientRect();
|
||||||
contextMenu = <ContextMenu props={contextMenuProps(buttonRect)} onFinished={closeMenu}>
|
contextMenu = <ContextMenu {...aboveLeft(buttonRect)} onFinished={closeMenu}>
|
||||||
<MessageContextMenu
|
<MessageContextMenu
|
||||||
mxEvent={mxEvent}
|
mxEvent={mxEvent}
|
||||||
permalinkCreator={permalinkCreator}
|
permalinkCreator={permalinkCreator}
|
||||||
@ -98,14 +65,12 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo
|
|||||||
</ContextMenu>;
|
</ContextMenu>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<AccessibleButton
|
<ContextMenuButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
|
||||||
title={_t("Options")}
|
label={_t("Options")}
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
aria-haspopup={true}
|
isExpanded={menuDisplayed}
|
||||||
aria-expanded={menuDisplayed}
|
|
||||||
inputRef={_button}
|
inputRef={_button}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -120,19 +85,17 @@ const ReactButton = ({mxEvent, reactions}) => {
|
|||||||
if (menuDisplayed) {
|
if (menuDisplayed) {
|
||||||
const buttonRect = _button.current.getBoundingClientRect();
|
const buttonRect = _button.current.getBoundingClientRect();
|
||||||
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
|
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
|
||||||
contextMenu = <ContextMenu props={contextMenuProps(buttonRect)} onFinished={closeMenu}>
|
contextMenu = <ContextMenu {...aboveLeft(buttonRect)} onFinished={closeMenu}>
|
||||||
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
|
<ReactionPicker mxEvent={mxEvent} reactions={reactions} onFinished={closeMenu} />
|
||||||
</ContextMenu>;
|
</ContextMenu>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
|
|
||||||
return <React.Fragment>
|
return <React.Fragment>
|
||||||
<AccessibleButton
|
<ContextMenuButton
|
||||||
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
className="mx_MessageActionBar_maskButton mx_MessageActionBar_reactButton"
|
||||||
title={_t("React")}
|
label={_t("React")}
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
aria-haspopup={true}
|
isExpanded={menuDisplayed}
|
||||||
aria-expanded={menuDisplayed}
|
|
||||||
inputRef={_button}
|
inputRef={_button}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -227,7 +190,7 @@ export default class MessageActionBar extends React.PureComponent {
|
|||||||
getReplyThread={this.props.getReplyThread}
|
getReplyThread={this.props.getReplyThread}
|
||||||
getTile={this.props.getTile}
|
getTile={this.props.getTile}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
onFocusChange={this.props.onFocusChange}
|
onFocusChange={this.onFocusChange}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ import dis from '../../../dispatcher';
|
|||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import DMRoomMap from '../../../utils/DMRoomMap';
|
import DMRoomMap from '../../../utils/DMRoomMap';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import {ContextMenu, toRightOf} from '../../structures/ContextualMenu';
|
import {ContextMenu, ContextMenuButton, toRightOf} from '../../structures/ContextualMenu';
|
||||||
import * as RoomNotifs from '../../../RoomNotifs';
|
import * as RoomNotifs from '../../../RoomNotifs';
|
||||||
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
import * as FormattingUtils from '../../../utils/FormattingUtils';
|
||||||
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
import ActiveRoomObserver from '../../../ActiveRoomObserver';
|
||||||
@ -344,7 +344,12 @@ module.exports = createReactClass({
|
|||||||
let contextMenuButton;
|
let contextMenuButton;
|
||||||
if (!MatrixClientPeg.get().isGuest()) {
|
if (!MatrixClientPeg.get().isGuest()) {
|
||||||
contextMenuButton = (
|
contextMenuButton = (
|
||||||
<AccessibleButton className="mx_RoomTile_menuButton" inputRef={this._contextMenuButton} onClick={this.openMenu} />
|
<ContextMenuButton
|
||||||
|
className="mx_RoomTile_menuButton"
|
||||||
|
inputRef={this._contextMenuButton}
|
||||||
|
label={_t("Options")}
|
||||||
|
isExpanded={this.state.menuDisplayed}
|
||||||
|
onClick={this.openMenu} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,7 +386,7 @@ module.exports = createReactClass({
|
|||||||
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
const elementRect = this._contextMenuButton.current.getBoundingClientRect();
|
||||||
const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
|
const RoomTileContextMenu = sdk.getComponent('context_menus.RoomTileContextMenu');
|
||||||
contextMenu = (
|
contextMenu = (
|
||||||
<ContextMenu props={toRightOf(elementRect)} onFinished={this.closeMenu}>
|
<ContextMenu {...toRightOf(elementRect)} onFinished={this.closeMenu}>
|
||||||
<RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} />
|
<RoomTileContextMenu room={this.props.room} onFinished={this.closeMenu} />
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user