feat(native-participants-pane) rebase, resolved conflicts pt. 1

This commit is contained in:
Calin Chitu 2021-07-12 18:14:38 +03:00 committed by Hristo Terezov
parent e8ad2365b6
commit d62e378528
14 changed files with 382 additions and 166 deletions

View File

@ -261,6 +261,23 @@ export function haveParticipantWithScreenSharingFeature(stateful: Object | Funct
return toState(stateful)['features/base/participants'].haveParticipantWithScreenSharingFeature; return toState(stateful)['features/base/participants'].haveParticipantWithScreenSharingFeature;
} }
/**
* Selectors for getting all known participant ids, with fake participants filtered
* out.
*
* @param {(Function|Object|Participant[])} stateful - The redux state
* features/base/participants, the (whole) redux state, or redux's
* {@code getState} function to be used to retrieve the state
* features/base/participants.
* @returns {Participant[]}
*/
export function getParticipantsById(stateful: Object | Function) {
const state = toState(stateful)['features/base/participants'];
const noFakeParticipants = state.filter(p => !p.fakeParticipants);
return noFakeParticipants.map(p => p.id);
}
/** /**
* Selectors for getting all remote participants. * Selectors for getting all remote participants.
* *

View File

@ -20,11 +20,11 @@ export function showContextMenuReject(participant: Object) {
/** /**
* Displays the context menu for the selected meeting participant. * Displays the context menu for the selected meeting participant.
* *
* @param {Object} participant - The selected meeting participant. * @param {string} participantID - The selected meeting participant id.
* @returns {Function} * @returns {Function}
*/ */
export function showContextMenuDetails(participant: Object) { export function showContextMenuDetails(participantID: String) {
return openDialog(ContextMenuMeetingParticipantDetails, { participant }); return openDialog(ContextMenuMeetingParticipantDetails, { participantID });
} }
/** /**

View File

@ -4,9 +4,10 @@ import React, { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native'; import { TouchableOpacity, View } from 'react-native';
import { Divider, Text } from 'react-native-paper'; import { Divider, Text } from 'react-native-paper';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch } from 'react-redux';
import { Avatar } from '../../../base/avatar'; import { Avatar } from '../../../base/avatar';
import { isToolbarButtonEnabled } from '../../../base/config';
import { hideDialog, openDialog } from '../../../base/dialog/actions'; import { hideDialog, openDialog } from '../../../base/dialog/actions';
import BottomSheet from '../../../base/dialog/components/native/BottomSheet'; import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
import { import {
@ -15,10 +16,14 @@ import {
IconMuteEveryoneElse, IconVideoOff IconMuteEveryoneElse, IconVideoOff
} from '../../../base/icons'; } from '../../../base/icons';
import { import {
getParticipantsById, getParticipantByIdOrUndefined, getParticipantDisplayName,
isLocalParticipantModerator isLocalParticipantModerator
} from '../../../base/participants'; } from '../../../base/participants/functions';
import { getIsParticipantVideoMuted } from '../../../base/tracks'; import { connect } from '../../../base/redux';
import {
isParticipantAudioMuted,
isParticipantVideoMuted
} from '../../../base/tracks/functions';
import { openChat } from '../../../chat/actions.native'; import { openChat } from '../../../chat/actions.native';
import { import {
KickRemoteParticipantDialog, KickRemoteParticipantDialog,
@ -32,131 +37,179 @@ import styles from './styles';
type Props = { type Props = {
/**
* The display name of the participant.
*/
_displayName: string,
/**
* 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,
/** /**
* Participant reference * Participant reference
*/ */
participant: Object _participant: Object,
/**
* The ID of the participant.
*/
participantID: string,
}; };
export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) => { export const ContextMenuMeetingParticipantDetails = (
{
_displayName,
_isLocalModerator,
_isChatButtonEnabled,
_isParticipantVideoMuted,
_isParticipantAudioMuted,
_participant,
participantID
}: Props) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const participantsIDArr = useSelector(getParticipantsById);
const participantIsAvailable = participantsIDArr.find(partId => partId === p.id);
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]); const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
const displayName = p.name;
const isLocalModerator = useSelector(isLocalParticipantModerator);
const isParticipantVideoMuted = useSelector(getIsParticipantVideoMuted(p));
const kickRemoteParticipant = useCallback(() => { const kickRemoteParticipant = useCallback(() => {
dispatch(openDialog(KickRemoteParticipantDialog, { dispatch(openDialog(KickRemoteParticipantDialog, {
participantID: p.id participantID
})); }));
}, [ dispatch, p ]); }, [ dispatch, participantID ]);
const muteAudio = useCallback(() => { const muteAudio = useCallback(() => {
dispatch(openDialog(MuteRemoteParticipantDialog, { dispatch(openDialog(MuteRemoteParticipantDialog, {
participantID: p.id participantID
})); }));
}, [ dispatch, p ]); }, [ dispatch, participantID ]);
const muteEveryoneElse = useCallback(() => { const muteEveryoneElse = useCallback(() => {
dispatch(openDialog(MuteEveryoneDialog, { dispatch(openDialog(MuteEveryoneDialog, {
exclude: [ p.id ] exclude: [ participantID ]
})); }));
}, [ dispatch, p ]); }, [ dispatch, participantID ]);
const muteVideo = useCallback(() => { const muteVideo = useCallback(() => {
dispatch(openDialog(MuteRemoteParticipantsVideoDialog, { dispatch(openDialog(MuteRemoteParticipantsVideoDialog, {
participantID: p.id participantID
})); }));
}, [ dispatch, p ]); }, [ dispatch, participantID ]);
const sendPrivateMessage = useCallback(() => { const sendPrivateMessage = useCallback(() => {
dispatch(hideDialog()); dispatch(hideDialog());
dispatch(openChat(p)); dispatch(openChat(_participant));
}, [ dispatch, p ]); }, [ dispatch, _participant ]);
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<BottomSheet <BottomSheet
addScrollViewPadding = { false } addScrollViewPadding = { false }
onCancel = { cancel } onCancel = { cancel }
showSlidingView = { Boolean(participantIsAvailable) }
style = { styles.contextMenuMeetingParticipantDetails }> style = { styles.contextMenuMeetingParticipantDetails }>
<View <View
style = { styles.contextMenuItemSectionAvatar }> style = { styles.contextMenuItemSectionAvatar }>
<Avatar <Avatar
className = 'participant-avatar' className = 'participant-avatar'
participantId = { p.id } participantId = { participantID }
size = { 20 } /> size = { 20 } />
<View style = { styles.contextMenuItemAvatarText }> <View style = { styles.contextMenuItemAvatarText }>
<Text style = { styles.contextMenuItemName }> <Text style = { styles.contextMenuItemName }>
{ displayName } { _displayName }
</Text> </Text>
</View> </View>
</View> </View>
<Divider style = { styles.divider } /> <Divider style = { styles.divider } />
{ {
isLocalModerator _isLocalModerator && (
&& <TouchableOpacity <>
onPress = { muteAudio } {
style = { styles.contextMenuItem }> !_isParticipantAudioMuted
<Icon && <TouchableOpacity
size = { 20 } onPress = { muteAudio }
src = { IconMicrophoneEmptySlash } /> style = { styles.contextMenuItem }>
<Text style = { styles.contextMenuItemText }> <Icon
{ t('participantsPane.actions.mute') } size = { 20 }
</Text> src = { IconMicrophoneEmptySlash } />
</TouchableOpacity> <Text style = { styles.contextMenuItemText }>
} { t('participantsPane.actions.mute') }
{ </Text>
isLocalModerator </TouchableOpacity>
&& <TouchableOpacity }
onPress = { muteEveryoneElse }
style = { styles.contextMenuItem }> <TouchableOpacity
<Icon onPress = { muteEveryoneElse }
size = { 20 } style = { styles.contextMenuItem }>
src = { IconMuteEveryoneElse } /> <Icon
<Text style = { styles.contextMenuItemText }> size = { 20 }
{ t('participantsPane.actions.muteEveryoneElse') } src = { IconMuteEveryoneElse } />
</Text> <Text style = { styles.contextMenuItemText }>
</TouchableOpacity> { t('participantsPane.actions.muteEveryoneElse') }
</Text>
</TouchableOpacity>
</>
)
} }
<Divider style = { styles.divider } /> <Divider style = { styles.divider } />
{ {
isLocalModerator && ( _isLocalModerator && (
isParticipantVideoMuted <>
|| <TouchableOpacity {
onPress = { muteVideo } _isParticipantVideoMuted
style = { styles.contextMenuItemSection }> || <TouchableOpacity
onPress = { muteVideo }
style = { styles.contextMenuItemSection }>
<Icon
size = { 20 }
src = { IconVideoOff } />
<Text style = { styles.contextMenuItemText }>
{ t('participantsPane.actions.stopVideo') }
</Text>
</TouchableOpacity>
}
<TouchableOpacity
onPress = { kickRemoteParticipant }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconCloseCircle } />
<Text style = { styles.contextMenuItemText }>
{ t('videothumbnail.kick') }
</Text>
</TouchableOpacity>
</>
)
}
{
_isChatButtonEnabled && (
<TouchableOpacity
onPress = { sendPrivateMessage }
style = { styles.contextMenuItem }>
<Icon <Icon
size = { 20 } size = { 20 }
src = { IconVideoOff } /> src = { IconMessage } />
<Text style = { styles.contextMenuItemText }> <Text style = { styles.contextMenuItemText }>
{ t('participantsPane.actions.stopVideo') } { t('toolbar.accessibilityLabel.privateMessage') }
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
) )
} }
{
isLocalModerator
&& <TouchableOpacity
onPress = { kickRemoteParticipant }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconCloseCircle } />
<Text style = { styles.contextMenuItemText }>
{ t('videothumbnail.kick') }
</Text>
</TouchableOpacity>
}
<TouchableOpacity
onPress = { sendPrivateMessage }
style = { styles.contextMenuItem }>
<Icon
size = { 20 }
src = { IconMessage } />
<Text style = { styles.contextMenuItemText }>
{ t('toolbar.accessibilityLabel.privateMessage') }
</Text>
</TouchableOpacity>
{/* We need design specs for this*/} {/* We need design specs for this*/}
{/* <TouchableOpacity*/} {/* <TouchableOpacity*/}
{/* style = { styles.contextMenuItemSection }>*/} {/* style = { styles.contextMenuItemSection }>*/}
@ -167,7 +220,36 @@ export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props)
{/* <Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.networkStats') }</Text>*/} {/* <Text style = { styles.contextMenuItemText }>{ t('participantsPane.actions.networkStats') }</Text>*/}
{/* </TouchableOpacity>*/} {/* </TouchableOpacity>*/}
<Divider style = { styles.divider } /> <Divider style = { styles.divider } />
<VolumeSlider participant = { p } /> <VolumeSlider participant = { _participant } />
</BottomSheet> </BottomSheet>
); );
}; };
/**
* 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 participant = getParticipantByIdOrUndefined(state, participantID);
const _isLocalModerator = isLocalParticipantModerator(state);
const _isChatButtonEnabled = isToolbarButtonEnabled('chat', state);
const _isParticipantVideoMuted = isParticipantVideoMuted(participant, state);
const _isParticipantAudioMuted = isParticipantAudioMuted(participant, state);
return {
_displayName: getParticipantDisplayName(state, participantID),
_isLocalModerator,
_isChatButtonEnabled,
_isParticipantAudioMuted,
_isParticipantVideoMuted,
_participant: participant
};
}
export default connect(_mapStateToProps)(ContextMenuMeetingParticipantDetails);

View File

@ -29,10 +29,13 @@ export const LobbyParticipantItem = ({ participant: p }: Props) => {
return ( return (
<ParticipantItem <ParticipantItem
audioMediaState = { MEDIA_STATE.NONE } audioMediaState = { MEDIA_STATE.NONE }
displayName = { p.name }
isKnockingParticipant = { true } isKnockingParticipant = { true }
name = { p.name } local = { p.local }
onPress = { openContextMenuReject } onPress = { openContextMenuReject }
participant = { p } participant = { p }
participantID = { p.id }
raisedHand = { p.raisedHand }
videoMediaState = { MEDIA_STATE.NONE }> videoMediaState = { MEDIA_STATE.NONE }>
<Button <Button
children = { t('lobby.admit') } children = { t('lobby.admit') }

View File

@ -1,14 +1,19 @@
// @flow // @flow
import React, { useCallback } from 'react'; import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { translate } from '../../../base/i18n';
import { import {
getIsParticipantAudioMuted, getParticipantByIdOrUndefined,
getIsParticipantVideoMuted getParticipantDisplayName
} from '../../../base/participants';
import { connect } from '../../../base/redux';
import {
isParticipantAudioMuted,
isParticipantVideoMuted
} from '../../../base/tracks'; } from '../../../base/tracks';
import { showContextMenuDetails } from '../../actions.native';
import { MEDIA_STATE } from '../../constants'; import { MEDIA_STATE } from '../../constants';
import type { MediaState } from '../../constants';
import { getParticipantAudioMediaState } from '../../functions'; import { getParticipantAudioMediaState } from '../../functions';
import ParticipantItem from './ParticipantItem'; import ParticipantItem from './ParticipantItem';
@ -17,26 +22,100 @@ import ParticipantItem from './ParticipantItem';
type Props = { type Props = {
/** /**
* Participant reference * Media state for audio.
*/ */
participant: Object _audioMediaState: MediaState,
/**
* The display name of the participant.
*/
_displayName: string,
/**
* True if the participant is video muted.
*/
_isVideoMuted: boolean,
/**
* True if the participant is the local participant.
*/
_local: boolean,
/**
* The participant ID.
*/
_participantID: string,
/**
* True if the participant have raised hand.
*/
_raisedHand: boolean,
/**
* Callback to invoke when item is pressed.
*/
onPress: Function,
/**
* The ID of the participant.
*/
participantID: ?string
}; };
export const MeetingParticipantItem = ({ participant: p }: Props) => { const MeetingParticipantItem = (
const dispatch = useDispatch(); {
const isAudioMuted = useSelector(getIsParticipantAudioMuted(p)); _audioMediaState,
const isVideoMuted = useSelector(getIsParticipantVideoMuted(p)); _displayName,
const audioMediaState = useSelector(getParticipantAudioMediaState(p, isAudioMuted)); _isVideoMuted,
const openContextMenuDetails = useCallback(() => !p.local && dispatch(showContextMenuDetails(p), [ dispatch ])); _local,
_participantID,
_raisedHand,
onPress
}: Props) => {
const showParticipantDetails = !_local && onPress;
return ( return (
<ParticipantItem <ParticipantItem
audioMediaState = { audioMediaState } audioMediaState = { _audioMediaState }
displayName = { _displayName }
isKnockingParticipant = { false } isKnockingParticipant = { false }
name = { p.name } local = { _local }
onPress = { openContextMenuDetails } onPress = { showParticipantDetails }
participant = { p } participantID = { _participantID }
videoMediaState = { isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } /> raisedHand = { _raisedHand }
videoMediaState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
); );
}; };
/**
* 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 participant = getParticipantByIdOrUndefined(state, participantID);
const _isAudioMuted = isParticipantAudioMuted(participant, state);
const isVideoMuted = isParticipantVideoMuted(participant, state);
const audioMediaState = getParticipantAudioMediaState(
participant, _isAudioMuted, state
);
return {
_audioMediaState: audioMediaState,
_displayName: getParticipantDisplayName(state, participant?.id),
_isAudioMuted,
_isVideoMuted: isVideoMuted,
_local: Boolean(participant?.local),
_participantID: participant?.id,
_raisedHand: Boolean(participant?.raisedHand)
};
}
export default translate(connect(mapStateToProps)(MeetingParticipantItem));

View File

@ -7,25 +7,49 @@ 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 { getParticipants } from '../../../base/participants'; import {
getLocalParticipant,
getParticipantCountWithFake,
getRemoteParticipants
} from '../../../base/participants';
import { doInvitePeople } from '../../../invite/actions.native'; import { doInvitePeople } from '../../../invite/actions.native';
import { showContextMenuDetails } 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 = () => { export const MeetingParticipantList = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const items = [];
const localParticipant = useSelector(getLocalParticipant);
const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]); const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]);
const participants = useSelector(getRemoteParticipants);
const participantsCount = useSelector(getParticipantCountWithFake);
const showInviteButton = useSelector(shouldRenderInviteButton); const showInviteButton = useSelector(shouldRenderInviteButton);
const participants = useSelector(getParticipants);
const { t } = useTranslation(); const { t } = useTranslation();
// eslint-disable-next-line react/no-multi-comp
const renderParticipant = id => (
<MeetingParticipantItem
key = { id }
/* eslint-disable-next-line react/jsx-no-bind */
onPress = { () => dispatch(showContextMenuDetails(id)) }
participantID = { id } />
);
localParticipant && items.push(renderParticipant(localParticipant?.id));
participants.forEach(p => {
items.push(renderParticipant(p?.id));
});
return ( return (
<View style = { styles.meetingList }> <View style = { styles.meetingList }>
<Text style = { styles.meetingListDescription }> <Text style = { styles.meetingListDescription }>
{t('participantsPane.headings.participantsList', {t('participantsPane.headings.participantsList',
{ count: participants.length })} { count: participantsCount })}
</Text> </Text>
{ {
showInviteButton showInviteButton
@ -42,13 +66,8 @@ export const MeetingParticipantList = () => {
onPress = { onInvite } onPress = { onInvite }
style = { styles.inviteButton } /> style = { styles.inviteButton } />
} }
{ { items }
participants.map(p => (
<MeetingParticipantItem
key = { p.id }
participant = { p } />)
)
}
</View> </View>
); );
}; };

View File

@ -5,10 +5,8 @@ import type { Node } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { TouchableOpacity, View } from 'react-native'; import { TouchableOpacity, View } from 'react-native';
import { Text } from 'react-native-paper'; import { Text } from 'react-native-paper';
import { useSelector } from 'react-redux';
import { Avatar } from '../../../base/avatar'; import { Avatar } from '../../../base/avatar';
import { getParticipantDisplayNameWithId } from '../../../base/participants';
import { MEDIA_STATE, type MediaState, AudioStateIcons, VideoStateIcons } from '../../constants'; import { MEDIA_STATE, type MediaState, AudioStateIcons, VideoStateIcons } from '../../constants';
import { RaisedHandIndicator } from './RaisedHandIndicator'; import { RaisedHandIndicator } from './RaisedHandIndicator';
@ -26,15 +24,20 @@ type Props = {
*/ */
children?: Node, children?: Node,
/**
* The name of the participant. Used for showing lobby names.
*/
displayName: string,
/** /**
* Is the participant waiting? * Is the participant waiting?
*/ */
isKnockingParticipant: boolean, isKnockingParticipant: boolean,
/** /**
* The name of the participant. Used for showing lobby names. * True if the participant is local.
*/ */
name?: string, local: boolean,
/** /**
* Callback to be invoked on pressing the participant item. * Callback to be invoked on pressing the participant item.
@ -42,9 +45,14 @@ type Props = {
onPress?: Function, onPress?: Function,
/** /**
* Participant reference * The ID of the participant.
*/ */
participant: Object, participantID: string,
/**
* True if the participant have raised hand.
*/
raisedHand: boolean,
/** /**
* Media state for video * Media state for video
@ -59,15 +67,16 @@ type Props = {
*/ */
function ParticipantItem({ function ParticipantItem({
children, children,
displayName,
isKnockingParticipant, isKnockingParticipant,
name, local,
onPress, onPress,
participant: p, participantID,
raisedHand,
audioMediaState = MEDIA_STATE.NONE, audioMediaState = MEDIA_STATE.NONE,
videoMediaState = MEDIA_STATE.NONE videoMediaState = MEDIA_STATE.NONE
}: Props) { }: Props) {
const displayName = name || useSelector(getParticipantDisplayNameWithId(p.id));
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -77,19 +86,19 @@ function ParticipantItem({
style = { styles.participantContent }> style = { styles.participantContent }>
<Avatar <Avatar
className = 'participant-avatar' className = 'participant-avatar'
participantId = { p.id } participantId = { participantID }
size = { 32 } /> size = { 32 } />
<View style = { styles.participantNameContainer }> <View style = { styles.participantNameContainer }>
<Text style = { styles.participantName }> <Text style = { styles.participantName }>
{ displayName } { displayName }
</Text> </Text>
{ p.local ? <Text style = { styles.isLocal }>({t('chat.you')})</Text> : null } { local ? <Text style = { styles.isLocal }>({t('chat.you')})</Text> : null }
</View> </View>
{ {
!isKnockingParticipant !isKnockingParticipant
&& <> && <>
{ {
p.raisedHand && <RaisedHandIndicator /> raisedHand && <RaisedHandIndicator />
} }
<View style = { styles.participantStatesContainer }> <View style = { styles.participantStatesContainer }>
<View style = { styles.participantStateVideo }>{VideoStateIcons[videoMediaState]}</View> <View style = { styles.participantStateVideo }>{VideoStateIcons[videoMediaState]}</View>
@ -98,7 +107,7 @@ function ParticipantItem({
</> </>
} }
</TouchableOpacity> </TouchableOpacity>
{ !p.local && children } { !local && children }
</View> </View>
); );
} }

View File

@ -2,9 +2,9 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { isToolbarButtonEnabled } from '../../base/config/functions.web'; import { isToolbarButtonEnabled } from '../../../base/config/functions.web';
import { openDialog } from '../../base/dialog'; import { openDialog } from '../../../base/dialog';
import { translate } from '../../base/i18n'; import { translate } from '../../../base/i18n';
import { import {
IconCloseCircle, IconCloseCircle,
IconCrown, IconCrown,
@ -12,18 +12,18 @@ import {
IconMicDisabled, IconMicDisabled,
IconMuteEveryoneElse, IconMuteEveryoneElse,
IconVideoOff IconVideoOff
} from '../../base/icons'; } from '../../../base/icons';
import { import {
getParticipantByIdOrUndefined, getParticipantByIdOrUndefined,
isLocalParticipantModerator, isLocalParticipantModerator,
isParticipantModerator isParticipantModerator
} from '../../base/participants'; } 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 { openChat } from '../../chat/actions'; import { openChat } from '../../../chat/actions';
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';
import { import {
ContextMenu, ContextMenu,

View File

@ -2,14 +2,14 @@
import React from 'react'; import React from 'react';
import { getParticipantByIdOrUndefined, getParticipantDisplayName } from '../../base/participants'; import { 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';
import { getParticipantAudioMediaState, getQuickActionButtonType } from '../functions'; import { getParticipantAudioMediaState, getQuickActionButtonType } from '../../functions';
import ParticipantQuickAction from '../ParticipantQuickAction';
import ParticipantItem from './ParticipantItem'; import ParticipantItem from './ParticipantItem';
import ParticipantQuickAction from './ParticipantQuickAction';
import { ParticipantActionEllipsis } from './styled'; import { ParticipantActionEllipsis } from './styled';
type Props = { type Props = {

View File

@ -4,14 +4,14 @@ import React, { useCallback, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector, useDispatch } from 'react-redux'; import { useSelector, useDispatch } from 'react-redux';
import { openDialog } from '../../base/dialog'; import { openDialog } from '../../../base/dialog';
import { import {
getLocalParticipant, getLocalParticipant,
getParticipantCountWithFake, getParticipantCountWithFake,
getRemoteParticipants getRemoteParticipants
} from '../../base/participants'; } from '../../../base/participants';
import MuteRemoteParticipantDialog from '../../video-menu/components/web/MuteRemoteParticipantDialog'; import MuteRemoteParticipantDialog from '../../../video-menu/components/web/MuteRemoteParticipantDialog';
import { findStyledAncestor, shouldRenderInviteButton } from '../functions'; import { findStyledAncestor, shouldRenderInviteButton } from '../../functions';
import { InviteButton } from './InviteButton'; import { InviteButton } from './InviteButton';
import MeetingParticipantContextMenu from './MeetingParticipantContextMenu'; import MeetingParticipantContextMenu from './MeetingParticipantContextMenu';

View File

@ -2,15 +2,15 @@
import React, { type Node } from 'react'; import React, { type Node } from 'react';
import { Avatar } from '../../base/avatar'; import { Avatar } from '../../../base/avatar';
import { import {
Icon, Icon,
IconCameraEmpty, IconCameraEmpty,
IconCameraEmptyDisabled, IconCameraEmptyDisabled,
IconMicrophoneEmpty, IconMicrophoneEmpty,
IconMicrophoneEmptySlash IconMicrophoneEmptySlash
} from '../../base/icons'; } from '../../../base/icons';
import { ACTION_TRIGGER, MEDIA_STATE, type ActionTrigger, type MediaState } from '../constants'; import { ACTION_TRIGGER, MEDIA_STATE, type ActionTrigger, type MediaState } from '../../constants';
import { RaisedHandIndicator } from './RaisedHandIndicator'; import { RaisedHandIndicator } from './RaisedHandIndicator';
import { import {

View File

@ -3,19 +3,19 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { openDialog } from '../../base/dialog'; import { openDialog } from '../../../base/dialog';
import { translate } from '../../base/i18n'; import { translate } from '../../../base/i18n';
import { import {
getParticipantCount, getParticipantCount,
isLocalParticipantModerator isLocalParticipantModerator
} from '../../base/participants'; } from '../../../base/participants';
import { connect } from '../../base/redux'; import { connect } from '../../../base/redux';
import { MuteEveryoneDialog } from '../../video-menu/components/'; import { MuteEveryoneDialog } from '../../../video-menu/components/';
import { close } from '../actions'; import { close } from '../../actions';
import { classList, findStyledAncestor, getParticipantsPaneOpen } from '../functions'; import { classList, findStyledAncestor, getParticipantsPaneOpen } from '../../functions';
import theme from '../theme.json'; import theme from '../../theme.json';
import { FooterContextMenu } from '../FooterContextMenu';
import { FooterContextMenu } from './FooterContextMenu';
import { LobbyParticipantList } from './LobbyParticipantList'; import { LobbyParticipantList } from './LobbyParticipantList';
import { MeetingParticipantList } from './MeetingParticipantList'; import { MeetingParticipantList } from './MeetingParticipantList';
import { import {

View File

@ -3,5 +3,5 @@ export * from './LobbyParticipantItem';
export * from './LobbyParticipantList'; export * from './LobbyParticipantList';
export * from './MeetingParticipantList'; export * from './MeetingParticipantList';
export { default as ParticipantsPane } from './ParticipantsPane'; export { default as ParticipantsPane } from './ParticipantsPane';
export * from './ParticipantsPaneButton'; export * from '../ParticipantsPaneButton';
export * from './RaisedHandIndicator'; export * from './RaisedHandIndicator';

View File

@ -5,8 +5,8 @@ import React, { PureComponent } from 'react';
import { Slider, View } from 'react-native'; import { Slider, View } from 'react-native';
import { withTheme } from 'react-native-paper'; import { withTheme } from 'react-native-paper';
import { translate } from '../../../base/i18n';
import { Icon, IconVolumeEmpty } from '../../../base/icons'; import { Icon, IconVolumeEmpty } from '../../../base/icons';
import { getParticipantByIdOrUndefined } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { setVolume } from '../../../participants-pane/actions.native'; import { setVolume } from '../../../participants-pane/actions.native';
import { VOLUME_SLIDER_SCALE } from '../../constants'; import { VOLUME_SLIDER_SCALE } from '../../constants';
@ -19,6 +19,11 @@ import styles from './styles';
*/ */
type Props = { type Props = {
/**
* Participant reference
*/
_participant: Object,
/** /**
* Whether the participant enters the conference silent. * Whether the participant enters the conference silent.
*/ */
@ -35,9 +40,9 @@ type Props = {
dispatch: Function, dispatch: Function,
/** /**
* Participant reference * The ID of the participant.
*/ */
participant: Object, participantID: string,
/** /**
* Theme used for styles. * Theme used for styles.
@ -126,8 +131,8 @@ class VolumeSlider extends PureComponent<Props, State> {
* @returns {void} * @returns {void}
*/ */
_onVolumeChange(volumeLevel) { _onVolumeChange(volumeLevel) {
const { dispatch, participant } = this.props; const { dispatch, _participant } = this.props;
const { id } = participant; const { id } = _participant;
dispatch(setVolume(id, volumeLevel)); dispatch(setVolume(id, volumeLevel));
} }
@ -142,16 +147,18 @@ class VolumeSlider extends PureComponent<Props, State> {
* @returns {Props} * @returns {Props}
*/ */
function mapStateToProps(state, ownProps): Object { function mapStateToProps(state, ownProps): Object {
const { participant } = ownProps; const { participantID } = ownProps;
const participant = getParticipantByIdOrUndefined(state, participantID);
const { id, local } = participant; const { id, local } = participant;
const { participantsVolume } = state['features/participants-pane']; const { participantsVolume } = state['features/participants-pane'];
const { startSilent } = state['features/base/config']; const { startSilent } = state['features/base/config'];
return { return {
_participant: participant,
_startSilent: Boolean(startSilent), _startSilent: Boolean(startSilent),
_volume: local ? undefined : participantsVolume[id] _volume: local ? undefined : participantsVolume[id]
}; };
} }
export default translate(connect(mapStateToProps)(withTheme(VolumeSlider))); export default connect(mapStateToProps)(withTheme(VolumeSlider));