Selaa lähdekoodia

fix(shared-video,video-menu) add ability to stop shared video from video menu

Specifically, in the bottom sheet (on mobile) and participants pane.
master
Calinteodor 4 vuotta sitten
vanhempi
commit
3c2ad24652
No account linked to committer's email address

+ 25
- 5
react/features/filmstrip/components/native/Thumbnail.js Näytä tiedosto

@@ -13,7 +13,8 @@ import {
13 13
     getParticipantCount,
14 14
     isEveryoneModerator,
15 15
     pinParticipant,
16
-    getParticipantByIdOrUndefined
16
+    getParticipantByIdOrUndefined,
17
+    getLocalParticipant
17 18
 } from '../../../base/participants';
18 19
 import { Container } from '../../../base/react';
19 20
 import { connect } from '../../../base/redux';
@@ -24,6 +25,8 @@ import { DisplayNameLabel } from '../../../display-name';
24 25
 import { toggleToolboxVisible } from '../../../toolbox/actions.native';
25 26
 import { RemoteVideoMenu } from '../../../video-menu';
26 27
 import ConnectionStatusComponent from '../../../video-menu/components/native/ConnectionStatusComponent';
28
+import SharedVideoMenu
29
+    from '../../../video-menu/components/native/SharedVideoMenu';
27 30
 
28 31
 import AudioMutedIndicator from './AudioMutedIndicator';
29 32
 import DominantSpeakerIndicator from './DominantSpeakerIndicator';
@@ -48,6 +51,11 @@ type Props = {
48 51
      */
49 52
     _largeVideo: Object,
50 53
 
54
+    /**
55
+     * Shared video local participant owner.
56
+     */
57
+    _localVideoOwner: boolean,
58
+
51 59
     /**
52 60
      * The Redux representation of the participant to display.
53 61
      */
@@ -116,6 +124,7 @@ function Thumbnail(props: Props) {
116 124
     const {
117 125
         _audioMuted: audioMuted,
118 126
         _largeVideo: largeVideo,
127
+        _localVideoOwner,
119 128
         _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
120 129
         _renderModeratorIndicator: renderModeratorIndicator,
121 130
         _participant: participant,
@@ -144,11 +153,19 @@ function Thumbnail(props: Props) {
144 153
             dispatch(openDialog(ConnectionStatusComponent, {
145 154
                 participantID: participant.id
146 155
             }));
147
-        } else {
148
-            dispatch(openDialog(RemoteVideoMenu, {
149
-                participant
150
-            }));
156
+        } else if (participant.isFakeParticipant) {
157
+            if (_localVideoOwner) {
158
+                dispatch(openDialog(SharedVideoMenu, {
159
+                    participant
160
+                }));
161
+            }
162
+
163
+            return null;
151 164
         }
165
+
166
+        dispatch(openDialog(RemoteVideoMenu, {
167
+            participant
168
+        }));
152 169
     }, [ participant, dispatch ]);
153 170
 
154 171
     return (
@@ -223,9 +240,11 @@ function _mapStateToProps(state, ownProps) {
223 240
     // filmstrip doesn't render the video of the participant who is rendered on
224 241
     // the stage i.e. as a large video.
225 242
     const largeVideo = state['features/large-video'];
243
+    const { ownerId } = state['features/shared-video'];
226 244
     const tracks = state['features/base/tracks'];
227 245
     const { participantID } = ownProps;
228 246
     const participant = getParticipantByIdOrUndefined(state, participantID);
247
+    const localParticipantId = getLocalParticipant(state).id;
229 248
     const id = participant?.id;
230 249
     const audioTrack
231 250
         = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.AUDIO, id);
@@ -240,6 +259,7 @@ function _mapStateToProps(state, ownProps) {
240 259
     return {
241 260
         _audioMuted: audioTrack?.muted ?? true,
242 261
         _largeVideo: largeVideo,
262
+        _localVideoOwner: Boolean(ownerId === localParticipantId),
243 263
         _participant: participant,
244 264
         _renderDominantSpeakerIndicator: renderDominantSpeakerIndicator,
245 265
         _renderModeratorIndicator: renderModeratorIndicator,

+ 11
- 0
react/features/participants-pane/actions.native.js Näytä tiedosto

@@ -1,6 +1,7 @@
1 1
 // @flow
2 2
 
3 3
 import { openDialog } from '../base/dialog';
4
+import { SharedVideoMenu } from '../video-menu';
4 5
 import ConnectionStatusComponent
5 6
     from '../video-menu/components/native/ConnectionStatusComponent';
6 7
 import RemoteVideoMenu from '../video-menu/components/native/RemoteVideoMenu';
@@ -42,6 +43,16 @@ export function showContextMenuDetails(participant: Object) {
42 43
     return openDialog(RemoteVideoMenu, { participant });
43 44
 }
44 45
 
46
+/**
47
+ * Displays the shared video menu.
48
+ *
49
+ * @param {Object} participant - The selected meeting participant.
50
+ * @returns {Function}
51
+ */
52
+export function showSharedVideoMenu(participant: Object) {
53
+    return openDialog(SharedVideoMenu, { participant });
54
+}
55
+
45 56
 /**
46 57
  * Sets the volume.
47 58
  *

+ 61
- 10
react/features/participants-pane/components/native/MeetingParticipantList.js Näytä tiedosto

@@ -6,20 +6,34 @@ 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
+
9 10
 import { Icon, IconInviteMore } from '../../../base/icons';
10 11
 import {
11 12
     getLocalParticipant,
12 13
     getParticipantCountWithFake,
13 14
     getRemoteParticipants
14 15
 } from '../../../base/participants';
16
+import { connect } from '../../../base/redux';
15 17
 import { doInvitePeople } from '../../../invite/actions.native';
16
-import { showConnectionStatus, showContextMenuDetails } from '../../actions.native';
18
+import {
19
+    showConnectionStatus,
20
+    showContextMenuDetails,
21
+    showSharedVideoMenu
22
+} from '../../actions.native';
17 23
 import { shouldRenderInviteButton } from '../../functions';
18 24
 
19 25
 import MeetingParticipantItem from './MeetingParticipantItem';
20 26
 import styles from './styles';
21 27
 
22
-export const MeetingParticipantList = () => {
28
+type Props = {
29
+
30
+    /**
31
+     * Shared video local participant owner.
32
+     */
33
+    _localVideoOwner: boolean
34
+}
35
+
36
+const MeetingParticipantList = ({ _localVideoOwner }: Props) => {
23 37
     const dispatch = useDispatch();
24 38
     const items = [];
25 39
     const localParticipant = useSelector(getLocalParticipant);
@@ -30,14 +44,34 @@ export const MeetingParticipantList = () => {
30 44
     const { t } = useTranslation();
31 45
 
32 46
     // eslint-disable-next-line react/no-multi-comp
33
-    const renderParticipant = p => (
34
-        <MeetingParticipantItem
35
-            key = { p.id }
36
-            /* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
37
-            onPress = { () => p.local
38
-                ? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) }
39
-            participantID = { p.id } />
40
-    );
47
+    const renderParticipant = p => {
48
+        if (p.isFakeParticipant) {
49
+            if (_localVideoOwner) {
50
+                return (
51
+                    <MeetingParticipantItem
52
+                        key = { p.id }
53
+                        /* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
54
+                        onPress = { () => dispatch(showSharedVideoMenu(p)) }
55
+                        participantID = { p.id } />
56
+                );
57
+            }
58
+
59
+            return (
60
+                <MeetingParticipantItem
61
+                    key = { p.id }
62
+                    participantID = { p.id } />
63
+            );
64
+        }
65
+
66
+        return (
67
+            <MeetingParticipantItem
68
+                key = { p.id }
69
+                /* eslint-disable-next-line react/jsx-no-bind,no-confusing-arrow */
70
+                onPress = { () => p.local
71
+                    ? dispatch(showConnectionStatus(p.id)) : dispatch(showContextMenuDetails(p)) }
72
+                participantID = { p.id } />
73
+        );
74
+    };
41 75
 
42 76
     items.push(renderParticipant(localParticipant));
43 77
 
@@ -71,3 +105,20 @@ export const MeetingParticipantList = () => {
71 105
     );
72 106
 };
73 107
 
108
+/**
109
+ * Maps (parts of) the redux state to the associated props for this component.
110
+ *
111
+ * @param {Object} state - The Redux state.
112
+ * @private
113
+ * @returns {Props}
114
+ */
115
+function _mapStateToProps(state): Object {
116
+    const { ownerId } = state['features/shared-video'];
117
+    const localParticipantId = getLocalParticipant(state).id;
118
+
119
+    return {
120
+        _localVideoOwner: Boolean(ownerId === localParticipantId)
121
+    };
122
+}
123
+
124
+export default connect(_mapStateToProps)(MeetingParticipantList);

+ 1
- 1
react/features/participants-pane/components/native/ParticipantsPane.js Näytä tiedosto

@@ -19,7 +19,7 @@ import { close } from '../../actions.native';
19 19
 
20 20
 import { ContextMenuMore } from './ContextMenuMore';
21 21
 import LobbyParticipantList from './LobbyParticipantList';
22
-import { MeetingParticipantList } from './MeetingParticipantList';
22
+import MeetingParticipantList from './MeetingParticipantList';
23 23
 import styles from './styles';
24 24
 
25 25
 /**

+ 1
- 0
react/features/participants-pane/components/native/index.js Näytä tiedosto

@@ -1,5 +1,6 @@
1 1
 // @flow
2 2
 
3
+export { default as MeetingParticipantList } from './MeetingParticipantList';
3 4
 export { default as ParticipantsPane } from './ParticipantsPane';
4 5
 export { default as ParticipantsPaneButton } from './ParticipantsPaneButton';
5 6
 export { default as ContextMenuLobbyParticipantReject } from './ContextMenuLobbyParticipantReject';

+ 94
- 54
react/features/participants-pane/components/web/MeetingParticipantContextMenu.js Näytä tiedosto

@@ -11,9 +11,11 @@ import {
11 11
     IconMessage,
12 12
     IconMicDisabled,
13 13
     IconMuteEveryoneElse,
14
+    IconShareVideo,
14 15
     IconVideoOff
15 16
 } from '../../../base/icons';
16 17
 import {
18
+    getLocalParticipant,
17 19
     getParticipantByIdOrUndefined,
18 20
     isLocalParticipantModerator,
19 21
     isParticipantModerator
@@ -21,6 +23,7 @@ import {
21 23
 import { connect } from '../../../base/redux';
22 24
 import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
23 25
 import { openChat } from '../../../chat/actions';
26
+import { stopSharedVideo } from '../../../shared-video/actions.any';
24 27
 import { GrantModeratorDialog, KickRemoteParticipantDialog, MuteEveryoneDialog } from '../../../video-menu';
25 28
 import MuteRemoteParticipantsVideoDialog from '../../../video-menu/components/web/MuteRemoteParticipantsVideoDialog';
26 29
 import { getComputedOuterHeight } from '../../functions';
@@ -60,6 +63,11 @@ type Props = {
60 63
      */
61 64
     _isParticipantAudioMuted: boolean,
62 65
 
66
+    /**
67
+     * Shared video local participant owner.
68
+     */
69
+    _localVideoOwner: boolean,
70
+
63 71
     /**
64 72
      * Participant reference
65 73
      */
@@ -143,6 +151,7 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
143 151
         this._onMuteEveryoneElse = this._onMuteEveryoneElse.bind(this);
144 152
         this._onMuteVideo = this._onMuteVideo.bind(this);
145 153
         this._onSendPrivateMessage = this._onSendPrivateMessage.bind(this);
154
+        this._onStopSharedVideo = this._onStopSharedVideo.bind(this);
146 155
         this._position = this._position.bind(this);
147 156
     }
148 157
 
@@ -176,6 +185,19 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
176 185
         }));
177 186
     }
178 187
 
188
+    _onStopSharedVideo: () => void;
189
+
190
+    /**
191
+     * Stops shared video.
192
+     *
193
+     * @returns {void}
194
+     */
195
+    _onStopSharedVideo() {
196
+        const { dispatch } = this.props;
197
+
198
+        dispatch(stopSharedVideo());
199
+    }
200
+
179 201
     _onMuteEveryoneElse: () => void;
180 202
 
181 203
     /**
@@ -282,6 +304,7 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
282 304
             _isParticipantModerator,
283 305
             _isParticipantVideoMuted,
284 306
             _isParticipantAudioMuted,
307
+            _localVideoOwner,
285 308
             _participant,
286 309
             onEnter,
287 310
             onLeave,
@@ -302,66 +325,81 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
302 325
                 onClick = { onSelect }
303 326
                 onMouseEnter = { onEnter }
304 327
                 onMouseLeave = { onLeave }>
305
-                <ContextMenuItemGroup>
306
-                    {
307
-                        _isLocalModerator && (
308
-                            <>
328
+                {
329
+                    !_participant.isFakeParticipant && (
330
+                        <>
331
+                            <ContextMenuItemGroup>
309 332
                                 {
310
-                                    !_isParticipantAudioMuted
311
-                                        && <ContextMenuItem onClick = { muteAudio(_participant) }>
312
-                                            <ContextMenuIcon src = { IconMicDisabled } />
313
-                                            <span>{t('dialog.muteParticipantButton')}</span>
314
-                                        </ContextMenuItem>
333
+                                    _isLocalModerator && (
334
+                                        <>
335
+                                            {
336
+                                                !_isParticipantAudioMuted
337
+                                                && <ContextMenuItem onClick = { muteAudio(_participant) }>
338
+                                                    <ContextMenuIcon src = { IconMicDisabled } />
339
+                                                    <span>{t('dialog.muteParticipantButton')}</span>
340
+                                                </ContextMenuItem>
341
+                                            }
342
+
343
+                                            <ContextMenuItem onClick = { this._onMuteEveryoneElse }>
344
+                                                <ContextMenuIcon src = { IconMuteEveryoneElse } />
345
+                                                <span>{t('toolbar.accessibilityLabel.muteEveryoneElse')}</span>
346
+                                            </ContextMenuItem>
347
+                                        </>
348
+                                    )
349
+                                }
350
+
351
+                                {
352
+                                    _isLocalModerator && (
353
+                                        _isParticipantVideoMuted || (
354
+                                            <ContextMenuItem onClick = { this._onMuteVideo }>
355
+                                                <ContextMenuIcon src = { IconVideoOff } />
356
+                                                <span>{t('participantsPane.actions.stopVideo')}</span>
357
+                                            </ContextMenuItem>
358
+                                        )
359
+                                    )
315 360
                                 }
361
+                            </ContextMenuItemGroup>
316 362
 
317
-                                <ContextMenuItem onClick = { this._onMuteEveryoneElse }>
318
-                                    <ContextMenuIcon src = { IconMuteEveryoneElse } />
319
-                                    <span>{t('toolbar.accessibilityLabel.muteEveryoneElse')}</span>
320
-                                </ContextMenuItem>
321
-                            </>
322
-                        )
323
-                    }
324
-
325
-                    {
326
-                        _isLocalModerator && (
327
-                            _isParticipantVideoMuted || (
328
-                                <ContextMenuItem onClick = { this._onMuteVideo }>
329
-                                    <ContextMenuIcon src = { IconVideoOff } />
330
-                                    <span>{t('participantsPane.actions.stopVideo')}</span>
331
-                                </ContextMenuItem>
332
-                            )
333
-                        )
334
-                    }
335
-                </ContextMenuItemGroup>
336
-
337
-                <ContextMenuItemGroup>
338
-                    {
339
-                        _isLocalModerator && (
340
-                            <>
363
+                            <ContextMenuItemGroup>
341 364
                                 {
342
-                                    !_isParticipantModerator && (
343
-                                        <ContextMenuItem onClick = { this._onGrantModerator }>
344
-                                            <ContextMenuIcon src = { IconCrown } />
345
-                                            <span>{t('toolbar.accessibilityLabel.grantModerator')}</span>
365
+                                    _isLocalModerator && (
366
+                                        <>
367
+                                            {
368
+                                                !_isParticipantModerator && (
369
+                                                    <ContextMenuItem onClick = { this._onGrantModerator }>
370
+                                                        <ContextMenuIcon src = { IconCrown } />
371
+                                                        <span>{t('toolbar.accessibilityLabel.grantModerator')}</span>
372
+                                                    </ContextMenuItem>
373
+                                                )
374
+                                            }
375
+                                            <ContextMenuItem onClick = { this._onKick }>
376
+                                                <ContextMenuIcon src = { IconCloseCircle } />
377
+                                                <span>{ t('videothumbnail.kick') }</span>
378
+                                            </ContextMenuItem>
379
+                                        </>
380
+                                    )
381
+                                }
382
+                                {
383
+                                    _isChatButtonEnabled && (
384
+                                        <ContextMenuItem onClick = { this._onSendPrivateMessage }>
385
+                                            <ContextMenuIcon src = { IconMessage } />
386
+                                            <span>{t('toolbar.accessibilityLabel.privateMessage')}</span>
346 387
                                         </ContextMenuItem>
347 388
                                     )
348 389
                                 }
349
-                                <ContextMenuItem onClick = { this._onKick }>
350
-                                    <ContextMenuIcon src = { IconCloseCircle } />
351
-                                    <span>{ t('videothumbnail.kick') }</span>
352
-                                </ContextMenuItem>
353
-                            </>
354
-                        )
355
-                    }
356
-                    {
357
-                        _isChatButtonEnabled && (
358
-                            <ContextMenuItem onClick = { this._onSendPrivateMessage }>
359
-                                <ContextMenuIcon src = { IconMessage } />
360
-                                <span>{t('toolbar.accessibilityLabel.privateMessage')}</span>
361
-                            </ContextMenuItem>
362
-                        )
363
-                    }
364
-                </ContextMenuItemGroup>
390
+                            </ContextMenuItemGroup>
391
+                        </>
392
+                    )
393
+                }
394
+
395
+                {
396
+                    _participant.isFakeParticipant && _localVideoOwner && (
397
+                        <ContextMenuItem onClick = { this._onStopSharedVideo }>
398
+                            <ContextMenuIcon src = { IconShareVideo } />
399
+                            <span>{t('toolbar.stopSharedVideo')}</span>
400
+                        </ContextMenuItem>
401
+                    )
402
+                }
365 403
             </ContextMenu>
366 404
         );
367 405
     }
@@ -377,7 +415,8 @@ class MeetingParticipantContextMenu extends Component<Props, State> {
377 415
  */
378 416
 function _mapStateToProps(state, ownProps): Object {
379 417
     const { participantID } = ownProps;
380
-
418
+    const { ownerId } = state['features/shared-video'];
419
+    const localParticipantId = getLocalParticipant(state).id;
381 420
     const participant = getParticipantByIdOrUndefined(state, participantID);
382 421
 
383 422
     const _isLocalModerator = isLocalParticipantModerator(state);
@@ -392,6 +431,7 @@ function _mapStateToProps(state, ownProps): Object {
392 431
         _isParticipantModerator,
393 432
         _isParticipantVideoMuted,
394 433
         _isParticipantAudioMuted,
434
+        _localVideoOwner: Boolean(ownerId === localParticipantId),
395 435
         _participant: participant
396 436
     };
397 437
 }

+ 43
- 10
react/features/participants-pane/components/web/MeetingParticipantItem.js Näytä tiedosto

@@ -2,7 +2,11 @@
2 2
 
3 3
 import React from 'react';
4 4
 
5
-import { getParticipantByIdOrUndefined, getParticipantDisplayName } from '../../../base/participants';
5
+import {
6
+    getLocalParticipant,
7
+    getParticipantByIdOrUndefined,
8
+    getParticipantDisplayName
9
+} from '../../../base/participants';
6 10
 import { connect } from '../../../base/redux';
7 11
 import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks';
8 12
 import { ACTION_TRIGGER, MEDIA_STATE, type MediaState } from '../../constants';
@@ -34,6 +38,16 @@ type Props = {
34 38
      */
35 39
     _local: boolean,
36 40
 
41
+    /**
42
+     * Shared video local participant owner.
43
+     */
44
+    _localVideoOwner: boolean,
45
+
46
+    /**
47
+     * The participant.
48
+     */
49
+    _participant: Object,
50
+
37 51
     /**
38 52
      * The participant ID.
39 53
      *
@@ -108,7 +122,9 @@ function MeetingParticipantItem({
108 122
     _audioMediaState,
109 123
     _displayName,
110 124
     _isVideoMuted,
125
+    _localVideoOwner,
111 126
     _local,
127
+    _participant,
112 128
     _participantID,
113 129
     _quickActionButtonType,
114 130
     _raisedHand,
@@ -133,15 +149,28 @@ function MeetingParticipantItem({
133 149
             raisedHand = { _raisedHand }
134 150
             videoMuteState = { _isVideoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.UNMUTED }
135 151
             youText = { youText }>
136
-            <ParticipantQuickAction
137
-                askUnmuteText = { askUnmuteText }
138
-                buttonType = { _quickActionButtonType }
139
-                muteAudio = { muteAudio }
140
-                muteParticipantButtonText = { muteParticipantButtonText }
141
-                participantID = { _participantID } />
142
-            <ParticipantActionEllipsis
143
-                aria-label = { participantActionEllipsisLabel }
144
-                onClick = { onContextMenu } />
152
+            {
153
+                !_participant.isFakeParticipant && (
154
+                    <>
155
+                        <ParticipantQuickAction
156
+                            askUnmuteText = { askUnmuteText }
157
+                            buttonType = { _quickActionButtonType }
158
+                            muteAudio = { muteAudio }
159
+                            muteParticipantButtonText = { muteParticipantButtonText }
160
+                            participantID = { _participantID } />
161
+                        <ParticipantActionEllipsis
162
+                            aria-label = { participantActionEllipsisLabel }
163
+                            onClick = { onContextMenu } />
164
+                    </>
165
+                )
166
+            }
167
+            {
168
+                _participant.isFakeParticipant && _localVideoOwner && (
169
+                    <ParticipantActionEllipsis
170
+                        aria-label = { participantActionEllipsisLabel }
171
+                        onClick = { onContextMenu } />
172
+                )
173
+            }
145 174
         </ParticipantItem>
146 175
     );
147 176
 }
@@ -156,6 +185,8 @@ function MeetingParticipantItem({
156 185
  */
157 186
 function _mapStateToProps(state, ownProps): Object {
158 187
     const { participantID } = ownProps;
188
+    const { ownerId } = state['features/shared-video'];
189
+    const localParticipantId = getLocalParticipant(state).id;
159 190
 
160 191
     const participant = getParticipantByIdOrUndefined(state, participantID);
161 192
 
@@ -170,6 +201,8 @@ function _mapStateToProps(state, ownProps): Object {
170 201
         _isAudioMuted,
171 202
         _isVideoMuted,
172 203
         _local: Boolean(participant?.local),
204
+        _localVideoOwner: Boolean(ownerId === localParticipantId),
205
+        _participant: participant,
173 206
         _participantID: participant?.id,
174 207
         _quickActionButtonType,
175 208
         _raisedHand: Boolean(participant?.raisedHand)

+ 0
- 1
react/features/participants-pane/components/web/styled.js Näytä tiedosto

@@ -235,7 +235,6 @@ export const ParticipantActionsHover = styled(ParticipantActions)`
235 235
     position: absolute;
236 236
     top: 0;
237 237
     transform: translateX(-100%);
238
-    width: 40px;
239 238
   }
240 239
 `;
241 240
 

+ 10
- 1
react/features/video-menu/actions.native.js Näytä tiedosto

@@ -1,7 +1,7 @@
1 1
 // @flow
2 2
 import { hideDialog } from '../base/dialog';
3 3
 
4
-import { RemoteVideoMenu } from './components/native';
4
+import { RemoteVideoMenu, SharedVideoMenu } from './components/native';
5 5
 
6 6
 /**
7 7
  * Hides the remote video menu.
@@ -12,4 +12,13 @@ export function hideRemoteVideoMenu() {
12 12
     return hideDialog(RemoteVideoMenu);
13 13
 }
14 14
 
15
+/**
16
+ * Hides the shared video menu.
17
+ *
18
+ * @returns {Function}
19
+ */
20
+export function hideSharedVideoMenu() {
21
+    return hideDialog(SharedVideoMenu);
22
+}
23
+
15 24
 export * from './actions.any';

+ 180
- 0
react/features/video-menu/components/native/SharedVideoMenu.js Näytä tiedosto

@@ -0,0 +1,180 @@
1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+import { Text, View } from 'react-native';
5
+import { Divider } from 'react-native-paper';
6
+
7
+import { Avatar } from '../../../base/avatar';
8
+import { ColorSchemeRegistry } from '../../../base/color-scheme';
9
+import { BottomSheet, isDialogOpen } from '../../../base/dialog';
10
+import {
11
+    getParticipantById,
12
+    getParticipantDisplayName
13
+} from '../../../base/participants';
14
+import { connect } from '../../../base/redux';
15
+import { StyleType } from '../../../base/styles';
16
+import { SharedVideoButton } from '../../../shared-video/components';
17
+import { hideSharedVideoMenu } from '../../actions.native';
18
+
19
+import styles from './styles';
20
+
21
+
22
+/**
23
+ * Size of the rendered avatar in the menu.
24
+ */
25
+const AVATAR_SIZE = 24;
26
+
27
+type Props = {
28
+
29
+    /**
30
+     * The Redux dispatch function.
31
+     */
32
+    dispatch: Function,
33
+
34
+    /**
35
+     * The participant for which this menu opened for.
36
+     */
37
+    participant: Object,
38
+
39
+    /**
40
+     * The color-schemed stylesheet of the BottomSheet.
41
+     */
42
+    _bottomSheetStyles: StyleType,
43
+
44
+    /**
45
+     * True if the menu is currently open, false otherwise.
46
+     */
47
+    _isOpen: boolean,
48
+
49
+    /**
50
+     * Whether the participant is present in the room or not.
51
+     */
52
+    _isParticipantAvailable?: boolean,
53
+
54
+    /**
55
+     * Display name of the participant retrieved from Redux.
56
+     */
57
+    _participantDisplayName: string,
58
+
59
+    /**
60
+     * The ID of the participant.
61
+     */
62
+    _participantID: ?string,
63
+}
64
+
65
+// eslint-disable-next-line prefer-const
66
+let SharedVideoMenu_;
67
+
68
+/**
69
+ * Class to implement a popup menu that opens upon long pressing a fake participant thumbnail.
70
+ */
71
+class SharedVideoMenu extends PureComponent<Props> {
72
+    /**
73
+     * Constructor of the component.
74
+     *
75
+     * @inheritdoc
76
+     */
77
+    constructor(props: Props) {
78
+        super(props);
79
+
80
+        this._onCancel = this._onCancel.bind(this);
81
+        this._renderMenuHeader = this._renderMenuHeader.bind(this);
82
+    }
83
+
84
+    /**
85
+     * Implements {@code Component#render}.
86
+     *
87
+     * @inheritdoc
88
+     */
89
+    render() {
90
+        const {
91
+            _isParticipantAvailable,
92
+            participant
93
+        } = this.props;
94
+
95
+        const buttonProps = {
96
+            afterClick: this._onCancel,
97
+            showLabel: true,
98
+            participantID: participant.id,
99
+            styles: this.props._bottomSheetStyles.buttons
100
+        };
101
+
102
+        return (
103
+            <BottomSheet
104
+                onCancel = { this._onCancel }
105
+                renderHeader = { this._renderMenuHeader }
106
+                showSlidingView = { _isParticipantAvailable }>
107
+                <Divider style = { styles.divider } />
108
+                <SharedVideoButton { ...buttonProps } />
109
+            </BottomSheet>
110
+        );
111
+    }
112
+
113
+    _onCancel: () => boolean;
114
+
115
+    /**
116
+     * Callback to hide the {@code SharedVideoMenu}.
117
+     *
118
+     * @private
119
+     * @returns {boolean}
120
+     */
121
+    _onCancel() {
122
+        if (this.props._isOpen) {
123
+            this.props.dispatch(hideSharedVideoMenu());
124
+
125
+            return true;
126
+        }
127
+
128
+        return false;
129
+    }
130
+
131
+    _renderMenuHeader: () => React$Element<any>;
132
+
133
+    /**
134
+     * Function to render the menu's header.
135
+     *
136
+     * @returns {React$Element}
137
+     */
138
+    _renderMenuHeader() {
139
+        const { _bottomSheetStyles, participant } = this.props;
140
+
141
+        return (
142
+            <View
143
+                style = { [
144
+                    _bottomSheetStyles.sheet,
145
+                    styles.participantNameContainer ] }>
146
+                <Avatar
147
+                    participantId = { participant.id }
148
+                    size = { AVATAR_SIZE } />
149
+                <Text style = { styles.participantNameLabel }>
150
+                    { this.props._participantDisplayName }
151
+                </Text>
152
+            </View>
153
+        );
154
+    }
155
+}
156
+
157
+/**
158
+ * Function that maps parts of Redux state tree into component props.
159
+ *
160
+ * @param {Object} state - Redux state.
161
+ * @param {Object} ownProps - Properties of component.
162
+ * @private
163
+ * @returns {Props}
164
+ */
165
+function _mapStateToProps(state, ownProps) {
166
+    const { participant } = ownProps;
167
+    const isParticipantAvailable = getParticipantById(state, participant.id);
168
+
169
+    return {
170
+        _bottomSheetStyles: ColorSchemeRegistry.get(state, 'BottomSheet'),
171
+        _isOpen: isDialogOpen(state, SharedVideoMenu_),
172
+        _isParticipantAvailable: Boolean(isParticipantAvailable),
173
+        _participantDisplayName: getParticipantDisplayName(state, participant.id),
174
+        _participantID: participant.id
175
+    };
176
+}
177
+
178
+SharedVideoMenu_ = connect(_mapStateToProps)(SharedVideoMenu);
179
+
180
+export default SharedVideoMenu_;

+ 1
- 0
react/features/video-menu/components/native/index.js Näytä tiedosto

@@ -8,4 +8,5 @@ export { default as MuteEveryonesVideoDialog } from './MuteEveryonesVideoDialog'
8 8
 export { default as MuteRemoteParticipantDialog } from './MuteRemoteParticipantDialog';
9 9
 export { default as MuteRemoteParticipantsVideoDialog } from './MuteRemoteParticipantsVideoDialog';
10 10
 export { default as RemoteVideoMenu } from './RemoteVideoMenu';
11
+export { default as SharedVideoMenu } from './SharedVideoMenu';
11 12
 export { default as VolumeSlider } from './VolumeSlider';

Loading…
Peruuta
Tallenna