Remove call bar

Also hide video mute button on voice calls (awaiting clarification from design)
and fix mute keyboard shortcuts by moving them out of roomview & into
callview.
This commit is contained in:
David Baker 2020-11-19 15:15:31 +00:00
parent 41e885dd67
commit 50965e41eb
6 changed files with 63 additions and 231 deletions

View File

@ -19,57 +19,6 @@ limitations under the License.
min-height: 50px; min-height: 50px;
} }
/* position the indicator in the same place horizontally as .mx_EventTile_avatar. */
.mx_RoomStatusBar_indicator {
padding-left: 17px;
padding-right: 12px;
margin-left: -73px;
margin-top: 15px;
float: left;
width: 24px;
text-align: center;
}
.mx_RoomStatusBar_callBar {
height: 50px;
line-height: $font-50px;
}
.mx_RoomStatusBar_placeholderIndicator span {
color: $primary-fg-color;
opacity: 0.5;
position: relative;
top: -4px;
/*
animation-duration: 1s;
animation-name: bounce;
animation-direction: alternate;
animation-iteration-count: infinite;
*/
}
.mx_RoomStatusBar_placeholderIndicator span:nth-child(1) {
animation-delay: 0.3s;
}
.mx_RoomStatusBar_placeholderIndicator span:nth-child(2) {
animation-delay: 0.6s;
}
.mx_RoomStatusBar_placeholderIndicator span:nth-child(3) {
animation-delay: 0.9s;
}
@keyframes bounce {
from {
opacity: 0.5;
top: 0;
}
to {
opacity: 0.2;
top: -3px;
}
}
.mx_RoomStatusBar_typingIndicatorAvatars { .mx_RoomStatusBar_typingIndicatorAvatars {
width: 52px; width: 52px;
margin-top: -1px; margin-top: -1px;
@ -162,11 +111,6 @@ limitations under the License.
margin-top: 10px; margin-top: 10px;
} }
.mx_RoomStatusBar_callBar {
height: 40px;
line-height: $font-40px;
}
.mx_RoomStatusBar_typingBar { .mx_RoomStatusBar_typingBar {
height: 40px; height: 40px;
line-height: $font-40px; line-height: $font-40px;

View File

@ -145,6 +145,9 @@ limitations under the License.
.mx_CallView_callControls_button { .mx_CallView_callControls_button {
cursor: pointer; cursor: pointer;
margin-left: 8px;
margin-right: 8px;
&::before { &::before {
content: ''; content: '';
@ -184,9 +187,6 @@ limitations under the License.
} }
.mx_CallView_callControls_button_hangup { .mx_CallView_callControls_button_hangup {
margin-left: 16px;
margin-right: 16px;
&::before { &::before {
background-image: url('$(res)/img/voip_buttons/hangup.svg'); background-image: url('$(res)/img/voip_buttons/hangup.svg');
} }

View File

@ -1,6 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 9C16 9 17 10.2857 17 12C17 13.7143 16 15 16 15" stroke="white" stroke-width="2" stroke-linecap="round"/>
<path d="M19 6C19 6 21 8.57143 21 12C21 15.4286 19 18 19 18" stroke="white" stroke-width="2" stroke-linecap="round"/>
<rect x="2" y="8" width="11" height="8" rx="2" fill="white"/>
<path d="M7 8L11.3598 4.36682C12.0111 3.82405 13 4.2872 13 5.13504V18.865C13 19.7128 12.0111 20.176 11.3598 19.6332L7 16V8Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 541 B

View File

@ -18,13 +18,11 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Matrix from 'matrix-js-sdk'; import Matrix from 'matrix-js-sdk';
import { _t, _td } from '../../languageHandler'; import { _t, _td } from '../../languageHandler';
import * as sdk from '../../index';
import {MatrixClientPeg} from '../../MatrixClientPeg'; import {MatrixClientPeg} from '../../MatrixClientPeg';
import Resend from '../../Resend'; import Resend from '../../Resend';
import dis from '../../dispatcher/dispatcher'; import dis from '../../dispatcher/dispatcher';
import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils'; import {messageForResourceLimitError, messageForSendError} from '../../utils/ErrorUtils';
import {Action} from "../../dispatcher/actions"; import {Action} from "../../dispatcher/actions";
import { CallState, CallType } from 'matrix-js-sdk/lib/webrtc/call';
const STATUS_BAR_HIDDEN = 0; const STATUS_BAR_HIDDEN = 0;
const STATUS_BAR_EXPANDED = 1; const STATUS_BAR_EXPANDED = 1;
@ -42,13 +40,6 @@ export default class RoomStatusBar extends React.Component {
// the room this statusbar is representing. // the room this statusbar is representing.
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,
// The active call in the room, if any (means we show the call bar
// along with the status of the call)
callState: PropTypes.string,
// The type of the call in progress, or null if no call is in progress
callType: PropTypes.string,
// true if the room is being peeked at. This affects components that shouldn't // true if the room is being peeked at. This affects components that shouldn't
// logically be shown when peeking, such as a prompt to invite people to a room. // logically be shown when peeking, such as a prompt to invite people to a room.
isPeeking: PropTypes.bool, isPeeking: PropTypes.bool,
@ -115,12 +106,6 @@ export default class RoomStatusBar extends React.Component {
}); });
}; };
_showCallBar() {
return (this.props.callState &&
(this.props.callState !== CallState.Ended && this.props.callState !== CallState.Ringing)
);
}
_onResendAllClick = () => { _onResendAllClick = () => {
Resend.resendUnsentEvents(this.props.room); Resend.resendUnsentEvents(this.props.room);
dis.fire(Action.FocusComposer); dis.fire(Action.FocusComposer);
@ -152,7 +137,7 @@ export default class RoomStatusBar extends React.Component {
// changed - so we use '0' to indicate normal size, and other values to // changed - so we use '0' to indicate normal size, and other values to
// indicate other sizes. // indicate other sizes.
_getSize() { _getSize() {
if (this._shouldShowConnectionError() || this._showCallBar()) { if (this._shouldShowConnectionError()) {
return STATUS_BAR_EXPANDED; return STATUS_BAR_EXPANDED;
} else if (this.state.unsentMessages.length > 0) { } else if (this.state.unsentMessages.length > 0) {
return STATUS_BAR_EXPANDED_LARGE; return STATUS_BAR_EXPANDED_LARGE;
@ -160,22 +145,6 @@ export default class RoomStatusBar extends React.Component {
return STATUS_BAR_HIDDEN; return STATUS_BAR_HIDDEN;
} }
// return suitable content for the image on the left of the status bar.
_getIndicator() {
if (this._showCallBar()) {
const TintableSvg = sdk.getComponent("elements.TintableSvg");
return (
<TintableSvg src={require("../../../res/img/element-icons/room/in-call.svg")} width="23" height="20" />
);
}
if (this._shouldShowConnectionError()) {
return null;
}
return null;
}
_shouldShowConnectionError() { _shouldShowConnectionError() {
// no conn bar trumps the "some not sent" msg since you can't resend without // no conn bar trumps the "some not sent" msg since you can't resend without
// a connection! // a connection!
@ -266,25 +235,6 @@ export default class RoomStatusBar extends React.Component {
</div>; </div>;
} }
_getCallStatusText() {
switch (this.props.callState) {
case CallState.CreateOffer:
case CallState.InviteSent:
return _t('Calling...');
case CallState.Connecting:
case CallState.CreateAnswer:
return _t('Call connecting...');
case CallState.Connected:
return _t('Active call');
case CallState.WaitLocalMedia:
if (this.props.callType === CallType.Video) {
return _t('Starting camera...');
} else {
return _t('Starting microphone...');
}
}
}
// return suitable content for the main (text) part of the status bar. // return suitable content for the main (text) part of the status bar.
_getContent() { _getContent() {
if (this._shouldShowConnectionError()) { if (this._shouldShowConnectionError()) {
@ -307,26 +257,14 @@ export default class RoomStatusBar extends React.Component {
return this._getUnsentMessageContent(); return this._getUnsentMessageContent();
} }
if (this._showCallBar()) {
return (
<div className="mx_RoomStatusBar_callBar">
<b>{ this._getCallStatusText() }</b>
</div>
);
}
return null; return null;
} }
render() { render() {
const content = this._getContent(); const content = this._getContent();
const indicator = this._getIndicator();
return ( return (
<div className="mx_RoomStatusBar"> <div className="mx_RoomStatusBar">
<div className="mx_RoomStatusBar_indicator">
{ indicator }
</div>
<div role="alert"> <div role="alert">
{ content } { content }
</div> </div>

View File

@ -41,7 +41,7 @@ import rateLimitedFunc from '../../ratelimitedfunc';
import * as ObjectUtils from '../../ObjectUtils'; import * as ObjectUtils from '../../ObjectUtils';
import * as Rooms from '../../Rooms'; import * as Rooms from '../../Rooms';
import eventSearch, {searchPagination} from '../../Searching'; import eventSearch, {searchPagination} from '../../Searching';
import {isOnlyCtrlOrCmdIgnoreShiftKeyEvent, isOnlyCtrlOrCmdKeyEvent, Key} from '../../Keyboard'; import {isOnlyCtrlOrCmdIgnoreShiftKeyEvent, Key} from '../../Keyboard';
import MainSplit from './MainSplit'; import MainSplit from './MainSplit';
import RightPanel from './RightPanel'; import RightPanel from './RightPanel';
import RoomViewStore from '../../stores/RoomViewStore'; import RoomViewStore from '../../stores/RoomViewStore';
@ -68,10 +68,9 @@ import RoomUpgradeWarningBar from "../views/rooms/RoomUpgradeWarningBar";
import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel"; import PinnedEventsPanel from "../views/rooms/PinnedEventsPanel";
import AuxPanel from "../views/rooms/AuxPanel"; import AuxPanel from "../views/rooms/AuxPanel";
import RoomHeader from "../views/rooms/RoomHeader"; import RoomHeader from "../views/rooms/RoomHeader";
import TintableSvg from "../views/elements/TintableSvg";
import {XOR} from "../../@types/common"; import {XOR} from "../../@types/common";
import { IThreepidInvite } from "../../stores/ThreepidInviteStore"; import { IThreepidInvite } from "../../stores/ThreepidInviteStore";
import { CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { CallState, MatrixCall } from "matrix-js-sdk/src/webrtc/call";
import WidgetStore from "../../stores/WidgetStore"; import WidgetStore from "../../stores/WidgetStore";
import {UPDATE_EVENT} from "../../stores/AsyncStore"; import {UPDATE_EVENT} from "../../stores/AsyncStore";
import Notifier from "../../Notifier"; import Notifier from "../../Notifier";
@ -508,8 +507,6 @@ export default class RoomView extends React.Component<IProps, IState> {
this.props.resizeNotifier.on("middlePanelResized", this.onResize); this.props.resizeNotifier.on("middlePanelResized", this.onResize);
} }
this.onResize(); this.onResize();
document.addEventListener("keydown", this.onNativeKeyDown);
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
@ -592,8 +589,6 @@ export default class RoomView extends React.Component<IProps, IState> {
this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize); this.props.resizeNotifier.removeListener("middlePanelResized", this.onResize);
} }
document.removeEventListener("keydown", this.onNativeKeyDown);
// Remove RoomStore listener // Remove RoomStore listener
if (this.roomStoreToken) { if (this.roomStoreToken) {
this.roomStoreToken.remove(); this.roomStoreToken.remove();
@ -642,33 +637,6 @@ export default class RoomView extends React.Component<IProps, IState> {
} }
}; };
// we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire
private onNativeKeyDown = ev => {
let handled = false;
const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev);
switch (ev.key) {
case Key.D:
if (ctrlCmdOnly) {
this.onMuteAudioClick();
handled = true;
}
break;
case Key.E:
if (ctrlCmdOnly) {
this.onMuteVideoClick();
handled = true;
}
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
};
private onReactKeyDown = ev => { private onReactKeyDown = ev => {
let handled = false; let handled = false;
@ -1758,8 +1726,6 @@ export default class RoomView extends React.Component<IProps, IState> {
isStatusAreaExpanded = this.state.statusBarVisible; isStatusAreaExpanded = this.state.statusBarVisible;
statusBar = <RoomStatusBar statusBar = <RoomStatusBar
room={this.state.room} room={this.state.room}
callState={this.state.callState}
callType={activeCall ? activeCall.type : null}
isPeeking={myMembership !== "join"} isPeeking={myMembership !== "join"}
onInviteClick={this.onInviteButtonClick} onInviteClick={this.onInviteButtonClick}
onVisible={this.onStatusBarVisible} onVisible={this.onStatusBarVisible}
@ -1883,56 +1849,6 @@ export default class RoomView extends React.Component<IProps, IState> {
}; };
} }
if (activeCall) {
let zoomButton; let videoMuteButton;
if (activeCall.type === CallType.Video) {
zoomButton = (
<div className="mx_RoomView_voipButton" onClick={this.onFullscreenClick} title={_t("Fill screen")}>
<TintableSvg
src={require("../../../res/img/element-icons/call/fullscreen.svg")}
width="29"
height="22"
style={{ marginTop: 1, marginRight: 4 }}
/>
</div>
);
videoMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteVideoClick}>
<TintableSvg
src={activeCall.isLocalVideoMuted() ?
require("../../../res/img/element-icons/call/video-muted.svg") :
require("../../../res/img/element-icons/call/video-call.svg")}
alt={activeCall.isLocalVideoMuted() ? _t("Click to unmute video") :
_t("Click to mute video")}
width=""
height="27"
/>
</div>;
}
const voiceMuteButton =
<div className="mx_RoomView_voipButton" onClick={this.onMuteAudioClick}>
<TintableSvg
src={activeCall.isMicrophoneMuted() ?
require("../../../res/img/element-icons/call/voice-muted.svg") :
require("../../../res/img/element-icons/call/voice-unmuted.svg")}
alt={activeCall.isMicrophoneMuted() ? _t("Click to unmute audio") : _t("Click to mute audio")}
width="21"
height="26"
/>
</div>;
// wrap the existing status bar into a 'callStatusBar' which adds more knobs.
statusBar =
<div className="mx_RoomView_callStatusBar">
{ voiceMuteButton }
{ videoMuteButton }
{ zoomButton }
{ statusBar }
</div>;
}
// if we have search results, we keep the messagepanel (so that it preserves its // if we have search results, we keep the messagepanel (so that it preserves its
// scroll state), but hide it. // scroll state), but hide it.
let searchResultsPanel; let searchResultsPanel;

View File

@ -27,6 +27,7 @@ import { CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
import { CallEvent } from 'matrix-js-sdk/src/webrtc/call'; import { CallEvent } from 'matrix-js-sdk/src/webrtc/call';
import classNames from 'classnames'; import classNames from 'classnames';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import {isOnlyCtrlOrCmdKeyEvent, Key} from '../../../Keyboard';
interface IProps { interface IProps {
// js-sdk room object. If set, we will only show calls for the given // js-sdk room object. If set, we will only show calls for the given
@ -108,9 +109,11 @@ export default class CallView extends React.Component<IProps, IState> {
public componentDidMount() { public componentDidMount() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
document.addEventListener('keydown', this.onNativeKeyDown);
} }
public componentWillUnmount() { public componentWillUnmount() {
document.removeEventListener("keydown", this.onNativeKeyDown);
this.updateCallListeners(this.state.call, null); this.updateCallListeners(this.state.call, null);
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
} }
@ -198,14 +201,18 @@ export default class CallView extends React.Component<IProps, IState> {
}); });
}; };
onControlsHideTimer = () => { private onControlsHideTimer = () => {
this.controlsHideTimer = null; this.controlsHideTimer = null;
this.setState({ this.setState({
controlsVisible: false, controlsVisible: false,
}); });
} }
onMouseMove = () => { private onMouseMove = () => {
this.showControls();
}
private showControls() {
if (!this.state.controlsVisible) { if (!this.state.controlsVisible) {
this.setState({ this.setState({
controlsVisible: true, controlsVisible: true,
@ -217,21 +224,58 @@ export default class CallView extends React.Component<IProps, IState> {
this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY);
} }
onMicMuteClick = () => { private onMicMuteClick = () => {
if (!this.state.call) return;
const newVal = !this.state.micMuted; const newVal = !this.state.micMuted;
this.state.call.setMicrophoneMuted(newVal); this.state.call.setMicrophoneMuted(newVal);
this.setState({micMuted: newVal}); this.setState({micMuted: newVal});
} }
onVidMuteClick = () => { private onVidMuteClick = () => {
if (!this.state.call) return;
const newVal = !this.state.vidMuted; const newVal = !this.state.vidMuted;
this.state.call.setLocalVideoMuted(newVal); this.state.call.setLocalVideoMuted(newVal);
this.setState({vidMuted: newVal}); this.setState({vidMuted: newVal});
} }
onRoomAvatarClick = () => { // we register global shortcuts here, they *must not conflict* with local shortcuts elsewhere or both will fire
// Note that this assumes we always have a callview on screen at any given time
// CallHandler would probably be a better place for this
private onNativeKeyDown = ev => {
let handled = false;
const ctrlCmdOnly = isOnlyCtrlOrCmdKeyEvent(ev);
switch (ev.key) {
case Key.D:
if (ctrlCmdOnly) {
this.onMicMuteClick();
// show the controls to give feedback
this.showControls();
handled = true;
}
break;
case Key.E:
if (ctrlCmdOnly) {
this.onVidMuteClick();
// show the controls to give feedback
this.showControls();
handled = true;
}
break;
}
if (handled) {
ev.stopPropagation();
ev.preventDefault();
}
};
private onRoomAvatarClick = () => {
dis.dispatch({ dis.dispatch({
action: 'view_room', action: 'view_room',
room_id: this.state.call.roomId, room_id: this.state.call.roomId,
@ -279,6 +323,11 @@ export default class CallView extends React.Component<IProps, IState> {
mx_CallView_callControls_hidden: !this.state.controlsVisible, mx_CallView_callControls_hidden: !this.state.controlsVisible,
}); });
const vidMuteButton = this.state.call.type === CallType.Video ? <div
className={vidClasses}
onClick={this.onVidMuteClick}
/> : null;
callControls = <div className={callControlsClasses}> callControls = <div className={callControlsClasses}>
<div <div
className={micClasses} className={micClasses}
@ -293,18 +342,9 @@ export default class CallView extends React.Component<IProps, IState> {
}); });
}} }}
/> />
<div {vidMuteButton}
className={vidClasses} <div className={micCacheClasses} />
onClick={this.onVidMuteClick} <div className={vidCacheClasses} />
/>
<div
className={micCacheClasses}
onClick={this.onMicMuteClick}
/>
<div
className={vidCacheClasses}
onClick={this.onMicMuteClick}
/>
</div>; </div>;
} }