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.
This commit is contained in:
parent
f1330b7359
commit
4e27b00cf3
6 changed files with 45 additions and 134 deletions
|
@ -227,18 +227,6 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
|
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 {
|
.mx_MessageComposer_emoji::before {
|
||||||
mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg');
|
mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg');
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,6 +252,19 @@ limitations under the License.
|
||||||
mask-image: url('$(res)/img/element-icons/room/search-inset.svg');
|
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 {
|
.mx_RoomHeader_showPanel {
|
||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ import ResizeNotifier from '../../utils/ResizeNotifier';
|
||||||
import ContentMessages from '../../ContentMessages';
|
import ContentMessages from '../../ContentMessages';
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
import * as sdk from '../../index';
|
import * as sdk from '../../index';
|
||||||
import CallHandler from '../../CallHandler';
|
import CallHandler, { PlaceCallType } from '../../CallHandler';
|
||||||
import dis from '../../dispatcher/dispatcher';
|
import dis from '../../dispatcher/dispatcher';
|
||||||
import Tinter from '../../Tinter';
|
import Tinter from '../../Tinter';
|
||||||
import rateLimitedFunc from '../../ratelimitedfunc';
|
import rateLimitedFunc from '../../ratelimitedfunc';
|
||||||
|
@ -1352,6 +1352,14 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
SettingsStore.setValue("PinnedEvents.isOpen", roomId, SettingLevel.ROOM_DEVICE, nowShowingPinned);
|
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 = () => {
|
private onSettingsClick = () => {
|
||||||
dis.dispatch({ action: "open_room_settings" });
|
dis.dispatch({ action: "open_room_settings" });
|
||||||
};
|
};
|
||||||
|
@ -2031,6 +2039,7 @@ export default class RoomView extends React.Component<IProps, IState> {
|
||||||
e2eStatus={this.state.e2eStatus}
|
e2eStatus={this.state.e2eStatus}
|
||||||
onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null}
|
onAppsClick={this.state.hasPinnedWidgets ? this.onAppsClick : null}
|
||||||
appsShown={this.state.showApps}
|
appsShown={this.state.showApps}
|
||||||
|
onCallPlaced={this.onCallPlaced}
|
||||||
/>
|
/>
|
||||||
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
|
<MainSplit panel={rightPanel} resizeNotifier={this.props.resizeNotifier}>
|
||||||
<div className="mx_RoomView_body">
|
<div className="mx_RoomView_body">
|
||||||
|
|
|
@ -50,97 +50,6 @@ ComposerAvatar.propTypes = {
|
||||||
me: PropTypes.object.isRequired,
|
me: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
function CallButton(props) {
|
|
||||||
const onVoiceCallClick = (ev) => {
|
|
||||||
dis.dispatch({
|
|
||||||
action: 'place_call',
|
|
||||||
type: PlaceCallType.Voice,
|
|
||||||
room_id: props.roomId,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (<AccessibleTooltipButton
|
|
||||||
className="mx_MessageComposer_button mx_MessageComposer_voicecall"
|
|
||||||
onClick={onVoiceCallClick}
|
|
||||||
title={_t('Voice call')}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 <AccessibleTooltipButton
|
|
||||||
className="mx_MessageComposer_button mx_MessageComposer_videocall"
|
|
||||||
onClick={onCallClick}
|
|
||||||
title={_t('Video call')}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<AccessibleTooltipButton
|
|
||||||
className="mx_MessageComposer_button mx_MessageComposer_hangup"
|
|
||||||
onClick={onHangupClick}
|
|
||||||
title={tooltip}
|
|
||||||
disabled={!canLeaveConference}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
HangupButton.propTypes = {
|
|
||||||
roomId: PropTypes.string.isRequired,
|
|
||||||
isConference: PropTypes.bool.isRequired,
|
|
||||||
canEndConference: PropTypes.bool,
|
|
||||||
isInConference: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
const EmojiButton = ({addEmoji}) => {
|
const EmojiButton = ({addEmoji}) => {
|
||||||
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
|
||||||
|
|
||||||
|
@ -265,7 +174,6 @@ export default class MessageComposer extends React.Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
tombstone: this._getRoomTombstone(),
|
tombstone: this._getRoomTombstone(),
|
||||||
canSendMessages: this.props.room.maySendMessage(),
|
canSendMessages: this.props.room.maySendMessage(),
|
||||||
showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"),
|
|
||||||
hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room),
|
hasConference: WidgetStore.instance.doesRoomHaveConference(this.props.room),
|
||||||
joinedConference: WidgetStore.instance.isJoinedToConferenceIn(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) {
|
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 SendMessageComposer = sdk.getComponent("rooms.SendMessageComposer");
|
||||||
const callInProgress = this.props.callState && this.props.callState !== 'ended';
|
|
||||||
|
|
||||||
controls.push(
|
controls.push(
|
||||||
<SendMessageComposer
|
<SendMessageComposer
|
||||||
|
@ -430,30 +333,6 @@ export default class MessageComposer extends React.Component {
|
||||||
SettingsStore.getValue("MessageComposerInput.showStickersButton")) {
|
SettingsStore.getValue("MessageComposerInput.showStickersButton")) {
|
||||||
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
|
controls.push(<Stickerpicker key="stickerpicker_controls_button" room={this.props.room} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.showCallButtons) {
|
|
||||||
if (this.state.hasConference) {
|
|
||||||
const canEndConf = WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
|
||||||
controls.push(
|
|
||||||
<HangupButton
|
|
||||||
key="controls_hangup"
|
|
||||||
roomId={this.props.room.roomId}
|
|
||||||
isConference={true}
|
|
||||||
canEndConference={canEndConf}
|
|
||||||
isInConference={this.state.joinedConference}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
} else if (callInProgress) {
|
|
||||||
controls.push(
|
|
||||||
<HangupButton key="controls_hangup" roomId={this.props.room.roomId} isConference={false} />,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
controls.push(
|
|
||||||
<CallButton key="controls_call" roomId={this.props.room.roomId} />,
|
|
||||||
<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'];
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {DefaultTagID} from "../../../stores/room-list/models";
|
||||||
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
|
||||||
import RoomTopic from "../elements/RoomTopic";
|
import RoomTopic from "../elements/RoomTopic";
|
||||||
import RoomName from "../elements/RoomName";
|
import RoomName from "../elements/RoomName";
|
||||||
|
import {PlaceCallType} from "../../../CallHandler";
|
||||||
|
|
||||||
export default class RoomHeader extends React.Component {
|
export default class RoomHeader extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -45,6 +46,7 @@ export default class RoomHeader extends React.Component {
|
||||||
e2eStatus: PropTypes.string,
|
e2eStatus: PropTypes.string,
|
||||||
onAppsClick: PropTypes.func,
|
onAppsClick: PropTypes.func,
|
||||||
appsShown: PropTypes.bool,
|
appsShown: PropTypes.bool,
|
||||||
|
onCallPlaced: PropTypes.func, // (PlaceCallType) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -226,8 +228,26 @@ export default class RoomHeader extends React.Component {
|
||||||
title={_t("Search")} />;
|
title={_t("Search")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let voiceCallButton;
|
||||||
|
let videoCallButton;
|
||||||
|
if (this.props.inRoom && SettingsStore.getValue("showCallButtonsInComposer")) {
|
||||||
|
voiceCallButton =
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
className="mx_RoomHeader_button mx_RoomHeader_voiceCallButton"
|
||||||
|
onClick={() => this.props.onCallPlaced(PlaceCallType.Voice)}
|
||||||
|
title={_t("Voice call")} />;
|
||||||
|
videoCallButton =
|
||||||
|
<AccessibleTooltipButton
|
||||||
|
className="mx_RoomHeader_button mx_RoomHeader_videoCallButton"
|
||||||
|
onClick={(ev) => this.props.onCallPlaced(
|
||||||
|
ev.shiftKey ? PlaceCallType.ScreenSharing : PlaceCallType.Video)}
|
||||||
|
title={_t("Video call")} />;
|
||||||
|
}
|
||||||
|
|
||||||
const rightRow =
|
const rightRow =
|
||||||
<div className="mx_RoomHeader_buttons">
|
<div className="mx_RoomHeader_buttons">
|
||||||
|
{ videoCallButton }
|
||||||
|
{ voiceCallButton }
|
||||||
{ pinnedEventsButton }
|
{ pinnedEventsButton }
|
||||||
{ forgetButton }
|
{ forgetButton }
|
||||||
{ appsButton }
|
{ appsButton }
|
||||||
|
|
|
@ -632,6 +632,8 @@ export const SETTINGS: {[setting: string]: ISetting} = {
|
||||||
default: 3000,
|
default: 3000,
|
||||||
},
|
},
|
||||||
"showCallButtonsInComposer": {
|
"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,
|
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||||
default: true,
|
default: true,
|
||||||
controller: new UIFeatureController(UIFeature.Voip),
|
controller: new UIFeatureController(UIFeature.Voip),
|
||||||
|
|
Loading…
Reference in a new issue