Refactor renderButtons() into MessageComposerButtons component (#7664)
This commit is contained in:
parent
db09d16205
commit
2229437424
4 changed files with 525 additions and 283 deletions
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue