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:
parent
ad98ff3d98
commit
1c6577f4db
@ -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()
|
||||
}
|
||||
|
@ -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']);
|
||||
|
@ -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,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user