diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss index 7752edddfa..498dd8e096 100644 --- a/res/css/views/voip/_CallView.scss +++ b/res/css/views/voip/_CallView.scss @@ -47,11 +47,11 @@ limitations under the License. height: 180px; } - .mx_CallView_callControls { + .mx_CallViewButtons { bottom: 0px; } - .mx_CallView_callControls_button { + .mx_CallViewButtons_button { &::before { width: 36px; height: 36px; @@ -199,20 +199,6 @@ limitations under the License. } } -.mx_CallView_callControls { - position: absolute; - display: flex; - justify-content: center; - bottom: 5px; - opacity: 1; - transition: opacity 0.5s; - z-index: 200; // To be above _all_ feeds -} - -.mx_CallView_callControls_hidden { - opacity: 0.001; // opacity 0 can cause a re-layout - pointer-events: none; -} .mx_CallView_presenting { opacity: 1; @@ -232,94 +218,3 @@ limitations under the License. opacity: 0.001; // opacity 0 can cause a re-layout pointer-events: none; } - -.mx_CallView_callControls_button { - cursor: pointer; - margin-left: 2px; - margin-right: 2px; - - - &::before { - content: ''; - display: inline-block; - - height: 48px; - width: 48px; - - background-repeat: no-repeat; - background-size: contain; - background-position: center; - } -} - -.mx_CallView_callControls_dialpad { - &::before { - background-image: url('$(res)/img/voip/dialpad.svg'); - } -} - -.mx_CallView_callControls_button_micOn { - &::before { - background-image: url('$(res)/img/voip/mic-on.svg'); - } -} - -.mx_CallView_callControls_button_micOff { - &::before { - background-image: url('$(res)/img/voip/mic-off.svg'); - } -} - -.mx_CallView_callControls_button_vidOn { - &::before { - background-image: url('$(res)/img/voip/vid-on.svg'); - } -} - -.mx_CallView_callControls_button_vidOff { - &::before { - background-image: url('$(res)/img/voip/vid-off.svg'); - } -} - -.mx_CallView_callControls_button_screensharingOn { - &::before { - background-image: url('$(res)/img/voip/screensharing-on.svg'); - } -} - -.mx_CallView_callControls_button_screensharingOff { - &::before { - background-image: url('$(res)/img/voip/screensharing-off.svg'); - } -} - -.mx_CallView_callControls_button_sidebarOn { - &::before { - background-image: url('$(res)/img/voip/sidebar-on.svg'); - } -} - -.mx_CallView_callControls_button_sidebarOff { - &::before { - background-image: url('$(res)/img/voip/sidebar-off.svg'); - } -} - -.mx_CallView_callControls_button_hangup { - &::before { - background-image: url('$(res)/img/voip/hangup.svg'); - } -} - -.mx_CallView_callControls_button_more { - &::before { - background-image: url('$(res)/img/voip/more.svg'); - } -} - -.mx_CallView_callControls_button_invisible { - visibility: hidden; - pointer-events: none; - position: absolute; -} diff --git a/src/components/views/voip/CallView.tsx b/src/components/views/voip/CallView.tsx index d3371b8456..a6ae71713b 100644 --- a/src/components/views/voip/CallView.tsx +++ b/src/components/views/voip/CallView.tsx @@ -1,6 +1,6 @@ /* Copyright 2015, 2016 OpenMarket Ltd -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. Copyright 2021 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,15 +27,7 @@ import { CallEvent, CallState, CallType, MatrixCall } from 'matrix-js-sdk/src/we import classNames from 'classnames'; import AccessibleButton from '../elements/AccessibleButton'; import { isOnlyCtrlOrCmdKeyEvent, Key } from '../../../Keyboard'; -import { - alwaysAboveLeftOf, - alwaysAboveRightOf, - ChevronFace, - ContextMenuTooltipButton, -} from '../../structures/ContextMenu'; -import CallContextMenu from '../context_menus/CallContextMenu'; import { avatarUrlForMember } from '../../../Avatar'; -import DialpadContextMenu from '../context_menus/DialpadContextMenu'; import { CallFeed } from 'matrix-js-sdk/src/webrtc/callFeed'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import DesktopCapturerSourcePicker from "../elements/DesktopCapturerSourcePicker"; @@ -43,8 +35,7 @@ import Modal from '../../../Modal'; import { SDPStreamMetadataPurpose } from 'matrix-js-sdk/src/webrtc/callEventTypes'; import CallViewSidebar from './CallViewSidebar'; import CallViewHeader from './CallView/CallViewHeader'; -import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; -import { Alignment } from "../elements/Tooltip"; +import CallViewButtons from "./CallView/CallViewButtons"; interface IProps { // The call for us to display @@ -83,8 +74,6 @@ interface IState { sidebarShown: boolean; } -const tooltipYOffset = -24; - function getFullScreenElement() { return ( document.fullscreenElement || @@ -113,18 +102,11 @@ function exitFullscreen() { if (exitMethod) exitMethod.call(document); } -const CONTROLS_HIDE_DELAY = 2000; -// Height of the header duplicated from CSS because we need to subtract it from our max -// height to get the max height of the video -const CONTEXT_MENU_VPADDING = 8; // How far the context menu sits above the button (px) - @replaceableComponent("views.voip.CallView") export default class CallView extends React.Component { private dispatcherRef: string; private contentRef = createRef(); - private controlsHideTimer: number = null; - private dialpadButton = createRef(); - private contextMenuButton = createRef(); + private buttonsRef = createRef(); constructor(props: IProps) { super(props); @@ -240,16 +222,8 @@ export default class CallView extends React.Component { }); }; - private onControlsHideTimer = () => { - if (this.state.hoveringControls || this.state.showDialpad || this.state.showMoreMenu) return; - this.controlsHideTimer = null; - this.setState({ - controlsVisible: false, - }); - }; - private onMouseMove = () => { - this.showControls(); + this.buttonsRef.current?.showControls(); }; private getOrderedFeeds(feeds: Array): { primary: CallFeed, secondary: Array } { @@ -275,29 +249,6 @@ export default class CallView extends React.Component { return { primary, secondary }; } - private showControls(): void { - if (this.state.showMoreMenu || this.state.showDialpad) return; - - if (!this.state.controlsVisible) { - this.setState({ - controlsVisible: true, - }); - } - if (this.controlsHideTimer !== null) { - clearTimeout(this.controlsHideTimer); - } - this.controlsHideTimer = window.setTimeout(this.onControlsHideTimer, CONTROLS_HIDE_DELAY); - } - - private onDialpadClick = (): void => { - if (!this.state.showDialpad) { - this.setState({ showDialpad: true }); - this.showControls(); - } else { - this.setState({ showDialpad: false }); - } - }; - private onMicMuteClick = (): void => { const newVal = !this.state.micMuted; @@ -328,19 +279,6 @@ export default class CallView extends React.Component { }); }; - private onMoreClick = (): void => { - this.setState({ showMoreMenu: true }); - this.showControls(); - }; - - private closeDialpad = (): void => { - this.setState({ showDialpad: false }); - }; - - private closeContextMenu = (): void => { - this.setState({ showMoreMenu: false }); - }; - // 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 @@ -353,7 +291,7 @@ export default class CallView extends React.Component { if (ctrlCmdOnly) { this.onMicMuteClick(); // show the controls to give feedback - this.showControls(); + this.buttonsRef.current?.showControls(); handled = true; } break; @@ -362,7 +300,7 @@ export default class CallView extends React.Component { if (ctrlCmdOnly) { this.onVidMuteClick(); // show the controls to give feedback - this.showControls(); + this.buttonsRef.current?.showControls(); handled = true; } break; @@ -374,15 +312,6 @@ export default class CallView extends React.Component { } }; - private onCallControlsMouseEnter = (): void => { - this.setState({ hoveringControls: true }); - this.showControls(); - }; - - private onCallControlsMouseLeave = (): void => { - this.setState({ hoveringControls: false }); - }; - private onCallResumeClick = (): void => { const userFacingRoomId = CallHandler.sharedInstance().roomIdForCall(this.props.call); CallHandler.sharedInstance().setActiveCallRoomId(userFacingRoomId); @@ -401,206 +330,60 @@ export default class CallView extends React.Component { }; private onToggleSidebar = (): void => { - this.setState({ - sidebarShown: !this.state.sidebarShown, - }); + this.setState({ sidebarShown: !this.state.sidebarShown }); }; private renderCallControls(): JSX.Element { - const micClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_micOn: !this.state.micMuted, - mx_CallView_callControls_button_micOff: this.state.micMuted, - }); - - const vidClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_vidOn: !this.state.vidMuted, - mx_CallView_callControls_button_vidOff: this.state.vidMuted, - }); - - const screensharingClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_screensharingOn: this.state.screensharing, - mx_CallView_callControls_button_screensharingOff: !this.state.screensharing, - }); - - const sidebarButtonClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_sidebarOn: this.state.sidebarShown, - mx_CallView_callControls_button_sidebarOff: !this.state.sidebarShown, - }); - - // Put the other states of the mic/video icons in the document to make sure they're cached - // (otherwise the icon disappears briefly when toggled) - const micCacheClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_micOn: this.state.micMuted, - mx_CallView_callControls_button_micOff: !this.state.micMuted, - mx_CallView_callControls_button_invisible: true, - }); - - const vidCacheClasses = classNames({ - mx_CallView_callControls_button: true, - mx_CallView_callControls_button_vidOn: this.state.micMuted, - mx_CallView_callControls_button_vidOff: !this.state.micMuted, - mx_CallView_callControls_button_invisible: true, - }); - - const callControlsClasses = classNames({ - mx_CallView_callControls: true, - mx_CallView_callControls_hidden: !this.state.controlsVisible, - }); - // We don't support call upgrades (yet) so hide the video mute button in voice calls - let vidMuteButton; - if (this.props.call.type === CallType.Video) { - vidMuteButton = ( - - ); - } - + const vidMuteButtonShown = this.props.call.type === CallType.Video; // Screensharing is possible, if we can send a second stream and // identify it using SDPStreamMetadata or if we can replace the already // existing usermedia track by a screensharing track. We also need to be // connected to know the state of the other side - let screensharingButton; - if ( + const screensharingButtonShown = ( (this.props.call.opponentSupportsSDPStreamMetadata() || this.props.call.type === CallType.Video) && this.props.call.state === CallState.Connected - ) { - screensharingButton = ( - - ); - } - + ); // To show the sidebar we need secondary feeds, if we don't have them, // we can hide this button. If we are in PiP, sidebar is also hidden, so // we can hide the button too - let sidebarButton; - if ( - !this.props.pipMode && - ( - this.state.primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare || - this.props.call.isScreensharing() - ) - ) { - sidebarButton = ( - - ); - } - + const sidebarButtonShown = ( + this.state.primaryFeed?.purpose === SDPStreamMetadataPurpose.Screenshare || + this.props.call.isScreensharing() + ); // The dial pad & 'more' button actions are only relevant in a connected call - let contextMenuButton; - if (this.state.callState === CallState.Connected) { - contextMenuButton = ( - - ); - } - let dialpadButton; - if (this.state.callState === CallState.Connected && this.props.call.opponentSupportsDTMF()) { - dialpadButton = ( - - ); - } - - let dialPad; - if (this.state.showDialpad) { - dialPad = ; - } - - let contextMenu; - if (this.state.showMoreMenu) { - contextMenu = ; - } + const contextMenuButtonShown = this.state.callState === CallState.Connected; + const dialpadButtonShown = ( + this.state.callState === CallState.Connected && + this.props.call.opponentSupportsDTMF() + ); return ( -
- { dialPad } - { contextMenu } - { dialpadButton } - - { vidMuteButton } -
-
- { screensharingButton } - { sidebarButton } - { contextMenuButton } - -
+ ); }