feat(rn) add mute everyone / (else) capabilities

This commit is contained in:
tmoldovan8x8 2020-11-10 16:49:38 +02:00 committed by GitHub
parent 721848da3f
commit 71fb5aef6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 274 additions and 147 deletions

View File

@ -0,0 +1,102 @@
// @flow
import React from 'react';
import { Dialog } from '../../base/dialog';
import { getLocalParticipant, getParticipantDisplayName } from '../../base/participants';
import { muteAllParticipants } from '../actions';
import AbstractMuteRemoteParticipantDialog, {
type Props as AbstractProps
} from './AbstractMuteRemoteParticipantDialog';
/**
* The type of the React {@code Component} props of
* {@link AbstractMuteEveryoneDialog}.
*/
export type Props = AbstractProps & {
content: string,
exclude: Array<string>,
title: string
};
/**
*
* An abstract Component with the contents for a dialog that asks for confirmation
* from the user before muting all remote participants.
*
* @extends AbstractMuteRemoteParticipantDialog
*/
export default class AbstractMuteEveryoneDialog<P: Props> extends AbstractMuteRemoteParticipantDialog<P> {
static defaultProps = {
exclude: [],
muteLocal: false
};
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { content, title } = this.props;
return (
<Dialog
okKey = 'dialog.muteParticipantButton'
onSubmit = { this._onSubmit }
titleString = { title }
width = 'small'>
<div>
{ content }
</div>
</Dialog>
);
}
_onSubmit: () => boolean;
/**
* Callback to be invoked when the value of this dialog is submitted.
*
* @returns {boolean}
*/
_onSubmit() {
const {
dispatch,
exclude
} = this.props;
dispatch(muteAllParticipants(exclude));
return true;
}
}
/**
* Maps (parts of) the Redux state to the associated {@code AbstractMuteEveryoneDialog}'s props.
*
* @param {Object} state - The redux state.
* @param {Object} ownProps - The properties explicitly passed to the component.
* @returns {Props}
*/
export function abstractMapStateToProps(state: Object, ownProps: Props) {
const { exclude, t } = ownProps;
const whom = exclude
// eslint-disable-next-line no-confusing-arrow
.map(id => id === getLocalParticipant(state).id
? t('dialog.muteEveryoneSelf')
: getParticipantDisplayName(state, id))
.join(', ');
return whom.length ? {
content: t('dialog.muteEveryoneElseDialog'),
title: t('dialog.muteEveryoneElseTitle', { whom })
} : {
content: t('dialog.muteEveryoneDialog'),
title: t('dialog.muteEveryoneTitle')
};
}

View File

@ -0,0 +1,48 @@
// @flow
import { createToolbarEvent, sendAnalytics } from '../../analytics';
import { openDialog } from '../../base/dialog';
import { IconMuteEveryone } from '../../base/icons';
import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { MuteEveryoneDialog } from '.';
export type Props = AbstractButtonProps & {
/**
* The redux {@code dispatch} function.
*/
dispatch: Function,
/**
* The ID of the participant object that this button is supposed to keep unmuted.
*/
participantID: string,
/**
* The function to be used to translate i18n labels.
*/
t: Function
};
/**
* An abstract remote video menu button which mutes all the other participants.
*/
export default class AbstractMuteEveryoneElseButton extends AbstractButton<Props, *> {
accessibilityLabel = 'toolbar.accessibilityLabel.muteEveryoneElse';
icon = IconMuteEveryone;
label = 'videothumbnail.domuteOthers';
/**
* Handles clicking / pressing the button, and opens a confirmation dialog.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, participantID } = this.props;
sendAnalytics(createToolbarEvent('mute.everyoneelse.pressed'));
dispatch(openDialog(MuteEveryoneDialog, { exclude: [ participantID ] }));
}
}

View File

@ -0,0 +1,67 @@
// @flow
import React from 'react';
import { Text } from 'react-native';
import { ColorSchemeRegistry } from '../../../base/color-scheme';
import { ConfirmDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux';
import { StyleType } from '../../../base/styles';
import AbstractMuteEveryoneDialog, {
abstractMapStateToProps,
type Props as AbstractProps } from '../AbstractMuteEveryoneDialog';
type Props = AbstractProps & {
/**
* The color-schemed stylesheet of the base/dialog feature.
*/
_dialogStyles: StyleType
}
/**
* A React Component with the contents for a dialog that asks for confirmation
* from the user before muting all remote participants.
*
* @extends AbstractMuteEveryoneDialog
*/
class MuteEveryoneDialog extends AbstractMuteEveryoneDialog<Props> {
/**
* Implements {@code Component#render}.
*
* @inheritdoc
*/
render() {
return (
<ConfirmDialog
okKey = 'dialog.muteParticipantButton'
onSubmit = { this._onSubmit } >
<Text style = { this.props._dialogStyles.text }>
{ `${this.props.title} \n\n ${this.props.content}` }
</Text>
</ConfirmDialog>
);
}
_onSubmit: () => boolean;
}
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {{
* _dialogStyles: StyleType
* }}
*/
function _mapStateToProps(state: Object, ownProps: Props) {
return {
...abstractMapStateToProps(state, ownProps),
_dialogStyles: ColorSchemeRegistry.get(state, 'Dialog')
};
}
export default translate(connect(_mapStateToProps)(MuteEveryoneDialog));

View File

@ -0,0 +1,20 @@
// @flow
import { translate } from '../../../base/i18n';
import { isLocalParticipantModerator } from '../../../base/participants';
import { connect } from '../../../base/redux';
import AbstractMuteEveryoneElseButton from '../AbstractMuteEveryoneElseButton';
/**
* Maps part of the Redux state to the props of this component.
*
* @param {Object} state - The Redux state.
* @returns {Props}
*/
function _mapStateToProps(state) {
return {
visible: isLocalParticipantModerator(state)
};
}
export default translate(connect(_mapStateToProps)(AbstractMuteEveryoneElseButton));

View File

@ -16,6 +16,7 @@ import { hideRemoteVideoMenu } from '../../actions';
import GrantModeratorButton from './GrantModeratorButton'; import GrantModeratorButton from './GrantModeratorButton';
import KickButton from './KickButton'; import KickButton from './KickButton';
import MuteButton from './MuteButton'; import MuteButton from './MuteButton';
import MuteEveryoneElseButton from './MuteEveryoneElseButton';
import PinButton from './PinButton'; import PinButton from './PinButton';
import styles from './styles'; import styles from './styles';
@ -104,6 +105,7 @@ class RemoteVideoMenu extends PureComponent<Props> {
<GrantModeratorButton { ...buttonProps } /> <GrantModeratorButton { ...buttonProps } />
<PinButton { ...buttonProps } /> <PinButton { ...buttonProps } />
<PrivateMessageButton { ...buttonProps } /> <PrivateMessageButton { ...buttonProps } />
<MuteEveryoneElseButton { ...buttonProps } />
</BottomSheet> </BottomSheet>
); );
} }

View File

@ -1,12 +1,7 @@
// @flow // @flow
export { export { default as GrantModeratorDialog } from './GrantModeratorDialog';
default as GrantModeratorDialog export { default as KickRemoteParticipantDialog } from './KickRemoteParticipantDialog';
} from './GrantModeratorDialog'; export { default as MuteEveryoneDialog } from './MuteEveryoneDialog';
export { export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog';
default as KickRemoteParticipantDialog
} from './KickRemoteParticipantDialog';
export {
default as MuteRemoteParticipantDialog
} from './MuteRemoteParticipantDialog';
export { default as RemoteVideoMenu } from './RemoteVideoMenu'; export { default as RemoteVideoMenu } from './RemoteVideoMenu';

View File

@ -5,53 +5,15 @@ import React from 'react';
import { Dialog } from '../../../base/dialog'; import { Dialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { muteAllParticipants } from '../../actions'; import AbstractMuteEveryoneDialog, { abstractMapStateToProps, type Props } from '../AbstractMuteEveryoneDialog';
import AbstractMuteRemoteParticipantDialog, {
type Props as AbstractProps
} from '../AbstractMuteRemoteParticipantDialog';
declare var APP: Object;
/**
* The type of the React {@code Component} props of
* {@link MuteEveryoneDialog}.
*/
type Props = AbstractProps & {
/**
* The IDs of the remote participants to exclude from being muted.
*/
exclude: Array<string>
};
/**
* Translations needed for dialog rendering.
*/
type Translations = {
/**
* Content text.
*/
content: string,
/**
* Title text.
*/
title: string
}
/** /**
* A React Component with the contents for a dialog that asks for confirmation * A React Component with the contents for a dialog that asks for confirmation
* from the user before muting a remote participant. * from the user before muting all remote participants.
* *
* @extends Component * @extends AbstractMuteEveryoneDialog
*/ */
class MuteEveryoneDialog extends AbstractMuteRemoteParticipantDialog<Props> { class MuteEveryoneDialog extends AbstractMuteEveryoneDialog<Props> {
static defaultProps = {
exclude: [],
muteLocal: false
};
/** /**
* Implements React's {@link Component#render()}. * Implements React's {@link Component#render()}.
* *
@ -59,64 +21,20 @@ class MuteEveryoneDialog extends AbstractMuteRemoteParticipantDialog<Props> {
* @returns {ReactElement} * @returns {ReactElement}
*/ */
render() { render() {
const { content, title } = this._getTranslations();
return ( return (
<Dialog <Dialog
okKey = 'dialog.muteParticipantButton' okKey = 'dialog.muteParticipantButton'
onSubmit = { this._onSubmit } onSubmit = { this._onSubmit }
titleString = { title } titleString = { this.props.title }
width = 'small'> width = 'small'>
<div> <div>
{ content } { this.props.content }
</div> </div>
</Dialog> </Dialog>
); );
} }
_onSubmit: () => boolean; _onSubmit: () => boolean;
/**
* Callback to be invoked when the value of this dialog is submitted.
*
* @returns {boolean}
*/
_onSubmit() {
const {
dispatch,
exclude
} = this.props;
dispatch(muteAllParticipants(exclude));
return true;
}
/**
* Method to get translations depending on whether we have an exclusive
* mute or not.
*
* @returns {Translations}
* @private
*/
_getTranslations(): Translations {
const { exclude, t } = this.props;
const { conference } = APP;
const whom = exclude
// eslint-disable-next-line no-confusing-arrow
.map(id => conference.isLocalId(id)
? t('dialog.muteEveryoneSelf')
: conference.getParticipantDisplayName(id))
.join(', ');
return whom.length ? {
content: t('dialog.muteEveryoneElseDialog'),
title: t('dialog.muteEveryoneElseTitle', { whom })
} : {
content: t('dialog.muteEveryoneDialog'),
title: t('dialog.muteEveryoneTitle')
};
}
} }
export default translate(connect()(MuteEveryoneDialog)); export default translate(connect(abstractMapStateToProps)(MuteEveryoneDialog));

View File

@ -2,17 +2,13 @@
import React from 'react'; import React from 'react';
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
import { openDialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { IconMuteEveryoneElse } from '../../../base/icons'; import { IconMuteEveryoneElse } from '../../../base/icons';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import AbstractMuteButton, { import AbstractMuteEveryoneElseButton, {
_mapStateToProps,
type Props type Props
} from '../AbstractMuteButton'; } from '../AbstractMuteEveryoneElseButton';
import MuteEveryoneDialog from './MuteEveryoneDialog';
import RemoteVideoMenuButton from './RemoteVideoMenuButton'; import RemoteVideoMenuButton from './RemoteVideoMenuButton';
/** /**
@ -20,9 +16,9 @@ import RemoteVideoMenuButton from './RemoteVideoMenuButton';
* every participant in the conference except the one with the given * every participant in the conference except the one with the given
* participantID * participantID
*/ */
class MuteEveryoneElseButton extends AbstractMuteButton { class MuteEveryoneElseButton extends AbstractMuteEveryoneElseButton {
/** /**
* Instantiates a new {@code MuteEveryoneElseButton}. * Instantiates a new {@code Component}.
* *
* @inheritdoc * @inheritdoc
*/ */
@ -53,19 +49,6 @@ class MuteEveryoneElseButton extends AbstractMuteButton {
} }
_handleClick: () => void; _handleClick: () => void;
/**
* Handles clicking / pressing the button, and opens a confirmation dialog.
*
* @private
* @returns {void}
*/
_handleClick() {
const { dispatch, participantID } = this.props;
sendAnalytics(createToolbarEvent('mute.everyoneelse.pressed'));
dispatch(openDialog(MuteEveryoneDialog, { exclude: [ participantID ] }));
}
} }
export default translate(connect(_mapStateToProps)(MuteEveryoneElseButton)); export default translate(connect()(MuteEveryoneElseButton));

View File

@ -9,10 +9,11 @@ import { Popover } from '../../../base/popover';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { isRemoteTrackMuted } from '../../../base/tracks'; import { isRemoteTrackMuted } from '../../../base/tracks';
import MuteEveryoneElseButton from './MuteEveryoneElseButton';
import { import {
GrantModeratorButton, GrantModeratorButton,
MuteButton, MuteButton,
MuteEveryoneElseButton,
KickButton, KickButton,
PrivateMessageMenuButton, PrivateMessageMenuButton,
RemoteControlButton, RemoteControlButton,

View File

@ -1,26 +1,15 @@
// @flow // @flow
export { default as GrantModeratorButton } from './GrantModeratorButton'; export { default as GrantModeratorButton } from './GrantModeratorButton';
export { export { default as GrantModeratorDialog } from './GrantModeratorDialog';
default as GrantModeratorDialog
} from './GrantModeratorDialog';
export { default as KickButton } from './KickButton'; export { default as KickButton } from './KickButton';
export { export { default as KickRemoteParticipantDialog } from './KickRemoteParticipantDialog';
default as KickRemoteParticipantDialog
} from './KickRemoteParticipantDialog';
export { default as MuteButton } from './MuteButton'; export { default as MuteButton } from './MuteButton';
export { default as MuteEveryoneElseButton } from './MuteEveryoneElseButton';
export { default as MuteEveryoneDialog } from './MuteEveryoneDialog'; export { default as MuteEveryoneDialog } from './MuteEveryoneDialog';
export { export { default as MuteEveryoneElseButton } from './MuteEveryoneElseButton';
default as MuteRemoteParticipantDialog export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog';
} from './MuteRemoteParticipantDialog';
export { default as PrivateMessageMenuButton } from './PrivateMessageMenuButton'; export { default as PrivateMessageMenuButton } from './PrivateMessageMenuButton';
export { export { REMOTE_CONTROL_MENU_STATES, default as RemoteControlButton } from './RemoteControlButton';
REMOTE_CONTROL_MENU_STATES,
default as RemoteControlButton
} from './RemoteControlButton';
export { default as RemoteVideoMenu } from './RemoteVideoMenu'; export { default as RemoteVideoMenu } from './RemoteVideoMenu';
export { export { default as RemoteVideoMenuTriggerButton } from './RemoteVideoMenuTriggerButton';
default as RemoteVideoMenuTriggerButton
} from './RemoteVideoMenuTriggerButton';
export { default as VolumeSlider } from './VolumeSlider'; export { default as VolumeSlider } from './VolumeSlider';

View File

@ -1,13 +1,13 @@
// @flow // @flow
import { createToolbarEvent, sendAnalytics } from '../../../analytics'; import { createToolbarEvent, sendAnalytics } from '../../analytics';
import { openDialog } from '../../../base/dialog'; import { openDialog } from '../../base/dialog';
import { translate } from '../../../base/i18n'; import { translate } from '../../base/i18n';
import { IconMuteEveryone } from '../../../base/icons'; import { IconMuteEveryone } from '../../base/icons';
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../../base/participants'; import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../base/redux';
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components'; import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
import { MuteEveryoneDialog } from '../../../remote-video-menu'; import { MuteEveryoneDialog } from '../../remote-video-menu/components';
type Props = AbstractButtonProps & { type Props = AbstractButtonProps & {

View File

@ -19,6 +19,7 @@ import { ClosedCaptionButton } from '../../../subtitles';
import { TileViewButton } from '../../../video-layout'; import { TileViewButton } from '../../../video-layout';
import { VideoShareButton } from '../../../youtube-player/components'; import { VideoShareButton } from '../../../youtube-player/components';
import HelpButton from '../HelpButton'; import HelpButton from '../HelpButton';
import MuteEveryoneButton from '../MuteEveryoneButton';
import AudioOnlyButton from './AudioOnlyButton'; import AudioOnlyButton from './AudioOnlyButton';
import MoreOptionsButton from './MoreOptionsButton'; import MoreOptionsButton from './MoreOptionsButton';
@ -143,6 +144,7 @@ class OverflowMenu extends PureComponent<Props, State> {
<RoomLockButton { ...buttonProps } /> <RoomLockButton { ...buttonProps } />
<ClosedCaptionButton { ...buttonProps } /> <ClosedCaptionButton { ...buttonProps } />
<SharedDocumentButton { ...buttonProps } /> <SharedDocumentButton { ...buttonProps } />
<MuteEveryoneButton { ...buttonProps } />
<HelpButton { ...buttonProps } /> <HelpButton { ...buttonProps } />
</Collapsible> </Collapsible>
</BottomSheet> </BottomSheet>

View File

@ -79,9 +79,9 @@ import { isToolboxVisible } from '../../functions';
import DownloadButton from '../DownloadButton'; import DownloadButton from '../DownloadButton';
import HangupButton from '../HangupButton'; import HangupButton from '../HangupButton';
import HelpButton from '../HelpButton'; import HelpButton from '../HelpButton';
import MuteEveryoneButton from '../MuteEveryoneButton';
import AudioSettingsButton from './AudioSettingsButton'; import AudioSettingsButton from './AudioSettingsButton';
import MuteEveryoneButton from './MuteEveryoneButton';
import OverflowMenuButton from './OverflowMenuButton'; import OverflowMenuButton from './OverflowMenuButton';
import OverflowMenuProfileItem from './OverflowMenuProfileItem'; import OverflowMenuProfileItem from './OverflowMenuProfileItem';
import ToolbarButton from './ToolbarButton'; import ToolbarButton from './ToolbarButton';