projects/deliberation_at_scale/packages/frontend/components/RoomParticipants/index.tsx (103 lines of code) (raw):

'use client'; import { useRoomConnection } from '@/components/RoomConnection/context'; import { useLocalMedia } from '@/hooks/useLocalMedia'; import { faVideoSlash } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import { AnimatePresence, motion } from 'framer-motion'; import { useCallback, useState } from 'react'; import LocalParticipantControls from './controls'; import { SHOW_VIDEO_CONTROLS_INITIALLY } from '@/utilities/constants'; export interface ParticipantsProps { variant: 'compact' | 'spacious'; } export default function RoomParticipants({ variant }: ParticipantsProps) { const [showLocalControls, setShowLocalControls] = useState(SHOW_VIDEO_CONTROLS_INITIALLY); const connection = useRoomConnection(); const { state } = useLocalMedia(); const { remoteParticipants } = connection?.state ?? {}; const { VideoView } = connection?.components ?? {}; const defaultFadeInVariants = { hidden: { opacity: 0 }, visible: { opacity: 1 }, }; const handleLocalParticipantClick = useCallback(() => { setShowLocalControls((current) => !current); }, [setShowLocalControls]); if (!connection || !VideoView) { return null; } return ( <motion.div className={classNames("relative z-20", variant === 'compact' && "max-h-[30vh]")} > <div className={classNames( "flex", variant === 'compact' && 'h-[20vh] md:h-[30vh] bg-gray-100 overflow-hidden border md:mx-4 gap-1 md:mx-1', variant === 'spacious' && 'h-[40vh] relative', )} > {remoteParticipants?.map((participant, i) => { if (!participant) { return null; } return ( <motion.div key={participant.id} className={classNames( 'flex-grow flex-shrink min-w-0', variant === 'compact' && 'relative', variant === 'spacious' && 'aspect-square rounded overflow-hidden bg-gray-100 w-[52%] absolute border', i === 0 && variant === 'spacious' && 'right-0 top-0 z-10', i === 1 && variant === 'spacious' && 'left-0 bottom-0', )} variants={defaultFadeInVariants} initial="hidden" animate="visible" > {(participant.stream && participant.isVideoEnabled) ? ( <VideoView className="w-full h-full object-cover rounded" stream={participant.stream} disablePictureInPicture /> ) : ( <div className="w-full h-full flex items-center justify-center border"> <FontAwesomeIcon icon={faVideoSlash} /> </div> )} {/* <div className={classNames( `absolute bottom-2 backdrop-blur-lg p-2 flex justify-center rounded gap-4 bg-gray-800/90 text-white z-20`, variant === 'spacious' && 'left-2', i === 0 && variant === 'compact' && 'left-2', i > 0 && variant === 'compact' && 'right-2', )} > <span>{participant.displayName || 'Guest'}</span> </div> */} </motion.div> ); })} </div> <motion.div className={classNames( "flex justify-center gap-2 absolute w-1/5 bottom-0 z-20", variant === 'compact' && 'aspect-square right-2 mb-[-12px]', variant === 'spacious' && 'aspect-square right-2', )} variants={defaultFadeInVariants} initial="hidden" animate="visible" > <button className="grow" onClick={handleLocalParticipantClick}> {(state.localStream && state.isVideoEnabled) ? ( <VideoView muted={true} className="w-full h-full object-cover border-4 rounded-full hover:scale-105 shadow" stream={state.localStream} disablePictureInPicture /> ) : ( <div className="w-full h-full flex items-center justify-center border rounded text-gray-300"> <FontAwesomeIcon icon={faVideoSlash} /> </div> )} </button> <AnimatePresence> {showLocalControls && ( <LocalParticipantControls /> )} </AnimatePresence> </motion.div> </motion.div> ); }