Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 2229437

Browse files
authored
Refactor renderButtons() into MessageComposerButtons component (#7664)
1 parent db09d16 commit 2229437

File tree

4 files changed

+525
-283
lines changed

4 files changed

+525
-283
lines changed

src/components/views/rooms/MessageComposer.tsx

Lines changed: 16 additions & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,16 @@ import { MatrixEvent, IEventRelation } from "matrix-js-sdk/src/models/event";
1919
import { Room } from "matrix-js-sdk/src/models/room";
2020
import { RoomMember } from "matrix-js-sdk/src/models/room-member";
2121
import { RelationType } from 'matrix-js-sdk/src/@types/event';
22-
import { M_POLL_START } from "matrix-events-sdk";
2322

2423
import { _t } from '../../../languageHandler';
2524
import { MatrixClientPeg } from '../../../MatrixClientPeg';
2625
import dis from '../../../dispatcher/dispatcher';
2726
import { ActionPayload } from "../../../dispatcher/payloads";
2827
import Stickerpicker from './Stickerpicker';
2928
import { makeRoomPermalink, RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
30-
import ContentMessages from '../../../ContentMessages';
3129
import E2EIcon from './E2EIcon';
3230
import SettingsStore from "../../../settings/SettingsStore";
33-
import ContextMenu, {
34-
aboveLeftOf,
35-
useContextMenu,
36-
MenuItem,
37-
AboveLeftOf,
38-
} from "../../structures/ContextMenu";
31+
import { aboveLeftOf, AboveLeftOf } from "../../structures/ContextMenu";
3932
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
4033
import ReplyPreview from "./ReplyPreview";
4134
import { UPDATE_EVENT } from "../../../stores/AsyncStore";
@@ -50,15 +43,10 @@ import SendMessageComposer, { SendMessageComposer as SendMessageComposerClass }
5043
import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload";
5144
import { Action } from "../../../dispatcher/actions";
5245
import EditorModel from "../../../editor/model";
53-
import EmojiPicker from '../emojipicker/EmojiPicker';
5446
import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
55-
import Modal from "../../../Modal";
5647
import RoomContext from '../../../contexts/RoomContext';
57-
import ErrorDialog from "../dialogs/ErrorDialog";
58-
import PollCreateDialog from "../elements/PollCreateDialog";
5948
import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
60-
import { CollapsibleButton, ICollapsibleButtonProps } from './CollapsibleButton';
61-
import LocationButton from '../location/LocationButton';
49+
import MessageComposerButtons from './MessageComposerButtons';
6250

6351
let instanceCount = 0;
6452
const NARROW_MODE_BREAKPOINT = 500;
@@ -78,164 +66,6 @@ function SendButton(props: ISendButtonProps) {
7866
);
7967
}
8068

81-
interface IEmojiButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
82-
addEmoji: (unicode: string) => boolean;
83-
menuPosition: AboveLeftOf;
84-
}
85-
86-
const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narrowMode }) => {
87-
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
88-
89-
let contextMenu;
90-
if (menuDisplayed) {
91-
const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
92-
contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
93-
<EmojiPicker onChoose={addEmoji} showQuickReactions={true} />
94-
</ContextMenu>;
95-
}
96-
97-
const className = classNames(
98-
"mx_MessageComposer_button",
99-
"mx_MessageComposer_emoji",
100-
{
101-
"mx_MessageComposer_button_highlight": menuDisplayed,
102-
},
103-
);
104-
105-
// TODO: replace ContextMenuTooltipButton with a unified representation of
106-
// the header buttons and the right panel buttons
107-
return <React.Fragment>
108-
<CollapsibleButton
109-
className={className}
110-
onClick={openMenu}
111-
narrowMode={narrowMode}
112-
title={_t("Add emoji")}
113-
/>
114-
115-
{ contextMenu }
116-
</React.Fragment>;
117-
};
118-
119-
interface IUploadButtonProps {
120-
roomId: string;
121-
relation?: IEventRelation | null;
122-
}
123-
124-
class UploadButton extends React.Component<IUploadButtonProps> {
125-
private uploadInput = React.createRef<HTMLInputElement>();
126-
private dispatcherRef: string;
127-
128-
constructor(props: IUploadButtonProps) {
129-
super(props);
130-
131-
this.dispatcherRef = dis.register(this.onAction);
132-
}
133-
134-
componentWillUnmount() {
135-
dis.unregister(this.dispatcherRef);
136-
}
137-
138-
private onAction = (payload: ActionPayload) => {
139-
if (payload.action === "upload_file") {
140-
this.onUploadClick();
141-
}
142-
};
143-
144-
private onUploadClick = () => {
145-
if (MatrixClientPeg.get().isGuest()) {
146-
dis.dispatch({ action: 'require_registration' });
147-
return;
148-
}
149-
this.uploadInput.current.click();
150-
};
151-
152-
private onUploadFileInputChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
153-
if (ev.target.files.length === 0) return;
154-
155-
// take a copy so we can safely reset the value of the form control
156-
// (Note it is a FileList: we can't use slice or sensible iteration).
157-
const tfiles = [];
158-
for (let i = 0; i < ev.target.files.length; ++i) {
159-
tfiles.push(ev.target.files[i]);
160-
}
161-
162-
ContentMessages.sharedInstance().sendContentListToRoom(
163-
tfiles,
164-
this.props.roomId,
165-
this.props.relation,
166-
MatrixClientPeg.get(),
167-
this.context.timelineRenderingType,
168-
);
169-
170-
// This is the onChange handler for a file form control, but we're
171-
// not keeping any state, so reset the value of the form control
172-
// to empty.
173-
// NB. we need to set 'value': the 'files' property is immutable.
174-
ev.target.value = '';
175-
};
176-
177-
render() {
178-
const uploadInputStyle = { display: 'none' };
179-
return (
180-
<AccessibleTooltipButton
181-
className="mx_MessageComposer_button mx_MessageComposer_upload"
182-
onClick={this.onUploadClick}
183-
title={_t('Upload file')}
184-
>
185-
<input
186-
ref={this.uploadInput}
187-
type="file"
188-
style={uploadInputStyle}
189-
multiple
190-
onChange={this.onUploadFileInputChange}
191-
/>
192-
</AccessibleTooltipButton>
193-
);
194-
}
195-
}
196-
197-
interface IPollButtonProps extends Pick<ICollapsibleButtonProps, "narrowMode"> {
198-
room: Room;
199-
}
200-
201-
class PollButton extends React.PureComponent<IPollButtonProps> {
202-
private onCreateClick = () => {
203-
const canSend = this.props.room.currentState.maySendEvent(
204-
M_POLL_START.name,
205-
MatrixClientPeg.get().getUserId(),
206-
);
207-
if (!canSend) {
208-
Modal.createTrackedDialog('Polls', 'permissions error: cannot start', ErrorDialog, {
209-
title: _t("Permission Required"),
210-
description: _t("You do not have permission to start polls in this room."),
211-
});
212-
} else {
213-
Modal.createTrackedDialog(
214-
'Polls',
215-
'create',
216-
PollCreateDialog,
217-
{
218-
room: this.props.room,
219-
},
220-
'mx_CompoundDialog',
221-
false, // isPriorityModal
222-
true, // isStaticModal
223-
);
224-
}
225-
};
226-
227-
render() {
228-
return (
229-
<CollapsibleButton
230-
className="mx_MessageComposer_button mx_MessageComposer_poll"
231-
onClick={this.onCreateClick}
232-
narrowMode={this.props.narrowMode}
233-
title={_t("Create poll")}
234-
/>
235-
);
236-
}
237-
}
238-
23969
interface IProps {
24070
room: Room;
24171
resizeNotifier: ResizeNotifier;
@@ -509,108 +339,6 @@ export default class MessageComposer extends React.Component<IProps, IState> {
509339
});
510340
};
511341

512-
private renderButtons(menuPosition: AboveLeftOf): JSX.Element | JSX.Element[] {
513-
if (this.state.haveRecording) {
514-
return [];
515-
}
516-
517-
let uploadButtonIndex = 0;
518-
const buttons: JSX.Element[] = [];
519-
buttons.push(
520-
<PollButton
521-
key="polls"
522-
room={this.props.room}
523-
narrowMode={this.state.narrowMode}
524-
/>,
525-
);
526-
uploadButtonIndex = buttons.length;
527-
buttons.push(
528-
<UploadButton key="controls_upload" roomId={this.props.room.roomId} relation={this.props.relation} />,
529-
);
530-
if (this.state.showLocationButton) {
531-
const sender = this.props.room.getMember(
532-
MatrixClientPeg.get().getUserId(),
533-
);
534-
buttons.push(
535-
<LocationButton
536-
key="location"
537-
roomId={this.props.room.roomId}
538-
sender={sender}
539-
menuPosition={menuPosition}
540-
narrowMode={this.state.narrowMode}
541-
/>,
542-
);
543-
}
544-
buttons.push(
545-
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />,
546-
);
547-
if (this.state.showStickersButton) {
548-
let title: string;
549-
if (!this.state.narrowMode) {
550-
title = this.state.isStickerPickerOpen ? _t("Hide Stickers") : _t("Show Stickers");
551-
}
552-
553-
buttons.push(
554-
<AccessibleTooltipButton
555-
id='stickersButton'
556-
key="controls_stickers"
557-
className="mx_MessageComposer_button mx_MessageComposer_stickers"
558-
onClick={() => this.setStickerPickerOpen(!this.state.isStickerPickerOpen)}
559-
title={title}
560-
label={this.state.narrowMode ? _t("Send a sticker") : null}
561-
/>,
562-
);
563-
}
564-
565-
// XXX: the recording UI does not work well in narrow mode, so we hide this button for now
566-
if (!this.state.narrowMode) {
567-
buttons.push(
568-
<CollapsibleButton
569-
key="voice_message_send"
570-
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
571-
onClick={() => this.voiceRecordingButton.current?.onRecordStartEndClick()}
572-
title={_t("Send voice message")}
573-
narrowMode={this.state.narrowMode}
574-
/>,
575-
);
576-
}
577-
578-
if (!this.state.narrowMode) {
579-
return buttons;
580-
}
581-
582-
const classnames = classNames({
583-
mx_MessageComposer_button: true,
584-
mx_MessageComposer_buttonMenu: true,
585-
mx_MessageComposer_closeButtonMenu: this.state.isMenuOpen,
586-
});
587-
588-
// we render the uploadButton at top level as it is a very common interaction, splice it out of the rest
589-
const [uploadButton] = buttons.splice(uploadButtonIndex, 1);
590-
return <>
591-
{ uploadButton }
592-
<AccessibleTooltipButton
593-
className={classnames}
594-
onClick={this.toggleButtonMenu}
595-
title={_t("More options")}
596-
tooltip={false}
597-
/>
598-
{ this.state.isMenuOpen && (
599-
<ContextMenu
600-
onFinished={this.toggleButtonMenu}
601-
{...menuPosition}
602-
wrapperClassName="mx_MessageComposer_Menu"
603-
>
604-
{ buttons.map((button, index) => (
605-
<MenuItem className="mx_CallContextMenu_item" key={index} onClick={this.toggleButtonMenu}>
606-
{ button }
607-
</MenuItem>
608-
)) }
609-
</ContextMenu>
610-
) }
611-
</>;
612-
}
613-
614342
render() {
615343
const controls = [
616344
this.props.e2eStatus ?
@@ -717,7 +445,20 @@ export default class MessageComposer extends React.Component<IProps, IState> {
717445
permalinkCreator={this.props.permalinkCreator} />
718446
<div className="mx_MessageComposer_row">
719447
{ controls }
720-
{ this.renderButtons(menuPosition) }
448+
<MessageComposerButtons
449+
addEmoji={this.addEmoji}
450+
haveRecording={this.state.haveRecording}
451+
isMenuOpen={this.state.isMenuOpen}
452+
isStickerPickerOpen={this.state.isStickerPickerOpen}
453+
menuPosition={menuPosition}
454+
narrowMode={this.state.narrowMode}
455+
relation={this.props.relation}
456+
onRecordStartEndClick={() => this.voiceRecordingButton.current?.onRecordStartEndClick()}
457+
setStickerPickerOpen={this.setStickerPickerOpen}
458+
showLocationButton={this.state.showLocationButton}
459+
showStickersButton={this.state.showStickersButton}
460+
toggleButtonMenu={this.toggleButtonMenu}
461+
/>
721462
{ showSendButton && (
722463
<SendButton
723464
key="controls_send"

0 commit comments

Comments
 (0)