Merge pull request #6750 from matrix-org/gsouquet/migrate-messageactionbar

This commit is contained in:
Germain 2021-09-07 14:11:20 +01:00 committed by GitHub
commit 42f5efaa28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -17,8 +17,7 @@ limitations under the License.
*/ */
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import PropTypes from 'prop-types'; import { MatrixEvent, EventStatus } from 'matrix-js-sdk/src/models/event';
import { EventStatus } from 'matrix-js-sdk/src/models/event';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
@ -37,48 +36,65 @@ import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { MediaEventHelper } from "../../../utils/MediaEventHelper"; import { MediaEventHelper } from "../../../utils/MediaEventHelper";
import DownloadActionButton from "./DownloadActionButton"; import DownloadActionButton from "./DownloadActionButton";
import SettingsStore from '../../../settings/SettingsStore'; import SettingsStore from '../../../settings/SettingsStore';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ReplyThread from '../elements/ReplyThread';
const OptionsButton = ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => { interface IOptionsButtonProps {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); mxEvent: MatrixEvent;
const [onFocus, isActive, ref] = useRovingTabIndex(button); getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
useEffect(() => { getReplyThread: () => ReplyThread;
onFocusChange(menuDisplayed); permalinkCreator: RoomPermalinkCreator;
}, [onFocusChange, menuDisplayed]); onFocusChange: (menuDisplayed: boolean) => void;
}
let contextMenu; const OptionsButton: React.FC<IOptionsButtonProps> =
if (menuDisplayed) { ({ mxEvent, getTile, getReplyThread, permalinkCreator, onFocusChange }) => {
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu'); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => {
onFocusChange(menuDisplayed);
}, [onFocusChange, menuDisplayed]);
const tile = getTile && getTile(); let contextMenu;
const replyThread = getReplyThread && getReplyThread(); if (menuDisplayed) {
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');
const buttonRect = button.current.getBoundingClientRect(); const tile = getTile && getTile();
contextMenu = <MessageContextMenu const replyThread = getReplyThread && getReplyThread();
{...aboveLeftOf(buttonRect)}
mxEvent={mxEvent}
permalinkCreator={permalinkCreator}
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
collapseReplyThread={replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined}
onFinished={closeMenu}
/>;
}
return <React.Fragment> const buttonRect = button.current.getBoundingClientRect();
<ContextMenuTooltipButton contextMenu = <MessageContextMenu
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton" {...aboveLeftOf(buttonRect)}
title={_t("Options")} mxEvent={mxEvent}
onClick={openMenu} permalinkCreator={permalinkCreator}
isExpanded={menuDisplayed} eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
inputRef={ref} collapseReplyThread={replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined}
onFocus={onFocus} onFinished={closeMenu}
tabIndex={isActive ? 0 : -1} />;
/> }
{ contextMenu } return <React.Fragment>
</React.Fragment>; <ContextMenuTooltipButton
}; className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
title={_t("Options")}
onClick={openMenu}
isExpanded={menuDisplayed}
inputRef={ref}
onFocus={onFocus}
tabIndex={isActive ? 0 : -1}
/>
const ReactButton = ({ mxEvent, reactions, onFocusChange }) => { { contextMenu }
</React.Fragment>;
};
interface IReactButtonProps {
mxEvent: MatrixEvent;
reactions: any; // TODO: types
onFocusChange: (menuDisplayed: boolean) => void;
}
const ReactButton: React.FC<IReactButtonProps> = ({ mxEvent, reactions, onFocusChange }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const [onFocus, isActive, ref] = useRovingTabIndex(button); const [onFocus, isActive, ref] = useRovingTabIndex(button);
useEffect(() => { useEffect(() => {
@ -109,21 +125,21 @@ const ReactButton = ({ mxEvent, reactions, onFocusChange }) => {
</React.Fragment>; </React.Fragment>;
}; };
interface IMessageActionBarProps {
mxEvent: MatrixEvent;
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions?: any; // TODO: types
permalinkCreator?: RoomPermalinkCreator;
getTile: () => any; // TODO: FIXME, haven't figured out what the return type is here
getReplyThread?: () => ReplyThread;
onFocusChange?: (menuDisplayed: boolean) => void;
}
@replaceableComponent("views.messages.MessageActionBar") @replaceableComponent("views.messages.MessageActionBar")
export default class MessageActionBar extends React.PureComponent { export default class MessageActionBar extends React.PureComponent<IMessageActionBarProps> {
static propTypes = { public static contextType = RoomContext;
mxEvent: PropTypes.object.isRequired,
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: PropTypes.object,
permalinkCreator: PropTypes.object,
getTile: PropTypes.func,
getReplyThread: PropTypes.func,
onFocusChange: PropTypes.func,
};
static contextType = RoomContext; public componentDidMount(): void {
componentDidMount() {
if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) { if (this.props.mxEvent.status && this.props.mxEvent.status !== EventStatus.SENT) {
this.props.mxEvent.on("Event.status", this.onSent); this.props.mxEvent.on("Event.status", this.onSent);
} }
@ -137,43 +153,43 @@ export default class MessageActionBar extends React.PureComponent {
this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction); this.props.mxEvent.on("Event.beforeRedaction", this.onBeforeRedaction);
} }
componentWillUnmount() { public componentWillUnmount(): void {
this.props.mxEvent.off("Event.status", this.onSent); this.props.mxEvent.off("Event.status", this.onSent);
this.props.mxEvent.off("Event.decrypted", this.onDecrypted); this.props.mxEvent.off("Event.decrypted", this.onDecrypted);
this.props.mxEvent.off("Event.beforeRedaction", this.onBeforeRedaction); this.props.mxEvent.off("Event.beforeRedaction", this.onBeforeRedaction);
} }
onDecrypted = () => { private onDecrypted = (): void => {
// When an event decrypts, it is likely to change the set of available // When an event decrypts, it is likely to change the set of available
// actions, so we force an update to check again. // actions, so we force an update to check again.
this.forceUpdate(); this.forceUpdate();
}; };
onBeforeRedaction = () => { private onBeforeRedaction = (): void => {
// When an event is redacted, we can't edit it so update the available actions. // When an event is redacted, we can't edit it so update the available actions.
this.forceUpdate(); this.forceUpdate();
}; };
onSent = () => { private onSent = (): void => {
// When an event is sent and echoed the possible actions change. // When an event is sent and echoed the possible actions change.
this.forceUpdate(); this.forceUpdate();
}; };
onFocusChange = (focused) => { private onFocusChange = (focused: boolean): void => {
if (!this.props.onFocusChange) { if (!this.props.onFocusChange) {
return; return;
} }
this.props.onFocusChange(focused); this.props.onFocusChange(focused);
}; };
onReplyClick = (ev) => { private onReplyClick = (ev: React.MouseEvent): void => {
dis.dispatch({ dis.dispatch({
action: 'reply_to_event', action: 'reply_to_event',
event: this.props.mxEvent, event: this.props.mxEvent,
}); });
}; };
onThreadClick = () => { private onThreadClick = (): void => {
dis.dispatch({ dis.dispatch({
action: Action.SetRightPanelPhase, action: Action.SetRightPanelPhase,
phase: RightPanelPhases.ThreadView, phase: RightPanelPhases.ThreadView,
@ -182,9 +198,9 @@ export default class MessageActionBar extends React.PureComponent {
event: this.props.mxEvent, event: this.props.mxEvent,
}, },
}); });
} };
onEditClick = (ev) => { private onEditClick = (ev: React.MouseEvent): void => {
dis.dispatch({ dis.dispatch({
action: 'edit_event', action: 'edit_event',
event: this.props.mxEvent, event: this.props.mxEvent,
@ -200,7 +216,7 @@ export default class MessageActionBar extends React.PureComponent {
* @param {Function} fn The execution function. * @param {Function} fn The execution function.
* @param {Function} checkFn The test function. * @param {Function} checkFn The test function.
*/ */
runActionOnFailedEv(fn, checkFn) { private runActionOnFailedEv(fn: (ev: MatrixEvent) => void, checkFn?: (ev: MatrixEvent) => boolean): void {
if (!checkFn) checkFn = () => true; if (!checkFn) checkFn = () => true;
const mxEvent = this.props.mxEvent; const mxEvent = this.props.mxEvent;
@ -215,18 +231,18 @@ export default class MessageActionBar extends React.PureComponent {
} }
} }
onResendClick = (ev) => { private onResendClick = (ev: React.MouseEvent): void => {
this.runActionOnFailedEv((tarEv) => Resend.resend(tarEv)); this.runActionOnFailedEv((tarEv) => Resend.resend(tarEv));
}; };
onCancelClick = (ev) => { private onCancelClick = (ev: React.MouseEvent): void => {
this.runActionOnFailedEv( this.runActionOnFailedEv(
(tarEv) => Resend.removeFromQueue(tarEv), (tarEv) => Resend.removeFromQueue(tarEv),
(testEv) => canCancel(testEv.status), (testEv) => canCancel(testEv.status),
); );
}; };
render() { public render(): JSX.Element {
const toolbarOpts = []; const toolbarOpts = [];
if (canEditContent(this.props.mxEvent)) { if (canEditContent(this.props.mxEvent)) {
toolbarOpts.push(<RovingAccessibleTooltipButton toolbarOpts.push(<RovingAccessibleTooltipButton
@ -249,7 +265,7 @@ export default class MessageActionBar extends React.PureComponent {
const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status; const editStatus = mxEvent.replacingEvent() && mxEvent.replacingEvent().status;
const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status; const redactStatus = mxEvent.localRedactionEvent() && mxEvent.localRedactionEvent().status;
const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus); const allowCancel = canCancel(mxEvent.status) || canCancel(editStatus) || canCancel(redactStatus);
const isFailed = [mxEvent.status, editStatus, redactStatus].includes("not_sent"); const isFailed = [mxEvent.status, editStatus, redactStatus].includes(EventStatus.NOT_SENT);
if (allowCancel && isFailed) { if (allowCancel && isFailed) {
// The resend button needs to appear ahead of the edit button, so insert to the // The resend button needs to appear ahead of the edit button, so insert to the
// start of the opts // start of the opts