feat(audio): unify audio buttons

Move device selection dropdown to microphone button
Move option 'leave audio' to an option within the dropdown
Remove the audio exit button when device !== mobile
This commit is contained in:
Max Franke 2022-06-08 16:42:41 -03:00
parent ad98ff3d98
commit 1c6577f4db
3 changed files with 193 additions and 108 deletions

View File

@ -6,7 +6,6 @@ import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
import InputStreamLiveSelectorContainer from './input-stream-live-selector/container';
import MutedAlert from '/imports/ui/components/muted-alert/component';
import Styled from './styles';
import Settings from '/imports/ui/services/settings';
const intlMessages = defineMessages({
joinAudio: {
@ -46,9 +45,7 @@ const propTypes = {
class AudioControls extends PureComponent {
constructor(props) {
super(props);
this.renderLeaveButtonWithoutLiveStreamSelector = this
.renderLeaveButtonWithoutLiveStreamSelector.bind(this);
this.renderButtonsAndStreamSelector = this.renderButtonsAndStreamSelector.bind(this);
this.renderJoinLeaveButton = this.renderJoinLeaveButton.bind(this);
}
@ -78,49 +75,20 @@ class AudioControls extends PureComponent {
);
}
static renderLeaveButtonWithLiveStreamSelector(props) {
const { handleLeaveAudio } = props;
return (
<InputStreamLiveSelectorContainer {...{ handleLeaveAudio }} />
);
}
renderLeaveButtonWithoutLiveStreamSelector() {
renderButtonsAndStreamSelector(_enableDynamicDeviceSelection) {
const {
handleJoinAudio,
handleLeaveAudio,
disable,
inAudio,
listenOnly,
intl,
shortcuts,
handleLeaveAudio, handleToggleMuteMicrophone, muted, disable, talking,
} = this.props;
let joinIcon = 'no_audio';
if (inAudio) {
if (listenOnly) {
joinIcon = 'listen';
} else {
joinIcon = 'volume_level_2';
}
}
return (
<Styled.LeaveButtonWithoutLiveStreamSelector
onClick={inAudio ? handleLeaveAudio : handleJoinAudio}
disabled={disable}
data-test={inAudio ? 'leaveAudio' : 'joinAudio'}
hideLabel
aria-label={inAudio ? intl.formatMessage(intlMessages.leaveAudio)
: intl.formatMessage(intlMessages.joinAudio)}
label={inAudio ? intl.formatMessage(intlMessages.leaveAudio)
: intl.formatMessage(intlMessages.joinAudio)}
color={inAudio ? 'primary' : 'default'}
ghost={!inAudio}
icon={joinIcon}
size="lg"
circle
accessKey={inAudio ? shortcuts.leaveaudio : shortcuts.joinaudio}
<InputStreamLiveSelectorContainer {...{
handleLeaveAudio,
handleToggleMuteMicrophone,
muted,
disable,
talking,
_enableDynamicDeviceSelection,
}}
/>
);
}
@ -138,16 +106,10 @@ class AudioControls extends PureComponent {
enableDynamicAudioDeviceSelection = true;
}
const _enableDynamicDeviceSelection = enableDynamicAudioDeviceSelection
&& !isMobile;
const _enableDynamicDeviceSelection = enableDynamicAudioDeviceSelection && !isMobile;
if (inAudio) {
if (_enableDynamicDeviceSelection) {
return AudioControls.renderLeaveButtonWithLiveStreamSelector(this
.props);
}
return this.renderLeaveButtonWithoutLiveStreamSelector();
return this.renderButtonsAndStreamSelector(_enableDynamicDeviceSelection);
}
return this.renderJoinButton();
@ -155,13 +117,8 @@ class AudioControls extends PureComponent {
render() {
const {
handleToggleMuteMicrophone,
showMute,
muted,
disable,
talking,
intl,
shortcuts,
isVoiceUser,
listenOnly,
inputStream,
@ -169,30 +126,6 @@ class AudioControls extends PureComponent {
isPresenter,
} = this.props;
const label = muted ? intl.formatMessage(intlMessages.unmuteAudio)
: intl.formatMessage(intlMessages.muteAudio);
const { animations } = Settings.application;
const toggleMuteBtn = (
<Styled.MuteToggleButton
onClick={handleToggleMuteMicrophone}
disabled={disable}
hideLabel
label={label}
aria-label={label}
color={!muted ? 'primary' : 'default'}
ghost={muted}
icon={muted ? 'mute' : 'unmute'}
size="lg"
circle
accessKey={shortcuts.togglemute}
talking={talking}
animations={animations}
data-test="toggleMicrophoneButton"
/>
);
const MUTE_ALERT_CONFIG = Meteor.settings.public.app.mutedAlert;
const { enabled: muteAlertEnabled } = MUTE_ALERT_CONFIG;
@ -204,7 +137,6 @@ class AudioControls extends PureComponent {
}}
/>
) : null}
{showMute && isVoiceUser ? toggleMuteBtn : null}
{
this.renderJoinLeaveButton()
}

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react';
import logger from '/imports/startup/client/logger';
import Auth from '/imports/ui/services/auth';
import Settings from '/imports/ui/services/settings';
import { defineMessages, injectIntl } from 'react-intl';
import PropTypes from 'prop-types';
import Button from '/imports/ui/components/common/button/component';
@ -39,6 +40,14 @@ const intlMessages = defineMessages({
id: 'app.audio.speakers',
description: 'Output audio dropdown item label',
},
muteAudio: {
id: 'app.actionsBar.muteLabel',
description: 'Mute audio button label',
},
unmuteAudio: {
id: 'app.actionsBar.unmuteLabel',
description: 'Unmute audio button label',
},
});
const propTypes = {
@ -53,6 +62,15 @@ const propTypes = {
currentOutputDeviceId: PropTypes.string.isRequired,
isListenOnly: PropTypes.bool.isRequired,
isAudioConnected: PropTypes.bool.isRequired,
_enableDynamicDeviceSelection: PropTypes.bool.isRequired,
handleToggleMuteMicrophone: PropTypes.func.isRequired,
muted: PropTypes.bool.isRequired,
disable: PropTypes.bool.isRequired,
talking: PropTypes.bool,
};
const defaultProps = {
talking: false,
};
class InputStreamLiveSelector extends Component {
@ -67,6 +85,10 @@ class InputStreamLiveSelector extends Component {
super(props);
this.updateDeviceList = this.updateDeviceList.bind(this);
this.renderDeviceList = this.renderDeviceList.bind(this);
this.renderListenOnlyButton = this.renderListenOnlyButton.bind(this);
this.renderMuteToggleButton = this.renderMuteToggleButton.bind(this);
this.renderButtonsWithSelectorDevice = this.renderButtonsWithSelectorDevice.bind(this);
this.renderButtonsWithoutSelectorDevice = this.renderButtonsWithoutSelectorDevice.bind(this);
this.state = {
audioInputDevices: null,
audioOutputDevices: null,
@ -206,8 +228,7 @@ class InputStreamLiveSelector extends Component {
});
}
renderDeviceList(deviceKind, list, callback, title, currentDeviceId,
renderSeparator = true) {
renderDeviceList(deviceKind, list, callback, title, currentDeviceId) {
const {
intl,
} = this.props;
@ -216,20 +237,18 @@ class InputStreamLiveSelector extends Component {
{
key: `audioDeviceList-${deviceKind}`,
label: title,
iconRight: (deviceKind === 'audioinput') ? 'unmute' : 'volume_level_2',
disabled: true,
dividerTop: (!renderSeparator),
divider: true,
},
];
const customStyles = { fontWeight: 'bold' };
const disableDeviceStyles = { pointerEvents: 'none' };
const deviceList = (listLength > 0)
? list.map((device) => (
{
key: `${device.deviceId}-${deviceKind}`,
label: InputStreamLiveSelector.truncateDeviceName(device.label),
customStyles: (device.deviceId === currentDeviceId) ? customStyles : null,
customStyles: (device.deviceId === currentDeviceId) && Styled.SelectedLabel,
iconRight: (device.deviceId === currentDeviceId) ? 'check' : null,
onClick: () => this.onDeviceListClick(device.deviceId, deviceKind, callback),
}
@ -240,13 +259,77 @@ class InputStreamLiveSelector extends Component {
label: listLength < 0
? intl.formatMessage(intlMessages.loading)
: intl.formatMessage(intlMessages.noDeviceFound),
customStyles: disableDeviceStyles,
},
];
return listTitle.concat(deviceList);
}
render() {
renderMuteToggleButton() {
const {
intl,
shortcuts,
handleToggleMuteMicrophone,
muted,
disable,
talking,
} = this.props;
const label = muted ? intl.formatMessage(intlMessages.unmuteAudio)
: intl.formatMessage(intlMessages.muteAudio);
const { animations } = Settings.application;
return (
<Styled.MuteToggleButton
onClick={(e) => {
e.stopPropagation();
handleToggleMuteMicrophone();
}}
disabled={disable}
hideLabel
label={label}
aria-label={label}
color={!muted ? 'primary' : 'default'}
ghost={muted}
icon={muted ? 'mute' : 'unmute'}
size="lg"
circle
accessKey={shortcuts.togglemute}
talking={talking || undefined}
animations={animations}
data-test="toggleMicrophoneButton"
/>
);
}
renderListenOnlyButton() {
const {
handleLeaveAudio,
intl,
shortcuts,
isListenOnly,
} = this.props;
return (
<Button
aria-label={intl.formatMessage(intlMessages.leaveAudio)}
label={intl.formatMessage(intlMessages.leaveAudio)}
accessKey={shortcuts.leaveaudio}
data-test="leaveAudio"
hideLabel
color="primary"
icon={isListenOnly ? 'listen' : 'volume_level_2'}
size="lg"
circle
onClick={(e) => {
e.stopPropagation();
handleLeaveAudio();
}}
/>
);
}
renderButtonsWithSelectorDevice() {
const {
audioInputDevices,
audioOutputDevices,
@ -259,7 +342,6 @@ class InputStreamLiveSelector extends Component {
handleLeaveAudio,
liveChangeOutputDevice,
intl,
shortcuts,
currentInputDeviceId,
currentOutputDeviceId,
isListenOnly,
@ -283,27 +365,24 @@ class InputStreamLiveSelector extends Component {
false,
);
const dropdownListComplete = inputDeviceList.concat(outputDeviceList);
const leaveAudioOption = {
iconRight: 'logout',
label: intl.formatMessage(intlMessages.leaveAudio),
key: 'leaveAudioOption',
customStyles: Styled.DangerColor,
dividerTop: true,
onClick: () => handleLeaveAudio(),
};
const dropdownListComplete = inputDeviceList.concat(outputDeviceList).concat(leaveAudioOption);
return (
<BBBMenu
trigger={(
<>
<Button
aria-label={intl.formatMessage(intlMessages.leaveAudio)}
label={intl.formatMessage(intlMessages.leaveAudio)}
accessKey={shortcuts.leaveaudio}
data-test="leaveAudio"
hideLabel
color="primary"
icon={isListenOnly ? 'listen' : 'volume_level_2'}
size="lg"
circle
onClick={(e) => {
e.stopPropagation();
handleLeaveAudio();
}}
/>
{isListenOnly
? this.renderListenOnlyButton()
: this.renderMuteToggleButton()}
<Styled.AudioDropdown
emoji="device_list_selector"
label={intl.formatMessage(intlMessages.changeAudioDevice)}
@ -316,9 +395,30 @@ class InputStreamLiveSelector extends Component {
/>
);
}
renderButtonsWithoutSelectorDevice() {
const { isListenOnly } = this.props;
return isListenOnly
? this.renderListenOnlyButton()
: (
<>
{this.renderMuteToggleButton()}
{this.renderListenOnlyButton()}
</>
);
}
render() {
const { _enableDynamicDeviceSelection } = this.props;
return _enableDynamicDeviceSelection
? this.renderButtonsWithSelectorDevice()
: this.renderButtonsWithoutSelectorDevice();
}
}
InputStreamLiveSelector.propTypes = propTypes;
InputStreamLiveSelector.defaultProps = defaultProps;
export default withShortcutHelper(injectIntl(InputStreamLiveSelector),
['leaveAudio']);

View File

@ -1,5 +1,19 @@
import styled from 'styled-components';
import styled, { css, keyframes } from 'styled-components';
import ButtonEmoji from '/imports/ui/components/common/button/button-emoji/ButtonEmoji';
import Button from '/imports/ui/components/common/button/component';
import { colorPrimary, colorDanger, colorWhite } from '/imports/ui/stylesheets/styled-components/palette';
const pulse = keyframes`
0% {
box-shadow: 0 0 0 0 white;
}
70% {
box-shadow: 0 0 0 0.5625rem transparent;
}
100% {
box-shadow: 0 0 0 0 transparent;
}
`;
const AudioDropdown = styled(ButtonEmoji)`
span {
@ -10,6 +24,45 @@ const AudioDropdown = styled(ButtonEmoji)`
}
`;
const MuteToggleButton = styled(Button)`
${({ ghost }) => ghost && `
span {
box-shadow: none;
background-color: transparent !important;
border-color: ${colorWhite} !important;
}
`}
${({ talking }) => talking && `
border-radius: 50%;
`}
${({ talking, animations }) => talking && animations && css`
animation: ${pulse} 1s infinite ease-in;
`}
${({ talking, animations }) => talking && !animations && css`
& span {
content: '';
outline: none !important;
background-clip: padding-box;
box-shadow: 0 0 0 4px rgba(255,255,255,.5);
}
`}
`;
const DangerColor = {
color: colorDanger,
};
const SelectedLabel = {
color: colorPrimary,
};
export default {
AudioDropdown,
MuteToggleButton,
DangerColor,
SelectedLabel,
};