123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- // @flow
-
- import React, { Component } from 'react';
-
- import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
- import { openDialog } from '../../../base/dialog';
- import { translate } from '../../../base/i18n';
- import {
- IconCloseCircle,
- IconCrown,
- IconMessage,
- IconMicDisabled,
- IconMuteEveryoneElse,
- IconShareVideo,
- IconVideoOff
- } from '../../../base/icons';
- import {
- getLocalParticipant,
- getParticipantByIdOrUndefined,
- isLocalParticipantModerator,
- isParticipantModerator
- } from '../../../base/participants';
- import { connect } from '../../../base/redux';
- import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
- import { openChat } from '../../../chat/actions';
- import { stopSharedVideo } from '../../../shared-video/actions.any';
- import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
- import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
- import { getComputedOuterHeight } from '../../functions';
-
- import {
- ContextMenu,
- ContextMenuIcon,
- ContextMenuItem,
- ContextMenuItemGroup,
- ignoredChildClassName
- } from './styled';
-
- type Props = {
-
- /**
- * True if the local participant is moderator and false otherwise.
- */
- _isLocalModerator: boolean,
-
- /**
- * True if the chat button is enabled and false otherwise.
- */
- _isChatButtonEnabled: boolean,
-
- /**
- * True if the participant is moderator and false otherwise.
- */
- _isParticipantModerator: boolean,
-
- /**
- * True if the participant is video muted and false otherwise.
- */
- _isParticipantVideoMuted: boolean,
-
- /**
- * True if the participant is audio muted and false otherwise.
- */
- _isParticipantAudioMuted: boolean,
-
- /**
- * Shared video local participant owner.
- */
- _localVideoOwner: boolean,
-
- /**
- * Participant reference
- */
- _participant: Object,
-
- /**
- * The dispatch function from redux.
- */
- dispatch: Function,
-
- /**
- * Callback used to open a confirmation dialog for audio muting.
- */
- muteAudio: Function,
-
- /**
- * Target elements against which positioning calculations are made
- */
- offsetTarget: HTMLElement,
-
- /**
- * Callback for the mouse entering the component
- */
- onEnter: Function,
-
- /**
- * Callback for the mouse leaving the component
- */
- onLeave: Function,
-
- /**
- * Callback for making a selection in the menu
- */
- onSelect: Function,
-
- /**
- * The ID of the participant.
- */
- participantID: string,
-
- /**
- * The translate function.
- */
- t: Function
- };
-
- type State = {
-
- /**
- * If true the context menu will be hidden.
- */
- isHidden: boolean
- };
-
- /**
- * Implements the MeetingParticipantContextMenu component.
- */
- class MeetingParticipantContextMenu extends Component<Props, State> {
-
- /**
- * Reference to the context menu container div.
- */
- _containerRef: Object;
-
- /**
- * Creates new instance of MeetingParticipantContextMenu.
- *
- * @param {Props} props - The props.
- */
- constructor(props: Props) {
- super(props);
-
- this.state = {
- isHidden: true
- };
-
- this._containerRef = React.createRef();
-
- this._onGrantModerator = this._onGrantModerator.bind(this);
- this._onKick = this._onKick.bind(this);
- this._onMuteEveryoneElse = this._onMuteEveryoneElse.bind(this);
- this._onMuteVideo = this._onMuteVideo.bind(this);
- this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this);
- this._onStopSharedVideo = this._onStopSharedVideo.bind(this);
- this._position = this._position.bind(this);
- }
-
- _onGrantModerator: () => void;
-
- /**
- * Grant moderator permissions.
- *
- * @returns {void}
- */
- _onGrantModerator() {
- const { _participant, dispatch } = this.props;
-
- dispatch(openDialog(GrantModeratorDialog, {
- participantID: _participant?.id
- }));
- }
-
- _onKick: () => void;
-
- /**
- * Kicks the participant.
- *
- * @returns {void}
- */
- _onKick() {
- const { _participant, dispatch } = this.props;
-
- dispatch(openDialog(KickRemoteParticipantDialog, {
- participantID: _participant?.id
- }));
- }
-
- _onStopSharedVideo: () => void;
-
- /**
- * Stops shared video.
- *
- * @returns {void}
- */
- _onStopSharedVideo() {
- const { dispatch } = this.props;
-
- dispatch(stopSharedVideo());
- }
-
- _onMuteEveryoneElse: () => void;
-
- /**
- * Mutes everyone else.
- *
- * @returns {void}
- */
- _onMuteEveryoneElse() {
- const { _participant, dispatch } = this.props;
-
- dispatch(openDialog(MuteEveryoneDialog, {
- exclude: [ _participant?.id ]
- }));
- }
-
- _onMuteVideo: () => void;
-
- /**
- * Mutes the video of the selected participant.
- *
- * @returns {void}
- */
- _onMuteVideo() {
- const { _participant, dispatch } = this.props;
-
- dispatch(openDialog(MuteRemoteParticipantsVideoDialog, {
- participantID: _participant?.id
- }));
- }
-
- _onSendPrivateMessage: () => void;
-
- /**
- * Sends private message.
- *
- * @returns {void}
- */
- _onSendPrivateMessage() {
- const { _participant, dispatch } = this.props;
-
- dispatch(openChat(_participant));
- }
-
- _position: () => void;
-
- /**
- * Positions the context menu.
- *
- * @returns {void}
- */
- _position() {
- const { _participant, offsetTarget } = this.props;
-
- if (_participant
- && this._containerRef.current
- && offsetTarget?.offsetParent
- && offsetTarget.offsetParent instanceof HTMLElement
- ) {
- const { current: container } = this._containerRef;
- const { offsetTop, offsetParent: { offsetHeight, scrollTop } } = offsetTarget;
- const outerHeight = getComputedOuterHeight(container);
-
- container.style.top = offsetTop + outerHeight > offsetHeight + scrollTop
- ? offsetTop - outerHeight
- : offsetTop;
-
- this.setState({ isHidden: false });
- } else {
- this.setState({ isHidden: true });
- }
- }
-
- /**
- * Implements React Component's componentDidMount.
- *
- * @inheritdoc
- * @returns {void}
- */
- componentDidMount() {
- this._position();
- }
-
- /**
- * Implements React Component's componentDidUpdate.
- *
- * @inheritdoc
- */
- componentDidUpdate(prevProps: Props) {
- if (prevProps.offsetTarget !== this.props.offsetTarget || prevProps._participant !== this.props._participant) {
- this._position();
- }
- }
-
- /**
- * Implements React's {@link Component#render()}.
- *
- * @inheritdoc
- * @returns {ReactElement}
- */
- render() {
- const {
- _isLocalModerator,
- _isChatButtonEnabled,
- _isParticipantModerator,
- _isParticipantVideoMuted,
- _isParticipantAudioMuted,
- _localVideoOwner,
- _participant,
- onEnter,
- onLeave,
- onSelect,
- muteAudio,
- t
- } = this.props;
-
- if (!_participant) {
- return null;
- }
-
- return (
- <ContextMenu
- className = { ignoredChildClassName }
- innerRef = { this._containerRef }
- isHidden = { this.state.isHidden }
- onClick = { onSelect }
- onMouseEnter = { onEnter }
- onMouseLeave = { onLeave }>
- {
- !_participant?.isFakeParticipant && (
- <>
- <ContextMenuItemGroup>
- {
- _isLocalModerator && (
- <>
- {
- !_isParticipantAudioMuted
- && <ContextMenuItem onClick = { muteAudio(_participant) }>
- <ContextMenuIcon src = { IconMicDisabled } />
- <span>{t('dialog.muteParticipantButton')}</span>
- </ContextMenuItem>
- }
-
- <ContextMenuItem onClick = { this._onMuteEveryoneElse }>
- <ContextMenuIcon src = { IconMuteEveryoneElse } />
- <span>{t('toolbar.accessibilityLabel.muteEveryoneElse')}</span>
- </ContextMenuItem>
- </>
- )
- }
-
- {
- _isLocalModerator && (
- _isParticipantVideoMuted || (
- <ContextMenuItem onClick = { this._onMuteVideo }>
- <ContextMenuIcon src = { IconVideoOff } />
- <span>{t('participantsPane.actions.stopVideo')}</span>
- </ContextMenuItem>
- )
- )
- }
- </ContextMenuItemGroup>
-
- <ContextMenuItemGroup>
- {
- _isLocalModerator && (
- <>
- {
- !_isParticipantModerator && (
- <ContextMenuItem onClick = { this._onGrantModerator }>
- <ContextMenuIcon src = { IconCrown } />
- <span>{t('toolbar.accessibilityLabel.grantModerator')}</span>
- </ContextMenuItem>
- )
- }
- <ContextMenuItem onClick = { this._onKick }>
- <ContextMenuIcon src = { IconCloseCircle } />
- <span>{ t('videothumbnail.kick') }</span>
- </ContextMenuItem>
- </>
- )
- }
- {
- _isChatButtonEnabled && (
- <ContextMenuItem onClick = { this._onSendPrivateMessage }>
- <ContextMenuIcon src = { IconMessage } />
- <span>{t('toolbar.accessibilityLabel.privateMessage')}</span>
- </ContextMenuItem>
- )
- }
- </ContextMenuItemGroup>
- </>
- )
- }
-
- {
- _participant?.isFakeParticipant && _localVideoOwner && (
- <ContextMenuItem onClick = { this._onStopSharedVideo }>
- <ContextMenuIcon src = { IconShareVideo } />
- <span>{t('toolbar.stopSharedVideo')}</span>
- </ContextMenuItem>
- )
- }
- </ContextMenu>
- );
- }
- }
-
- /**
- * Maps (parts of) the redux state to the associated props for this component.
- *
- * @param {Object} state - The Redux state.
- * @param {Object} ownProps - The own props of the component.
- * @private
- * @returns {Props}
- */
- function _mapStateToProps(state, ownProps): Object {
- const { participantID } = ownProps;
- const { ownerId } = state['features/shared-video'];
- const localParticipantId = getLocalParticipant(state).id;
- const participant = getParticipantByIdOrUndefined(state, participantID);
-
- const _isLocalModerator = isLocalParticipantModerator(state);
- const _isChatButtonEnabled = isToolbarButtonEnabled('chat', state);
- const _isParticipantVideoMuted = isParticipantVideoMuted(participant, state);
- const _isParticipantAudioMuted = isParticipantAudioMuted(participant, state);
- const _isParticipantModerator = isParticipantModerator(participant);
-
- return {
- _isLocalModerator,
- _isChatButtonEnabled,
- _isParticipantModerator,
- _isParticipantVideoMuted,
- _isParticipantAudioMuted,
- _localVideoOwner: Boolean(ownerId === localParticipantId),
- _participant: participant
- };
- }
-
- export default translate(connect(_mapStateToProps)(MeetingParticipantContextMenu));
|