Refactor renderButtons() into MessageComposerButtons component (#7664)

This commit is contained in:
Andy Balaam 2022-01-28 15:44:03 +00:00 committed by GitHub
parent db09d16205
commit 2229437424
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 525 additions and 283 deletions

View file

@ -19,7 +19,6 @@ import { MatrixEvent, IEventRelation } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room";
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
import { RelationType } from 'matrix-js-sdk/src/@types/event';
import { M_POLL_START } from "matrix-events-sdk";
import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg';
@ -27,15 +26,9 @@ import dis from '../../../dispatcher/dispatcher';
import { ActionPayload } from "../../../dispatcher/payloads";
import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages';
import E2EIcon from './E2EIcon';
import SettingsStore from "../../../settings/SettingsStore";
import ContextMenu, {
aboveLeftOf,
useContextMenu,
MenuItem,
AboveLeftOf,
} from "../../structures/ContextMenu";
import { aboveLeftOf, AboveLeftOf } from "../../structures/ContextMenu";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import ReplyPreview from "./ReplyPreview";
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
@ -50,15 +43,10 @@ import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass }
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { Action } from "../../../dispatcher/actions";
import EditorModel from "../../../editor/model";
import EmojiPicker from '../emojipicker/EmojiPicker';
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
import Modal from "../../../Modal";
import RoomContext from '../../../contexts/RoomContext';
import ErrorDialog from "../dialogs/ErrorDialog";
import PollCreateDialog from "../elements/PollCreateDialog";
import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
import { CollapsibleButton, ICollapsibleButtonProps } from './CollapsibleButton';
import LocationButton from '../location/LocationButton';
import MessageComposerButtons from './MessageComposerButtons';
let instanceCount = 0;
const NARROW_MODE_BREAKPOINT = 500;
@ -78,164 +66,6 @@ function SendButton(props: ISendButtonProps) {
);
}
interface IEmojiButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
addEmoji: (unicode: string) => boolean;
menuPosition: AboveLeftOf;
}
const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narrowMode }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
let contextMenu;
if (menuDisplayed) {
const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
</ContextMenu>;
}
const className = classNames(
"mx_MessageComposer_button",
"mx_MessageComposer_emoji",
{
"mx_MessageComposer_button_highlight": menuDisplayed,
},
);
// TODO: replace ContextMenuTooltipButton with a unified representation of
// the header buttons and the right panel buttons
return <React.Fragment>
<CollapsibleButton
className={className}
onClick={openMenu}
narrowMode={narrowMode}
title={_t("Add emoji")}
/>
{ contextMenu }
</React.Fragment>;
};
interface IUploadButtonProps {
roomId: string;
relation?: IEventRelation | null;
}
class UploadButton extends React.Component<IUploadButtonProps> {
private uploadInput = React.createRef<HTMLInputElement>();
private dispatcherRef: string;
constructor(props: IUploadButtonProps) {
super(props);
this.dispatcherRef = dis.register(this.onAction);
}
componentWillUnmount() {
dis.unregister(this.dispatcherRef);
}
private onAction = (payload: ActionPayload) => {
if (payload.action === "upload_file") {
this.onUploadClick();
}
};
private onUploadClick = () => {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({ action: 'require_registration' });
return;
}
this.uploadInput.current.click();
};
private onUploadFileInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
if (ev.target.files.length === 0) return;
// take a copy so we can safely reset the value of the form control
// (Note it is a FileList: we can't use slice or sensible iteration).
const tfiles = [];
for (let i = 0; i < ev.target.files.length; ++i) {
tfiles.push(ev.target.files[i]);
}
ContentMessages.sharedInstance().sendContentListToRoom(
tfiles,
this.props.roomId,
this.props.relation,
MatrixClientPeg.get(),
this.context.timelineRenderingType,
);
// This is the onChange handler for a file form control, but we're
// not keeping any state, so reset the value of the form control
// to empty.
// NB. we need to set 'value': the 'files' property is immutable.
ev.target.value = '';
};
render() {
const uploadInputStyle = { display: 'none' };
return (
<AccessibleTooltipButton
className="mx_MessageComposer_button mx_MessageComposer_upload"
onClick={this.onUploadClick}
title={_t('Upload file')}
>
<input
ref={this.uploadInput}
type="file"
style={uploadInputStyle}
multiple
onChange={this.onUploadFileInputChange}
/>
</AccessibleTooltipButton>
);
}
}
interface IPollButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
room: Room;
}
class PollButton extends React.PureComponent<IPollButtonProps> {
private onCreateClick = () => {
const canSend = this.props.room.currentState.maySendEvent(
M_POLL_START.name,
MatrixClientPeg.get().getUserId(),
);
if (!canSend) {
Modal.createTrackedDialog('Polls', 'permissions error: cannot start', ErrorDialog, {
title: _t("Permission Required"),
description: _t("You do not have permission to start polls in this room."),
});
} else {
Modal.createTrackedDialog(
'Polls',
'create',
PollCreateDialog,
{
room: this.props.room,
},
'mx_CompoundDialog',
false, // isPriorityModal
true, // isStaticModal
);
}
};
render() {
return (
<CollapsibleButton
className="mx_MessageComposer_button mx_MessageComposer_poll"
onClick={this.onCreateClick}
narrowMode={this.props.narrowMode}
title={_t("Create poll")}
/>
);
}
}
interface IProps {
room: Room;
resizeNotifier: ResizeNotifier;
@ -509,108 +339,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
});
};
private renderButtons(menuPosition: AboveLeftOf): JSX.Element | JSX.Element[] {
if (this.state.haveRecording) {
return [];
}
let uploadButtonIndex = 0;
const buttons: JSX.Element[] = [];
buttons.push(
<PollButton
key="polls"
room={this.props.room}
narrowMode={this.state.narrowMode}
/>,
);
uploadButtonIndex = buttons.length;
buttons.push(
<UploadButton key="controls_upload" roomId={this.props.room.roomId} relation={this.props.relation} />,
);
if (this.state.showLocationButton) {
const sender = this.props.room.getMember(
MatrixClientPeg.get().getUserId(),
);
buttons.push(
<LocationButton
key="location"
roomId={this.props.room.roomId}
sender={sender}
menuPosition={menuPosition}
narrowMode={this.state.narrowMode}
/>,
);
}
buttons.push(
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />,
);
if (this.state.showStickersButton) {
let title: string;
if (!this.state.narrowMode) {
title = this.state.isStickerPickerOpen ? _t("Hide Stickers") : _t("Show Stickers");
}
buttons.push(
<AccessibleTooltipButton
id='stickersButton'
key="controls_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={() => this.setStickerPickerOpen(!this.state.isStickerPickerOpen)}
title={title}
label={this.state.narrowMode ? _t("Send a sticker") : null}
/>,
);
}
// XXX: the recording UI does not work well in narrow mode, so we hide this button for now
if (!this.state.narrowMode) {
buttons.push(
<CollapsibleButton
key="voice_message_send"
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
onClick={() => this.voiceRecordingButton.current?.onRecordStartEndClick()}
title={_t("Send voice message")}
narrowMode={this.state.narrowMode}
/>,
);
}
if (!this.state.narrowMode) {
return buttons;
}
const classnames = classNames({
mx_MessageComposer_button: true,
mx_MessageComposer_buttonMenu: true,
mx_MessageComposer_closeButtonMenu: this.state.isMenuOpen,
});
// we render the uploadButton at top level as it is a very common interaction, splice it out of the rest
const [uploadButton] = buttons.splice(uploadButtonIndex, 1);
return <>
{ uploadButton }
<AccessibleTooltipButton
className={classnames}
onClick={this.toggleButtonMenu}
title={_t("More options")}
tooltip={false}
/>
{ this.state.isMenuOpen && (
<ContextMenu
onFinished={this.toggleButtonMenu}
{...menuPosition}
wrapperClassName="mx_MessageComposer_Menu"
>
{ buttons.map((button, index) => (
<MenuItem className="mx_CallContextMenu_item" key={index} onClick={this.toggleButtonMenu}>
{ button }
</MenuItem>
)) }
</ContextMenu>
) }
</>;
}
render() {
const controls = [
this.props.e2eStatus ?
@ -717,7 +445,20 @@ export default class MessageComposer extends React.Component<IProps, IState> {
permalinkCreator={this.props.permalinkCreator} />
<div className="mx_MessageComposer_row">
{ controls }
{ this.renderButtons(menuPosition) }
<MessageComposerButtons
addEmoji={this.addEmoji}
haveRecording={this.state.haveRecording}
isMenuOpen={this.state.isMenuOpen}
isStickerPickerOpen={this.state.isStickerPickerOpen}
menuPosition={menuPosition}
narrowMode={this.state.narrowMode}
relation={this.props.relation}
onRecordStartEndClick={() => this.voiceRecordingButton.current?.onRecordStartEndClick()}
setStickerPickerOpen={this.setStickerPickerOpen}
showLocationButton={this.state.showLocationButton}
showStickersButton={this.state.showStickersButton}
toggleButtonMenu={this.toggleButtonMenu}
/>
{ showSendButton && (
<SendButton
key="controls_send"