diff --git a/src/components/MainParticipant/MainParticipant.tsx b/src/components/MainParticipant/MainParticipant.tsx new file mode 100644 index 000000000..f5275cd8d --- /dev/null +++ b/src/components/MainParticipant/MainParticipant.tsx @@ -0,0 +1,15 @@ +import MainParticipantInfo from '../MainParticipantInfo/MainParticipantInfo'; +import ParticipantTracks from '../ParticipantTracks/ParticipantTracks'; +import React from 'react'; +import useMainSpeaker from '../../hooks/useMainSpeaker/useMainSpeaker'; + +export default function MainParticipant() { + const mainParticipant = useMainSpeaker(); + return ( + /* audio is disabled for this participant component because this participant's audio + is already being rendered in the component. */ + + + + ); +} diff --git a/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx b/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx new file mode 100644 index 000000000..56718a580 --- /dev/null +++ b/src/components/MainParticipantInfo/MainParticipantInfo.test.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import MainParticipantInfo from './MainParticipantInfo'; +import { shallow } from 'enzyme'; +import usePublications from '../../hooks/usePublications/usePublications'; + +jest.mock('../../hooks/useParticipantNetworkQualityLevel/useParticipantNetworkQualityLevel', () => () => 4); + +jest.mock('../../hooks/usePublications/usePublications'); + +const mockUsePublications = usePublications as jest.Mock; + +describe('the MainParticipantInfo component', () => { + it('should render correctly', () => { + mockUsePublications.mockImplementation(() => []); + const wrapper = shallow( + mock children + ); + expect(wrapper).toMatchSnapshot(); + }); + + it('should add hideVideoProp to InfoContainer component when video is disabled', () => { + mockUsePublications.mockImplementation(() => [{ trackName: 'camera', isTrackEnabled: false }]); + const wrapper = shallow( + mock children + ); + expect( + wrapper + .find('Styled(div)') + .at(1) + .prop('hideVideo') + ).toEqual(true); + }); + + it('should not add hideVideoProp to InfoContainer component when video is enabled', () => { + mockUsePublications.mockImplementation(() => [{ trackName: 'camera', isTrackEnabled: true }]); + const wrapper = shallow( + mock children + ); + expect( + wrapper + .find('Styled(div)') + .at(1) + .prop('hideVideo') + ).toEqual(false); + }); +}); diff --git a/src/components/MainParticipantInfo/MainParticipantInfo.tsx b/src/components/MainParticipantInfo/MainParticipantInfo.tsx new file mode 100644 index 000000000..4d2a9485e --- /dev/null +++ b/src/components/MainParticipantInfo/MainParticipantInfo.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { styled } from '@material-ui/core/styles'; +import { LocalParticipant, RemoteParticipant } from 'twilio-video'; +import usePublications from '../../hooks/usePublications/usePublications'; +import usePublicationIsTrackEnabled from '../../hooks/usePublicationIsTrackEnabled/usePublicationIsTrackEnabled'; + +const Container = styled('div')({ + position: 'relative', + display: 'flex', + alignItems: 'center', +}); + +const InfoContainer = styled('div')({ + position: 'absolute', + zIndex: 1, + height: '100%', + padding: '0.4em', + width: '100%', + background: ({ hideVideo }: { hideVideo?: boolean }) => (hideVideo ? 'black' : 'transparent'), +}); + +const Identity = styled('h4')({ + background: 'rgba(0, 0, 0, 0.7)', + padding: '0.1em 0.3em', + margin: '1em', + fontSize: '1.2em', + display: 'inline-block', +}); + +interface ParticipantInfoProps { + participant: LocalParticipant | RemoteParticipant; + children: React.ReactNode; +} + +export default function ParticipantInfo({ participant, children }: ParticipantInfoProps) { + const publications = usePublications(participant); + const isVideoEnabled = usePublicationIsTrackEnabled(publications.find(p => p.trackName === 'camera')); + + return ( + + + {participant.identity} + + {children} + + ); +} diff --git a/src/components/MainParticipantInfo/__snapshots__/MainParticipantInfo.test.tsx.snap b/src/components/MainParticipantInfo/__snapshots__/MainParticipantInfo.test.tsx.snap new file mode 100644 index 000000000..311a159de --- /dev/null +++ b/src/components/MainParticipantInfo/__snapshots__/MainParticipantInfo.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`the MainParticipantInfo component should render correctly 1`] = ` + + + + mockIdentity + + + mock children + +`; diff --git a/src/components/Menu/Menu.test.tsx b/src/components/Menu/Menu.test.tsx index b822a5552..6e06552a4 100644 --- a/src/components/Menu/Menu.test.tsx +++ b/src/components/Menu/Menu.test.tsx @@ -1,6 +1,5 @@ import React from 'react'; import Menu from './Menu'; -import { getToken, receiveToken } from '../../store/main/main'; import useRoomState from '../../hooks/useRoomState/useRoomState'; import { IVideoContext, useVideoContext } from '../../hooks/context'; import { fireEvent, render } from '@testing-library/react'; @@ -64,22 +63,4 @@ describe('the Menu component', () => { fireEvent.change(getByLabelText('Room'), { target: { value: 'Foo' } }); expect(getByRole('button').getAttribute('disabled')).toEqual(''); }); - - it('should dispatch a redux action when the Join Room button is clicked', () => { - mockedUseRoomState.mockImplementation(() => 'disconnected'); - mockedUseVideoContext.mockImplementation(() => ({ isConnecting: false } as any)); - const { getByLabelText, getByRole } = render(); - fireEvent.change(getByLabelText('Name'), { target: { value: 'Username' } }); - fireEvent.change(getByLabelText('Room'), { target: { value: 'Roomname' } }); - fireEvent.click(getByRole('button')); - expect(getToken).toHaveBeenCalledWith('Username', 'Roomname'); - }); - - it('should dispatch a redux action when the Leave Room button is clicked', () => { - mockedUseRoomState.mockImplementation(() => 'connected'); - mockedUseVideoContext.mockImplementation(() => ({ isConnecting: false } as any)); - const { getByRole } = render(); - fireEvent.click(getByRole('button')); - expect(receiveToken).toHaveBeenCalledWith(''); - }); }); diff --git a/src/components/Menu/Menu.tsx b/src/components/Menu/Menu.tsx index b1beaa53f..9a1059d81 100644 --- a/src/components/Menu/Menu.tsx +++ b/src/components/Menu/Menu.tsx @@ -8,7 +8,7 @@ import CircularProgress from '@material-ui/core/CircularProgress'; import TextField from '@material-ui/core/TextField'; import Toolbar from '@material-ui/core/Toolbar'; -import { getToken, receiveToken } from '../../store/main/main'; +import { getToken } from '../../store/main/main'; import useRoomState from '../../hooks/useRoomState/useRoomState'; import { useVideoContext } from '../../hooks/context'; @@ -26,6 +26,9 @@ const useStyles = makeStyles((theme: Theme) => marginRight: theme.spacing(1), width: 200, }, + loadingSpinner: { + marginLeft: '1em', + }, }) ); @@ -75,10 +78,10 @@ export default function Menu() { - {isConnecting && } + {isConnecting && } ) : ( - +

{roomName}

)} diff --git a/src/components/ParticipantStrip/ParticipantStrip.tsx b/src/components/ParticipantStrip/ParticipantStrip.tsx index 8ae2db606..a93da73b3 100644 --- a/src/components/ParticipantStrip/ParticipantStrip.tsx +++ b/src/components/ParticipantStrip/ParticipantStrip.tsx @@ -11,6 +11,7 @@ const Container = styled('aside')(({ theme }) => ({ right: `calc(100% - ${theme.sidebarPosition})`, left: 0, padding: '0.5em', + overflowY: 'auto', })); export default function ParticipantStrip() { diff --git a/src/components/Room/Room.tsx b/src/components/Room/Room.tsx index 54bf43019..632201aa1 100644 --- a/src/components/Room/Room.tsx +++ b/src/components/Room/Room.tsx @@ -1,8 +1,7 @@ import React from 'react'; -import Participant from '../Participant/Participant'; import ParticipantStrip from '../ParticipantStrip/ParticipantStrip'; import { styled } from '@material-ui/core/styles'; -import useMainSpeaker from '../../hooks/useMainSpeaker/useMainSpeaker'; +import MainParticipant from '../MainParticipant/MainParticipant'; const Container = styled('div')({ position: 'relative', @@ -21,14 +20,11 @@ const MainParticipantContainer = styled('div')(({ theme }) => ({ })); export default function Room() { - const mainParticipant = useMainSpeaker(); return ( - {/* audio is disabled for this participant component because this participant's audio - is already being rendered in the component. */} - + );