@@ -16,7 +16,7 @@ See the License for the specific language governing permissions and
1616limitations under the License.
1717*/
1818
19- import React , { createRef } from "react" ;
19+ import React , { createRef , useState } from "react" ;
2020import classNames from "classnames" ;
2121import { MatrixCall } from "matrix-js-sdk/src/webrtc/call" ;
2222
@@ -26,10 +26,14 @@ import DialpadContextMenu from "../../context_menus/DialpadContextMenu";
2626import { Alignment } from "../../elements/Tooltip" ;
2727import {
2828 alwaysAboveLeftOf ,
29+ alwaysAboveRightOf ,
2930 ChevronFace ,
3031 ContextMenuTooltipButton ,
32+ useContextMenu ,
3133} from '../../../structures/ContextMenu' ;
3234import { _t } from "../../../../languageHandler" ;
35+ import DeviceContextMenu from "../../context_menus/DeviceContextMenu" ;
36+ import { MediaDeviceKindEnum } from "../../../../MediaDeviceHandler" ;
3337
3438// Height of the header duplicated from CSS because we need to subtract it from our max
3539// height to get the max height of the video
@@ -39,15 +43,22 @@ const TOOLTIP_Y_OFFSET = -24;
3943
4044const CONTROLS_HIDE_DELAY = 2000 ;
4145
42- interface IButtonProps {
46+ interface IButtonProps extends Omit < React . ComponentProps < typeof AccessibleTooltipButton > , "title" > {
4347 state : boolean ;
4448 className : string ;
45- onLabel : string ;
46- offLabel : string ;
47- onClick : ( ) => void ;
49+ onLabel ? : string ;
50+ offLabel ? : string ;
51+ onClick : ( event : React . MouseEvent ) => void ;
4852}
4953
50- const CallViewToggleButton : React . FC < IButtonProps > = ( { state : isOn , className, onLabel, offLabel, onClick } ) => {
54+ const CallViewToggleButton : React . FC < IButtonProps > = ( {
55+ children,
56+ state : isOn ,
57+ className,
58+ onLabel,
59+ offLabel,
60+ ...props
61+ } ) => {
5162 const classes = classNames ( "mx_CallViewButtons_button" , className , {
5263 mx_CallViewButtons_button_on : isOn ,
5364 mx_CallViewButtons_button_off : ! isOn ,
@@ -56,11 +67,48 @@ const CallViewToggleButton: React.FC<IButtonProps> = ({ state: isOn, className,
5667 return (
5768 < AccessibleTooltipButton
5869 className = { classes }
59- onClick = { onClick }
6070 title = { isOn ? onLabel : offLabel }
6171 alignment = { Alignment . Top }
6272 yOffset = { TOOLTIP_Y_OFFSET }
63- />
73+ { ...props }
74+ >
75+ { children }
76+ </ AccessibleTooltipButton >
77+ ) ;
78+ } ;
79+
80+ interface IDropdownButtonProps extends IButtonProps {
81+ deviceKinds : MediaDeviceKindEnum [ ] ;
82+ }
83+
84+ const CallViewDropdownButton : React . FC < IDropdownButtonProps > = ( { state, deviceKinds, ...props } ) => {
85+ const [ menuDisplayed , buttonRef , openMenu , closeMenu ] = useContextMenu ( ) ;
86+ const [ hoveringDropdown , setHoveringDropdown ] = useState ( false ) ;
87+
88+ const classes = classNames ( "mx_CallViewButtons_button" , "mx_CallViewButtons_dropdownButton" , {
89+ mx_CallViewButtons_dropdownButton_collapsed : ! menuDisplayed ,
90+ } ) ;
91+
92+ const onClick = ( event : React . MouseEvent ) : void => {
93+ event . stopPropagation ( ) ;
94+ openMenu ( ) ;
95+ } ;
96+
97+ return (
98+ < CallViewToggleButton inputRef = { buttonRef } forceHide = { menuDisplayed || hoveringDropdown } state = { state } { ...props } >
99+ < CallViewToggleButton
100+ className = { classes }
101+ onClick = { onClick }
102+ onHover = { ( hovering ) => setHoveringDropdown ( hovering ) }
103+ state = { state }
104+ />
105+ { menuDisplayed && < DeviceContextMenu
106+ { ...alwaysAboveRightOf ( buttonRef . current ?. getBoundingClientRect ( ) ) }
107+
108+ onFinished = { closeMenu }
109+ deviceKinds = { deviceKinds }
110+ /> }
111+ </ CallViewToggleButton >
64112 ) ;
65113} ;
66114
@@ -221,19 +269,21 @@ export default class CallViewButtons extends React.Component<IProps, IState> {
221269 alignment = { Alignment . Top }
222270 yOffset = { TOOLTIP_Y_OFFSET }
223271 /> }
224- < CallViewToggleButton
272+ < CallViewDropdownButton
225273 state = { ! this . props . buttonsState . micMuted }
226274 className = "mx_CallViewButtons_button_mic"
227275 onLabel = { _t ( "Mute the microphone" ) }
228276 offLabel = { _t ( "Unmute the microphone" ) }
229277 onClick = { this . props . handlers . onMicMuteClick }
278+ deviceKinds = { [ MediaDeviceKindEnum . AudioInput , MediaDeviceKindEnum . AudioOutput ] }
230279 />
231- { this . props . buttonsVisibility . vidMute && < CallViewToggleButton
280+ { this . props . buttonsVisibility . vidMute && < CallViewDropdownButton
232281 state = { ! this . props . buttonsState . vidMuted }
233282 className = "mx_CallViewButtons_button_vid"
234283 onLabel = { _t ( "Stop the camera" ) }
235284 offLabel = { _t ( "Start the camera" ) }
236285 onClick = { this . props . handlers . onVidMuteClick }
286+ deviceKinds = { [ MediaDeviceKindEnum . VideoInput ] }
237287 /> }
238288 { this . props . buttonsVisibility . screensharing && < CallViewToggleButton
239289 state = { this . props . buttonsState . screensharing }
0 commit comments