mirror of https://github.com/jitsi/jitsi-meet
pull/13596/head
jitsi-meet_8833
parent
470e987fad
commit
1b7a81afa5
@ -1,43 +1,42 @@ |
|||||||
import React, { useCallback } from 'react'; |
import React, { useCallback } from 'react'; |
||||||
import { WithTranslation } from 'react-i18next'; |
import { useTranslation } from 'react-i18next'; |
||||||
import { connect } from 'react-redux'; |
import { useDispatch } from 'react-redux'; |
||||||
|
|
||||||
import { IStore } from '../../../app/types'; |
|
||||||
import { translate } from '../../../base/i18n/functions'; |
|
||||||
import { IconInfoCircle } from '../../../base/icons/svg'; |
import { IconInfoCircle } from '../../../base/icons/svg'; |
||||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
||||||
|
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants'; |
||||||
import { renderConnectionStatus } from '../../actions.web'; |
import { renderConnectionStatus } from '../../actions.web'; |
||||||
|
import { IButtonProps } from '../../types'; |
||||||
interface IProps extends WithTranslation { |
|
||||||
|
/** |
||||||
/** |
* Implements a React {@link Component} which displays a button that shows |
||||||
* The Redux dispatch function. |
* the connection status for the given participant. |
||||||
*/ |
* |
||||||
dispatch: IStore['dispatch']; |
* @returns {JSX.Element} |
||||||
|
*/ |
||||||
/** |
|
||||||
* The ID of the participant for which to show connection stats. |
|
||||||
*/ |
|
||||||
participantId: string; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
const ConnectionStatusButton = ({ |
const ConnectionStatusButton = ({ |
||||||
dispatch, |
notifyClick, |
||||||
t |
notifyMode |
||||||
}: IProps) => { |
}: IButtonProps): JSX.Element => { |
||||||
const onClick = useCallback(e => { |
const { t } = useTranslation(); |
||||||
|
const dispatch = useDispatch(); |
||||||
|
|
||||||
|
const handleClick = useCallback(e => { |
||||||
e.stopPropagation(); |
e.stopPropagation(); |
||||||
|
notifyClick?.(); |
||||||
|
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) { |
||||||
|
return; |
||||||
|
} |
||||||
dispatch(renderConnectionStatus(true)); |
dispatch(renderConnectionStatus(true)); |
||||||
}, [ dispatch ]); |
}, [ dispatch, notifyClick, notifyMode ]); |
||||||
|
|
||||||
return ( |
return ( |
||||||
<ContextMenuItem |
<ContextMenuItem |
||||||
accessibilityLabel = { t('videothumbnail.connectionInfo') } |
accessibilityLabel = { t('videothumbnail.connectionInfo') } |
||||||
icon = { IconInfoCircle } |
icon = { IconInfoCircle } |
||||||
onClick = { onClick } |
onClick = { handleClick } |
||||||
text = { t('videothumbnail.connectionInfo') } /> |
text = { t('videothumbnail.connectionInfo') } /> |
||||||
); |
); |
||||||
}; |
}; |
||||||
|
|
||||||
export default translate(connect()(ConnectionStatusButton)); |
export default ConnectionStatusButton; |
||||||
|
@ -1,52 +1,56 @@ |
|||||||
import React from 'react'; |
import React, { useCallback, useMemo } from 'react'; |
||||||
import { connect } from 'react-redux'; |
import { useTranslation } from 'react-i18next'; |
||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
|
||||||
import { translate } from '../../../base/i18n/functions'; |
import { IReduxState } from '../../../app/types'; |
||||||
|
import { openDialog } from '../../../base/dialog/actions'; |
||||||
import { IconModerator } from '../../../base/icons/svg'; |
import { IconModerator } from '../../../base/icons/svg'; |
||||||
|
import { PARTICIPANT_ROLE } from '../../../base/participants/constants'; |
||||||
|
import { getLocalParticipant, getParticipantById, isParticipantModerator } from '../../../base/participants/functions'; |
||||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
||||||
import AbstractGrantModeratorButton, { IProps, _mapStateToProps } from '../AbstractGrantModeratorButton'; |
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants'; |
||||||
|
import { IButtonProps } from '../../types'; |
||||||
|
|
||||||
|
import GrantModeratorDialog from './GrantModeratorDialog'; |
||||||
|
|
||||||
/** |
/** |
||||||
* Implements a React {@link Component} which displays a button for granting |
* Implements a React {@link Component} which displays a button for granting |
||||||
* moderator to a participant. |
* moderator to a participant. |
||||||
|
* |
||||||
|
* @returns {JSX.Element|null} |
||||||
*/ |
*/ |
||||||
class GrantModeratorButton extends AbstractGrantModeratorButton { |
const GrantModeratorButton = ({ |
||||||
/** |
notifyClick, |
||||||
* Instantiates a new {@code GrantModeratorButton}. |
notifyMode, |
||||||
* |
participantID |
||||||
* @inheritdoc |
}: IButtonProps): JSX.Element | null => { |
||||||
*/ |
const { t } = useTranslation(); |
||||||
constructor(props: IProps) { |
const dispatch = useDispatch(); |
||||||
super(props); |
const localParticipant = useSelector(getLocalParticipant); |
||||||
|
const targetParticipant = useSelector((state: IReduxState) => getParticipantById(state, participantID)); |
||||||
this._handleClick = this._handleClick.bind(this); |
const visible = useMemo(() => Boolean(localParticipant?.role === PARTICIPANT_ROLE.MODERATOR) |
||||||
} |
&& !isParticipantModerator(targetParticipant), [ isParticipantModerator, localParticipant, targetParticipant ]); |
||||||
|
|
||||||
/** |
const handleClick = useCallback(() => { |
||||||
* Implements React's {@link Component#render()}. |
notifyClick?.(); |
||||||
* |
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) { |
||||||
* @inheritdoc |
return; |
||||||
* @returns {ReactElement} |
|
||||||
*/ |
|
||||||
render() { |
|
||||||
const { t, visible } = this.props; |
|
||||||
|
|
||||||
if (!visible) { |
|
||||||
return null; |
|
||||||
} |
} |
||||||
|
dispatch(openDialog(GrantModeratorDialog, { participantID })); |
||||||
|
}, [ dispatch, notifyClick, notifyMode, participantID ]); |
||||||
|
|
||||||
return ( |
if (!visible) { |
||||||
<ContextMenuItem |
return null; |
||||||
accessibilityLabel = { t('toolbar.accessibilityLabel.grantModerator') } |
|
||||||
className = 'grantmoderatorlink' |
|
||||||
icon = { IconModerator } |
|
||||||
// eslint-disable-next-line react/jsx-handler-names
|
|
||||||
onClick = { this._handleClick } |
|
||||||
text = { t('videothumbnail.grantModerator') } /> |
|
||||||
); |
|
||||||
} |
} |
||||||
|
|
||||||
_handleClick: () => void; |
return ( |
||||||
} |
<ContextMenuItem |
||||||
|
accessibilityLabel = { t('toolbar.accessibilityLabel.grantModerator') } |
||||||
export default translate(connect(_mapStateToProps)(GrantModeratorButton)); |
className = 'grantmoderatorlink' |
||||||
|
icon = { IconModerator } |
||||||
|
onClick = { handleClick } |
||||||
|
text = { t('videothumbnail.grantModerator') } /> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default GrantModeratorButton; |
||||||
|
@ -1,54 +1,46 @@ |
|||||||
import React from 'react'; |
import React, { useCallback } from 'react'; |
||||||
import { connect } from 'react-redux'; |
import { useTranslation } from 'react-i18next'; |
||||||
|
import { useDispatch } from 'react-redux'; |
||||||
|
|
||||||
import { translate } from '../../../base/i18n/functions'; |
import { openDialog } from '../../../base/dialog/actions'; |
||||||
import { IconUserDeleted } from '../../../base/icons/svg'; |
import { IconUserDeleted } from '../../../base/icons/svg'; |
||||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
||||||
import AbstractKickButton, { IProps } from '../AbstractKickButton'; |
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants'; |
||||||
|
import { IButtonProps } from '../../types'; |
||||||
|
|
||||||
|
import KickRemoteParticipantDialog from './KickRemoteParticipantDialog'; |
||||||
|
|
||||||
/** |
/** |
||||||
* Implements a React {@link Component} which displays a button for kicking out |
* Implements a React {@link Component} which displays a button for kicking out |
||||||
* a participant from the conference. |
* a participant from the conference. |
||||||
* |
* |
||||||
* NOTE: At the time of writing this is a button that doesn't use the |
* @returns {JSX.Element} |
||||||
* {@code AbstractButton} base component, but is inherited from the same |
|
||||||
* super class ({@code AbstractKickButton} that extends {@code AbstractButton}) |
|
||||||
* for the sake of code sharing between web and mobile. Once web uses the |
|
||||||
* {@code AbstractButton} base component, this can be fully removed. |
|
||||||
*/ |
*/ |
||||||
class KickButton extends AbstractKickButton { |
const KickButton = ({ |
||||||
/** |
notifyClick, |
||||||
* Instantiates a new {@code Component}. |
notifyMode, |
||||||
* |
participantID |
||||||
* @inheritdoc |
}: IButtonProps): JSX.Element => { |
||||||
*/ |
const { t } = useTranslation(); |
||||||
constructor(props: IProps) { |
const dispatch = useDispatch(); |
||||||
super(props); |
|
||||||
|
|
||||||
this._handleClick = this._handleClick.bind(this); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
const handleClick = useCallback(() => { |
||||||
* Implements React's {@link Component#render()}. |
notifyClick?.(); |
||||||
* |
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) { |
||||||
* @inheritdoc |
return; |
||||||
* @returns {ReactElement} |
} |
||||||
*/ |
dispatch(openDialog(KickRemoteParticipantDialog, { participantID })); |
||||||
render() { |
}, [ dispatch, notifyClick, notifyMode, participantID ]); |
||||||
const { participantID, t } = this.props; |
|
||||||
|
|
||||||
return ( |
return ( |
||||||
<ContextMenuItem |
<ContextMenuItem |
||||||
accessibilityLabel = { t('videothumbnail.kick') } |
accessibilityLabel = { t('videothumbnail.kick') } |
||||||
className = 'kicklink' |
className = 'kicklink' |
||||||
icon = { IconUserDeleted } |
icon = { IconUserDeleted } |
||||||
id = { `ejectlink_${participantID}` } |
id = { `ejectlink_${participantID}` } |
||||||
// eslint-disable-next-line react/jsx-handler-names
|
onClick = { handleClick } |
||||||
onClick = { this._handleClick } |
text = { t('videothumbnail.kick') } /> |
||||||
text = { t('videothumbnail.kick') } /> |
); |
||||||
); |
}; |
||||||
} |
|
||||||
|
|
||||||
_handleClick: () => void; |
export default KickButton; |
||||||
} |
|
||||||
export default translate(connect()(KickButton)); |
|
||||||
|
@ -1,59 +1,65 @@ |
|||||||
import React from 'react'; |
import React, { useCallback, useMemo } from 'react'; |
||||||
import { connect } from 'react-redux'; |
import { useTranslation } from 'react-i18next'; |
||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
|
||||||
import { translate } from '../../../base/i18n/functions'; |
import { createRemoteVideoMenuButtonEvent } from '../../../analytics/AnalyticsEvents'; |
||||||
|
import { sendAnalytics } from '../../../analytics/functions'; |
||||||
|
import { IReduxState } from '../../../app/types'; |
||||||
|
import { rejectParticipantAudio } from '../../../av-moderation/actions'; |
||||||
import { IconMicSlash } from '../../../base/icons/svg'; |
import { IconMicSlash } from '../../../base/icons/svg'; |
||||||
|
import { MEDIA_TYPE } from '../../../base/media/constants'; |
||||||
|
import { isRemoteTrackMuted } from '../../../base/tracks/functions.any'; |
||||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
||||||
import AbstractMuteButton, { IProps, _mapStateToProps } from '../AbstractMuteButton'; |
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants'; |
||||||
|
import { muteRemote } from '../../actions.any'; |
||||||
|
import { IButtonProps } from '../../types'; |
||||||
|
|
||||||
/** |
/** |
||||||
* Implements a React {@link Component} which displays a button for audio muting |
* Implements a React {@link Component} which displays a button for audio muting |
||||||
* a participant in the conference. |
* a participant in the conference. |
||||||
* |
* |
||||||
* NOTE: At the time of writing this is a button that doesn't use the |
* @returns {JSX.Element|null} |
||||||
* {@code AbstractButton} base component, but is inherited from the same |
|
||||||
* super class ({@code AbstractMuteButton} that extends {@code AbstractButton}) |
|
||||||
* for the sake of code sharing between web and mobile. Once web uses the |
|
||||||
* {@code AbstractButton} base component, this can be fully removed. |
|
||||||
*/ |
*/ |
||||||
class MuteButton extends AbstractMuteButton { |
const MuteButton = ({ |
||||||
/** |
notifyClick, |
||||||
* Instantiates a new {@code Component}. |
notifyMode, |
||||||
* |
participantID |
||||||
* @inheritdoc |
}: IButtonProps): JSX.Element | null => { |
||||||
*/ |
const { t } = useTranslation(); |
||||||
constructor(props: IProps) { |
const dispatch = useDispatch(); |
||||||
super(props); |
const tracks = useSelector((state: IReduxState) => state['features/base/tracks']); |
||||||
|
const audioTrackMuted = useMemo( |
||||||
this._handleClick = this._handleClick.bind(this); |
() => isRemoteTrackMuted(tracks, MEDIA_TYPE.AUDIO, participantID), |
||||||
} |
[ isRemoteTrackMuted, participantID, tracks ] |
||||||
|
); |
||||||
|
|
||||||
/** |
const handleClick = useCallback(() => { |
||||||
* Implements React's {@link Component#render()}. |
notifyClick?.(); |
||||||
* |
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) { |
||||||
* @inheritdoc |
return; |
||||||
* @returns {ReactElement} |
|
||||||
*/ |
|
||||||
render() { |
|
||||||
const { _audioTrackMuted, t } = this.props; |
|
||||||
|
|
||||||
if (_audioTrackMuted) { |
|
||||||
return null; |
|
||||||
} |
} |
||||||
|
sendAnalytics(createRemoteVideoMenuButtonEvent( |
||||||
|
'mute', |
||||||
|
{ |
||||||
|
'participant_id': participantID |
||||||
|
})); |
||||||
|
|
||||||
|
dispatch(muteRemote(participantID, MEDIA_TYPE.AUDIO)); |
||||||
|
dispatch(rejectParticipantAudio(participantID)); |
||||||
|
}, [ dispatch, notifyClick, notifyMode, participantID, sendAnalytics ]); |
||||||
|
|
||||||
return ( |
if (audioTrackMuted) { |
||||||
<ContextMenuItem |
return null; |
||||||
accessibilityLabel = { t('dialog.muteParticipantButton') } |
|
||||||
className = 'mutelink' |
|
||||||
icon = { IconMicSlash } |
|
||||||
// eslint-disable-next-line react/jsx-handler-names
|
|
||||||
onClick = { this._handleClick } |
|
||||||
text = { t('dialog.muteParticipantButton') } /> |
|
||||||
); |
|
||||||
} |
} |
||||||
|
|
||||||
_handleClick: () => void; |
return ( |
||||||
} |
<ContextMenuItem |
||||||
|
accessibilityLabel = { t('dialog.muteParticipantButton') } |
||||||
|
className = 'mutelink' |
||||||
|
icon = { IconMicSlash } |
||||||
|
onClick = { handleClick } |
||||||
|
text = { t('dialog.muteParticipantButton') } /> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
export default translate(connect(_mapStateToProps)(MuteButton)); |
export default MuteButton; |
||||||
|
@ -1,48 +1,48 @@ |
|||||||
import React from 'react'; |
import React, { useCallback } from 'react'; |
||||||
import { connect } from 'react-redux'; |
import { useTranslation } from 'react-i18next'; |
||||||
|
import { useDispatch } from 'react-redux'; |
||||||
|
|
||||||
import { translate } from '../../../base/i18n/functions'; |
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents'; |
||||||
|
import { sendAnalytics } from '../../../analytics/functions'; |
||||||
|
import { openDialog } from '../../../base/dialog/actions'; |
||||||
import { IconMicSlash } from '../../../base/icons/svg'; |
import { IconMicSlash } from '../../../base/icons/svg'; |
||||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
||||||
import AbstractMuteEveryoneElseButton, { IProps } from '../AbstractMuteEveryoneElseButton'; |
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants'; |
||||||
|
import { IButtonProps } from '../../types'; |
||||||
|
|
||||||
|
import MuteEveryoneDialog from './MuteEveryoneDialog'; |
||||||
|
|
||||||
/** |
/** |
||||||
* Implements a React {@link Component} which displays a button for audio muting |
* Implements a React {@link Component} which displays a button for audio muting |
||||||
* every participant in the conference except the one with the given |
* every participant in the conference except the one with the given |
||||||
* participantID. |
* participantID. |
||||||
|
* |
||||||
|
* @returns {JSX.Element} |
||||||
*/ |
*/ |
||||||
class MuteEveryoneElseButton extends AbstractMuteEveryoneElseButton { |
const MuteEveryoneElseButton = ({ |
||||||
/** |
notifyClick, |
||||||
* Instantiates a new {@code Component}. |
notifyMode, |
||||||
* |
participantID |
||||||
* @inheritdoc |
}: IButtonProps): JSX.Element => { |
||||||
*/ |
const { t } = useTranslation(); |
||||||
constructor(props: IProps) { |
const dispatch = useDispatch(); |
||||||
super(props); |
|
||||||
|
|
||||||
this._handleClick = this._handleClick.bind(this); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Implements React's {@link Component#render()}. |
|
||||||
* |
|
||||||
* @inheritdoc |
|
||||||
* @returns {ReactElement} |
|
||||||
*/ |
|
||||||
render() { |
|
||||||
const { t } = this.props; |
|
||||||
|
|
||||||
return ( |
const handleClick = useCallback(() => { |
||||||
<ContextMenuItem |
notifyClick?.(); |
||||||
accessibilityLabel = { t('toolbar.accessibilityLabel.muteEveryoneElse') } |
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) { |
||||||
icon = { IconMicSlash } |
return; |
||||||
// eslint-disable-next-line react/jsx-handler-names
|
} |
||||||
onClick = { this._handleClick } |
sendAnalytics(createToolbarEvent('mute.everyoneelse.pressed')); |
||||||
text = { t('videothumbnail.domuteOthers') } /> |
dispatch(openDialog(MuteEveryoneDialog, { exclude: [ participantID ] })); |
||||||
); |
}, [ dispatch, notifyMode, notifyClick, participantID, sendAnalytics ]); |
||||||
} |
|
||||||
|
|
||||||
_handleClick: () => void; |
return ( |
||||||
} |
<ContextMenuItem |
||||||
|
accessibilityLabel = { t('toolbar.accessibilityLabel.muteEveryoneElse') } |
||||||
|
icon = { IconMicSlash } |
||||||
|
onClick = { handleClick } |
||||||
|
text = { t('videothumbnail.domuteOthers') } /> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
export default translate(connect()(MuteEveryoneElseButton)); |
export default MuteEveryoneElseButton; |
||||||
|
@ -1,48 +1,48 @@ |
|||||||
import React from 'react'; |
import React, { useCallback } from 'react'; |
||||||
import { connect } from 'react-redux'; |
import { useTranslation } from 'react-i18next'; |
||||||
|
import { useDispatch } from 'react-redux'; |
||||||
|
|
||||||
import { translate } from '../../../base/i18n/functions'; |
import { createToolbarEvent } from '../../../analytics/AnalyticsEvents'; |
||||||
|
import { sendAnalytics } from '../../../analytics/functions'; |
||||||
|
import { openDialog } from '../../../base/dialog/actions'; |
||||||
import { IconVideoOff } from '../../../base/icons/svg'; |
import { IconVideoOff } from '../../../base/icons/svg'; |
||||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
||||||
import AbstractMuteEveryoneElsesVideoButton, { IProps } from '../AbstractMuteEveryoneElsesVideoButton'; |
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants'; |
||||||
|
import { IButtonProps } from '../../types'; |
||||||
|
|
||||||
|
import MuteEveryonesVideoDialog from './MuteEveryonesVideoDialog'; |
||||||
|
|
||||||
/** |
/** |
||||||
* Implements a React {@link Component} which displays a button for audio muting |
* Implements a React {@link Component} which displays a button for audio muting |
||||||
* every participant in the conference except the one with the given |
* every participant in the conference except the one with the given |
||||||
* participantID. |
* participantID. |
||||||
|
* |
||||||
|
* @returns {JSX.Element} |
||||||
*/ |
*/ |
||||||
class MuteEveryoneElsesVideoButton extends AbstractMuteEveryoneElsesVideoButton { |
const MuteEveryoneElsesVideoButton = ({ |
||||||
/** |
notifyClick, |
||||||
* Instantiates a new {@code Component}. |
notifyMode, |
||||||
* |
participantID |
||||||
* @inheritdoc |
}: IButtonProps): JSX.Element => { |
||||||
*/ |
const { t } = useTranslation(); |
||||||
constructor(props: IProps) { |
const dispatch = useDispatch(); |
||||||
super(props); |
|
||||||
|
|
||||||
this._handleClick = this._handleClick.bind(this); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Implements React's {@link Component#render()}. |
|
||||||
* |
|
||||||
* @inheritdoc |
|
||||||
* @returns {ReactElement} |
|
||||||
*/ |
|
||||||
render() { |
|
||||||
const { t } = this.props; |
|
||||||
|
|
||||||
return ( |
const handleClick = useCallback(() => { |
||||||
<ContextMenuItem |
notifyClick?.(); |
||||||
accessibilityLabel = { t('toolbar.accessibilityLabel.muteEveryoneElsesVideoStream') } |
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) { |
||||||
icon = { IconVideoOff } |
return; |
||||||
// eslint-disable-next-line react/jsx-handler-names
|
} |
||||||
onClick = { this._handleClick } |
sendAnalytics(createToolbarEvent('mute.everyoneelsesvideo.pressed')); |
||||||
text = { t('videothumbnail.domuteVideoOfOthers') } /> |
dispatch(openDialog(MuteEveryonesVideoDialog, { exclude: [ participantID ] })); |
||||||
); |
}, [ notifyClick, notifyMode, participantID ]); |
||||||
} |
|
||||||
|
|
||||||
_handleClick: () => void; |
return ( |
||||||
} |
<ContextMenuItem |
||||||
|
accessibilityLabel = { t('toolbar.accessibilityLabel.muteEveryoneElsesVideoStream') } |
||||||
|
icon = { IconVideoOff } |
||||||
|
onClick = { handleClick } |
||||||
|
text = { t('videothumbnail.domuteVideoOfOthers') } /> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
export default translate(connect()(MuteEveryoneElsesVideoButton)); |
export default MuteEveryoneElsesVideoButton; |
||||||
|
@ -1,58 +1,66 @@ |
|||||||
import React from 'react'; |
import React, { useCallback, useMemo } from 'react'; |
||||||
import { connect } from 'react-redux'; |
import { useTranslation } from 'react-i18next'; |
||||||
|
import { useDispatch, useSelector } from 'react-redux'; |
||||||
|
|
||||||
import { translate } from '../../../base/i18n/functions'; |
import { createRemoteVideoMenuButtonEvent } from '../../../analytics/AnalyticsEvents'; |
||||||
|
import { sendAnalytics } from '../../../analytics/functions'; |
||||||
|
import { IReduxState } from '../../../app/types'; |
||||||
|
import { openDialog } from '../../../base/dialog/actions'; |
||||||
import { IconVideoOff } from '../../../base/icons/svg'; |
import { IconVideoOff } from '../../../base/icons/svg'; |
||||||
|
import { MEDIA_TYPE } from '../../../base/media/constants'; |
||||||
|
import { isRemoteTrackMuted } from '../../../base/tracks/functions.any'; |
||||||
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem'; |
||||||
import AbstractMuteVideoButton, { IProps, _mapStateToProps } from '../AbstractMuteVideoButton'; |
import { NOTIFY_CLICK_MODE } from '../../../toolbox/constants'; |
||||||
|
import { IButtonProps } from '../../types'; |
||||||
|
|
||||||
|
import MuteRemoteParticipantsVideoDialog from './MuteRemoteParticipantsVideoDialog'; |
||||||
|
|
||||||
/** |
/** |
||||||
* Implements a React {@link Component} which displays a button for disabling |
* Implements a React {@link Component} which displays a button for disabling |
||||||
* the camera of a participant in the conference. |
* the camera of a participant in the conference. |
||||||
* |
* |
||||||
* NOTE: At the time of writing this is a button that doesn't use the |
* @returns {JSX.Element|null} |
||||||
* {@code AbstractButton} base component, but is inherited from the same |
|
||||||
* super class ({@code AbstractMuteVideoButton} that extends {@code AbstractButton}) |
|
||||||
* for the sake of code sharing between web and mobile. Once web uses the |
|
||||||
* {@code AbstractButton} base component, this can be fully removed. |
|
||||||
*/ |
*/ |
||||||
class MuteVideoButton extends AbstractMuteVideoButton { |
const MuteVideoButton = ({ |
||||||
/** |
notifyClick, |
||||||
* Instantiates a new {@code Component}. |
notifyMode, |
||||||
* |
participantID |
||||||
* @inheritdoc |
}: IButtonProps): JSX.Element | null => { |
||||||
*/ |
const { t } = useTranslation(); |
||||||
constructor(props: IProps) { |
const dispatch = useDispatch(); |
||||||
super(props); |
const tracks = useSelector((state: IReduxState) => state['features/base/tracks']); |
||||||
|
|
||||||
this._handleClick = this._handleClick.bind(this); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
const videoTrackMuted = useMemo( |
||||||
* Implements React's {@link Component#render()}. |
() => isRemoteTrackMuted(tracks, MEDIA_TYPE.VIDEO, participantID), |
||||||
* |
[ isRemoteTrackMuted, participantID, tracks ] |
||||||
* @inheritdoc |
); |
||||||
* @returns {ReactElement} |
|
||||||
*/ |
const handleClick = useCallback(() => { |
||||||
render() { |
notifyClick?.(); |
||||||
const { _videoTrackMuted, t } = this.props; |
if (notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) { |
||||||
|
return; |
||||||
if (_videoTrackMuted) { |
|
||||||
return null; |
|
||||||
} |
} |
||||||
|
sendAnalytics(createRemoteVideoMenuButtonEvent( |
||||||
|
'video.mute.button', |
||||||
|
{ |
||||||
|
'participant_id': participantID |
||||||
|
})); |
||||||
|
|
||||||
|
dispatch(openDialog(MuteRemoteParticipantsVideoDialog, { participantID })); |
||||||
|
}, [ dispatch, notifyClick, notifyClick, participantID, sendAnalytics ]); |
||||||
|
|
||||||
return ( |
if (videoTrackMuted) { |
||||||
<ContextMenuItem |
return null; |
||||||
accessibilityLabel = { t('participantsPane.actions.stopVideo') } |
|
||||||
className = 'mutevideolink' |
|
||||||
icon = { IconVideoOff } |
|
||||||
// eslint-disable-next-line react/jsx-handler-names
|
|
||||||
onClick = { this._handleClick } |
|
||||||
text = { t('participantsPane.actions.stopVideo') } /> |
|
||||||
); |
|
||||||
} |
} |
||||||
|
|
||||||
_handleClick: () => void; |
return ( |
||||||
} |
<ContextMenuItem |
||||||
|
accessibilityLabel = { t('participantsPane.actions.stopVideo') } |
||||||
|
className = 'mutevideolink' |
||||||
|
icon = { IconVideoOff } |
||||||
|
onClick = { handleClick } |
||||||
|
text = { t('participantsPane.actions.stopVideo') } /> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
export default translate(connect(_mapStateToProps)(MuteVideoButton)); |
export default MuteVideoButton; |
||||||
|
@ -0,0 +1,18 @@ |
|||||||
|
export interface IButtonProps { |
||||||
|
|
||||||
|
/** |
||||||
|
* Callback to execute when the button is clicked. |
||||||
|
*/ |
||||||
|
notifyClick?: Function; |
||||||
|
|
||||||
|
/** |
||||||
|
* Notify mode for the `participantMenuButtonClicked` event - |
||||||
|
* whether to only notify or to also prevent button click routine. |
||||||
|
*/ |
||||||
|
notifyMode?: string; |
||||||
|
|
||||||
|
/** |
||||||
|
* The ID of the participant that's linked to the button. |
||||||
|
*/ |
||||||
|
participantID: string; |
||||||
|
} |
Loading…
Reference in new issue