From d08fbb4872fb147ae0bbb81a706d830edca4aca6 Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 20:37:23 -0700 Subject: [PATCH 1/9] Make call buttons into separate components Make the call/voice-call/hangup buttons separate react components to reduce the amount of complexity on MessageComposer. Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 121 ++++++++++-------- 1 file changed, 68 insertions(+), 53 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 576f45a3bc..5c9ef3f15f 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -42,14 +42,76 @@ const formatButtonList = [ _td("numbered-list"), ]; +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, +} + + 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); @@ -166,35 +228,6 @@ export default class MessageComposer extends React.Component { 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 @@ -275,28 +308,10 @@ export default class MessageComposer extends React.Component { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); // Call buttons if (this.props.callState && this.props.callState !== 'ended') { - hangupButton = - - ; + hangupButton = ; } else { - callButton = - - ; - videoCallButton = - - ; + callButton = ; + videoCallButton = ; } if (!this.state.tombstone && this.state.canSendMessages) { From 6aa9f068b3bd3414ea47544585d465e19a90a408 Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 20:44:22 -0700 Subject: [PATCH 2/9] Tighten controls code in MessageComposer Make the user avatar a separate function component, make the logic for laying out components a little more concise Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 5c9ef3f15f..089fbc13f4 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -42,6 +42,17 @@ const formatButtonList = [ _td("numbered-list"), ]; +function Avatar(props) { + const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); + return
+ +
; +} + +Avatar.propTypes = { + me: PropTypes.object.isRequired, +} + function CallButton(props) { const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); const onVoiceCallClick = (ev) => { @@ -280,26 +291,12 @@ 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( - ); - } + const controls = [ + this.state.me ? : null, + this.props.e2eStatus ? : null, + ]; let callButton; let videoCallButton; From cfb9172121d1415b58fddcc3cfc609edff5226a8 Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 20:48:13 -0700 Subject: [PATCH 3/9] Tighten up code around call buttons Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 089fbc13f4..353b7cc643 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -293,23 +293,14 @@ export default class MessageComposer extends React.Component { const uploadInputStyle = {display: 'none'}; const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); + const callInProgress = this.props.callState && this.props.callState !== 'ended'; + const controls = [ this.state.me ? : null, this.props.e2eStatus ? : null, ]; - let callButton; - let videoCallButton; - let hangupButton; - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - // Call buttons - if (this.props.callState && this.props.callState !== 'ended') { - hangupButton = ; - } else { - callButton = ; - videoCallButton = ; - } if (!this.state.tombstone && this.state.canSendMessages) { // This also currently includes the call buttons. Really we should @@ -367,9 +358,9 @@ export default class MessageComposer extends React.Component { formattingButton, stickerpickerButton, uploadButton, - hangupButton, - callButton, - videoCallButton, + callInProgress ? : null, + callInProgress ? null : , + callInProgress ? null : , ); } else if (this.state.tombstone) { const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; From 452f0e6dcc85f69151929bffe78c470a55040b05 Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 20:57:45 -0700 Subject: [PATCH 4/9] Generate placeholder text in separate method To make the MessageComposer render() method a bit less busy Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 353b7cc643..379c3f8ab2 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -131,6 +131,7 @@ 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.state = { inputState: { @@ -289,6 +290,23 @@ export default class MessageComposer extends React.Component { }); } + 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 { + if (roomIsEncrypted) { + return _t('Send an encrypted message…'); + } else { + return _t('Send a message (unencrypted)…'); + } + } + } + render() { const uploadInputStyle = {display: 'none'}; const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); @@ -329,22 +347,6 @@ export default class MessageComposer extends React.Component { 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 = ; controls.push( @@ -352,7 +354,7 @@ export default class MessageComposer extends React.Component { ref={(c) => this.messageComposerInput = c} key="controls_input" room={this.props.room} - placeholder={placeholderText} + placeholder={this.renderPlaceholderText()} onInputStateChanged={this.onInputStateChanged} permalinkCreator={this.props.permalinkCreator} />, formattingButton, From 485ad6a3f06da4594e9712272864f7555f11e0e9 Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 21:14:29 -0700 Subject: [PATCH 5/9] Make UploadButton a separate component Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 103 ++++++++++-------- 1 file changed, 57 insertions(+), 46 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 379c3f8ab2..b1ad3ae0a1 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -117,12 +117,66 @@ HangupButton.propTypes = { roomId: PropTypes.string.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.onUploadClick = this.onUploadClick.bind(this); - this._onUploadFileInputChange = this._onUploadFileInputChange.bind(this); this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this); this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this); this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this); @@ -210,36 +264,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 = ''; - } - onInputStateChanged(inputState) { // Merge the new input state with old to support partial updates @@ -308,7 +332,6 @@ export default class MessageComposer extends React.Component { } render() { - const uploadInputStyle = {display: 'none'}; const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); const callInProgress = this.props.callState && this.props.callState !== 'ended'; @@ -324,18 +347,6 @@ export default class MessageComposer extends React.Component { // 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 ? ( , formattingButton, stickerpickerButton, - uploadButton, + , callInProgress ? : null, callInProgress ? null : , callInProgress ? null : , From 419cb4e8b20e9ac8068e11c774a65cc3f6b8c13e Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 21:19:18 -0700 Subject: [PATCH 6/9] Define Stickerpicker inline Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index b1ad3ae0a1..689b549bc3 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -358,7 +358,6 @@ export default class MessageComposer extends React.Component { key="controls_formatting" /> ) : null; - const stickerpickerButton = ; controls.push( , formattingButton, - stickerpickerButton, + , , callInProgress ? : null, callInProgress ? null : , From 817f1d482f088fd2e338881e1a344ce5e8f56ece Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 21:43:15 -0700 Subject: [PATCH 7/9] Move format bar rendering to separate method To reduce the complexity in render(), move the format bar rendering to a separate method Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 81 ++++++++++--------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 689b549bc3..3c7a5b077b 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -186,6 +186,7 @@ export default class MessageComposer extends React.Component { 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: { @@ -331,6 +332,47 @@ export default class MessageComposer extends React.Component { } } + 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 MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); @@ -403,42 +445,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, @@ -451,7 +458,7 @@ export default class MessageComposer extends React.Component { { controls } - { formatBar } + { showFormatBar ? this.renderFormatBar() : null } ); } From 3dae9f3d58f475e9c25b076e04fdb0987c1a5481 Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 6 Apr 2019 21:58:05 -0700 Subject: [PATCH 8/9] Move FormattingButton to separate component Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 3c7a5b077b..51c7dbe710 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -117,6 +117,24 @@ 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, @@ -374,32 +392,19 @@ export default class MessageComposer extends React.Component { } render() { - const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); - - const callInProgress = this.props.callState && this.props.callState !== 'ended'; - const controls = [ this.state.me ? : null, this.props.e2eStatus ? : null, ]; - const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); - 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 formattingButton = this.state.inputState.isRichTextEnabled ? ( - - ) : null; - + const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput"); + const showFormattingButton = this.state.inputState.isRichTextEnabled; + const callInProgress = this.props.callState && this.props.callState !== 'ended'; controls.push( , - formattingButton, + showFormattingButton ? : null, , , callInProgress ? : null, From f7462371b1e955f269480d0b9c6956ed49c10125 Mon Sep 17 00:00:00 2001 From: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> Date: Sat, 13 Apr 2019 01:44:36 -0700 Subject: [PATCH 9/9] Rename: Avatar -> ComposerAvatar To avoid confusion with other components that also might be named Avatar Signed-off-by: YaoiFangirl420 <48789208+YaoiFangirl420@users.noreply.github.com> --- src/components/views/rooms/MessageComposer.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 51c7dbe710..5f9014db7a 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -42,14 +42,14 @@ const formatButtonList = [ _td("numbered-list"), ]; -function Avatar(props) { +function ComposerAvatar(props) { const MemberStatusMessageAvatar = sdk.getComponent('avatars.MemberStatusMessageAvatar'); return
; } -Avatar.propTypes = { +ComposerAvatar.propTypes = { me: PropTypes.object.isRequired, } @@ -393,7 +393,7 @@ export default class MessageComposer extends React.Component { render() { const controls = [ - this.state.me ? : null, + this.state.me ? : null, this.props.e2eStatus ? : null, ];