Browse Source

feat(native-participants-pane) implemented review remarks pt. 1

master
Calin Chitu 4 years ago
parent
commit
c6e50ad439

+ 9
- 1
react/features/base/config/functions.any.js View File

@@ -60,7 +60,15 @@ export function getRecordingSharingUrl(state: Object) {
60 60
     return state['features/base/config'].recordingSharingUrl;
61 61
 }
62 62
 
63
-/* eslint-disable max-params, no-shadow */
63
+/**
64
+ * Selector used to get if the new participant is starting silent.
65
+ *
66
+ * @param {Object} state - The global state.
67
+ * @returns {string}
68
+ */
69
+export function getIsStartingSilent(state: Object) {
70
+    return Boolean(state['features/base/config'].startSilent);
71
+}
64 72
 
65 73
 /**
66 74
  * Overrides JSON properties in {@code config} and

+ 1
- 5
react/features/base/react/components/native/HeaderWithNavigation.js View File

@@ -56,9 +56,7 @@ class HeaderWithNavigation extends Component<Props> {
56 56
         const { hideHeaderWithNavigation, onPressBack, onPressForward } = this.props;
57 57
 
58 58
         return (
59
-            <>
60
-                {
61
-                    !hideHeaderWithNavigation
59
+            !{ hideHeaderWithNavigation }
62 60
                     && <Header>
63 61
                         { onPressBack && <BackButton onPress = { onPressBack } /> }
64 62
                         <HeaderLabel labelKey = { this.props.headerLabelKey } />
@@ -67,8 +65,6 @@ class HeaderWithNavigation extends Component<Props> {
67 65
                             labelKey = { this.props.forwardLabelKey }
68 66
                             onPress = { onPressForward } /> }
69 67
                     </Header>
70
-                }
71
-            </>
72 68
         );
73 69
     }
74 70
 }

+ 4
- 4
react/features/conference/components/native/Conference.js View File

@@ -75,7 +75,7 @@ type Props = AbstractProps & {
75 75
     /**
76 76
      * The indicator which determines if the participants pane is open.
77 77
      */
78
-    _isOpen: boolean,
78
+    _isParticipantsPaneOpen: boolean,
79 79
 
80 80
     /**
81 81
      * The ID of the participant currently on stage (if any)
@@ -243,7 +243,7 @@ class Conference extends AbstractConference<Props, *> {
243 243
     _renderContent() {
244 244
         const {
245 245
             _connecting,
246
-            _isOpen,
246
+            _isParticipantsPaneOpen,
247 247
             _largeVideoParticipantId,
248 248
             _reducedUI,
249 249
             _shouldDisplayTileView
@@ -309,7 +309,7 @@ class Conference extends AbstractConference<Props, *> {
309 309
 
310 310
                 {_shouldDisplayTileView && <Toolbox />}
311 311
 
312
-                { _isOpen && <ParticipantsPane /> }
312
+                { _isParticipantsPaneOpen && <ParticipantsPane /> }
313 313
 
314 314
             </>
315 315
         );
@@ -423,7 +423,7 @@ function _mapStateToProps(state) {
423 423
         _connecting: Boolean(connecting_),
424 424
         _filmstripVisible: isFilmstripVisible(state),
425 425
         _fullscreenEnabled: getFeatureFlag(state, FULLSCREEN_ENABLED, true),
426
-        _isOpen: isOpen,
426
+        _isParticipantsPaneOpen: isOpen,
427 427
         _largeVideoParticipantId: state['features/large-video'].participantId,
428 428
         _pictureInPictureEnabled: getFeatureFlag(state, PIP_ENABLED),
429 429
         _reducedUI: reducedUI,

+ 6
- 5
react/features/participants-pane/components/native/ContextMenuMeetingParticipantDetails.js View File

@@ -4,9 +4,10 @@ import React, { useCallback, useState } from 'react';
4 4
 import { useTranslation } from 'react-i18next';
5 5
 import { TouchableOpacity, View } from 'react-native';
6 6
 import { Divider, Text } from 'react-native-paper';
7
-import { useDispatch, useSelector, useStore } from 'react-redux';
7
+import { useDispatch, useSelector } from 'react-redux';
8 8
 
9 9
 import { Avatar } from '../../../base/avatar';
10
+import { getIsStartingSilent } from '../../../base/config';
10 11
 import { hideDialog, openDialog } from '../../../base/dialog/actions';
11 12
 import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
12 13
 import {
@@ -37,14 +38,14 @@ type Props = {
37 38
 
38 39
 export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props) => {
39 40
     const [ volume, setVolume ] = useState(undefined);
40
-    const store = useStore();
41
-    const startSilent = store.getState['features/base/config'];
42 41
     const dispatch = useDispatch();
43 42
     const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
44
-    const changeVolume = useCallback(() => setVolume(volume), [ volume ]);
43
+    const changeVolume = useCallback(() => setVolume(volume), [ setVolume, volume ]);
45 44
     const displayName = p.name;
46 45
     const isLocalModerator = useSelector(isLocalParticipantModerator);
47 46
     const isParticipantVideoMuted = useSelector(getIsParticipantVideoMuted(p));
47
+    const isStartingSilent = useSelector(getIsStartingSilent);
48
+
48 49
     const kickRemoteParticipant = useCallback(() => {
49 50
         dispatch(openDialog(KickRemoteParticipantDialog, {
50 51
             participantID: p.id
@@ -65,7 +66,7 @@ export const ContextMenuMeetingParticipantDetails = ({ participant: p }: Props)
65 66
             participantID: p.id
66 67
         }));
67 68
     }, [ dispatch, p ]);
68
-    const onVolumeChange = startSilent ? undefined : changeVolume;
69
+    const onVolumeChange = isStartingSilent ? undefined : changeVolume;
69 70
     const sendPrivateMessage = useCallback(() => {
70 71
         dispatch(hideDialog());
71 72
         dispatch(openChat(p));

+ 11
- 15
react/features/participants-pane/components/native/ContextMenuMore.js View File

@@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
4 4
 import { useTranslation } from 'react-i18next';
5 5
 import { TouchableOpacity } from 'react-native';
6 6
 import { Text } from 'react-native-paper';
7
-import { useDispatch } from 'react-redux';
7
+import { useDispatch, useSelector } from 'react-redux';
8 8
 
9 9
 import { openDialog, hideDialog } from '../../../base/dialog/actions';
10 10
 import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
@@ -12,26 +12,22 @@ import {
12 12
     Icon, IconMicDisabledHollow,
13 13
     IconVideoOff
14 14
 } from '../../../base/icons';
15
-import { MEDIA_TYPE } from '../../../base/media';
15
+import { getLocalParticipant } from '../../../base/participants';
16 16
 import { BlockAudioVideoDialog } from '../../../video-menu';
17
-import {
18
-    muteAllParticipants
19
-} from '../../../video-menu/actions.any';
17
+import MuteEveryonesVideoDialog
18
+    from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
20 19
 
21 20
 import styles from './styles';
22
-type Props = {
23
-
24
-    /**
25
-     * Array of participant IDs to not mute
26
-     */
27
-    exclude: Array<string>
28
-};
29 21
 
30
-export const ContextMenuMore = ({ exclude }: Props) => {
22
+export const ContextMenuMore = () => {
31 23
     const dispatch = useDispatch();
32 24
     const blockAudioVideo = useCallback(() => dispatch(openDialog(BlockAudioVideoDialog)), [ dispatch ]);
33 25
     const cancel = useCallback(() => dispatch(hideDialog()), [ dispatch ]);
34
-    const muteEveryoneVideo = useCallback(() => dispatch(muteAllParticipants(exclude, MEDIA_TYPE.VIDEO)), [ dispatch ]);
26
+    const { id } = useSelector(getLocalParticipant);
27
+    const muteAllVideo = useCallback(() =>
28
+        dispatch(openDialog(MuteEveryonesVideoDialog,
29
+            { exclude: [ id ] })),
30
+        [ dispatch ]);
35 31
     const { t } = useTranslation();
36 32
 
37 33
     return (
@@ -39,7 +35,7 @@ export const ContextMenuMore = ({ exclude }: Props) => {
39 35
             onCancel = { cancel }
40 36
             style = { styles.contextMenuMore }>
41 37
             <TouchableOpacity
42
-                onPress = { muteEveryoneVideo }
38
+                onPress = { muteAllVideo }
43 39
                 style = { styles.contextMenuItem }>
44 40
                 <Icon
45 41
                     size = { 20 }

+ 5
- 5
react/features/participants-pane/components/native/LobbyParticipantItem.js View File

@@ -5,9 +5,9 @@ import { useTranslation } from 'react-i18next';
5 5
 import { Button } from 'react-native-paper';
6 6
 import { useDispatch } from 'react-redux';
7 7
 
8
-import { setKnockingParticipantApproval } from '../../../lobby/actions.native';
8
+import { approveKnockingParticipant } from '../../../lobby/actions.native';
9 9
 import { showContextMenuReject } from '../../actions.native';
10
-import { MediaState } from '../../constants';
10
+import { MEDIA_STATE } from '../../constants';
11 11
 
12 12
 import ParticipantItem from './ParticipantItem';
13 13
 import styles from './styles';
@@ -22,18 +22,18 @@ type Props = {
22 22
 
23 23
 export const LobbyParticipantItem = ({ participant: p }: Props) => {
24 24
     const dispatch = useDispatch();
25
-    const admit = useCallback(() => dispatch(setKnockingParticipantApproval(p.id, true), [ dispatch ]));
25
+    const admit = useCallback(() => dispatch(approveKnockingParticipant(p.id), [ dispatch ]));
26 26
     const openContextMenuReject = useCallback(() => dispatch(showContextMenuReject(p), [ dispatch ]));
27 27
     const { t } = useTranslation();
28 28
 
29 29
     return (
30 30
         <ParticipantItem
31
-            audioMuteState = { MediaState.Muted }
31
+            audioMediaState = { MEDIA_STATE.NONE }
32 32
             isKnockingParticipant = { true }
33 33
             name = { p.name }
34 34
             onPress = { openContextMenuReject }
35 35
             participant = { p }
36
-            videoMuteState = { MediaState.ForceMuted }>
36
+            videoMediaState = { MEDIA_STATE.NONE }>
37 37
             <Button
38 38
                 children = { t('lobby.admit') }
39 39
                 contentStyle = { styles.participantActionsButtonContent }

+ 2
- 2
react/features/participants-pane/components/native/LobbyParticipantList.js View File

@@ -6,8 +6,8 @@ import { Text, View } from 'react-native';
6 6
 import { Button } from 'react-native-paper';
7 7
 import { useDispatch, useSelector } from 'react-redux';
8 8
 
9
+import { admitMultiple } from '../../../lobby/actions.native';
9 10
 import { getLobbyState } from '../../../lobby/functions';
10
-import { admitAllKnockingParticipants } from '../../../video-menu/actions.any';
11 11
 
12 12
 import { LobbyParticipantItem } from './LobbyParticipantItem';
13 13
 import styles from './styles';
@@ -20,7 +20,7 @@ export const LobbyParticipantList = () => {
20 20
 
21 21
     const dispatch = useDispatch();
22 22
     const admitAll = useCallback(() =>
23
-        dispatch(admitAllKnockingParticipants(participants, lobbyEnabled)),
23
+        dispatch(admitMultiple(participants)),
24 24
         [ dispatch ]);
25 25
     const { t } = useTranslation();
26 26
 

+ 5
- 2
react/features/participants-pane/components/native/MeetingParticipantItem.js View File

@@ -9,6 +9,7 @@ import {
9 9
 } from '../../../base/tracks';
10 10
 import { showContextMenuDetails } from '../../actions.native';
11 11
 import { MEDIA_STATE } from '../../constants';
12
+import { getParticipantAudioMediaState } from '../../functions';
12 13
 
13 14
 import ParticipantItem from './ParticipantItem';
14 15
 
@@ -25,15 +26,17 @@ export const MeetingParticipantItem = ({ participant: p }: Props) => {
25 26
     const dispatch = useDispatch();
26 27
     const isAudioMuted = useSelector(getIsParticipantAudioMuted(p));
27 28
     const isVideoMuted = useSelector(getIsParticipantVideoMuted(p));
29
+    const audioMediaState = useSelector(getParticipantAudioMediaState(p, isAudioMuted));
28 30
     const openContextMenuDetails = useCallback(() => !p.local && dispatch(showContextMenuDetails(p), [ dispatch ]));
29 31
 
30 32
     return (
31 33
         <ParticipantItem
32
-            audioMuteState = { isAudioMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED }
34
+            audioMediaState = { audioMediaState }
33 35
             isKnockingParticipant = { false }
34 36
             name = { p.name }
35 37
             onPress = { openContextMenuDetails }
36 38
             participant = { p }
37
-            videoMuteState = { isVideoMuted ? MEDIA_STATE.Muted : MEDIA_STATE.Unmuted } />
39
+            videoMediaState = { isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED } />
38 40
     );
39 41
 };
42
+

+ 2
- 4
react/features/participants-pane/components/native/MeetingParticipantList.js View File

@@ -4,7 +4,7 @@ import React, { useCallback } from 'react';
4 4
 import { useTranslation } from 'react-i18next';
5 5
 import { Text, View } from 'react-native';
6 6
 import { Button } from 'react-native-paper';
7
-import { useDispatch, useSelector, useStore } from 'react-redux';
7
+import { useDispatch, useSelector } from 'react-redux';
8 8
 
9 9
 import { Icon, IconInviteMore } from '../../../base/icons';
10 10
 import { getParticipants } from '../../../base/participants';
@@ -18,9 +18,7 @@ export const MeetingParticipantList = () => {
18 18
     const dispatch = useDispatch();
19 19
     const onInvite = useCallback(() => dispatch(doInvitePeople()), [ dispatch ]);
20 20
     const showInviteButton = useSelector(shouldRenderInviteButton);
21
-    const store = useStore();
22
-    const state = store.getState();
23
-    const participants = getParticipants(state);
21
+    const participants = useSelector(getParticipants);
24 22
     const { t } = useTranslation();
25 23
 
26 24
     return (

+ 3
- 13
react/features/participants-pane/components/native/ParticipantItem.js View File

@@ -9,11 +9,7 @@ import { useSelector } from 'react-redux';
9 9
 
10 10
 import { Avatar } from '../../../base/avatar';
11 11
 import { getParticipantDisplayNameWithId } from '../../../base/participants';
12
-import {
13
-    AudioStateIcons,
14
-    MediaState,
15
-    VideoStateIcons
16
-} from '../../constants';
12
+import { MEDIA_STATE, type MediaState, AudioStateIcons, VideoStateIcons } from '../../constants';
17 13
 
18 14
 import { RaisedHandIndicator } from './RaisedHandIndicator';
19 15
 import styles from './styles';
@@ -40,11 +36,6 @@ type Props = {
40 36
      */
41 37
     name?: string,
42 38
 
43
-    /**
44
-     * Callback for when the mouse leaves this component
45
-     */
46
-    onLeave?: Function,
47
-
48 39
     /**
49 40
      * Callback to be invoked on pressing the participant item.
50 41
      */
@@ -72,8 +63,8 @@ function ParticipantItem({
72 63
     name,
73 64
     onPress,
74 65
     participant: p,
75
-    audioMediaState = MediaState.None,
76
-    videoMediaState = MediaState.None
66
+    audioMediaState = MEDIA_STATE.NONE,
67
+    videoMediaState = MEDIA_STATE.NONE
77 68
 }: Props) {
78 69
 
79 70
     const displayName = name || useSelector(getParticipantDisplayNameWithId(p.id));
@@ -82,7 +73,6 @@ function ParticipantItem({
82 73
     return (
83 74
         <View style = { styles.participantContainer } >
84 75
             <TouchableOpacity
85
-                /* eslint-disable-next-line react/jsx-no-bind */
86 76
                 onPress = { onPress }
87 77
                 style = { styles.participantContent }>
88 78
                 <Avatar

+ 20
- 13
react/features/participants-pane/components/native/ParticipantsPane.js View File

@@ -10,6 +10,7 @@ import { openDialog } from '../../../base/dialog';
10 10
 import { Icon, IconClose, IconHorizontalPoints } from '../../../base/icons';
11 11
 import { JitsiModal } from '../../../base/modal';
12 12
 import {
13
+    getParticipantCount, isEveryoneModerator,
13 14
     isLocalParticipantModerator
14 15
 } from '../../../base/participants';
15 16
 import MuteEveryoneDialog
@@ -19,7 +20,7 @@ import { close } from '../../actions.native';
19 20
 import { ContextMenuMore } from './ContextMenuMore';
20 21
 import { LobbyParticipantList } from './LobbyParticipantList';
21 22
 import { MeetingParticipantList } from './MeetingParticipantList';
22
-import styles from './styles';
23
+import styles, { button } from './styles';
23 24
 
24 25
 /**
25 26
  * Participant pane.
@@ -31,6 +32,9 @@ const ParticipantsPane = () => {
31 32
     const openMoreMenu = useCallback(() => dispatch(openDialog(ContextMenuMore)), [ dispatch ]);
32 33
     const closePane = useCallback(() => dispatch(close()), [ dispatch ]);
33 34
     const isLocalModerator = useSelector(isLocalParticipantModerator);
35
+    const participantsCount = useSelector(getParticipantCount);
36
+    const everyoneModerator = useSelector(isEveryoneModerator);
37
+    const showContextMenu = !everyoneModerator && participantsCount > 2;
34 38
     const muteAll = useCallback(() => dispatch(openDialog(MuteEveryoneDialog)),
35 39
         [ dispatch ]);
36 40
     const { t } = useTranslation();
@@ -65,18 +69,21 @@ const ParticipantsPane = () => {
65 69
                         labelStyle = { styles.muteAllLabel }
66 70
                         mode = 'contained'
67 71
                         onPress = { muteAll }
68
-                        style = { styles.muteAllButton } />
69
-                    <Button
70
-                        contentStyle = { styles.moreIcon }
71
-                        /* eslint-disable-next-line react/jsx-no-bind */
72
-                        icon = { () =>
73
-                            (<Icon
74
-                                size = { 24 }
75
-                                src = { IconHorizontalPoints } />)
76
-                        }
77
-                        mode = 'contained'
78
-                        onPress = { openMoreMenu }
79
-                        style = { styles.moreButton } />
72
+                        style = { showContextMenu ? styles.muteAllButton : button } />
73
+                    {
74
+                        showContextMenu
75
+                        && <Button
76
+                            contentStyle = { styles.moreIcon }
77
+                            /* eslint-disable-next-line react/jsx-no-bind */
78
+                            icon = { () =>
79
+                                (<Icon
80
+                                    size = { 24 }
81
+                                    src = { IconHorizontalPoints } />)
82
+                            }
83
+                            mode = 'contained'
84
+                            onPress = { openMoreMenu }
85
+                            style = { styles.moreButton } />
86
+                    }
80 87
                 </View>
81 88
             }
82 89
         </JitsiModal>

+ 1
- 1
react/features/participants-pane/components/native/styles.js View File

@@ -25,7 +25,7 @@ const flexContent = {
25 25
 /**
26 26
  * The style of the participants pane buttons.
27 27
  */
28
-const button = {
28
+export const button = {
29 29
     alignItems: 'center',
30 30
     backgroundColor: BaseTheme.palette.action02,
31 31
     borderRadius: BaseTheme.shape.borderRadius,

+ 54
- 0
react/features/participants-pane/constants.js View File

@@ -1,5 +1,13 @@
1 1
 // @flow
2 2
 
3
+import React from 'react';
4
+
5
+import {
6
+    Icon, IconCameraEmpty, IconCameraEmptyDisabled,
7
+    IconMicrophoneEmpty,
8
+    IconMicrophoneEmptySlash
9
+} from '../base/icons';
10
+
3 11
 /**
4 12
  * Reducer key for the feature.
5 13
  */
@@ -46,3 +54,49 @@ export const QUICK_ACTION_BUTTON: {
46 54
     ASK_TO_UNMUTE: 'AskToUnmute',
47 55
     NONE: 'None'
48 56
 };
57
+
58
+/**
59
+ * Icon mapping for possible participant audio states.
60
+ */
61
+export const AudioStateIcons: {[MediaState]: React$Element<any> | null} = {
62
+    [MEDIA_STATE.FORCE_MUTED]: (
63
+        <Icon
64
+            color = '#E04757'
65
+            size = { 16 }
66
+            src = { IconMicrophoneEmptySlash } />
67
+    ),
68
+    [MEDIA_STATE.MUTED]: (
69
+        <Icon
70
+            size = { 16 }
71
+            src = { IconMicrophoneEmptySlash } />
72
+    ),
73
+    [MEDIA_STATE.UNMUTED]: (
74
+        <Icon
75
+            color = '#1EC26A'
76
+            size = { 16 }
77
+            src = { IconMicrophoneEmpty } />
78
+    ),
79
+    [MEDIA_STATE.NONE]: null
80
+};
81
+
82
+/**
83
+ * Icon mapping for possible participant video states.
84
+ */
85
+export const VideoStateIcons = {
86
+    [MEDIA_STATE.FORCE_MUTED]: (
87
+        <Icon
88
+            size = { 16 }
89
+            src = { IconCameraEmptyDisabled } />
90
+    ),
91
+    [MEDIA_STATE.MUTED]: (
92
+        <Icon
93
+            size = { 16 }
94
+            src = { IconCameraEmptyDisabled } />
95
+    ),
96
+    [MEDIA_STATE.UNMUTED]: (
97
+        <Icon
98
+            size = { 16 }
99
+            src = { IconCameraEmpty } />
100
+    ),
101
+    [MEDIA_STATE.NONE]: null
102
+};

+ 0
- 19
react/features/video-menu/actions.any.js View File

@@ -24,7 +24,6 @@ import {
24 24
     muteRemoteParticipant
25 25
 } from '../base/participants';
26 26
 import { getIsParticipantAudioMuted } from '../base/tracks';
27
-import { setKnockingParticipantApproval } from '../lobby/actions';
28 27
 
29 28
 declare var APP: Object;
30 29
 
@@ -109,24 +108,6 @@ export function muteAllParticipants(exclude: Array<string>, mediaType: MEDIA_TYP
109 108
     };
110 109
 }
111 110
 
112
-/**
113
- * Admit all knocking participants.
114
- *
115
- * @param {Array<Object>} knockingParticipants - Array of participants waiting in lobby.
116
- * @param {boolean} lobbyEnabled - Is lobby mode enabled.
117
- *
118
- * @returns {Function}
119
- */
120
-export function admitAllKnockingParticipants(knockingParticipants: Array<Object>, lobbyEnabled: boolean) {
121
-    return (dispatch: Dispatch<any>) => {
122
-        const knockingParticipantsIds = knockingParticipants.map(participant => participant.id);
123
-
124
-        knockingParticipantsIds
125
-            .map(id => lobbyEnabled && setKnockingParticipantApproval(id, true))
126
-            .map(dispatch);
127
-    };
128
-}
129
-
130 111
 
131 112
 /**
132 113
  * Don't allow participants to unmute video/audio.

Loading…
Cancel
Save