fix(shared-video,video-menu) add ability to stop shared video from video menu
Specifically, in the bottom sheet (on mobile) and participants pane.
This commit is contained in:
parent
e421a119e1
commit
3c2ad24652
|
@ -13,7 +13,8 @@ import {
|
||||||
getParticipantCount,
|
getParticipantCount,
|
||||||
isEveryoneModerator,
|
isEveryoneModerator,
|
||||||
pinParticipant,
|
pinParticipant,
|
||||||
getParticipantByIdOrUndefined
|
getParticipantByIdOrUndefined,
|
||||||
|
getLocalParticipant
|
||||||
} from '../../../base/participants';
|
} from '../../../base/participants';
|
||||||
import { Container } from '../../../base/react';
|
import { Container } from '../../../base/react';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
|
@ -24,6 +25,8 @@ import { DisplayNameLabel } from '../../../display-name';
|
||||||
import { toggleToolboxVisible } from '../../../toolbox/actions.native';
|
import { toggleToolboxVisible } from '../../../toolbox/actions.native';
|
||||||
import { RemoteVideoMenu } from '../../../video-menu';
|
import { RemoteVideoMenu } from '../../../video-menu';
|
||||||
import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent';
|
import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent';
|
||||||
|
import SharedVideoMenu
|
||||||
|
from '../../../video-menu/components/native/SharedVideoMenu';
|
||||||
|
|
||||||
import AudioMutedIndicator from './AudioMutedIndicator';
|
import AudioMutedIndicator from './AudioMutedIndicator';
|
||||||
import DominantSpeakerIndicator from './DominantSpeakerIndicator';
|
import DominantSpeakerIndicator from './DominantSpeakerIndicator';
|
||||||
|
@ -48,6 +51,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_largeVideo: Object,
|
_largeVideo: Object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared video local participant owner.
|
||||||
|
*/
|
||||||
|
_localVideoOwner: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Redux representation of the participant to display.
|
* The Redux representation of the participant to display.
|
||||||
*/
|
*/
|
||||||
|
@ -116,6 +124,7 @@ function Thumbnail(props: Props) {
|
||||||
const {
|
const {
|
||||||
_audioMuted: audioMuted,
|
_audioMuted: audioMuted,
|
||||||
_largeVideo: largeVideo,
|
_largeVideo: largeVideo,
|
||||||
|
_localVideoOwner,
|
||||||
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
||||||
_renderModeratorIndicator: renderModeratorIndicator,
|
_renderModeratorIndicator: renderModeratorIndicator,
|
||||||
_participant: participant,
|
_participant: participant,
|
||||||
|
@ -144,11 +153,19 @@ function Thumbnail(props: Props) {
|
||||||
dispatch(openDialog(ConnectionStatusComponent, {
|
dispatch(openDialog(ConnectionStatusComponent, {
|
||||||
participantID: participant.id
|
participantID: participant.id
|
||||||
}));
|
}));
|
||||||
} else {
|
} else if (participant.isFakeParticipant) {
|
||||||
dispatch(openDialog(RemoteVideoMenu, {
|
if (_localVideoOwner) {
|
||||||
|
dispatch(openDialog(SharedVideoMenu, {
|
||||||
participant
|
participant
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(openDialog(RemoteVideoMenu, {
|
||||||
|
participant
|
||||||
|
}));
|
||||||
}, [ participant, dispatch ]);
|
}, [ participant, dispatch ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -223,9 +240,11 @@ function _mapStateToProps(state, ownProps) {
|
||||||
// filmstrip doesn't render the video of the participant who is rendered on
|
// filmstrip doesn't render the video of the participant who is rendered on
|
||||||
// the stage i.e. as a large video.
|
// the stage i.e. as a large video.
|
||||||
const largeVideo = state['features/large-video'];
|
const largeVideo = state['features/large-video'];
|
||||||
|
const { ownerId } = state['features/shared-video'];
|
||||||
const tracks = state['features/base/tracks'];
|
const tracks = state['features/base/tracks'];
|
||||||
const { participantID } = ownProps;
|
const { participantID } = ownProps;
|
||||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||||
|
const localParticipantId = getLocalParticipant(state).id;
|
||||||
const id = participant?.id;
|
const id = participant?.id;
|
||||||
const audioTrack
|
const audioTrack
|
||||||
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
|
= getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
|
||||||
|
@ -240,6 +259,7 @@ function _mapStateToProps(state, ownProps) {
|
||||||
return {
|
return {
|
||||||
_audioMuted: audioTrack?.muted ?? true,
|
_audioMuted: audioTrack?.muted ?? true,
|
||||||
_largeVideo: largeVideo,
|
_largeVideo: largeVideo,
|
||||||
|
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
||||||
_participant: participant,
|
_participant: participant,
|
||||||
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
_renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
|
||||||
_renderModeratorIndicator: renderModeratorIndicator,
|
_renderModeratorIndicator: renderModeratorIndicator,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import { openDialog } from '../base/dialog';
|
import { openDialog } from '../base/dialog';
|
||||||
|
import { SharedVideoMenu } from '../video-menu';
|
||||||
import ConnectionStatusComponent
|
import ConnectionStatusComponent
|
||||||
from '../video-menu/components/native/ConnectionStatusComponent';
|
from '../video-menu/components/native/ConnectionStatusComponent';
|
||||||
import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
|
import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
|
||||||
|
@ -42,6 +43,16 @@ export function showContextMenuDetails(participant: Object) {
|
||||||
return openDialog(RemoteVideoMenu, { participant });
|
return openDialog(RemoteVideoMenu, { participant });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays the shared video menu.
|
||||||
|
*
|
||||||
|
* @param {Object} participant - The selected meeting participant.
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function showSharedVideoMenu(participant: Object) {
|
||||||
|
return openDialog(SharedVideoMenu, { participant });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the volume.
|
* Sets the volume.
|
||||||
*
|
*
|
||||||
|
|
|
@ -6,20 +6,34 @@ import { Text, View } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
|
||||||
import { Icon, IconInviteMore } from '../../../base/icons';
|
import { Icon, IconInviteMore } from '../../../base/icons';
|
||||||
import {
|
import {
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
getParticipantCountWithFake,
|
getParticipantCountWithFake,
|
||||||
getRemoteParticipants
|
getRemoteParticipants
|
||||||
} from '../../../base/participants';
|
} from '../../../base/participants';
|
||||||
|
import { connect } from '../../../base/redux';
|
||||||
import { doInvitePeople } from '../../../invite/actions.native';
|
import { doInvitePeople } from '../../../invite/actions.native';
|
||||||
import { showConnectionStatus, showContextMenuDetails } from '../../actions.native';
|
import {
|
||||||
|
showConnectionStatus,
|
||||||
|
showContextMenuDetails,
|
||||||
|
showSharedVideoMenu
|
||||||
|
} from '../../actions.native';
|
||||||
import { shouldRenderInviteButton } from '../../functions';
|
import { shouldRenderInviteButton } from '../../functions';
|
||||||
|
|
||||||
import MeetingParticipantItem from './MeetingParticipantItem';
|
import MeetingParticipantItem from './MeetingParticipantItem';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
export const MeetingParticipantList = () => {
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared video local participant owner.
|
||||||
|
*/
|
||||||
|
_localVideoOwner: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const MeetingParticipantList = ({ _localVideoOwner }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const items = [];
|
const items = [];
|
||||||
const localParticipant = useSelector(getLocalParticipant);
|
const localParticipant = useSelector(getLocalParticipant);
|
||||||
|
@ -30,7 +44,26 @@ export const MeetingParticipantList = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// eslint-disable-next-line react/no-multi-comp
|
// eslint-disable-next-line react/no-multi-comp
|
||||||
const renderParticipant = p => (
|
const renderParticipant = p => {
|
||||||
|
if (p.isFakeParticipant) {
|
||||||
|
if (_localVideoOwner) {
|
||||||
|
return (
|
||||||
|
<MeetingParticipantItem
|
||||||
|
key = { p.id }
|
||||||
|
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
|
||||||
|
onPress = { () => dispatch(showSharedVideoMenu(p)) }
|
||||||
|
participantID = { p.id } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MeetingParticipantItem
|
||||||
|
key = { p.id }
|
||||||
|
participantID = { p.id } />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<MeetingParticipantItem
|
<MeetingParticipantItem
|
||||||
key = { p.id }
|
key = { p.id }
|
||||||
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
|
/* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
|
||||||
|
@ -38,6 +71,7 @@ export const MeetingParticipantList = () => {
|
||||||
? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) }
|
? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) }
|
||||||
participantID = { p.id } />
|
participantID = { p.id } />
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
items.push(renderParticipant(localParticipant));
|
items.push(renderParticipant(localParticipant));
|
||||||
|
|
||||||
|
@ -71,3 +105,20 @@ export const MeetingParticipantList = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the redux state to the associated props for this component.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The Redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {Props}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state): Object {
|
||||||
|
const { ownerId } = state['features/shared-video'];
|
||||||
|
const localParticipantId = getLocalParticipant(state).id;
|
||||||
|
|
||||||
|
return {
|
||||||
|
_localVideoOwner: Boolean(ownerId === localParticipantId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(_mapStateToProps)(MeetingParticipantList);
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { close } from '../../actions.native';
|
||||||
|
|
||||||
import { ContextMenuMore } from './ContextMenuMore';
|
import { ContextMenuMore } from './ContextMenuMore';
|
||||||
import LobbyParticipantList from './LobbyParticipantList';
|
import LobbyParticipantList from './LobbyParticipantList';
|
||||||
import { MeetingParticipantList } from './MeetingParticipantList';
|
import MeetingParticipantList from './MeetingParticipantList';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
export { default as MeetingParticipantList } from './MeetingParticipantList';
|
||||||
export { default as ParticipantsPane } from './ParticipantsPane';
|
export { default as ParticipantsPane } from './ParticipantsPane';
|
||||||
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
|
export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
|
||||||
export { default as ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject';
|
export { default as ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject';
|
||||||
|
|
|
@ -11,9 +11,11 @@ import {
|
||||||
IconMessage,
|
IconMessage,
|
||||||
IconMicDisabled,
|
IconMicDisabled,
|
||||||
IconMuteEveryoneElse,
|
IconMuteEveryoneElse,
|
||||||
|
IconShareVideo,
|
||||||
IconVideoOff
|
IconVideoOff
|
||||||
} from '../../../base/icons';
|
} from '../../../base/icons';
|
||||||
import {
|
import {
|
||||||
|
getLocalParticipant,
|
||||||
getParticipantByIdOrUndefined,
|
getParticipantByIdOrUndefined,
|
||||||
isLocalParticipantModerator,
|
isLocalParticipantModerator,
|
||||||
isParticipantModerator
|
isParticipantModerator
|
||||||
|
@ -21,6 +23,7 @@ import {
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
|
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
|
||||||
import { openChat } from '../../../chat/actions';
|
import { openChat } from '../../../chat/actions';
|
||||||
|
import { stopSharedVideo } from '../../../shared-video/actions.any';
|
||||||
import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
|
import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
|
||||||
import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
|
import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
|
||||||
import { getComputedOuterHeight } from '../../functions';
|
import { getComputedOuterHeight } from '../../functions';
|
||||||
|
@ -60,6 +63,11 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_isParticipantAudioMuted: boolean,
|
_isParticipantAudioMuted: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared video local participant owner.
|
||||||
|
*/
|
||||||
|
_localVideoOwner: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Participant reference
|
* Participant reference
|
||||||
*/
|
*/
|
||||||
|
@ -143,6 +151,7 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
|
||||||
this._onMuteEveryoneElse = this._onMuteEveryoneElse.bind(this);
|
this._onMuteEveryoneElse = this._onMuteEveryoneElse.bind(this);
|
||||||
this._onMuteVideo = this._onMuteVideo.bind(this);
|
this._onMuteVideo = this._onMuteVideo.bind(this);
|
||||||
this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this);
|
this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this);
|
||||||
|
this._onStopSharedVideo = this._onStopSharedVideo.bind(this);
|
||||||
this._position = this._position.bind(this);
|
this._position = this._position.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +185,19 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onStopSharedVideo: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops shared video.
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
_onStopSharedVideo() {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
|
||||||
|
dispatch(stopSharedVideo());
|
||||||
|
}
|
||||||
|
|
||||||
_onMuteEveryoneElse: () => void;
|
_onMuteEveryoneElse: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,6 +304,7 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
|
||||||
_isParticipantModerator,
|
_isParticipantModerator,
|
||||||
_isParticipantVideoMuted,
|
_isParticipantVideoMuted,
|
||||||
_isParticipantAudioMuted,
|
_isParticipantAudioMuted,
|
||||||
|
_localVideoOwner,
|
||||||
_participant,
|
_participant,
|
||||||
onEnter,
|
onEnter,
|
||||||
onLeave,
|
onLeave,
|
||||||
|
@ -302,6 +325,9 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
|
||||||
onClick = { onSelect }
|
onClick = { onSelect }
|
||||||
onMouseEnter = { onEnter }
|
onMouseEnter = { onEnter }
|
||||||
onMouseLeave = { onLeave }>
|
onMouseLeave = { onLeave }>
|
||||||
|
{
|
||||||
|
!_participant.isFakeParticipant && (
|
||||||
|
<>
|
||||||
<ContextMenuItemGroup>
|
<ContextMenuItemGroup>
|
||||||
{
|
{
|
||||||
_isLocalModerator && (
|
_isLocalModerator && (
|
||||||
|
@ -362,6 +388,18 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</ContextMenuItemGroup>
|
</ContextMenuItemGroup>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
_participant.isFakeParticipant && _localVideoOwner && (
|
||||||
|
<ContextMenuItem onClick = { this._onStopSharedVideo }>
|
||||||
|
<ContextMenuIcon src = { IconShareVideo } />
|
||||||
|
<span>{t('toolbar.stopSharedVideo')}</span>
|
||||||
|
</ContextMenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -377,7 +415,8 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state, ownProps): Object {
|
function _mapStateToProps(state, ownProps): Object {
|
||||||
const { participantID } = ownProps;
|
const { participantID } = ownProps;
|
||||||
|
const { ownerId } = state['features/shared-video'];
|
||||||
|
const localParticipantId = getLocalParticipant(state).id;
|
||||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||||
|
|
||||||
const _isLocalModerator = isLocalParticipantModerator(state);
|
const _isLocalModerator = isLocalParticipantModerator(state);
|
||||||
|
@ -392,6 +431,7 @@ function _mapStateToProps(state, ownProps): Object {
|
||||||
_isParticipantModerator,
|
_isParticipantModerator,
|
||||||
_isParticipantVideoMuted,
|
_isParticipantVideoMuted,
|
||||||
_isParticipantAudioMuted,
|
_isParticipantAudioMuted,
|
||||||
|
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
||||||
_participant: participant
|
_participant: participant
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getParticipantByIdOrUndefined, getParticipantDisplayName } from '../../../base/participants';
|
import {
|
||||||
|
getLocalParticipant,
|
||||||
|
getParticipantByIdOrUndefined,
|
||||||
|
getParticipantDisplayName
|
||||||
|
} from '../../../base/participants';
|
||||||
import { connect } from '../../../base/redux';
|
import { connect } from '../../../base/redux';
|
||||||
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
|
import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
|
||||||
import { ACTION_TRIGGER, MEDIA_STATE, type MediaState } from '../../constants';
|
import { ACTION_TRIGGER, MEDIA_STATE, type MediaState } from '../../constants';
|
||||||
|
@ -34,6 +38,16 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
_local: boolean,
|
_local: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared video local participant owner.
|
||||||
|
*/
|
||||||
|
_localVideoOwner: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The participant.
|
||||||
|
*/
|
||||||
|
_participant: Object,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The participant ID.
|
* The participant ID.
|
||||||
*
|
*
|
||||||
|
@ -108,7 +122,9 @@ function MeetingParticipantItem({
|
||||||
_audioMediaState,
|
_audioMediaState,
|
||||||
_displayName,
|
_displayName,
|
||||||
_isVideoMuted,
|
_isVideoMuted,
|
||||||
|
_localVideoOwner,
|
||||||
_local,
|
_local,
|
||||||
|
_participant,
|
||||||
_participantID,
|
_participantID,
|
||||||
_quickActionButtonType,
|
_quickActionButtonType,
|
||||||
_raisedHand,
|
_raisedHand,
|
||||||
|
@ -133,6 +149,9 @@ function MeetingParticipantItem({
|
||||||
raisedHand = { _raisedHand }
|
raisedHand = { _raisedHand }
|
||||||
videoMuteState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED }
|
videoMuteState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED }
|
||||||
youText = { youText }>
|
youText = { youText }>
|
||||||
|
{
|
||||||
|
!_participant.isFakeParticipant && (
|
||||||
|
<>
|
||||||
<ParticipantQuickAction
|
<ParticipantQuickAction
|
||||||
askUnmuteText = { askUnmuteText }
|
askUnmuteText = { askUnmuteText }
|
||||||
buttonType = { _quickActionButtonType }
|
buttonType = { _quickActionButtonType }
|
||||||
|
@ -142,6 +161,16 @@ function MeetingParticipantItem({
|
||||||
<ParticipantActionEllipsis
|
<ParticipantActionEllipsis
|
||||||
aria-label = { participantActionEllipsisLabel }
|
aria-label = { participantActionEllipsisLabel }
|
||||||
onClick = { onContextMenu } />
|
onClick = { onContextMenu } />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
_participant.isFakeParticipant && _localVideoOwner && (
|
||||||
|
<ParticipantActionEllipsis
|
||||||
|
aria-label = { participantActionEllipsisLabel }
|
||||||
|
onClick = { onContextMenu } />
|
||||||
|
)
|
||||||
|
}
|
||||||
</ParticipantItem>
|
</ParticipantItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -156,6 +185,8 @@ function MeetingParticipantItem({
|
||||||
*/
|
*/
|
||||||
function _mapStateToProps(state, ownProps): Object {
|
function _mapStateToProps(state, ownProps): Object {
|
||||||
const { participantID } = ownProps;
|
const { participantID } = ownProps;
|
||||||
|
const { ownerId } = state['features/shared-video'];
|
||||||
|
const localParticipantId = getLocalParticipant(state).id;
|
||||||
|
|
||||||
const participant = getParticipantByIdOrUndefined(state, participantID);
|
const participant = getParticipantByIdOrUndefined(state, participantID);
|
||||||
|
|
||||||
|
@ -170,6 +201,8 @@ function _mapStateToProps(state, ownProps): Object {
|
||||||
_isAudioMuted,
|
_isAudioMuted,
|
||||||
_isVideoMuted,
|
_isVideoMuted,
|
||||||
_local: Boolean(participant?.local),
|
_local: Boolean(participant?.local),
|
||||||
|
_localVideoOwner: Boolean(ownerId === localParticipantId),
|
||||||
|
_participant: participant,
|
||||||
_participantID: participant?.id,
|
_participantID: participant?.id,
|
||||||
_quickActionButtonType,
|
_quickActionButtonType,
|
||||||
_raisedHand: Boolean(participant?.raisedHand)
|
_raisedHand: Boolean(participant?.raisedHand)
|
||||||
|
|
|
@ -235,7 +235,6 @@ export const ParticipantActionsHover = styled(ParticipantActions)`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
transform: translateX(-100%);
|
transform: translateX(-100%);
|
||||||
width: 40px;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
import { hideDialog } from '../base/dialog';
|
import { hideDialog } from '../base/dialog';
|
||||||
|
|
||||||
import { RemoteVideoMenu } from './components/native';
|
import { RemoteVideoMenu, SharedVideoMenu } from './components/native';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the remote video menu.
|
* Hides the remote video menu.
|
||||||
|
@ -12,4 +12,13 @@ export function hideRemoteVideoMenu() {
|
||||||
return hideDialog(RemoteVideoMenu);
|
return hideDialog(RemoteVideoMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the shared video menu.
|
||||||
|
*
|
||||||
|
* @returns {Function}
|
||||||
|
*/
|
||||||
|
export function hideSharedVideoMenu() {
|
||||||
|
return hideDialog(SharedVideoMenu);
|
||||||
|
}
|
||||||
|
|
||||||
export * from './actions.any';
|
export * from './actions.any';
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import { Text, View } from 'react-native';
|
||||||
|
import { Divider } from 'react-native-paper';
|
||||||
|
|
||||||
|
import { Avatar } from '../../../base/avatar';
|
||||||
|
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||||
|
import { BottomSheet, isDialogOpen } from '../../../base/dialog';
|
||||||
|
import {
|
||||||
|
getParticipantById,
|
||||||
|
getParticipantDisplayName
|
||||||
|
} from '../../../base/participants';
|
||||||
|
import { connect } from '../../../base/redux';
|
||||||
|
import { StyleType } from '../../../base/styles';
|
||||||
|
import { SharedVideoButton } from '../../../shared-video/components';
|
||||||
|
import { hideSharedVideoMenu } from '../../actions.native';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of the rendered avatar in the menu.
|
||||||
|
*/
|
||||||
|
const AVATAR_SIZE = 24;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Redux dispatch function.
|
||||||
|
*/
|
||||||
|
dispatch: Function,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The participant for which this menu opened for.
|
||||||
|
*/
|
||||||
|
participant: Object,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color-schemed stylesheet of the BottomSheet.
|
||||||
|
*/
|
||||||
|
_bottomSheetStyles: StyleType,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the menu is currently open, false otherwise.
|
||||||
|
*/
|
||||||
|
_isOpen: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the participant is present in the room or not.
|
||||||
|
*/
|
||||||
|
_isParticipantAvailable?: boolean,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display name of the participant retrieved from Redux.
|
||||||
|
*/
|
||||||
|
_participantDisplayName: string,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the participant.
|
||||||
|
*/
|
||||||
|
_participantID: ?string,
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let SharedVideoMenu_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to implement a popup menu that opens upon long pressing a fake participant thumbnail.
|
||||||
|
*/
|
||||||
|
class SharedVideoMenu extends PureComponent<Props> {
|
||||||
|
/**
|
||||||
|
* Constructor of the component.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this._onCancel = this._onCancel.bind(this);
|
||||||
|
this._renderMenuHeader = this._renderMenuHeader.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements {@code Component#render}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
_isParticipantAvailable,
|
||||||
|
participant
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const buttonProps = {
|
||||||
|
afterClick: this._onCancel,
|
||||||
|
showLabel: true,
|
||||||
|
participantID: participant.id,
|
||||||
|
styles: this.props._bottomSheetStyles.buttons
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BottomSheet
|
||||||
|
onCancel = { this._onCancel }
|
||||||
|
renderHeader = { this._renderMenuHeader }
|
||||||
|
showSlidingView = { _isParticipantAvailable }>
|
||||||
|
<Divider style = { styles.divider } />
|
||||||
|
<SharedVideoButton { ...buttonProps } />
|
||||||
|
</BottomSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onCancel: () => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to hide the {@code SharedVideoMenu}.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
_onCancel() {
|
||||||
|
if (this.props._isOpen) {
|
||||||
|
this.props.dispatch(hideSharedVideoMenu());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderMenuHeader: () => React$Element<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to render the menu's header.
|
||||||
|
*
|
||||||
|
* @returns {React$Element}
|
||||||
|
*/
|
||||||
|
_renderMenuHeader() {
|
||||||
|
const { _bottomSheetStyles, participant } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style = { [
|
||||||
|
_bottomSheetStyles.sheet,
|
||||||
|
styles.participantNameContainer ] }>
|
||||||
|
<Avatar
|
||||||
|
participantId = { participant.id }
|
||||||
|
size = { AVATAR_SIZE } />
|
||||||
|
<Text style = { styles.participantNameLabel }>
|
||||||
|
{ this.props._participantDisplayName }
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function that maps parts of Redux state tree into component props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - Redux state.
|
||||||
|
* @param {Object} ownProps - Properties of component.
|
||||||
|
* @private
|
||||||
|
* @returns {Props}
|
||||||
|
*/
|
||||||
|
function _mapStateToProps(state, ownProps) {
|
||||||
|
const { participant } = ownProps;
|
||||||
|
const isParticipantAvailable = getParticipantById(state, participant.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
_bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
|
||||||
|
_isOpen: isDialogOpen(state, SharedVideoMenu_),
|
||||||
|
_isParticipantAvailable: Boolean(isParticipantAvailable),
|
||||||
|
_participantDisplayName: getParticipantDisplayName(state, participant.id),
|
||||||
|
_participantID: participant.id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedVideoMenu_ = connect(_mapStateToProps)(SharedVideoMenu);
|
||||||
|
|
||||||
|
export default SharedVideoMenu_;
|
|
@ -8,4 +8,5 @@ export { default as MuteEveryonesVideoDialog } from './MuteEveryonesVideoDialog'
|
||||||
export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog';
|
export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog';
|
||||||
export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog';
|
export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog';
|
||||||
export { default as RemoteVideoMenu } from './RemoteVideoMenu';
|
export { default as RemoteVideoMenu } from './RemoteVideoMenu';
|
||||||
|
export { default as SharedVideoMenu } from './SharedVideoMenu';
|
||||||
export { default as VolumeSlider } from './VolumeSlider';
|
export { default as VolumeSlider } from './VolumeSlider';
|
||||||
|
|
Loading…
Reference in New Issue