mirror of
https://github.com/element-hq/element-web.git
synced 2024-11-30 23:31:28 +03:00
Merge pull request #6750 from matrix-org/gsouquet/migrate-messageactionbar
This commit is contained in:
commit
42f5efaa28
1 changed files with 80 additions and 64 deletions
|
@ -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
|
Loading…
Reference in a new issue