Merge pull request #2883 from YaoiFangirl420/cleanup_message_composer

Cleanup message composer render() method
pull/21833/head
J. Ryan Stinnett 2019-04-18 10:14:01 +01:00 committed by GitHub
commit 9917bed942
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 226 additions and 198 deletions

View File

@ -42,14 +42,159 @@ const formatButtonList = [
_td("numbered-list"), _td("numbered-list"),
]; ];
function ComposerAvatar(props) {
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar');
return <div className="mx_MessageComposer_avatar">
<MemberStatusMessageAvatar member={props.me} width={24} height={24} />
</div>;
}
ComposerAvatar.propTypes = {
me: PropTypes.object.isRequired,
}
function CallButton(props) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const onVoiceCallClick = (ev) => {
dis.dispatch({
action: 'place_call',
type: "voice",
room_id: props.roomId,
});
};
return <AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_voicecall"
onClick={onVoiceCallClick}
title={_t('Voice call')}
/>
}
CallButton.propTypes = {
roomId: PropTypes.string.isRequired
}
function VideoCallButton(props) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const onCallClick = (ev) => {
dis.dispatch({
action: 'place_call',
type: ev.shiftKey ? "screensharing" : "video",
room_id: props.roomId,
});
};
return <AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_videocall"
onClick={onCallClick}
title={_t('Video call')}
/>;
}
VideoCallButton.propTypes = {
roomId: PropTypes.string.isRequired,
};
function HangupButton(props) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const onHangupClick = () => {
const call = CallHandler.getCallForRoom(props.roomId);
if (!call) {
return;
}
dis.dispatch({
action: 'hangup',
// hangup the call for this room, which may not be the room in props
// (e.g. conferences which will hangup the 1:1 room instead)
room_id: call.roomId,
});
};
return <AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_hangup"
onClick={onHangupClick}
title={_t('Hangup')}
/>;
}
HangupButton.propTypes = {
roomId: PropTypes.string.isRequired,
}
function FormattingButton(props) {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <AccessibleButton
element="img"
className="mx_MessageComposer_formatting"
alt={_t("Show Text Formatting Toolbar")}
title={_t("Show Text Formatting Toolbar")}
src={require("../../../../res/img/button-text-formatting.svg")}
style={{visibility: props.showFormatting ? 'hidden' : 'visible'}}
onClick={props.onClickHandler}
/>;
}
FormattingButton.propTypes = {
showFormatting: PropTypes.bool.isRequired,
onClickHandler: PropTypes.func.isRequired,
}
class UploadButton extends React.Component {
static propTypes = {
roomId: PropTypes.string.isRequired,
}
constructor(props, context) {
super(props, context);
this.onUploadClick = this.onUploadClick.bind(this);
this.onUploadFileInputChange = this.onUploadFileInputChange.bind(this);
}
onUploadClick(ev) {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
}
this.refs.uploadInput.click();
}
onUploadFileInputChange(ev) {
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 sesnible 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, MatrixClientPeg.get(),
);
// 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'};
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return (
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_upload"
onClick={this.onUploadClick}
title={_t('Upload file')}
>
<input ref="uploadInput" type="file"
style={uploadInputStyle}
multiple
onChange={this.onUploadFileInputChange}
/>
</AccessibleButton>
);
}
}
export default class MessageComposer extends React.Component { export default class MessageComposer extends React.Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.onCallClick = this.onCallClick.bind(this);
this.onHangupClick = this.onHangupClick.bind(this);
this.onUploadClick = this.onUploadClick.bind(this);
this._onUploadFileInputChange = this._onUploadFileInputChange.bind(this);
this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this); this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this); this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this); this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
@ -58,6 +203,8 @@ export default class MessageComposer extends React.Component {
this._onRoomStateEvents = this._onRoomStateEvents.bind(this); this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this); this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);
this._onTombstoneClick = this._onTombstoneClick.bind(this); this._onTombstoneClick = this._onTombstoneClick.bind(this);
this.renderPlaceholderText = this.renderPlaceholderText.bind(this);
this.renderFormatBar = this.renderFormatBar.bind(this);
this.state = { this.state = {
inputState: { inputState: {
@ -136,65 +283,6 @@ export default class MessageComposer extends React.Component {
this.setState({ isQuoting }); this.setState({ isQuoting });
} }
onUploadClick(ev) {
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({action: 'require_registration'});
return;
}
this.refs.uploadInput.click();
}
_onUploadFileInputChange(ev) {
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 sesnible 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.room.roomId, MatrixClientPeg.get(),
);
// 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 = '';
}
onHangupClick() {
const call = CallHandler.getCallForRoom(this.props.room.roomId);
//var call = CallHandler.getAnyActiveCall();
if (!call) {
return;
}
dis.dispatch({
action: 'hangup',
// hangup the call for this room, which may not be the room in props
// (e.g. conferences which will hangup the 1:1 room instead)
room_id: call.roomId,
});
}
onCallClick(ev) {
dis.dispatch({
action: 'place_call',
type: ev.shiftKey ? "screensharing" : "video",
room_id: this.props.room.roomId,
});
}
onVoiceCallClick(ev) {
dis.dispatch({
action: 'place_call',
type: "voice",
room_id: this.props.room.roomId,
});
}
onInputStateChanged(inputState) { onInputStateChanged(inputState) {
// Merge the new input state with old to support partial updates // Merge the new input state with old to support partial updates
@ -245,119 +333,94 @@ export default class MessageComposer extends React.Component {
}); });
} }
render() { renderPlaceholderText() {
const uploadInputStyle = {display: 'none'}; const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); if (this.state.isQuoting) {
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); if (roomIsEncrypted) {
return _t('Send an encrypted reply…');
const controls = []; } else {
return _t('Send a reply (unencrypted)…');
if (this.state.me) { }
controls.push(
<div key="controls_avatar" className="mx_MessageComposer_avatar">
<MemberStatusMessageAvatar member={this.state.me} width={24} height={24} />
</div>,
);
}
if (this.props.e2eStatus) {
controls.push(<E2EIcon
status={this.props.e2eStatus}
key="e2eIcon"
className="mx_MessageComposer_e2eIcon" />
);
}
let callButton;
let videoCallButton;
let hangupButton;
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
// Call buttons
if (this.props.callState && this.props.callState !== 'ended') {
hangupButton =
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_hangup"
key="controls_hangup"
onClick={this.onHangupClick}
title={_t('Hangup')}
>
</AccessibleButton>;
} else { } else {
callButton = if (roomIsEncrypted) {
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_voicecall" return _t('Send an encrypted message…');
key="controls_call" } else {
onClick={this.onVoiceCallClick} return _t('Send a message (unencrypted)…');
title={_t('Voice call')} }
>
</AccessibleButton>;
videoCallButton =
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_videocall"
key="controls_videocall"
onClick={this.onCallClick}
title={_t('Video call')}
>
</AccessibleButton>;
} }
}
renderFormatBar() {
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const {marks, blockType} = this.state.inputState;
const formatButtons = formatButtonList.map((name) => {
// special-case to match the md serializer and the special-case in MessageComposerInput.js
const markName = name === 'inline-code' ? 'code' : name;
const active = marks.some(mark => mark.type === markName) || blockType === name;
const suffix = active ? '-on' : '';
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
return (
<img className={className}
title={_t(name)}
onMouseDown={onFormatButtonClicked}
key={name}
src={require(`../../../../res/img/button-text-${name}${suffix}.svg`)}
height="17"
/>
);
})
return (
<div className="mx_MessageComposer_formatbar_wrapper">
<div className="mx_MessageComposer_formatbar">
{ formatButtons }
<div style={{ flex: 1 }}></div>
<AccessibleButton
className="mx_MessageComposer_formatbar_markdown mx_MessageComposer_markdownDisabled"
onClick={this.onToggleMarkdownClicked}
title={_t("Markdown is disabled")}
/>
<AccessibleButton element="img" title={_t("Hide Text Formatting Toolbar")}
onClick={this.onToggleFormattingClicked}
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
src={require("../../../../res/img/icon-text-cancel.svg")}
/>
</div>
</div>
);
}
render() {
const controls = [
this.state.me ? <ComposerAvatar key="controls_avatar" me={this.state.me} /> : null,
this.props.e2eStatus ? <E2EIcon key="e2eIcon" status={this.props.e2eStatus} className="mx_MessageComposer_e2eIcon" /> : null,
];
if (!this.state.tombstone && this.state.canSendMessages) { if (!this.state.tombstone && this.state.canSendMessages) {
// This also currently includes the call buttons. Really we should // This also currently includes the call buttons. Really we should
// check separately for whether we can call, but this is slightly // check separately for whether we can call, but this is slightly
// complex because of conference calls. // complex because of conference calls.
const uploadButton = (
<AccessibleButton className="mx_MessageComposer_button mx_MessageComposer_upload"
key="controls_upload"
onClick={this.onUploadClick}
title={_t('Upload file')}
>
<input ref="uploadInput" type="file"
style={uploadInputStyle}
multiple
onChange={this._onUploadFileInputChange} />
</AccessibleButton>
);
const formattingButton = this.state.inputState.isRichTextEnabled ? ( const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
<AccessibleButton element="img" className="mx_MessageComposer_formatting" const showFormattingButton = this.state.inputState.isRichTextEnabled;
alt={_t("Show Text Formatting Toolbar")} const callInProgress = this.props.callState && this.props.callState !== 'ended';
title={_t("Show Text Formatting Toolbar")}
src={require("../../../../res/img/button-text-formatting.svg")}
onClick={this.onToggleFormattingClicked}
style={{visibility: this.state.showFormatting ? 'hidden' : 'visible'}}
key="controls_formatting" />
) : null;
const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
let placeholderText;
if (this.state.isQuoting) {
if (roomIsEncrypted) {
placeholderText = _t('Send an encrypted reply…');
} else {
placeholderText = _t('Send a reply (unencrypted)…');
}
} else {
if (roomIsEncrypted) {
placeholderText = _t('Send an encrypted message…');
} else {
placeholderText = _t('Send a message (unencrypted)…');
}
}
const stickerpickerButton = <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />;
controls.push( controls.push(
<MessageComposerInput <MessageComposerInput
ref={(c) => this.messageComposerInput = c} ref={(c) => this.messageComposerInput = c}
key="controls_input" key="controls_input"
room={this.props.room} room={this.props.room}
placeholder={placeholderText} placeholder={this.renderPlaceholderText()}
onInputStateChanged={this.onInputStateChanged} onInputStateChanged={this.onInputStateChanged}
permalinkCreator={this.props.permalinkCreator} />, permalinkCreator={this.props.permalinkCreator} />,
formattingButton, showFormattingButton ? <FormattingButton key="controls_formatting"
stickerpickerButton, showFormatting={this.state.showFormatting} onClickHandler={this.onToggleFormattingClicked} /> : null,
uploadButton, <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />,
hangupButton, <UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
callButton, callInProgress ? <HangupButton key="controls_hangup" roomId={this.props.room.roomId} /> : null,
videoCallButton, callInProgress ? null : <CallButton key="controls_call" roomId={this.props.room.roomId} />,
callInProgress ? null : <VideoCallButton key="controls_videocall" roomId={this.props.room.roomId} />,
); );
} else if (this.state.tombstone) { } else if (this.state.tombstone) {
const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; const replacementRoomId = this.state.tombstone.getContent()['replacement_room'];
@ -388,42 +451,7 @@ export default class MessageComposer extends React.Component {
); );
} }
let formatBar; const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled;
if (this.state.showFormatting && this.state.inputState.isRichTextEnabled) {
const {marks, blockType} = this.state.inputState;
const formatButtons = formatButtonList.map((name) => {
// special-case to match the md serializer and the special-case in MessageComposerInput.js
const markName = name === 'inline-code' ? 'code' : name;
const active = marks.some(mark => mark.type === markName) || blockType === name;
const suffix = active ? '-on' : '';
const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
return <img className={className}
title={_t(name)}
onMouseDown={onFormatButtonClicked}
key={name}
src={require(`../../../../res/img/button-text-${name}${suffix}.svg`)}
height="17" />;
},
);
formatBar =
<div className="mx_MessageComposer_formatbar_wrapper">
<div className="mx_MessageComposer_formatbar">
{ formatButtons }
<div style={{ flex: 1 }}></div>
<AccessibleButton className="mx_MessageComposer_formatbar_markdown mx_MessageComposer_markdownDisabled"
onClick={this.onToggleMarkdownClicked}
title={_t("Markdown is disabled")}
/>
<AccessibleButton element="img" title={_t("Hide Text Formatting Toolbar")}
onClick={this.onToggleFormattingClicked}
className="mx_MessageComposer_formatbar_cancel mx_filterFlipColor"
src={require("../../../../res/img/icon-text-cancel.svg")}
/>
</div>
</div>;
}
const wrapperClasses = classNames({ const wrapperClasses = classNames({
mx_MessageComposer_wrapper: true, mx_MessageComposer_wrapper: true,
@ -436,7 +464,7 @@ export default class MessageComposer extends React.Component {
{ controls } { controls }
</div> </div>
</div> </div>
{ formatBar } { showFormatBar ? this.renderFormatBar() : null }
</div> </div>
); );
} }