Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

RemoteVideoMenuTriggerButton.tsx 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import React, { useCallback } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { batch, connect } from 'react-redux';
  4. import { makeStyles } from 'tss-react/mui';
  5. import { IReduxState, IStore } from '../../../app/types';
  6. import { isMobileBrowser } from '../../../base/environment/utils';
  7. import { IconDotsHorizontal } from '../../../base/icons/svg';
  8. import { getLocalParticipant, getParticipantById } from '../../../base/participants/functions';
  9. import { IParticipant } from '../../../base/participants/types';
  10. import Popover from '../../../base/popover/components/Popover.web';
  11. import { setParticipantContextMenuOpen } from '../../../base/responsive-ui/actions';
  12. import Button from '../../../base/ui/components/web/Button';
  13. import ConnectionIndicatorContent from
  14. '../../../connection-indicator/components/web/ConnectionIndicatorContent';
  15. import { THUMBNAIL_TYPE } from '../../../filmstrip/constants';
  16. import { renderConnectionStatus } from '../../actions.web';
  17. import FakeParticipantContextMenu from './FakeParticipantContextMenu';
  18. import ParticipantContextMenu from './ParticipantContextMenu';
  19. import { REMOTE_CONTROL_MENU_STATES } from './RemoteControlButton';
  20. /**
  21. * The type of the React {@code Component} props of
  22. * {@link RemoteVideoMenuTriggerButton}.
  23. */
  24. interface IProps {
  25. /**
  26. * Whether the remote video context menu is disabled.
  27. */
  28. _disabled: Boolean;
  29. /**
  30. * Shared video local participant owner.
  31. */
  32. _localVideoOwner?: boolean;
  33. /**
  34. * The position relative to the trigger the remote menu should display
  35. * from.
  36. */
  37. _menuPosition: string;
  38. /**
  39. * Participant reference.
  40. */
  41. _participant: IParticipant;
  42. /**
  43. * The ID for the participant on which the remote video menu will act.
  44. */
  45. _participantDisplayName: string;
  46. /**
  47. * The current state of the participant's remote control session.
  48. */
  49. _remoteControlState?: number;
  50. /**
  51. * Whether the popover should render the Connection Info stats.
  52. */
  53. _showConnectionInfo: Boolean;
  54. /**
  55. * Whether or not the button should be visible.
  56. */
  57. buttonVisible: boolean;
  58. /**
  59. * The redux dispatch function.
  60. */
  61. dispatch: IStore['dispatch'];
  62. /**
  63. * Hides popover.
  64. */
  65. hidePopover?: Function;
  66. /**
  67. * The ID for the participant on which the remote video menu will act.
  68. */
  69. participantID: string;
  70. /**
  71. * Whether the popover is visible or not.
  72. */
  73. popoverVisible?: boolean;
  74. /**
  75. * Shows popover.
  76. */
  77. showPopover?: Function;
  78. /**
  79. * The type of the thumbnail.
  80. */
  81. thumbnailType: string;
  82. }
  83. const useStyles = makeStyles()(() => {
  84. return {
  85. triggerButton: {
  86. padding: '3px !important',
  87. borderRadius: '4px',
  88. '& svg': {
  89. width: '18px',
  90. height: '18px'
  91. }
  92. },
  93. contextMenu: {
  94. position: 'relative',
  95. marginTop: 0,
  96. right: 'auto',
  97. marginRight: '4px',
  98. marginBottom: '4px'
  99. }
  100. };
  101. });
  102. const RemoteVideoMenuTriggerButton = ({
  103. _disabled,
  104. _localVideoOwner,
  105. _menuPosition,
  106. _participant,
  107. _participantDisplayName,
  108. _remoteControlState,
  109. _showConnectionInfo,
  110. buttonVisible,
  111. dispatch,
  112. hidePopover,
  113. participantID,
  114. popoverVisible,
  115. showPopover
  116. }: IProps) => {
  117. const { classes } = useStyles();
  118. const { t } = useTranslation();
  119. const _onPopoverOpen = useCallback(() => {
  120. showPopover?.();
  121. dispatch(setParticipantContextMenuOpen(true));
  122. }, []);
  123. const _onPopoverClose = useCallback(() => {
  124. hidePopover?.();
  125. batch(() => {
  126. dispatch(setParticipantContextMenuOpen(false));
  127. dispatch(renderConnectionStatus(false));
  128. });
  129. }, []);
  130. // eslint-disable-next-line react/no-multi-comp
  131. const _renderRemoteVideoMenu = () => {
  132. const props = {
  133. className: classes.contextMenu,
  134. onSelect: _onPopoverClose,
  135. participant: _participant,
  136. thumbnailMenu: true
  137. };
  138. if (_participant?.fakeParticipant) {
  139. return (
  140. <FakeParticipantContextMenu
  141. { ...props }
  142. localVideoOwner = { _localVideoOwner } />
  143. );
  144. }
  145. return (
  146. <ParticipantContextMenu
  147. { ...props }
  148. remoteControlState = { _remoteControlState } />
  149. );
  150. };
  151. let content;
  152. if (_showConnectionInfo) {
  153. content = <ConnectionIndicatorContent participantId = { participantID } />;
  154. } else if (!_disabled) {
  155. content = _renderRemoteVideoMenu();
  156. }
  157. if (!content) {
  158. return null;
  159. }
  160. const username = _participantDisplayName;
  161. return (
  162. <Popover
  163. content = { content }
  164. headingLabel = { t('dialog.remoteUserControls', { username }) }
  165. id = 'remote-video-menu-trigger'
  166. onPopoverClose = { _onPopoverClose }
  167. onPopoverOpen = { _onPopoverOpen }
  168. position = { _menuPosition }
  169. visible = { Boolean(popoverVisible) }>
  170. {buttonVisible && !_disabled && (
  171. !isMobileBrowser() && <Button
  172. accessibilityLabel = { t('dialog.remoteUserControls', { username }) }
  173. className = { classes.triggerButton }
  174. icon = { IconDotsHorizontal }
  175. size = 'small' />
  176. )}
  177. </Popover>
  178. );
  179. };
  180. /**
  181. * Maps (parts of) the Redux state to the associated {@code RemoteVideoMenuTriggerButton}'s props.
  182. *
  183. * @param {Object} state - The Redux state.
  184. * @param {Object} ownProps - The own props of the component.
  185. * @private
  186. * @returns {IProps}
  187. */
  188. function _mapStateToProps(state: IReduxState, ownProps: Partial<IProps>) {
  189. const { participantID, thumbnailType } = ownProps;
  190. let _remoteControlState;
  191. const localParticipantId = getLocalParticipant(state)?.id;
  192. const participant = getParticipantById(state, participantID ?? '');
  193. const _participantDisplayName = participant?.name;
  194. const _isRemoteControlSessionActive = participant?.remoteControlSessionStatus ?? false;
  195. const _supportsRemoteControl = participant?.supportsRemoteControl ?? false;
  196. const { active, controller } = state['features/remote-control'];
  197. const { requestedParticipant, controlled } = controller;
  198. const activeParticipant = requestedParticipant || controlled;
  199. const { showConnectionInfo } = state['features/base/connection'];
  200. const { remoteVideoMenu } = state['features/base/config'];
  201. const { ownerId } = state['features/shared-video'];
  202. if (_supportsRemoteControl
  203. && ((!active && !_isRemoteControlSessionActive) || activeParticipant === participantID)) {
  204. if (requestedParticipant === participantID) {
  205. _remoteControlState = REMOTE_CONTROL_MENU_STATES.REQUESTING;
  206. } else if (controlled) {
  207. _remoteControlState = REMOTE_CONTROL_MENU_STATES.STARTED;
  208. } else {
  209. _remoteControlState = REMOTE_CONTROL_MENU_STATES.NOT_STARTED;
  210. }
  211. }
  212. let _menuPosition;
  213. switch (thumbnailType) {
  214. case THUMBNAIL_TYPE.TILE:
  215. _menuPosition = 'left-start';
  216. break;
  217. case THUMBNAIL_TYPE.VERTICAL:
  218. _menuPosition = 'left-end';
  219. break;
  220. case THUMBNAIL_TYPE.HORIZONTAL:
  221. _menuPosition = 'top';
  222. break;
  223. default:
  224. _menuPosition = 'auto';
  225. }
  226. return {
  227. _disabled: Boolean(remoteVideoMenu?.disabled),
  228. _localVideoOwner: Boolean(ownerId === localParticipantId),
  229. _menuPosition,
  230. _participant: participant ?? { id: '' },
  231. _participantDisplayName: _participantDisplayName ?? '',
  232. _remoteControlState,
  233. _showConnectionInfo: Boolean(showConnectionInfo)
  234. };
  235. }
  236. export default connect(_mapStateToProps)(RemoteVideoMenuTriggerButton);