您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

MessageMenu.tsx 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import React, { useCallback, useRef, useState } from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { useTranslation } from 'react-i18next';
  4. import { useDispatch, useSelector } from 'react-redux';
  5. import { makeStyles } from 'tss-react/mui';
  6. import { IReduxState } from '../../../app/types';
  7. import { IconDotsHorizontal } from '../../../base/icons/svg';
  8. import { getParticipantById } from '../../../base/participants/functions';
  9. import Popover from '../../../base/popover/components/Popover.web';
  10. import Button from '../../../base/ui/components/web/Button';
  11. import { BUTTON_TYPES } from '../../../base/ui/constants.any';
  12. import { copyText } from '../../../base/util/copyText.web';
  13. import { handleLobbyChatInitialized, openChat } from '../../actions.web';
  14. export interface IProps {
  15. className?: string;
  16. isLobbyMessage: boolean;
  17. message: string;
  18. participantId: string;
  19. shouldDisplayChatMessageMenu: boolean;
  20. }
  21. const useStyles = makeStyles()(theme => {
  22. return {
  23. messageMenuButton: {
  24. padding: '2px'
  25. },
  26. menuItem: {
  27. padding: '8px 16px',
  28. cursor: 'pointer',
  29. color: 'white',
  30. '&:hover': {
  31. backgroundColor: theme.palette.action03
  32. }
  33. },
  34. menuPanel: {
  35. backgroundColor: theme.palette.ui03,
  36. borderRadius: theme.shape.borderRadius,
  37. boxShadow: theme.shadows[3],
  38. overflow: 'hidden'
  39. },
  40. copiedMessage: {
  41. position: 'fixed',
  42. backgroundColor: theme.palette.ui03,
  43. color: 'white',
  44. padding: '4px 8px',
  45. borderRadius: '4px',
  46. fontSize: '12px',
  47. zIndex: 1000,
  48. opacity: 0,
  49. transition: 'opacity 0.3s ease-in-out',
  50. pointerEvents: 'none'
  51. },
  52. showCopiedMessage: {
  53. opacity: 1
  54. }
  55. };
  56. });
  57. const MessageMenu = ({ message, participantId, isLobbyMessage, shouldDisplayChatMessageMenu }: IProps) => {
  58. const dispatch = useDispatch();
  59. const { classes, cx } = useStyles();
  60. const { t } = useTranslation();
  61. const [ isPopoverOpen, setIsPopoverOpen ] = useState(false);
  62. const [ showCopiedMessage, setShowCopiedMessage ] = useState(false);
  63. const [ popupPosition, setPopupPosition ] = useState({ top: 0,
  64. left: 0 });
  65. const buttonRef = useRef<HTMLDivElement>(null);
  66. const participant = useSelector((state: IReduxState) => getParticipantById(state, participantId));
  67. const handleMenuClick = useCallback(() => {
  68. setIsPopoverOpen(true);
  69. }, []);
  70. const handleClose = useCallback(() => {
  71. setIsPopoverOpen(false);
  72. }, []);
  73. const handlePrivateClick = useCallback(() => {
  74. if (isLobbyMessage) {
  75. dispatch(handleLobbyChatInitialized(participantId));
  76. } else {
  77. dispatch(openChat(participant));
  78. }
  79. handleClose();
  80. }, [ dispatch, isLobbyMessage, participant, participantId ]);
  81. const handleCopyClick = useCallback(() => {
  82. copyText(message)
  83. .then(success => {
  84. if (success) {
  85. if (buttonRef.current) {
  86. const rect = buttonRef.current.getBoundingClientRect();
  87. setPopupPosition({
  88. top: rect.top - 30,
  89. left: rect.left
  90. });
  91. }
  92. setShowCopiedMessage(true);
  93. setTimeout(() => {
  94. setShowCopiedMessage(false);
  95. }, 2000);
  96. } else {
  97. console.error('Failed to copy text');
  98. }
  99. })
  100. .catch(error => {
  101. console.error('Error copying text:', error);
  102. });
  103. handleClose();
  104. }, [ message ]);
  105. const popoverContent = (
  106. <div className = { classes.menuPanel }>
  107. {shouldDisplayChatMessageMenu && (
  108. <div
  109. className = { classes.menuItem }
  110. onClick = { handlePrivateClick }>
  111. {t('Private Message')}
  112. </div>
  113. )}
  114. <div
  115. className = { classes.menuItem }
  116. onClick = { handleCopyClick }>
  117. {t('Copy')}
  118. </div>
  119. </div>
  120. );
  121. return (
  122. <div>
  123. <div ref = { buttonRef }>
  124. <Popover
  125. content = { popoverContent }
  126. onPopoverClose = { handleClose }
  127. position = 'top'
  128. trigger = 'click'
  129. visible = { isPopoverOpen }>
  130. <Button
  131. accessibilityLabel = { t('toolbar.accessibilityLabel.moreOptions') }
  132. className = { classes.messageMenuButton }
  133. icon = { IconDotsHorizontal }
  134. onClick = { handleMenuClick }
  135. type = { BUTTON_TYPES.TERTIARY } />
  136. </Popover>
  137. </div>
  138. {showCopiedMessage && ReactDOM.createPortal(
  139. <div
  140. className = { cx(classes.copiedMessage, { [classes.showCopiedMessage]: showCopiedMessage }) }
  141. style = {{ top: `${popupPosition.top}px`,
  142. left: `${popupPosition.left}px` }}>
  143. {t('Message Copied')}
  144. </div>,
  145. document.body
  146. )}
  147. </div>
  148. );
  149. };
  150. export default MessageMenu;