Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

ReactionsMenu.tsx 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import React, { useCallback, useEffect } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { connect } from 'react-redux';
  4. import { makeStyles } from 'tss-react/mui';
  5. import { createReactionMenuEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
  6. import { sendAnalytics } from '../../../analytics/functions';
  7. import { IReduxState, IStore } from '../../../app/types';
  8. import { raiseHand } from '../../../base/participants/actions';
  9. import { getLocalParticipant, hasRaisedHand } from '../../../base/participants/functions';
  10. import GifsMenu from '../../../gifs/components/web/GifsMenu';
  11. import GifsMenuButton from '../../../gifs/components/web/GifsMenuButton';
  12. import { isGifEnabled, isGifsMenuOpen } from '../../../gifs/functions';
  13. import { dockToolbox } from '../../../toolbox/actions.web';
  14. import { addReactionToBuffer } from '../../actions.any';
  15. import { toggleReactionsMenuVisibility } from '../../actions.web';
  16. import {
  17. GIFS_MENU_HEIGHT_IN_OVERFLOW_MENU,
  18. RAISE_HAND_ROW_HEIGHT, REACTIONS,
  19. REACTIONS_MENU_HEIGHT_DRAWER,
  20. REACTIONS_MENU_HEIGHT_IN_OVERFLOW_MENU
  21. } from '../../constants';
  22. import { IReactionsMenuParent } from '../../types';
  23. import ReactionButton from './ReactionButton';
  24. interface IProps {
  25. /**
  26. * Docks the toolbox.
  27. */
  28. _dockToolbox: Function;
  29. /**
  30. * Whether or not the GIF feature is enabled.
  31. */
  32. _isGifEnabled: boolean;
  33. /**
  34. * Whether or not the GIF menu is visible.
  35. */
  36. _isGifMenuVisible: boolean;
  37. /**
  38. * The ID of the local participant.
  39. */
  40. _localParticipantID?: string;
  41. /**
  42. * Whether or not the local participant's hand is raised.
  43. */
  44. _raisedHand: boolean;
  45. /**
  46. * The Redux Dispatch function.
  47. */
  48. dispatch: IStore['dispatch'];
  49. /**
  50. * Indicates the parent of the reactions menu.
  51. */
  52. parent: IReactionsMenuParent;
  53. /**
  54. * Whether to show the raised hand button.
  55. */
  56. showRaisedHand?: boolean;
  57. }
  58. const useStyles = makeStyles<IProps>()((theme, props: IProps) => {
  59. const { parent, showRaisedHand, _isGifMenuVisible } = props;
  60. let reactionsMenuHeight = REACTIONS_MENU_HEIGHT_DRAWER;
  61. if (parent === IReactionsMenuParent.OverflowDrawer || parent === IReactionsMenuParent.OverflowMenu) {
  62. if (parent === IReactionsMenuParent.OverflowMenu) {
  63. reactionsMenuHeight = REACTIONS_MENU_HEIGHT_IN_OVERFLOW_MENU;
  64. if (_isGifMenuVisible) {
  65. reactionsMenuHeight += GIFS_MENU_HEIGHT_IN_OVERFLOW_MENU;
  66. }
  67. }
  68. if (!showRaisedHand) {
  69. reactionsMenuHeight -= RAISE_HAND_ROW_HEIGHT;
  70. }
  71. }
  72. return {
  73. reactionsMenuInOverflowMenu: {
  74. '&.reactions-menu': {
  75. '&.with-gif': {
  76. width: 'inherit'
  77. },
  78. '.reactions-row': {
  79. '.toolbox-icon': {
  80. width: '24px',
  81. height: '24px',
  82. 'span.emoji': {
  83. width: '24px',
  84. height: '24px',
  85. lineHeight: '24px',
  86. fontSize: '16px'
  87. }
  88. }
  89. },
  90. '.raise-hand-row': {
  91. '.toolbox-icon': {
  92. height: '32px'
  93. }
  94. }
  95. }
  96. },
  97. overflow: {
  98. width: 'auto',
  99. paddingBottom: 'max(env(safe-area-inset-bottom, 0), 16px)',
  100. backgroundColor: theme.palette.ui01,
  101. boxShadow: 'none',
  102. borderRadius: 0,
  103. position: 'relative',
  104. boxSizing: 'border-box',
  105. height: `${reactionsMenuHeight}px`
  106. }
  107. };
  108. });
  109. const _getReactionButtons = (dispatch: IStore['dispatch'], t: Function) => {
  110. let modifierKey = 'Alt';
  111. if (window.navigator?.platform) {
  112. if (window.navigator.platform.indexOf('Mac') !== -1) {
  113. modifierKey = '⌥';
  114. }
  115. }
  116. return Object.keys(REACTIONS).map(key => {
  117. /**
  118. * Sends reaction message.
  119. *
  120. * @returns {void}
  121. */
  122. function doSendReaction() {
  123. dispatch(addReactionToBuffer(key));
  124. sendAnalytics(createReactionMenuEvent(key));
  125. }
  126. return (<ReactionButton
  127. accessibilityLabel = { t(`toolbar.accessibilityLabel.${key}`) }
  128. icon = { REACTIONS[key].emoji }
  129. key = { key }
  130. // eslint-disable-next-line react/jsx-no-bind
  131. onClick = { doSendReaction }
  132. toggled = { false }
  133. tooltip = { `${t(`toolbar.${key}`)} (${modifierKey} + ${REACTIONS[key].shortcutChar})` } />);
  134. });
  135. };
  136. const ReactionsMenu = (props: IProps) => {
  137. const {
  138. _dockToolbox,
  139. _isGifEnabled,
  140. _isGifMenuVisible,
  141. _raisedHand,
  142. dispatch,
  143. parent,
  144. showRaisedHand = false
  145. } = props;
  146. const isInOverflowMenu
  147. = parent === IReactionsMenuParent.OverflowDrawer || parent === IReactionsMenuParent.OverflowMenu;
  148. const { classes, cx } = useStyles(props);
  149. const { t } = useTranslation();
  150. useEffect(() => {
  151. _dockToolbox(true);
  152. return () => {
  153. _dockToolbox(false);
  154. };
  155. }, []);
  156. const _doToggleRaiseHand = useCallback(() => {
  157. dispatch(raiseHand(!_raisedHand));
  158. }, [ _raisedHand ]);
  159. const _onToolbarToggleRaiseHand = useCallback(() => {
  160. sendAnalytics(createToolbarEvent(
  161. 'raise.hand',
  162. { enable: !_raisedHand }));
  163. _doToggleRaiseHand();
  164. dispatch(toggleReactionsMenuVisibility());
  165. }, [ _raisedHand ]);
  166. const buttons = _getReactionButtons(dispatch, t);
  167. if (_isGifEnabled) {
  168. buttons.push(<GifsMenuButton parent = { parent } />);
  169. }
  170. return (
  171. <div
  172. className = { cx('reactions-menu',
  173. parent === IReactionsMenuParent.OverflowMenu && classes.reactionsMenuInOverflowMenu,
  174. _isGifEnabled && 'with-gif',
  175. isInOverflowMenu && `overflow ${classes.overflow}`) }>
  176. {_isGifEnabled && _isGifMenuVisible
  177. && <GifsMenu
  178. columns = { parent === IReactionsMenuParent.OverflowMenu ? 1 : undefined }
  179. parent = { parent } />}
  180. <div className = 'reactions-row'>
  181. { buttons }
  182. </div>
  183. {showRaisedHand && (
  184. <div className = 'raise-hand-row'>
  185. <ReactionButton
  186. accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
  187. icon = '✋'
  188. key = 'raisehand'
  189. label = {
  190. `${t(`toolbar.${_raisedHand ? 'lowerYourHand' : 'raiseYourHand'}`)}
  191. ${isInOverflowMenu ? '' : ' (R)'}`
  192. }
  193. onClick = { _onToolbarToggleRaiseHand }
  194. toggled = { true } />
  195. </div>
  196. )}
  197. </div>
  198. );
  199. };
  200. /**
  201. * Function that maps parts of Redux state tree into component props.
  202. *
  203. * @param {Object} state - Redux state.
  204. * @returns {Object}
  205. */
  206. function mapStateToProps(state: IReduxState) {
  207. const localParticipant = getLocalParticipant(state);
  208. return {
  209. _localParticipantID: localParticipant?.id,
  210. _isGifEnabled: isGifEnabled(state),
  211. _isGifMenuVisible: isGifsMenuOpen(state),
  212. _raisedHand: hasRaisedHand(localParticipant)
  213. };
  214. }
  215. /**
  216. * Function that maps parts of Redux actions into component props.
  217. *
  218. * @param {Object} dispatch - Redux dispatch.
  219. * @returns {Object}
  220. */
  221. function mapDispatchToProps(dispatch: IStore['dispatch']) {
  222. return {
  223. dispatch,
  224. _dockToolbox: (dock: boolean) => dispatch(dockToolbox(dock))
  225. };
  226. }
  227. export default connect(mapStateToProps, mapDispatchToProps)(ReactionsMenu);