Commit e6ce68d9 authored by Thisara Kavinda's avatar Thisara Kavinda

feat: implemented the pinned grid

parent 16cb267f
......@@ -6,21 +6,28 @@ import {
useLocalMicrophoneTrack,
useRemoteUsers,
usePublish,
useRTCClient,
useClientEvent,
} from 'agora-rtc-react'
import SymmetricGrid from '../SymmetricGrid/SymmetricGrid'
import PinnedGrid from '../PinnedGrid/PinnedGrid'
import type { IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
const MeetingLayout = () => {
const theme: Theme = useTheme()
const agoraEngine = useRTCClient()
const { localCameraTrack } = useLocalCameraTrack()
const { localMicrophoneTrack } = useLocalMicrophoneTrack()
usePublish([localMicrophoneTrack, localCameraTrack])
const remoteUsers = useRemoteUsers()
const remoteUsers: IAgoraRTCRemoteUser[] = useRemoteUsers()
const gridContainerRef = useRef<HTMLDivElement>(null)
const [gridContainerHeight, setGridContainerHeight] = useState(0)
const [totalUsers, setTotalUsers] = useState(20)
const [totalUsers, setTotalUsers] = useState(0)
const [isPinned, setIsPinned] = useState(false)
const [pinnedUser, setPinnedUser] = useState<IAgoraRTCRemoteUser | null>(null)
useEffect(() => {
setTotalUsers(remoteUsers.length + 1)
......@@ -32,6 +39,15 @@ const MeetingLayout = () => {
}
}, [gridContainerRef?.current?.offsetHeight])
const handleUserLeft = (user: IAgoraRTCRemoteUser) => {
if (user.uid === pinnedUser?.uid) {
setIsPinned(false)
setPinnedUser(null)
}
}
useClientEvent(agoraEngine, 'user-left', handleUserLeft)
return (
<Box
sx={{
......@@ -52,12 +68,23 @@ const MeetingLayout = () => {
}}
ref={gridContainerRef}
>
<SymmetricGrid
totalUsers={totalUsers}
setTotalUsers={setTotalUsers}
gridContainerHeight={gridContainerHeight}
setGridContainerHeight={setGridContainerHeight}
/>
{isPinned && pinnedUser ? (
<PinnedGrid
totalUsers={totalUsers}
gridContainerHeight={gridContainerHeight}
setIsPinned={setIsPinned}
pinnedUser={pinnedUser}
setPinnedUser={setPinnedUser}
/>
) : (
<SymmetricGrid
totalUsers={totalUsers}
gridContainerHeight={gridContainerHeight}
isPinned={isPinned}
setIsPinned={setIsPinned}
setPinnedUser={setPinnedUser}
/>
)}
</Box>
</Box>
)
......
import React, { useEffect } from 'react'
import type { IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
import { Box, Button, IconButton, Popover, Stack } from '@mui/material'
import useMediaQuery from '@mui/material/useMediaQuery'
import { RemoteUser, useRemoteUsers } from 'agora-rtc-react'
import { useTheme, type Theme } from '@mui/material/styles'
import UserCard from '../UserCard/UserCard'
import MoreVertIcon from '@mui/icons-material/MoreVert'
interface Props {
totalUsers: number
gridContainerHeight: number
setIsPinned: (value: boolean) => void
pinnedUser: IAgoraRTCRemoteUser
setPinnedUser: (value: IAgoraRTCRemoteUser | null) => void
}
const PinnedGrid = ({
totalUsers,
gridContainerHeight,
setIsPinned,
pinnedUser,
setPinnedUser,
}: Props) => {
const theme: Theme = useTheme()
const isSm = useMediaQuery(theme.breakpoints.up('sm'))
const remoteUsers = useRemoteUsers()
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
const [itemHeight, setItemHeight] = React.useState((gridContainerHeight - 16 * 3) / 4)
const open = Boolean(anchorEl)
const menuId = open ? 'simple-popover' : undefined
const handleClickMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
const handleCloseMenu = () => {
setAnchorEl(null)
}
const handleUnpinUser = () => {
setIsPinned(false)
setPinnedUser(null)
setAnchorEl(null)
}
useEffect(() => {
setItemHeight((gridContainerHeight - 16 * 2) / 3)
}, [gridContainerHeight])
return (
<Box
sx={{
display: 'flex',
width: '100%',
flexDirection: 'row',
justifyContent: 'space-between',
height: '100%',
}}
>
<Box
sx={{
width: isSm ? '65%' : '100%',
backgroundColor: '#262625',
height: '100%',
borderRadius: '15px',
marginX: '10px',
position: 'relative',
}}
>
<RemoteUser
user={pinnedUser}
playVideo={true}
playAudio={true}
style={{ width: '100%', height: '100%', borderRadius: '15px' }}
/>
<IconButton
aria-lable='Menu'
sx={{
position: 'absolute',
top: '5%',
right: '5%',
borderRadius: '50%',
padding: '10px',
zIndex: 100,
pointerEvents: 'auto',
'&:hover': { backgroundColor: '#363739', opacity: 0.8 },
}}
onClick={handleClickMenu}
>
<MoreVertIcon />
</IconButton>
<Popover
id={menuId}
open={open}
anchorEl={anchorEl}
onClose={handleCloseMenu}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<Box
sx={{
padding: '5px 20px',
display: 'flex',
flexDirection: 'columnÍ',
backgroundColor: '#363739',
width: '150px',
}}
>
<Button
sx={{
color: theme.palette.common.white,
width: '100%',
textAlign: 'left',
justifyContent: 'start',
textTransform: 'none',
}}
onClick={handleUnpinUser}
>
Unpin User
</Button>
</Box>
</Popover>
</Box>
{isSm && (
<Box
sx={{
width: '30%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
}}
>
<Stack spacing={4} sx={{ width: '100%', height: 'fit-content' }}>
<UserCard
type={'local'}
itemHeight={itemHeight}
setIsPinned={setIsPinned}
setPinnedUser={setPinnedUser}
/>
{remoteUsers.length > 1 &&
remoteUsers
.filter((user) => user.uid !== pinnedUser.uid)
.map((user) => (
<UserCard
key={user.uid}
type={'remote'}
remoteUser={user}
itemHeight={itemHeight}
setIsPinned={setIsPinned}
setPinnedUser={setPinnedUser}
/>
))}
</Stack>
</Box>
)}
</Box>
)
}
export default PinnedGrid
import React, { useEffect, useState } from 'react'
import { Box, Grid } from '@mui/material'
import useMediaQuery from '@mui/material/useMediaQuery'
import { LocalVideoTrack, RemoteUser, useLocalCameraTrack, useRemoteUsers } from 'agora-rtc-react'
import { useRemoteUsers } from 'agora-rtc-react'
import { useTheme, type Theme } from '@mui/material/styles'
import type { IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
import UserCard from '../UserCard/UserCard'
interface Props {
totalUsers: number
setTotalUsers: (value: number) => void
gridContainerHeight: number
setGridContainerHeight: (value: number) => void
isPinned: boolean
setIsPinned: (value: boolean) => void
setPinnedUser: (value: IAgoraRTCRemoteUser | null) => void
}
const SymmetricGrid = ({
totalUsers,
setTotalUsers,
gridContainerHeight,
setGridContainerHeight,
isPinned,
setIsPinned,
setPinnedUser,
}: Props) => {
const theme: Theme = useTheme()
const isSm = useMediaQuery(theme.breakpoints.up('sm'))
const isMd = useMediaQuery(theme.breakpoints.up('md'))
const isLg = useMediaQuery(theme.breakpoints.up('lg'))
const { localCameraTrack } = useLocalCameraTrack()
const remoteUsers = useRemoteUsers()
const [itemOffset, setItemOffset] = useState(12)
......@@ -31,10 +34,6 @@ const SymmetricGrid = ({
const maxUsersDisplay = isLg ? 12 : isMd ? 9 : isSm ? 4 : 2
useEffect(() => {
setTotalUsers(remoteUsers.length + 1)
}, [remoteUsers])
useEffect(() => {
setNumOfItems(totalUsers > maxUsersDisplay ? maxUsersDisplay : totalUsers)
}, [totalUsers])
......@@ -129,36 +128,22 @@ const SymmetricGrid = ({
<Box sx={{ display: 'flex', width: '100%' }}>
<Grid container spacing={3} width={'100%'} padding={0}>
<Grid item xs={itemOffset}>
<Box
sx={{
backgroundColor: '#262625',
height: `${itemHeight}px`,
borderRadius: '15px',
}}
>
<LocalVideoTrack
track={localCameraTrack}
play={true}
style={{ width: '100%', height: '100%', borderRadius: '20%' }}
/>
</Box>
<UserCard
type={'local'}
itemHeight={itemHeight}
setIsPinned={setIsPinned}
setPinnedUser={setPinnedUser}
/>
</Grid>
{remoteUsers.map((remoteUser, index) => (
<Grid item xs={itemOffset} key={index}>
<Box
sx={{
backgroundColor: '#262625',
height: `${itemHeight}px`,
borderRadius: '15px',
}}
>
<RemoteUser
user={remoteUser}
playVideo={true}
playAudio={true}
style={{ width: '100%', height: '100%', borderRadius: '15px' }}
/>
</Box>
<UserCard
type={'remote'}
remoteUser={remoteUser}
itemHeight={itemHeight}
setIsPinned={setIsPinned}
setPinnedUser={setPinnedUser}
/>
</Grid>
))}
</Grid>
......
import { Box, Button, IconButton, Popover } from '@mui/material'
import MoreVertIcon from '@mui/icons-material/MoreVert'
import { LocalVideoTrack, RemoteUser, useLocalCameraTrack } from 'agora-rtc-react'
import type { IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'
import { useTheme, type Theme } from '@mui/material/styles'
import React, { useEffect } from 'react'
interface Props {
type: 'remote' | 'local'
itemHeight: number
setIsPinned: (value: boolean) => void
setPinnedUser: (value: IAgoraRTCRemoteUser | null) => void
remoteUser?: IAgoraRTCRemoteUser
}
const UserCard = ({ type, remoteUser, itemHeight, setIsPinned, setPinnedUser }: Props) => {
const theme: Theme = useTheme()
const { localCameraTrack } = useLocalCameraTrack()
const [anchorEl, setAnchorEl] = React.useState<HTMLButtonElement | null>(null)
const [isLocal, setIsLocal] = React.useState<boolean>(type === 'local')
const open = Boolean(anchorEl)
const menuId = open ? 'simple-popover' : undefined
useEffect(() => {
setIsLocal(type === 'local')
}, [type])
const handleClickMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget)
}
const handleCloseMenu = () => {
setAnchorEl(null)
}
const handlePinUser = () => {
setIsPinned(true)
setPinnedUser(remoteUser ?? null)
setAnchorEl(null)
}
return (
<Box
sx={{
backgroundColor: '#262625',
height: `${itemHeight}px`,
borderRadius: '15px',
width: '100%',
position: 'relative',
overflow: 'hidden',
}}
>
{isLocal ? (
<LocalVideoTrack
track={localCameraTrack}
play={true}
style={{ width: '100%', height: '100%', borderRadius: '20%' }}
/>
) : (
<RemoteUser
user={remoteUser}
playVideo={true}
playAudio={true}
style={{ width: '100%', height: '100%', borderRadius: '15px' }}
/>
)}
{!isLocal && (
<>
<IconButton
aria-lable='Menu'
sx={{
position: 'absolute',
top: '5%',
right: '5%',
borderRadius: '50%',
padding: '10px',
zIndex: 100,
pointerEvents: 'auto',
'&:hover': { backgroundColor: '#363739', opacity: 0.8 },
}}
onClick={handleClickMenu}
>
<MoreVertIcon />
</IconButton>
<Popover
id={menuId}
open={open}
anchorEl={anchorEl}
onClose={handleCloseMenu}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
>
<Box
sx={{
padding: '5px 20px',
display: 'flex',
flexDirection: 'columnÍ',
backgroundColor: '#363739',
width: '150px',
}}
>
<Button
sx={{
color: theme.palette.common.white,
width: '100%',
textAlign: 'left',
justifyContent: 'start',
textTransform: 'none',
}}
onClick={handlePinUser}
>
Pin User
</Button>
</Box>
</Popover>
</>
)}
</Box>
)
}
export default UserCard
......@@ -5,7 +5,7 @@ import type { IMicrophoneAudioTrack, ICameraVideoTrack } from 'agora-rtc-sdk-ng'
import { Box } from '@mui/material'
import { useTheme, type Theme } from '@mui/material/styles'
import MeetingLoading from '../../Components/MeetingLoading/MeetingLoading'
import MeetingGrid from '../../Components/MeetingLayout/MeetingLayout'
import MeetingLayout from '../../Components/MeetingLayout/MeetingLayout'
interface AgoraContextType {
localCameraTrack: ICameraVideoTrack | null
......@@ -73,7 +73,7 @@ export const AgoraManager = ({ children }: { children: React.ReactNode }) => {
localCameraTrack={localCameraTrack}
localMicrophoneTrack={localMicrophoneTrack}
>
<MeetingGrid />
<MeetingLayout />
</AgoraProvider>
) : joinError ? (
<MeetingLoading variant='error' />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment