Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat rerendering issue #20

Merged
merged 2 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 0 additions & 20 deletions api-server/test.js

This file was deleted.

75 changes: 75 additions & 0 deletions client/src/components/Chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// src/components/Chat.js
import React, { useState, useRef, useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import toast from 'react-hot-toast';
import Button from './Button';
import ChatHandler from '../services/chatHandler';
import ChatInput from './ChatInput';


const Chat = ({ meeting_id, username, socket }) => {
const [chatHistory, setChatHistory] = useState([]);
const chatHandler = useRef(null);

const initializeChat = () => {
if (!username) {
toast.error('Please enter a username before joining a meeting.');
return;
}

const errorHandler = (error) => {
console.error(error);
toast.error(error.message);
}

// Assuming ChatHandler is a utility to handle the chat logic
chatHandler.current = new ChatHandler(meeting_id, username, socket, setChatHistory, errorHandler);
chatHandler.current.initialize();
};

useEffect(() => {
initializeChat();
}, [meeting_id, username, socket]);

const handleSendMessage = useCallback((message) => {
if (!message.trim()) {
toast.error('Please enter a message before sending.');
return;
}
chatHandler.current.sendMessage(message);
setChatHistory(prevHistory => [...prevHistory, { sender: username, text: message }]);
}, [username]);

const getProfilePicture = (name) => {
return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`;
};

return (
<div className="w-1/4 bg-white p-4 flex flex-col">
<h2 className="text-xl mb-4">Chat</h2>
<div className="flex-1 overflow-y-auto mb-4">
{chatHistory.map((msg, index) => (
<div key={index} className="mb-2 flex items-center">
<img
src={getProfilePicture(msg.sender)}
alt={`${msg.sender}'s avatar`}
className="w-8 h-8 rounded-full mr-2"
/>
<div>
<strong>{msg.sender}:</strong> {msg.text}
</div>
</div>
))}
</div>
<ChatInput onSend={handleSendMessage} />
</div>
);
};

Chat.propTypes = {
meeting_id: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
socket: PropTypes.object.isRequired,
};

export default Chat;
34 changes: 34 additions & 0 deletions client/src/components/ChatInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useRef } from 'react';
import Button from '../components/Button';
import PropTypes from 'prop-types';

const ChatInputSection = ({ onSend }) => {
const inputRef = useRef();

const handleSendMessage = (e) => {
e.preventDefault(); // Prevent the form from causing a page reload
const message = inputRef.current.value.trim();
if (message) {
onSend(message);
inputRef.current.value = ''; // Clear the input after sending
}
};

return (
<form onSubmit={handleSendMessage} className="flex">
<input
ref={inputRef}
type="text"
className="flex-1 px-2 py-1 border rounded-l"
placeholder="Type a message..."
/>
<Button type="submit" className="rounded-l-none">Send</Button>
</form>
);
};

ChatInputSection.propTypes = {
onSend: PropTypes.func.isRequired,
};

export default ChatInputSection;
89 changes: 23 additions & 66 deletions client/src/pages/MeetingPage.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useParams, useLocation, useNavigate } from 'react-router-dom';
import io from 'socket.io-client';
import ChatHandler from '../services/chatHandler';
import RTCHandler from '../services/rtcHandler';
import Chat from '../components/Chat';

import Button from '../components/Button';
import toast, { Toaster } from 'react-hot-toast';
Expand All @@ -21,11 +21,7 @@ const MeetingPage = () => {
const [isMuted, setIsMuted] = useState(false);
const [isVideoOff, setIsVideoOff] = useState(false);

const chatHandler = useRef(null);
const [chatHistory, setChatHistory] = useState([]);
const [message, setMessage] = useState('');

useEffect(() => {
const initializeMeeting = () => {
if (!username) {
toast.error('Please enter a username before joining a meeting.');
navigate('/');
Expand All @@ -47,25 +43,26 @@ const MeetingPage = () => {
console.error(error);
navigate('/');
toast.error(error.message);
}
};

chatHandler.current = new ChatHandler(meeting_id, username, socketRef.current, setChatHistory, errorHandler);
chatHandler.current.initialize();
rtcHandler.current = new RTCHandler(meeting_id, username, socketRef.current, setPeers, errorHandler);
rtcHandler.current.initialize();
};

useEffect(() => {
initializeMeeting();

return () => {
rtcHandler.current.cleanup();
socketRef.current.disconnect();
}
};
}, []);

const toggleMute = () => {
setIsMuted(prevState => {
const newMutedState = !prevState;
// Mute or unmute the audio track in the local stream
if (rtcHandler.current && rtcHandler.current.localStream) {
rtcHandler.current.localStream.getAudioTracks().forEach(track => {
console.log(track);
track.enabled = !newMutedState;
});
}
Expand All @@ -76,35 +73,15 @@ const MeetingPage = () => {
const toggleVideo = () => {
setIsVideoOff(prevState => {
const newVideoState = !prevState;
// Turn on or off the video track in the local stream
if (rtcHandler.current && rtcHandler.current.localStream) {
rtcHandler.current.localStream.getVideoTracks().forEach(track => {
console.log(track);
track.enabled = !newVideoState;
});
}
return newVideoState;
});
};

const sendMessage = (e) => {
e.preventDefault();
if (!message.trim()) {
toast.error('Please enter a message before sending.');
return;
}
chatHandler.current.sendMessage(message);
setMessage('');
};

const getProfilePicture = (name) => {
return `https://ui-avatars.com/api/?name=${encodeURIComponent(name)}&background=random`;
};

if (!username || !rtcHandler.current) {
return null;
}

const VideoElement = React.memo(({ stream, muted, peerName }) => {
const videoRef = useRef();

Expand All @@ -116,12 +93,16 @@ const MeetingPage = () => {

return (
<div className="video-container">
<video ref={videoRef} autoPlay playsInline muted={muted} className="video-element"/>
<video ref={videoRef} autoPlay playsInline muted={muted} className="video-element" />
<p className="video-username">{peerName}</p>
</div>
);
});

if (!username || !rtcHandler.current) {
return null;
}

return (
<div className="flex flex-col h-screen bg-gray-100">
<Toaster position="top-center" reverseOrder={false} />
Expand All @@ -131,45 +112,21 @@ const MeetingPage = () => {
</header>
<main className="flex flex-1 overflow-hidden">
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
<VideoElement stream={rtcHandler.current.localStream} muted={true} peerName="You" />
{rtcHandler.current.localStream && (
<VideoElement stream={rtcHandler.current.localStream} muted={true} peerName="You" />
)}
{Object.entries(peers).map(([peerUsername, peer]) => (
peerUsername !== username && (
<VideoElement
key={peerUsername}
stream={peer.stream}
muted={false}
<VideoElement
key={peerUsername}
stream={peer.stream}
muted={false}
peerName={peerUsername}
/>
)
))}
</div>
<div className="w-1/4 bg-white p-4 flex flex-col">
<h2 className="text-xl mb-4">Chat</h2>
<div className="flex-1 overflow-y-auto mb-4">
{chatHistory.map((msg, index) => (
<div key={index} className="mb-2 flex items-center">
<img
src={getProfilePicture(msg.sender)}
alt={`${msg.sender}'s avatar`}
className="w-8 h-8 rounded-full mr-2"
/>
<div>
<strong>{msg.sender}:</strong> {msg.text}
</div>
</div>
))}
</div>
<form onSubmit={sendMessage} className="flex">
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
className="flex-1 px-2 py-1 border rounded-l"
placeholder="Type a message..."
/>
<Button type="submit" className="rounded-l-none">Send</Button>
</form>
</div>
<Chat meeting_id={meeting_id} username={username} socket={socketRef.current} />
</main>
<footer className="bg-gray-200 p-4 flex justify-center space-x-4">
<Button onClick={toggleMute}>{isMuted ? 'Unmute' : 'Mute'}</Button>
Expand Down
1 change: 1 addition & 0 deletions client/src/services/chatHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class ChatHandler {

setupSocketListeners() {
this.socket.on('chat_message', (data) => {
console.log("WebSocket event received:", data);
this.chatHistory.push(data);
if (this.setChatHistory) {
this.setChatHistory([...this.chatHistory]); // update UI
Expand Down
Loading