From 4e27b00cf370e2e4bbf42da9aee48823dd1c4daa Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 26 Feb 2021 13:46:39 -0700 Subject: [PATCH] Move call buttons to the room header This is to make some room in the composer for voice messages. The hangup behaviour is intentionally lost by this change as the VOIP UX is intended to rely on dedicated hangup buttons instead. --- res/css/views/rooms/_MessageComposer.scss | 12 -- res/css/views/rooms/_RoomHeader.scss | 13 ++ src/components/structures/RoomView.tsx | 11 +- src/components/views/rooms/MessageComposer.js | 121 ------------------ src/components/views/rooms/RoomHeader.js | 20 +++ src/settings/Settings.ts | 2 + 6 files changed, 45 insertions(+), 134 deletions(-) diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss index 71c0db947e..69e8b50501 100644 --- a/res/css/views/rooms/_MessageComposer.scss +++ b/res/css/views/rooms/_MessageComposer.scss @@ -227,18 +227,6 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/composer/attach.svg'); } -.mx_MessageComposer_hangup::before { - mask-image: url('$(res)/img/element-icons/call/hangup.svg'); -} - -.mx_MessageComposer_voicecall::before { - mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); -} - -.mx_MessageComposer_videocall::before { - mask-image: url('$(res)/img/element-icons/call/video-call.svg'); -} - .mx_MessageComposer_emoji::before { mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg'); } diff --git a/res/css/views/rooms/_RoomHeader.scss b/res/css/views/rooms/_RoomHeader.scss index a23a44906f..387d1588a3 100644 --- a/res/css/views/rooms/_RoomHeader.scss +++ b/res/css/views/rooms/_RoomHeader.scss @@ -252,6 +252,19 @@ limitations under the License. mask-image: url('$(res)/img/element-icons/room/search-inset.svg'); } +.mx_RoomHeader_voiceCallButton::before { + mask-image: url('$(res)/img/element-icons/call/voice-call.svg'); + + // The call button SVG is padded slightly differently, so match it up to the size + // of the other icons + mask-size: 20px; + mask-position: center; +} + +.mx_RoomHeader_videoCallButton::before { + mask-image: url('$(res)/img/element-icons/call/video-call.svg'); +} + .mx_RoomHeader_showPanel { height: 16px; } diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 68ab3c6e0c..6c8560f42c 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -34,7 +34,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier'; import ContentMessages from '../../ContentMessages'; import Modal from '../../Modal'; import * as sdk from '../../index'; -import CallHandler from '../../CallHandler'; +import CallHandler, { PlaceCallType } from '../../CallHandler'; import dis from '../../dispatcher/dispatcher'; import Tinter from '../../Tinter'; import rateLimitedFunc from '../../ratelimitedfunc'; @@ -1352,6 +1352,14 @@ export default class RoomView extends React.Component { SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned); }; + private onCallPlaced = (type: PlaceCallType) => { + dis.dispatch({ + action: 'place_call', + type: type, + room_id: this.state.room.roomId, + }); + }; + private onSettingsClick = () => { dis.dispatch({ action: "open_room_settings" }); }; @@ -2031,6 +2039,7 @@ export default class RoomView extends React.Component { e2eStatus={this.state.e2eStatus} onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null} appsShown={this.state.showApps} + onCallPlaced={this.onCallPlaced} />
diff --git a/src/components/views/rooms/MessageComposer.js b/src/components/views/rooms/MessageComposer.js index 6a867386f7..c38d40020f 100644 --- a/src/components/views/rooms/MessageComposer.js +++ b/src/components/views/rooms/MessageComposer.js @@ -50,97 +50,6 @@ ComposerAvatar.propTypes = { me: PropTypes.object.isRequired, }; -function CallButton(props) { - const onVoiceCallClick = (ev) => { - dis.dispatch({ - action: 'place_call', - type: PlaceCallType.Voice, - room_id: props.roomId, - }); - }; - - return (); -} - -CallButton.propTypes = { - roomId: PropTypes.string.isRequired, -}; - -function VideoCallButton(props) { - const onCallClick = (ev) => { - dis.dispatch({ - action: 'place_call', - type: ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video, - room_id: props.roomId, - }); - }; - - return ; -} - -VideoCallButton.propTypes = { - roomId: PropTypes.string.isRequired, -}; - -function HangupButton(props) { - const onHangupClick = () => { - if (props.isConference) { - dis.dispatch({ - action: props.canEndConference ? 'end_conference' : 'hangup_conference', - room_id: props.roomId, - }); - return; - } - - const call = CallHandler.sharedInstance().getCallForRoom(props.roomId); - if (!call) { - return; - } - - const action = call.state === CallState.Ringing ? 'reject' : 'hangup'; - - dis.dispatch({ - action, - // hangup the call for this room. NB. We use the room in props as the room ID - // as call.roomId may be the 'virtual room', and the dispatch actions always - // use the user-facing room (there was a time when we deliberately used - // call.roomId and *not* props.roomId, but that was for the old - // style Freeswitch conference calls and those times are gone.) - room_id: props.roomId, - }); - }; - - let tooltip = _t("Hangup"); - if (props.isConference && props.canEndConference) { - tooltip = _t("End conference"); - } - - const canLeaveConference = !props.isConference ? true : props.isInConference; - return ( - - ); -} - -HangupButton.propTypes = { - roomId: PropTypes.string.isRequired, - isConference: PropTypes.bool.isRequired, - canEndConference: PropTypes.bool, - isInConference: PropTypes.bool, -}; - const EmojiButton = ({addEmoji}) => { const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu(); @@ -265,7 +174,6 @@ export default class MessageComposer extends React.Component { this.state = { tombstone: this._getRoomTombstone(), canSendMessages: this.props.room.maySendMessage(), - showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"), hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room), joinedConference: WidgetStore.instance.isJoinedToConferenceIn(this.props.room), }; @@ -405,12 +313,7 @@ export default class MessageComposer extends React.Component { ]; 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 SendMessageComposer = sdk.getComponent("rooms.SendMessageComposer"); - const callInProgress = this.props.callState && this.props.callState !== 'ended'; controls.push( ); } - - if (this.state.showCallButtons) { - if (this.state.hasConference) { - const canEndConf = WidgetUtils.canUserModifyWidgets(this.props.room.roomId); - controls.push( - , - ); - } else if (callInProgress) { - controls.push( - , - ); - } else { - controls.push( - , - , - ); - } - } } else if (this.state.tombstone) { const replacementRoomId = this.state.tombstone.getContent()['replacement_room']; diff --git a/src/components/views/rooms/RoomHeader.js b/src/components/views/rooms/RoomHeader.js index 93055c69f5..6736600bc8 100644 --- a/src/components/views/rooms/RoomHeader.js +++ b/src/components/views/rooms/RoomHeader.js @@ -31,6 +31,7 @@ import {DefaultTagID} from "../../../stores/room-list/models"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import RoomTopic from "../elements/RoomTopic"; import RoomName from "../elements/RoomName"; +import {PlaceCallType} from "../../../CallHandler"; export default class RoomHeader extends React.Component { static propTypes = { @@ -45,6 +46,7 @@ export default class RoomHeader extends React.Component { e2eStatus: PropTypes.string, onAppsClick: PropTypes.func, appsShown: PropTypes.bool, + onCallPlaced: PropTypes.func, // (PlaceCallType) => void; }; static defaultProps = { @@ -226,8 +228,26 @@ export default class RoomHeader extends React.Component { title={_t("Search")} />; } + let voiceCallButton; + let videoCallButton; + if (this.props.inRoom && SettingsStore.getValue("showCallButtonsInComposer")) { + voiceCallButton = + this.props.onCallPlaced(PlaceCallType.Voice)} + title={_t("Voice call")} />; + videoCallButton = + this.props.onCallPlaced( + ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video)} + title={_t("Video call")} />; + } + const rightRow =
+ { videoCallButton } + { voiceCallButton } { pinnedEventsButton } { forgetButton } { appsButton } diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b452f10e73..43210021e5 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -632,6 +632,8 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: 3000, }, "showCallButtonsInComposer": { + // Dev note: This is no longer "in composer" but is instead "in room header". + // TODO: Rename with settings v3 supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, default: true, controller: new UIFeatureController(UIFeature.Voip),