diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 576f45a3bc..5f9014db7a 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -42,14 +42,159 @@ const formatButtonList = [ _td("numbered-list"), ]; +function ComposerAvatar(props) { + const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); + return
+ +
; +} + +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 +} + +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 ; +} + +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 ; +} + +HangupButton.propTypes = { + roomId: PropTypes.string.isRequired, +} + +function FormattingButton(props) { + const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); + return ; +} + +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 ( + + + + ); + } +} + export default class MessageComposer extends React.Component { constructor(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.onToggleFormattingClicked = this.onToggleFormattingClicked.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._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this); this._onTombstoneClick = this._onTombstoneClick.bind(this); + this.renderPlaceholderText = this.renderPlaceholderText.bind(this); + this.renderFormatBar = this.renderFormatBar.bind(this); this.state = { inputState: { @@ -136,65 +283,6 @@ export default class MessageComposer extends React.Component { 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) { // Merge the new input state with old to support partial updates @@ -245,119 +333,94 @@ export default class MessageComposer extends React.Component { }); } - render() { - const uploadInputStyle = {display: 'none'}; - const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); - const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); - - const controls = []; - - if (this.state.me) { - controls.push( -
- -
, - ); - } - - if (this.props.e2eStatus) { - controls.push( - ); - } - - let callButton; - let videoCallButton; - let hangupButton; - - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - // Call buttons - if (this.props.callState && this.props.callState !== 'ended') { - hangupButton = - - ; + renderPlaceholderText() { + const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId); + if (this.state.isQuoting) { + if (roomIsEncrypted) { + return _t('Send an encrypted reply…'); + } else { + return _t('Send a reply (unencrypted)…'); + } } else { - callButton = - - ; - videoCallButton = - - ; + if (roomIsEncrypted) { + return _t('Send an encrypted message…'); + } else { + return _t('Send a message (unencrypted)…'); + } } + } + + 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 ( + + ); + }) + + return ( +
+
+ { formatButtons } +
+ + +
+
+ ); + } + + render() { + const controls = [ + this.state.me ? : null, + this.props.e2eStatus ? : null, + ]; if (!this.state.tombstone && this.state.canSendMessages) { // This also currently includes the call buttons. Really we should // check separately for whether we can call, but this is slightly // complex because of conference calls. - const uploadButton = ( - - - - ); - const formattingButton = this.state.inputState.isRichTextEnabled ? ( - - ) : 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 = ; + const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); + const showFormattingButton = this.state.inputState.isRichTextEnabled; + const callInProgress = this.props.callState && this.props.callState !== 'ended'; controls.push( this.messageComposerInput = c} key="controls_input" room={this.props.room} - placeholder={placeholderText} + placeholder={this.renderPlaceholderText()} onInputStateChanged={this.onInputStateChanged} permalinkCreator={this.props.permalinkCreator} />, - formattingButton, - stickerpickerButton, - uploadButton, - hangupButton, - callButton, - videoCallButton, + showFormattingButton ? : null, + , + , + callInProgress ? : null, + callInProgress ? null : , + callInProgress ? null : , ); } else if (this.state.tombstone) { const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; @@ -388,42 +451,7 @@ export default class MessageComposer extends React.Component { ); } - let formatBar; - 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 ; - }, - ); - - formatBar = -
-
- { formatButtons } -
- - -
-
; - } + const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled; const wrapperClasses = classNames({ mx_MessageComposer_wrapper: true, @@ -436,7 +464,7 @@ export default class MessageComposer extends React.Component { { controls } - { formatBar } + { showFormatBar ? this.renderFormatBar() : null } ); }