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

MeetingParticipantList.js 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. // @flow
  2. import _ from 'lodash';
  3. import React, { useCallback, useRef, useState } from 'react';
  4. import { useTranslation } from 'react-i18next';
  5. import { useSelector, useDispatch } from 'react-redux';
  6. import { openDialog } from '../../base/dialog';
  7. import { getParticipants } from '../../base/participants';
  8. import MuteRemoteParticipantDialog from '../../video-menu/components/web/MuteRemoteParticipantDialog';
  9. import { findStyledAncestor, shouldRenderInviteButton } from '../functions';
  10. import { InviteButton } from './InviteButton';
  11. import { MeetingParticipantContextMenu } from './MeetingParticipantContextMenu';
  12. import { MeetingParticipantItem } from './MeetingParticipantItem';
  13. import { Heading, ParticipantContainer } from './styled';
  14. type NullProto = {
  15. [key: string]: any,
  16. __proto__: null
  17. };
  18. type RaiseContext = NullProto | {
  19. /**
  20. * Target elements against which positioning calculations are made
  21. */
  22. offsetTarget?: HTMLElement,
  23. /**
  24. * Participant reference
  25. */
  26. participant?: Object,
  27. };
  28. const initialState = Object.freeze(Object.create(null));
  29. export const MeetingParticipantList = () => {
  30. const dispatch = useDispatch();
  31. const isMouseOverMenu = useRef(false);
  32. const participants = useSelector(getParticipants, _.isEqual);
  33. const showInviteButton = useSelector(shouldRenderInviteButton);
  34. const [ raiseContext, setRaiseContext ] = useState<RaiseContext>(initialState);
  35. const { t } = useTranslation();
  36. const lowerMenu = useCallback(() => {
  37. /**
  38. * We are tracking mouse movement over the active participant item and
  39. * the context menu. Due to the order of enter/leave events, we need to
  40. * defer checking if the mouse is over the context menu with
  41. * queueMicrotask
  42. */
  43. window.queueMicrotask(() => {
  44. if (isMouseOverMenu.current) {
  45. return;
  46. }
  47. if (raiseContext !== initialState) {
  48. setRaiseContext(initialState);
  49. }
  50. });
  51. }, [ raiseContext ]);
  52. const raiseMenu = useCallback((participant, target) => {
  53. setRaiseContext({
  54. participant,
  55. offsetTarget: findStyledAncestor(target, ParticipantContainer)
  56. });
  57. }, [ raiseContext ]);
  58. const toggleMenu = useCallback(participant => e => {
  59. const { participant: raisedParticipant } = raiseContext;
  60. if (raisedParticipant && raisedParticipant === participant) {
  61. lowerMenu();
  62. } else {
  63. raiseMenu(participant, e.target);
  64. }
  65. }, [ raiseContext ]);
  66. const menuEnter = useCallback(() => {
  67. isMouseOverMenu.current = true;
  68. }, []);
  69. const menuLeave = useCallback(() => {
  70. isMouseOverMenu.current = false;
  71. lowerMenu();
  72. }, [ lowerMenu ]);
  73. const muteAudio = useCallback(id => () => {
  74. dispatch(openDialog(MuteRemoteParticipantDialog, { participantID: id }));
  75. });
  76. return (
  77. <>
  78. <Heading>{t('participantsPane.headings.participantsList', { count: participants.length })}</Heading>
  79. {showInviteButton && <InviteButton />}
  80. <div>
  81. {participants.map(p => (
  82. <MeetingParticipantItem
  83. isHighlighted = { raiseContext.participant === p }
  84. key = { p.id }
  85. muteAudio = { muteAudio }
  86. onContextMenu = { toggleMenu(p) }
  87. onLeave = { lowerMenu }
  88. participant = { p } />
  89. ))}
  90. </div>
  91. <MeetingParticipantContextMenu
  92. muteAudio = { muteAudio }
  93. onEnter = { menuEnter }
  94. onLeave = { menuLeave }
  95. onSelect = { lowerMenu }
  96. { ...raiseContext } />
  97. </>
  98. );
  99. };