import React, { Fragment, useCallback, useState } from 'react'; import { useDebounce, useMedia } from 'react-use'; import { HMSPeer, HMSPeerType, HMSRoleName, selectAvailableRoleNames, selectHandRaisedPeers, selectHasPeerHandRaised, selectIsLargeRoom, selectIsPeerAudioEnabled, selectLocalPeerID, selectPeerCount, selectPermissions, useHMSStore, } from '@100mslive/react-sdk'; import { AddIcon, CallIcon, ChangeRoleIcon, CrossIcon, HandIcon, MicOffIcon, PeopleIcon, PersonSettingsIcon, SearchIcon, VerticalMenuIcon, } from '@100mslive/react-icons'; import { Accordion, Box, Button, config as cssConfig, Dropdown, Flex, Input, Text, textEllipsis } from '../../..'; // @ts-ignore: No implicit Any import IconButton from '../../IconButton'; import { ConnectionIndicator } from '../Connection/ConnectionIndicator'; import { RemoveParticipant } from '../RemoveParticipant'; import { RoleChangeModal } from '../RoleChangeModal'; import { RoleAccordion } from './RoleAccordion'; import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen'; // @ts-ignore: No implicit Any import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane'; import { useSidepaneResetOnLayoutUpdate } from '../AppData/useSidepaneResetOnLayoutUpdate'; import { usePeerOnStageActions } from '../hooks/usePeerOnStageActions'; import { useParticipants } from '../../common/hooks'; // @ts-ignore: No implicit Any import { getFormattedCount } from '../../common/utils'; import { SIDE_PANE_OPTIONS } from '../../common/constants'; export const ParticipantList = ({ offStageRoles = [], onActive, }: { offStageRoles: HMSRoleName[]; onActive: (role: string) => void; }) => { const [filter, setFilter] = useState<{ search?: string } | undefined>(); const { participants, isConnected, peerCount } = useParticipants(filter); const isLargeRoom = useHMSStore(selectIsLargeRoom); const peersOrderedByRoles: Record = {}; const handRaisedPeers = useHMSStore(selectHandRaisedPeers); participants.forEach(participant => { if (participant.roleName) { if (peersOrderedByRoles[participant.roleName] === undefined) { peersOrderedByRoles[participant.roleName] = []; } peersOrderedByRoles[participant.roleName].push(participant); } }); // prefill off_stage roles of large rooms to load more peers if (isLargeRoom) { offStageRoles.forEach(role => { if (!peersOrderedByRoles[role]) { peersOrderedByRoles[role] = []; } }); } useSidepaneResetOnLayoutUpdate('participant_list', SIDE_PANE_OPTIONS.PARTICIPANTS); const onSearch = useCallback((value: string) => { setFilter(filterValue => { if (!filterValue) { filterValue = {}; } filterValue.search = value.toLowerCase(); return { ...filterValue }; }); }, []); if (peerCount === 0) { return null; } return ( {!filter?.search && participants.length === 0 ? null : } {participants.length === 0 ? ( {!filter ? 'No participants' : 'No matching participants'} ) : null} ); }; export const ParticipantCount = () => { const peerCount = useHMSStore(selectPeerCount); const toggleSidepane = useSidepaneToggle(SIDE_PANE_OPTIONS.PARTICIPANTS); const isPeerListOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.PARTICIPANTS); if (peerCount === 0) { return null; } return ( { if (peerCount > 0) { toggleSidepane(); } }} data-testid="participant_list" > {getFormattedCount(peerCount)} ); }; export const Participant = ({ peer, isConnected, isHandRaisedAccordion, style, }: { peer: HMSPeer; isConnected: boolean; isHandRaisedAccordion?: boolean; style: React.CSSProperties; }) => { const localPeerId = useHMSStore(selectLocalPeerID); return ( {peer.name} {localPeerId === peer.id ? '(You)' : ''} {isConnected && peer.roleName ? ( ) : null} ); }; const VirtualizedParticipants = ({ peersOrderedByRoles = {}, isConnected, filter, handRaisedList = [], offStageRoles, isLargeRoom, onActive, children, }: { peersOrderedByRoles: Record; isConnected: boolean; filter: undefined | { search?: string }; handRaisedList: HMSPeer[]; offStageRoles: HMSRoleName[]; isLargeRoom: boolean; onActive: (role: string) => void; children: React.ReactNode; }) => { return ( div:empty ~ .emptyParticipants': { display: 'flex', }, }} > {handRaisedList.length > 0 ? ( ) : null} {Object.keys(peersOrderedByRoles).map(role => ( ))} {children} ); }; /** * shows settings to change for a participant like changing their role */ const ParticipantActions = React.memo( ({ peerId, peerType, role, isHandRaisedAccordion, }: { peerId: string; role: string; isHandRaisedAccordion?: boolean; peerType: HMSPeerType; }) => { const isHandRaised = useHMSStore(selectHasPeerHandRaised(peerId)); const canChangeRole = useHMSStore(selectPermissions)?.changeRole; const canRemoveOthers = useHMSStore(selectPermissions)?.removeOthers; const { elements } = useRoomLayoutConferencingScreen(); const { on_stage_exp } = elements || {}; const shouldShowMoreActions = (on_stage_exp && canChangeRole) || canRemoveOthers; const isAudioMuted = !useHMSStore(selectIsPeerAudioEnabled(peerId)); return ( {isHandRaisedAccordion ? ( ) : ( <> {peerType === HMSPeerType.SIP && ( )} {isHandRaised && ( )} {isAudioMuted ? ( ) : null} {shouldShowMoreActions ? : null} )} ); }, ); const quickActionStyle = { p: '$1', borderRadius: '$round' }; const HandRaisedAccordionParticipantActions = ({ peerId, role }: { peerId: string; role: string }) => { const { handleStageAction, lowerPeerHand, shouldShowStageRoleChange, isInStage } = usePeerOnStageActions({ peerId, role, }); if (!shouldShowStageRoleChange) { return null; } return ( <> {!isInStage && ( )} ); }; const ParticipantMoreActions = ({ peerId, role }: { peerId: string; role: string }) => { const { open, setOpen, bring_to_stage_label, remove_from_stage_label, handleStageAction, isInStage, shouldShowStageRoleChange, } = usePeerOnStageActions({ peerId, role }); const canChangeRole = !!useHMSStore(selectPermissions)?.changeRole; const [openRoleChangeModal, setOpenRoleChangeModal] = useState(false); const roles = useHMSStore(selectAvailableRoleNames); return ( <> setOpen(value)} modal={false}> {shouldShowStageRoleChange ? ( handleStageAction()}> {isInStage ? remove_from_stage_label : bring_to_stage_label} ) : null} {canChangeRole && roles.length > 1 ? ( setOpenRoleChangeModal(true)}> Switch Role ) : null} {openRoleChangeModal && } ); }; export const ParticipantSearch = ({ onSearch, placeholder = 'Search for participants', inSidePane = false, }: { inSidePane?: boolean; placeholder?: string; onSearch: (val: string) => void; }) => { const [value, setValue] = React.useState(''); const isMobile = useMedia(cssConfig.media.md); useDebounce( () => { onSearch(value); }, 300, [value, onSearch], ); return ( e.stopPropagation()} > { event.stopPropagation(); }} onChange={event => { setValue(event.currentTarget.value); }} autoComplete="off" aria-autocomplete="none" /> ); };