Refactor MessageComposerButtons (#7668)
parent
7c20eb9b74
commit
b5a56698d6
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { IEventRelation } from "matrix-js-sdk/src/models/event";
|
import { IEventRelation } from "matrix-js-sdk/src/models/event";
|
||||||
import { M_POLL_START } from "matrix-events-sdk";
|
import { M_POLL_START } from "matrix-events-sdk";
|
||||||
import React, { useContext } from 'react';
|
import React, { ReactElement, useContext } from 'react';
|
||||||
import { Room } from 'matrix-js-sdk/src/models/room';
|
import { Room } from 'matrix-js-sdk/src/models/room';
|
||||||
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
|
|
||||||
|
@ -56,85 +56,55 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
||||||
const matrixClient: MatrixClient = useContext(MatrixClientContext);
|
const matrixClient: MatrixClient = useContext(MatrixClientContext);
|
||||||
const { room, roomId } = useContext(RoomContext);
|
const { room, roomId } = useContext(RoomContext);
|
||||||
|
|
||||||
if (props.haveRecording) {
|
return (
|
||||||
return null;
|
props.haveRecording
|
||||||
}
|
? null
|
||||||
|
: props.narrowMode
|
||||||
let uploadButtonIndex = 0;
|
? narrowMode(props, room, roomId, matrixClient)
|
||||||
const buttons: JSX.Element[] = [];
|
: wideMode(props, room, roomId, matrixClient)
|
||||||
buttons.push(
|
|
||||||
<PollButton
|
|
||||||
key="polls"
|
|
||||||
room={room}
|
|
||||||
narrowMode={props.narrowMode}
|
|
||||||
/>,
|
|
||||||
);
|
);
|
||||||
uploadButtonIndex = buttons.length;
|
};
|
||||||
buttons.push(
|
|
||||||
<UploadButton key="controls_upload" roomId={roomId} relation={props.relation} />,
|
|
||||||
);
|
|
||||||
if (props.showLocationButton) {
|
|
||||||
const sender = room.getMember(matrixClient.getUserId());
|
|
||||||
buttons.push(
|
|
||||||
<LocationButton
|
|
||||||
key="location"
|
|
||||||
roomId={roomId}
|
|
||||||
sender={sender}
|
|
||||||
menuPosition={props.menuPosition}
|
|
||||||
narrowMode={props.narrowMode}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
buttons.push(
|
|
||||||
<EmojiButton key="emoji_button" addEmoji={props.addEmoji} menuPosition={props.menuPosition} narrowMode={props.narrowMode} />,
|
|
||||||
);
|
|
||||||
if (props.showStickersButton) {
|
|
||||||
let title: string;
|
|
||||||
if (!props.narrowMode) {
|
|
||||||
title = props.isStickerPickerOpen ? _t("Hide Stickers") : _t("Show Stickers");
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons.push(
|
function wideMode(
|
||||||
<AccessibleTooltipButton
|
props: IProps,
|
||||||
id='stickersButton'
|
room: Room,
|
||||||
key="controls_stickers"
|
roomId: string,
|
||||||
className="mx_MessageComposer_button mx_MessageComposer_stickers"
|
matrixClient: MatrixClient,
|
||||||
onClick={() => props.setStickerPickerOpen(!props.isStickerPickerOpen)}
|
): ReactElement {
|
||||||
title={title}
|
return <>
|
||||||
label={props.narrowMode ? _t("Send a sticker") : null}
|
{ pollButton(props, room) }
|
||||||
/>,
|
{ uploadButton(props, roomId) }
|
||||||
);
|
{ showLocationButton(props, room, roomId, matrixClient) }
|
||||||
}
|
{ emojiButton(props) }
|
||||||
|
{ showStickersButton(props) }
|
||||||
|
{ voiceRecordingButton(props) }
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
// XXX: the recording UI does not work well in narrow mode, so we hide this button for now
|
function narrowMode(
|
||||||
if (!props.narrowMode) {
|
props: IProps,
|
||||||
buttons.push(
|
room: Room,
|
||||||
<CollapsibleButton
|
roomId: string,
|
||||||
key="voice_message_send"
|
matrixClient: MatrixClient,
|
||||||
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
|
): ReactElement {
|
||||||
onClick={props.onRecordStartEndClick}
|
const moreOptionsClasses = classNames({
|
||||||
title={_t("Send voice message")}
|
|
||||||
narrowMode={props.narrowMode}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!props.narrowMode) {
|
|
||||||
return <>{ buttons }</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const classnames = classNames({
|
|
||||||
mx_MessageComposer_button: true,
|
mx_MessageComposer_button: true,
|
||||||
mx_MessageComposer_buttonMenu: true,
|
mx_MessageComposer_buttonMenu: true,
|
||||||
mx_MessageComposer_closeButtonMenu: props.isMenuOpen,
|
mx_MessageComposer_closeButtonMenu: props.isMenuOpen,
|
||||||
});
|
});
|
||||||
|
|
||||||
// we render the uploadButton at top level as it is a very common interaction, splice it out of the rest
|
const moreButtons = [
|
||||||
const [uploadButton] = buttons.splice(uploadButtonIndex, 1);
|
pollButton(props, room),
|
||||||
|
showLocationButton(props, room, roomId, matrixClient),
|
||||||
|
emojiButton(props),
|
||||||
|
showStickersButton(props),
|
||||||
|
voiceRecordingButton(props),
|
||||||
|
].filter(x => x);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
{ uploadButton }
|
{ uploadButton(props, roomId) }
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className={classnames}
|
className={moreOptionsClasses}
|
||||||
onClick={props.toggleButtonMenu}
|
onClick={props.toggleButtonMenu}
|
||||||
title={_t("More options")}
|
title={_t("More options")}
|
||||||
tooltip={false}
|
tooltip={false}
|
||||||
|
@ -145,28 +115,50 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
|
||||||
{...props.menuPosition}
|
{...props.menuPosition}
|
||||||
wrapperClassName="mx_MessageComposer_Menu"
|
wrapperClassName="mx_MessageComposer_Menu"
|
||||||
>
|
>
|
||||||
{ buttons.map((button, index) => (
|
{ moreButtons.map((button, index) => (
|
||||||
<MenuItem className="mx_CallContextMenu_item" key={index} onClick={props.toggleButtonMenu}>
|
<MenuItem
|
||||||
|
className="mx_CallContextMenu_item"
|
||||||
|
key={index}
|
||||||
|
onClick={props.toggleButtonMenu}
|
||||||
|
>
|
||||||
{ button }
|
{ button }
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)) }
|
)) }
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
) }
|
) }
|
||||||
</>;
|
</>;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
function emojiButton(props: IProps): ReactElement {
|
||||||
|
return <EmojiButton
|
||||||
|
key="emoji_button"
|
||||||
|
addEmoji={props.addEmoji}
|
||||||
|
menuPosition={props.menuPosition}
|
||||||
|
narrowMode={props.narrowMode}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
interface IEmojiButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
|
interface IEmojiButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
|
||||||
addEmoji: (unicode: string) => boolean;
|
addEmoji: (unicode: string) => boolean;
|
||||||
menuPosition: AboveLeftOf;
|
menuPosition: AboveLeftOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narrowMode }) => {
|
const EmojiButton: React.FC<IEmojiButtonProps> = (
|
||||||
|
{ addEmoji, menuPosition, narrowMode },
|
||||||
|
) => {
|
||||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||||
|
|
||||||
let contextMenu: React.ReactElement | null = null;
|
let contextMenu: React.ReactElement | null = null;
|
||||||
if (menuDisplayed) {
|
if (menuDisplayed) {
|
||||||
const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
|
const position = (
|
||||||
contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
|
menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect())
|
||||||
|
);
|
||||||
|
|
||||||
|
contextMenu = <ContextMenu
|
||||||
|
{...position}
|
||||||
|
onFinished={closeMenu}
|
||||||
|
managed={false}
|
||||||
|
>
|
||||||
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
|
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
|
||||||
</ContextMenu>;
|
</ContextMenu>;
|
||||||
}
|
}
|
||||||
|
@ -193,6 +185,14 @@ const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narr
|
||||||
</React.Fragment>;
|
</React.Fragment>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function uploadButton(props: IProps, roomId: string): ReactElement {
|
||||||
|
return <UploadButton
|
||||||
|
key="controls_upload"
|
||||||
|
roomId={roomId}
|
||||||
|
relation={props.relation}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
interface IUploadButtonProps {
|
interface IUploadButtonProps {
|
||||||
roomId: string;
|
roomId: string;
|
||||||
relation?: IEventRelation | null;
|
relation?: IEventRelation | null;
|
||||||
|
@ -270,6 +270,51 @@ class UploadButton extends React.Component<IUploadButtonProps> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showStickersButton(props: IProps): ReactElement {
|
||||||
|
return (
|
||||||
|
props.showStickersButton
|
||||||
|
? <AccessibleTooltipButton
|
||||||
|
id='stickersButton'
|
||||||
|
key="controls_stickers"
|
||||||
|
className="mx_MessageComposer_button mx_MessageComposer_stickers"
|
||||||
|
onClick={() => props.setStickerPickerOpen(!props.isStickerPickerOpen)}
|
||||||
|
title={
|
||||||
|
props.narrowMode
|
||||||
|
? null
|
||||||
|
: props.isStickerPickerOpen
|
||||||
|
? _t("Hide Stickers")
|
||||||
|
: _t("Show Stickers")
|
||||||
|
}
|
||||||
|
label={props.narrowMode ? _t("Send a sticker") : null}
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function voiceRecordingButton(props: IProps): ReactElement {
|
||||||
|
// XXX: recording UI does not work well in narrow mode, so hide for now
|
||||||
|
return (
|
||||||
|
props.narrowMode
|
||||||
|
? null
|
||||||
|
: <CollapsibleButton
|
||||||
|
key="voice_message_send"
|
||||||
|
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
|
||||||
|
onClick={props.onRecordStartEndClick}
|
||||||
|
title={_t("Send voice message")}
|
||||||
|
narrowMode={props.narrowMode}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pollButton(props: IProps, room: Room): ReactElement {
|
||||||
|
return <PollButton
|
||||||
|
key="polls"
|
||||||
|
room={room}
|
||||||
|
narrowMode={props.narrowMode}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
interface IPollButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
|
interface IPollButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
|
||||||
room: Room;
|
room: Room;
|
||||||
}
|
}
|
||||||
|
@ -281,10 +326,17 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
|
||||||
MatrixClientPeg.get().getUserId(),
|
MatrixClientPeg.get().getUserId(),
|
||||||
);
|
);
|
||||||
if (!canSend) {
|
if (!canSend) {
|
||||||
Modal.createTrackedDialog('Polls', 'permissions error: cannot start', ErrorDialog, {
|
Modal.createTrackedDialog(
|
||||||
title: _t("Permission Required"),
|
'Polls',
|
||||||
description: _t("You do not have permission to start polls in this room."),
|
'permissions error: cannot start',
|
||||||
});
|
ErrorDialog,
|
||||||
|
{
|
||||||
|
title: _t("Permission Required"),
|
||||||
|
description: _t(
|
||||||
|
"You do not have permission to start polls in this room.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialog(
|
Modal.createTrackedDialog(
|
||||||
'Polls',
|
'Polls',
|
||||||
|
@ -312,4 +364,23 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showLocationButton(
|
||||||
|
props: IProps,
|
||||||
|
room: Room,
|
||||||
|
roomId: string,
|
||||||
|
matrixClient: MatrixClient,
|
||||||
|
): ReactElement {
|
||||||
|
return (
|
||||||
|
props.showLocationButton
|
||||||
|
? <LocationButton
|
||||||
|
key="location"
|
||||||
|
roomId={roomId}
|
||||||
|
sender={room.getMember(matrixClient.getUserId())}
|
||||||
|
menuPosition={props.menuPosition}
|
||||||
|
narrowMode={props.narrowMode}
|
||||||
|
/>
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default MessageComposerButtons;
|
export default MessageComposerButtons;
|
||||||
|
|
|
@ -1694,11 +1694,11 @@
|
||||||
"You do not have permission to post to this room": "You do not have permission to post to this room",
|
"You do not have permission to post to this room": "You do not have permission to post to this room",
|
||||||
"%(seconds)ss left": "%(seconds)ss left",
|
"%(seconds)ss left": "%(seconds)ss left",
|
||||||
"Send voice message": "Send voice message",
|
"Send voice message": "Send voice message",
|
||||||
|
"Add emoji": "Add emoji",
|
||||||
|
"Upload file": "Upload file",
|
||||||
"Hide Stickers": "Hide Stickers",
|
"Hide Stickers": "Hide Stickers",
|
||||||
"Show Stickers": "Show Stickers",
|
"Show Stickers": "Show Stickers",
|
||||||
"Send a sticker": "Send a sticker",
|
"Send a sticker": "Send a sticker",
|
||||||
"Add emoji": "Add emoji",
|
|
||||||
"Upload file": "Upload file",
|
|
||||||
"You do not have permission to start polls in this room.": "You do not have permission to start polls in this room.",
|
"You do not have permission to start polls in this room.": "You do not have permission to start polls in this room.",
|
||||||
"Create poll": "Create poll",
|
"Create poll": "Create poll",
|
||||||
"Bold": "Bold",
|
"Bold": "Bold",
|
||||||
|
|
Loading…
Reference in New Issue