You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

MeetingParticipantList.js 3.3KB

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