feat(native-participants-pane) implemented review remarks pt. 1
This commit is contained in:
parent
36cb896680
commit
c6e50ad439
|
@ -60,7 +60,15 @@ export function getRecordingSharingUrl(state: Object) {
|
||||||
return state['features/base/config'].recordingSharingUrl;
|
return state['features/base/config'].recordingSharingUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* eslint-disable max-params, no-shadow */
|
/**
|
||||||
|
* Selector used to get if the new participant is starting silent.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The global state.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getIsStartingSilent(state: Object) {
|
||||||
|
return Boolean(state['features/base/config'].startSilent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overrides JSON properties in {@code config} and
|
* Overrides JSON properties in {@code config} and
|
||||||
|
|
|
@ -56,9 +56,7 @@ class HeaderWithNavigation extends Component<Props> {
|
||||||
const { hideHeaderWithNavigation, onPressBack, onPressForward } = this.props;
|
const { hideHeaderWithNavigation, onPressBack, onPressForward } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
!{ hideHeaderWithNavigation }
|
||||||
{
|
|
||||||
!hideHeaderWithNavigation
|
|
||||||
&& <Header>
|
&& <Header>
|
||||||
{ onPressBack && <BackButton onPress = { onPressBack } /> }
|
{ onPressBack && <BackButton onPress = { onPressBack } /> }
|
||||||
<HeaderLabel labelKey = { this.props.headerLabelKey } />
|
<HeaderLabel labelKey = { this.props.headerLabelKey } />
|
||||||
|
@ -67,8 +65,6 @@ class HeaderWithNavigation extends Component<Props> {
|
||||||
labelKey = { this.props.forwardLabelKey }
|
labelKey = { this.props.forwardLabelKey }
|
||||||
onPress = { onPressForward } /> }
|
onPress = { onPressForward } /> }
|
||||||
</Header>
|
</Header>
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ type Props = AbstractProps & {
|
||||||
/**
|
/**
|
||||||
* The indicator which determines if the participants pane is open.
|
* The indicator which determines if the participants pane is open.
|
||||||
*/
|
*/
|
||||||
_isOpen: boolean,
|
_isParticipantsPaneOpen: boolean,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ID of the participant currently on stage (if any)
|
* The ID of the participant currently on stage (if any)
|
||||||
|
@ -243,7 +243,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||||
_renderContent() {
|
_renderContent() {
|
||||||
const {
|
const {
|
||||||
_connecting,
|
_connecting,
|
||||||
_isOpen,
|
_isParticipantsPaneOpen,
|
||||||
_largeVideoParticipantId,
|
_largeVideoParticipantId,
|
||||||
_reducedUI,
|
_reducedUI,
|
||||||
_shouldDisplayTileView
|
_shouldDisplayTileView
|
||||||
|
@ -309,7 +309,7 @@ class Conference extends AbstractConference<Props, *> {
|
||||||
|
|
||||||
{_shouldDisplayTileView && <Toolbox />}
|
{_shouldDisplayTileView && <Toolbox />}
|
||||||
|
|
||||||
{ _isOpen && <ParticipantsPane /> }
|
{ _isParticipantsPaneOpen && <ParticipantsPane /> }
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -423,7 +423,7 @@ function _mapStateToProps(state) {
|
||||||
_connecting: Boolean(connecting_),
|
_connecting: Boolean(connecting_),
|
||||||
_filmstripVisible: isFilmstripVisible(state),
|
_filmstripVisible: isFilmstripVisible(state),
|
||||||
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
|
_fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
|
||||||
_isOpen: isOpen,
|
_isParticipantsPaneOpen: isOpen,
|
||||||
_largeVideoParticipantId: state['features/large-video'].participantId,
|
_largeVideoParticipantId: state['features/large-video'].participantId,
|
||||||
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
_pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
|
||||||
_reducedUI: reducedUI,
|
_reducedUI: reducedUI,
|
||||||
|
|
|
@ -4,9 +4,10 @@ import React, { useCallback, useState } 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, useStore } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { Avatar } from '../../../base/avatar';
|
import { Avatar } from '../../../base/avatar';
|
||||||
|
import { getIsStartingSilent } 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 {
|
||||||
|
@ -37,14 +38,14 @@ type Props = {
|
||||||
|
|
||||||
export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) => {
|
export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) => {
|
||||||
const [ volume, setVolume ] = useState(undefined);
|
const [ volume, setVolume ] = useState(undefined);
|
||||||
const store = useStore();
|
|
||||||
const startSilent = store.getState['features/base/config'];
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||||
const changeVolume = useCallback(() => setVolume(volume), [ volume ]);
|
const changeVolume = useCallback(() => setVolume(volume), [ setVolume, volume ]);
|
||||||
const displayName = p.name;
|
const displayName = p.name;
|
||||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||||
const isParticipantVideoMuted = useSelector(getIsParticipantVideoMuted(p));
|
const isParticipantVideoMuted = useSelector(getIsParticipantVideoMuted(p));
|
||||||
|
const isStartingSilent = useSelector(getIsStartingSilent);
|
||||||
|
|
||||||
const kickRemoteParticipant = useCallback(() => {
|
const kickRemoteParticipant = useCallback(() => {
|
||||||
dispatch(openDialog(KickRemoteParticipantDialog, {
|
dispatch(openDialog(KickRemoteParticipantDialog, {
|
||||||
participantID: p.id
|
participantID: p.id
|
||||||
|
@ -65,7 +66,7 @@ export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props)
|
||||||
participantID: p.id
|
participantID: p.id
|
||||||
}));
|
}));
|
||||||
}, [ dispatch, p ]);
|
}, [ dispatch, p ]);
|
||||||
const onVolumeChange = startSilent ? undefined : changeVolume;
|
const onVolumeChange = isStartingSilent ? undefined : changeVolume;
|
||||||
const sendPrivateMessage = useCallback(() => {
|
const sendPrivateMessage = useCallback(() => {
|
||||||
dispatch(hideDialog());
|
dispatch(hideDialog());
|
||||||
dispatch(openChat(p));
|
dispatch(openChat(p));
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { TouchableOpacity } from 'react-native';
|
import { TouchableOpacity } from 'react-native';
|
||||||
import { Text } from 'react-native-paper';
|
import { Text } from 'react-native-paper';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { openDialog, hideDialog } from '../../../base/dialog/actions';
|
import { openDialog, hideDialog } from '../../../base/dialog/actions';
|
||||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||||
|
@ -12,26 +12,22 @@ import {
|
||||||
Icon, IconMicDisabledHollow,
|
Icon, IconMicDisabledHollow,
|
||||||
IconVideoOff
|
IconVideoOff
|
||||||
} from '../../../base/icons';
|
} from '../../../base/icons';
|
||||||
import { MEDIA_TYPE } from '../../../base/media';
|
import { getLocalParticipant } from '../../../base/participants';
|
||||||
import { BlockAudioVideoDialog } from '../../../video-menu';
|
import { BlockAudioVideoDialog } from '../../../video-menu';
|
||||||
import {
|
import MuteEveryonesVideoDialog
|
||||||
muteAllParticipants
|
from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
|
||||||
} from '../../../video-menu/actions.any';
|
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
type Props = {
|
|
||||||
|
|
||||||
/**
|
export const ContextMenuMore = () => {
|
||||||
* Array of participant IDs to not mute
|
|
||||||
*/
|
|
||||||
exclude: Array<string>
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ContextMenuMore = ({ exclude }: Props) => {
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const blockAudioVideo = useCallback(() => dispatch(openDialog(BlockAudioVideoDialog)), [ dispatch ]);
|
const blockAudioVideo = useCallback(() => dispatch(openDialog(BlockAudioVideoDialog)), [ dispatch ]);
|
||||||
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
|
||||||
const muteEveryoneVideo = useCallback(() => dispatch(muteAllParticipants(exclude, MEDIA_TYPE.VIDEO)), [ dispatch ]);
|
const { id } = useSelector(getLocalParticipant);
|
||||||
|
const muteAllVideo = useCallback(() =>
|
||||||
|
dispatch(openDialog(MuteEveryonesVideoDialog,
|
||||||
|
{ exclude: [ id ] })),
|
||||||
|
[ dispatch ]);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -39,7 +35,7 @@ export const ContextMenuMore = ({ exclude }: Props) => {
|
||||||
onCancel = { cancel }
|
onCancel = { cancel }
|
||||||
style = { styles.contextMenuMore }>
|
style = { styles.contextMenuMore }>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress = { muteEveryoneVideo }
|
onPress = { muteAllVideo }
|
||||||
style = { styles.contextMenuItem }>
|
style = { styles.contextMenuItem }>
|
||||||
<Icon
|
<Icon
|
||||||
size = { 20 }
|
size = { 20 }
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { useTranslation } from 'react-i18next';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
|
import { approveKnockingParticipant } from '../../../lobby/actions.native';
|
||||||
import { showContextMenuReject } from '../../actions.native';
|
import { showContextMenuReject } from '../../actions.native';
|
||||||
import { MediaState } from '../../constants';
|
import { MEDIA_STATE } from '../../constants';
|
||||||
|
|
||||||
import ParticipantItem from './ParticipantItem';
|
import ParticipantItem from './ParticipantItem';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -22,18 +22,18 @@ type Props = {
|
||||||
|
|
||||||
export const LobbyParticipantItem = ({ participant: p }: Props) => {
|
export const LobbyParticipantItem = ({ participant: p }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true), [ dispatch ]));
|
const admit = useCallback(() => dispatch(approveKnockingParticipant(p.id), [ dispatch ]));
|
||||||
const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
|
const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ParticipantItem
|
<ParticipantItem
|
||||||
audioMuteState = { MediaState.Muted }
|
audioMediaState = { MEDIA_STATE.NONE }
|
||||||
isKnockingParticipant = { true }
|
isKnockingParticipant = { true }
|
||||||
name = { p.name }
|
name = { p.name }
|
||||||
onPress = { openContextMenuReject }
|
onPress = { openContextMenuReject }
|
||||||
participant = { p }
|
participant = { p }
|
||||||
videoMuteState = { MediaState.ForceMuted }>
|
videoMediaState = { MEDIA_STATE.NONE }>
|
||||||
<Button
|
<Button
|
||||||
children = { t('lobby.admit') }
|
children = { t('lobby.admit') }
|
||||||
contentStyle = { styles.participantActionsButtonContent }
|
contentStyle = { styles.participantActionsButtonContent }
|
||||||
|
|
|
@ -6,8 +6,8 @@ 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 { admitMultiple } from '../../../lobby/actions.native';
|
||||||
import { getLobbyState } from '../../../lobby/functions';
|
import { getLobbyState } from '../../../lobby/functions';
|
||||||
import { admitAllKnockingParticipants } from '../../../video-menu/actions.any';
|
|
||||||
|
|
||||||
import { LobbyParticipantItem } from './LobbyParticipantItem';
|
import { LobbyParticipantItem } from './LobbyParticipantItem';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -20,7 +20,7 @@ export const LobbyParticipantList = () => {
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const admitAll = useCallback(() =>
|
const admitAll = useCallback(() =>
|
||||||
dispatch(admitAllKnockingParticipants(participants, lobbyEnabled)),
|
dispatch(admitMultiple(participants)),
|
||||||
[ dispatch ]);
|
[ dispatch ]);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
} from '../../../base/tracks';
|
} from '../../../base/tracks';
|
||||||
import { showContextMenuDetails } from '../../actions.native';
|
import { showContextMenuDetails } from '../../actions.native';
|
||||||
import { MEDIA_STATE } from '../../constants';
|
import { MEDIA_STATE } from '../../constants';
|
||||||
|
import { getParticipantAudioMediaState } from '../../functions';
|
||||||
|
|
||||||
import ParticipantItem from './ParticipantItem';
|
import ParticipantItem from './ParticipantItem';
|
||||||
|
|
||||||
|
@ -25,15 +26,17 @@ export const MeetingParticipantItem = ({ participant: p }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isAudioMuted = useSelector(getIsParticipantAudioMuted(p));
|
const isAudioMuted = useSelector(getIsParticipantAudioMuted(p));
|
||||||
const isVideoMuted = useSelector(getIsParticipantVideoMuted(p));
|
const isVideoMuted = useSelector(getIsParticipantVideoMuted(p));
|
||||||
|
const audioMediaState = useSelector(getParticipantAudioMediaState(p, isAudioMuted));
|
||||||
const openContextMenuDetails = useCallback(() => !p.local && dispatch(showContextMenuDetails(p), [ dispatch ]));
|
const openContextMenuDetails = useCallback(() => !p.local && dispatch(showContextMenuDetails(p), [ dispatch ]));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ParticipantItem
|
<ParticipantItem
|
||||||
audioMuteState = { isAudioMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED }
|
audioMediaState = { audioMediaState }
|
||||||
isKnockingParticipant = { false }
|
isKnockingParticipant = { false }
|
||||||
name = { p.name }
|
name = { p.name }
|
||||||
onPress = { openContextMenuDetails }
|
onPress = { openContextMenuDetails }
|
||||||
participant = { p }
|
participant = { p }
|
||||||
videoMuteState = { isVideoMuted ? MEDIA_STATE.Muted : MEDIA_STATE.Unmuted } />
|
videoMediaState = { isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Text, View } from 'react-native';
|
import { Text, View } from 'react-native';
|
||||||
import { Button } from 'react-native-paper';
|
import { Button } from 'react-native-paper';
|
||||||
import { useDispatch, useSelector, useStore } 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 { getParticipants } from '../../../base/participants';
|
||||||
|
@ -18,9 +18,7 @@ export const MeetingParticipantList = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]);
|
const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]);
|
||||||
const showInviteButton = useSelector(shouldRenderInviteButton);
|
const showInviteButton = useSelector(shouldRenderInviteButton);
|
||||||
const store = useStore();
|
const participants = useSelector(getParticipants);
|
||||||
const state = store.getState();
|
|
||||||
const participants = getParticipants(state);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -9,11 +9,7 @@ import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { Avatar } from '../../../base/avatar';
|
import { Avatar } from '../../../base/avatar';
|
||||||
import { getParticipantDisplayNameWithId } from '../../../base/participants';
|
import { getParticipantDisplayNameWithId } from '../../../base/participants';
|
||||||
import {
|
import { MEDIA_STATE, type MediaState, AudioStateIcons, VideoStateIcons } from '../../constants';
|
||||||
AudioStateIcons,
|
|
||||||
MediaState,
|
|
||||||
VideoStateIcons
|
|
||||||
} from '../../constants';
|
|
||||||
|
|
||||||
import { RaisedHandIndicator } from './RaisedHandIndicator';
|
import { RaisedHandIndicator } from './RaisedHandIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
@ -40,11 +36,6 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
name?: string,
|
name?: string,
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for when the mouse leaves this component
|
|
||||||
*/
|
|
||||||
onLeave?: Function,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback to be invoked on pressing the participant item.
|
* Callback to be invoked on pressing the participant item.
|
||||||
*/
|
*/
|
||||||
|
@ -72,8 +63,8 @@ function ParticipantItem({
|
||||||
name,
|
name,
|
||||||
onPress,
|
onPress,
|
||||||
participant: p,
|
participant: p,
|
||||||
audioMediaState = MediaState.None,
|
audioMediaState = MEDIA_STATE.NONE,
|
||||||
videoMediaState = MediaState.None
|
videoMediaState = MEDIA_STATE.NONE
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
|
||||||
const displayName = name || useSelector(getParticipantDisplayNameWithId(p.id));
|
const displayName = name || useSelector(getParticipantDisplayNameWithId(p.id));
|
||||||
|
@ -82,7 +73,6 @@ function ParticipantItem({
|
||||||
return (
|
return (
|
||||||
<View style = { styles.participantContainer } >
|
<View style = { styles.participantContainer } >
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
/* eslint-disable-next-line react/jsx-no-bind */
|
|
||||||
onPress = { onPress }
|
onPress = { onPress }
|
||||||
style = { styles.participantContent }>
|
style = { styles.participantContent }>
|
||||||
<Avatar
|
<Avatar
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { openDialog } from '../../../base/dialog';
|
||||||
import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
|
import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
|
||||||
import { JitsiModal } from '../../../base/modal';
|
import { JitsiModal } from '../../../base/modal';
|
||||||
import {
|
import {
|
||||||
|
getParticipantCount, isEveryoneModerator,
|
||||||
isLocalParticipantModerator
|
isLocalParticipantModerator
|
||||||
} from '../../../base/participants';
|
} from '../../../base/participants';
|
||||||
import MuteEveryoneDialog
|
import MuteEveryoneDialog
|
||||||
|
@ -19,7 +20,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, { button } from './styles';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Participant pane.
|
* Participant pane.
|
||||||
|
@ -31,6 +32,9 @@ const ParticipantsPane = () => {
|
||||||
const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
|
const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
|
||||||
const closePane = useCallback(() => dispatch(close()), [ dispatch ]);
|
const closePane = useCallback(() => dispatch(close()), [ dispatch ]);
|
||||||
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
const isLocalModerator = useSelector(isLocalParticipantModerator);
|
||||||
|
const participantsCount = useSelector(getParticipantCount);
|
||||||
|
const everyoneModerator = useSelector(isEveryoneModerator);
|
||||||
|
const showContextMenu = !everyoneModerator && participantsCount > 2;
|
||||||
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
|
const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
|
||||||
[ dispatch ]);
|
[ dispatch ]);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -65,8 +69,10 @@ const ParticipantsPane = () => {
|
||||||
labelStyle = { styles.muteAllLabel }
|
labelStyle = { styles.muteAllLabel }
|
||||||
mode = 'contained'
|
mode = 'contained'
|
||||||
onPress = { muteAll }
|
onPress = { muteAll }
|
||||||
style = { styles.muteAllButton } />
|
style = { showContextMenu ? styles.muteAllButton : button } />
|
||||||
<Button
|
{
|
||||||
|
showContextMenu
|
||||||
|
&& <Button
|
||||||
contentStyle = { styles.moreIcon }
|
contentStyle = { styles.moreIcon }
|
||||||
/* eslint-disable-next-line react/jsx-no-bind */
|
/* eslint-disable-next-line react/jsx-no-bind */
|
||||||
icon = { () =>
|
icon = { () =>
|
||||||
|
@ -77,6 +83,7 @@ const ParticipantsPane = () => {
|
||||||
mode = 'contained'
|
mode = 'contained'
|
||||||
onPress = { openMoreMenu }
|
onPress = { openMoreMenu }
|
||||||
style = { styles.moreButton } />
|
style = { styles.moreButton } />
|
||||||
|
}
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
</JitsiModal>
|
</JitsiModal>
|
||||||
|
|
|
@ -25,7 +25,7 @@ const flexContent = {
|
||||||
/**
|
/**
|
||||||
* The style of the participants pane buttons.
|
* The style of the participants pane buttons.
|
||||||
*/
|
*/
|
||||||
const button = {
|
export const button = {
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
backgroundColor: BaseTheme.palette.action02,
|
backgroundColor: BaseTheme.palette.action02,
|
||||||
borderRadius: BaseTheme.shape.borderRadius,
|
borderRadius: BaseTheme.shape.borderRadius,
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Icon, IconCameraEmpty, IconCameraEmptyDisabled,
|
||||||
|
IconMicrophoneEmpty,
|
||||||
|
IconMicrophoneEmptySlash
|
||||||
|
} from '../base/icons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reducer key for the feature.
|
* Reducer key for the feature.
|
||||||
*/
|
*/
|
||||||
|
@ -46,3 +54,49 @@ export const QUICK_ACTION_BUTTON: {
|
||||||
ASK_TO_UNMUTE: 'AskToUnmute',
|
ASK_TO_UNMUTE: 'AskToUnmute',
|
||||||
NONE: 'None'
|
NONE: 'None'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon mapping for possible participant audio states.
|
||||||
|
*/
|
||||||
|
export const AudioStateIcons: {[MediaState]: React$Element<any> | null} = {
|
||||||
|
[MEDIA_STATE.FORCE_MUTED]: (
|
||||||
|
<Icon
|
||||||
|
color = '#E04757'
|
||||||
|
size = { 16 }
|
||||||
|
src = { IconMicrophoneEmptySlash } />
|
||||||
|
),
|
||||||
|
[MEDIA_STATE.MUTED]: (
|
||||||
|
<Icon
|
||||||
|
size = { 16 }
|
||||||
|
src = { IconMicrophoneEmptySlash } />
|
||||||
|
),
|
||||||
|
[MEDIA_STATE.UNMUTED]: (
|
||||||
|
<Icon
|
||||||
|
color = '#1EC26A'
|
||||||
|
size = { 16 }
|
||||||
|
src = { IconMicrophoneEmpty } />
|
||||||
|
),
|
||||||
|
[MEDIA_STATE.NONE]: null
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon mapping for possible participant video states.
|
||||||
|
*/
|
||||||
|
export const VideoStateIcons = {
|
||||||
|
[MEDIA_STATE.FORCE_MUTED]: (
|
||||||
|
<Icon
|
||||||
|
size = { 16 }
|
||||||
|
src = { IconCameraEmptyDisabled } />
|
||||||
|
),
|
||||||
|
[MEDIA_STATE.MUTED]: (
|
||||||
|
<Icon
|
||||||
|
size = { 16 }
|
||||||
|
src = { IconCameraEmptyDisabled } />
|
||||||
|
),
|
||||||
|
[MEDIA_STATE.UNMUTED]: (
|
||||||
|
<Icon
|
||||||
|
size = { 16 }
|
||||||
|
src = { IconCameraEmpty } />
|
||||||
|
),
|
||||||
|
[MEDIA_STATE.NONE]: null
|
||||||
|
};
|
||||||
|
|
|
@ -24,7 +24,6 @@ import {
|
||||||
muteRemoteParticipant
|
muteRemoteParticipant
|
||||||
} from '../base/participants';
|
} from '../base/participants';
|
||||||
import { getIsParticipantAudioMuted } from '../base/tracks';
|
import { getIsParticipantAudioMuted } from '../base/tracks';
|
||||||
import { setKnockingParticipantApproval } from '../lobby/actions';
|
|
||||||
|
|
||||||
declare var APP: Object;
|
declare var APP: Object;
|
||||||
|
|
||||||
|
@ -109,24 +108,6 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MEDIA_TYP
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Admit all knocking participants.
|
|
||||||
*
|
|
||||||
* @param {Array<Object>} knockingParticipants - Array of participants waiting in lobby.
|
|
||||||
* @param {boolean} lobbyEnabled - Is lobby mode enabled.
|
|
||||||
*
|
|
||||||
* @returns {Function}
|
|
||||||
*/
|
|
||||||
export function admitAllKnockingParticipants(knockingParticipants: Array<Object>, lobbyEnabled: boolean) {
|
|
||||||
return (dispatch: Dispatch<any>) => {
|
|
||||||
const knockingParticipantsIds = knockingParticipants.map(participant => participant.id);
|
|
||||||
|
|
||||||
knockingParticipantsIds
|
|
||||||
.map(id => lobbyEnabled && setKnockingParticipantApproval(id, true))
|
|
||||||
.map(dispatch);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Don't allow participants to unmute video/audio.
|
* Don't allow participants to unmute video/audio.
|
||||||
|
|
Loading…
Reference in New Issue