-
+
+
+ Question {questionInfos.question.id}/{questions.length}
@@ -66,31 +74,30 @@ const StudentModeQuiz: React.FC
= ({
handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question as Question}
showAnswer={isAnswerSubmitted}
+ answer={answers[Number(questionInfos.question.id)-1]?.answer}
/>
-
-
- {/* }
- disabled={Number(questionInfos.question.id) <= 1}
- >
- Question précédente
- */}
-
-
- }
- disabled={Number(questionInfos.question.id) >= questions.length}
- >
- Question suivante
-
-
+
+
+
+
+
+
+
diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx
index d4c7793..8925c09 100644
--- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx
+++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx
@@ -1,55 +1,59 @@
// TeacherModeQuiz.tsx
import React, { useEffect, useState } from 'react';
-
import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
-
import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType';
-// import { QuestionService } from '../../services/QuestionService';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material';
import { Question } from 'gift-pegjs';
+import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService';
+import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
+// import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
interface TeacherModeQuizProps {
questionInfos: QuestionType;
- submitAnswer: (_answer: string | number | boolean, _idQuestion: number) => void;
+ answers: AnswerSubmissionToBackendType[];
+ submitAnswer: (_answer: AnswerType, _idQuestion: number) => void;
disconnectWebSocket: () => void;
}
const TeacherModeQuiz: React.FC
= ({
questionInfos,
+ answers,
submitAnswer,
disconnectWebSocket
}) => {
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
const [isFeedbackDialogOpen, setIsFeedbackDialogOpen] = useState(false);
- const [feedbackMessage, setFeedbackMessage] = useState('');
-
- const renderFeedbackMessage = (answer: string) => {
+ const [answer, setAnswer] = useState();
- if(answer === 'true' || answer === 'false'){
- return (
- Votre réponse est: {answer==="true" ? 'Vrai' : 'Faux'}
- )
- }
- else{
- return (
-
- Votre réponse est: {answer.toString()}
-
- );}
- };
+
+ // arrive here the first time after waiting for next question
useEffect(() => {
- // Close the feedback dialog when the question changes
- handleFeedbackDialogClose();
- setIsAnswerSubmitted(false);
-
- }, [questionInfos.question]);
+ console.log(`TeacherModeQuiz: useEffect: answers: ${JSON.stringify(answers)}`);
+ console.log(`TeacherModeQuiz: useEffect: questionInfos.question.id: ${questionInfos.question.id} answer: ${answer}`);
+ const oldAnswer = answers[Number(questionInfos.question.id) -1 ]?.answer;
+ console.log(`TeacherModeQuiz: useEffect: oldAnswer: ${oldAnswer}`);
+ setAnswer(oldAnswer);
+ setIsFeedbackDialogOpen(false);
+ }, [questionInfos.question, answers]);
- const handleOnSubmitAnswer = (answer: string | number | boolean) => {
+ // handle showing the feedback dialog
+ useEffect(() => {
+ console.log(`TeacherModeQuiz: useEffect: answer: ${answer}`);
+ setIsAnswerSubmitted(answer !== undefined);
+ setIsFeedbackDialogOpen(answer !== undefined);
+ }, [answer]);
+
+ useEffect(() => {
+ console.log(`TeacherModeQuiz: useEffect: isAnswerSubmitted: ${isAnswerSubmitted}`);
+ setIsFeedbackDialogOpen(isAnswerSubmitted);
+ }, [isAnswerSubmitted]);
+
+ const handleOnSubmitAnswer = (answer: AnswerType) => {
const idQuestion = Number(questionInfos.question.id) || -1;
submitAnswer(answer, idQuestion);
- setFeedbackMessage(renderFeedbackMessage(answer.toString()));
+ // setAnswer(answer);
setIsFeedbackDialogOpen(true);
};
@@ -60,21 +64,21 @@ const TeacherModeQuiz: React.FC = ({
return (
-
+
-
-
-
-
Question {questionInfos.question.id}
-
-
-
+
+
+
Question {questionInfos.question.id}
- {isAnswerSubmitted ? (
+
+
+
+
+ {isAnswerSubmitted ? (
En attente pour la prochaine question...
@@ -82,6 +86,7 @@ const TeacherModeQuiz: React.FC
= ({
)}
@@ -92,20 +97,21 @@ const TeacherModeQuiz: React.FC = ({
Rétroaction
- {feedbackMessage}
-
Question :
+ wordWrap: 'break-word',
+ whiteSpace: 'pre-wrap',
+ maxHeight: '400px',
+ overflowY: 'auto',
+ }}>
+
Question :
-
-
@@ -114,7 +120,7 @@ const TeacherModeQuiz: React.FC = ({
-
+
);
};
diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx
index 5ff30b0..dc7e80c 100644
--- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx
+++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx
@@ -17,6 +17,8 @@ import LoginContainer from 'src/components/LoginContainer/LoginContainer'
import ApiService from '../../../services/ApiService'
+export type AnswerType = string | number | boolean;
+
const JoinRoom: React.FC = () => {
const [roomName, setRoomName] = useState('');
const [username, setUsername] = useState(ApiService.getUsername());
@@ -25,6 +27,7 @@ const JoinRoom: React.FC = () => {
const [question, setQuestion] = useState();
const [quizMode, setQuizMode] = useState();
const [questions, setQuestions] = useState([]);
+ const [answers, setAnswers] = useState([]);
const [connectionError, setConnectionError] = useState('');
const [isConnecting, setIsConnecting] = useState(false);
@@ -35,6 +38,13 @@ const JoinRoom: React.FC = () => {
};
}, []);
+ useEffect(() => {
+ // init the answers array, one for each question
+ setAnswers(Array(questions.length).fill({} as AnswerSubmissionToBackendType));
+ console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`);
+ }, [questions]);
+
+
const handleCreateSocket = () => {
console.log(`JoinRoom: handleCreateSocket: ${ENV_VARIABLES.VITE_BACKEND_URL}`);
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_URL);
@@ -45,11 +55,18 @@ const JoinRoom: React.FC = () => {
console.log(`on(join-success): Successfully joined the room ${roomJoinedName}`);
});
socket.on('next-question', (question: QuestionType) => {
- console.log('on(next-question): Received next-question:', question);
+ console.log('JoinRoom: on(next-question): Received next-question:', question);
setQuizMode('teacher');
setIsWaitingForTeacher(false);
setQuestion(question);
});
+ socket.on('launch-teacher-mode', (questions: QuestionType[]) => {
+ console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions);
+ setQuizMode('teacher');
+ setIsWaitingForTeacher(true);
+ setQuestions(questions);
+ // wait for next-question
+ });
socket.on('launch-student-mode', (questions: QuestionType[]) => {
console.log('on(launch-student-mode): Received launch-student-mode:', questions);
@@ -83,6 +100,7 @@ const JoinRoom: React.FC = () => {
};
const disconnect = () => {
+// localStorage.clear();
webSocketService.disconnect();
setSocket(null);
setQuestion(undefined);
@@ -107,14 +125,22 @@ const JoinRoom: React.FC = () => {
}
};
- const handleOnSubmitAnswer = (answer: string | number | boolean, idQuestion: number) => {
+ const handleOnSubmitAnswer = (answer: AnswerType, idQuestion: number) => {
+ console.info(`JoinRoom: handleOnSubmitAnswer: answer: ${answer}, idQuestion: ${idQuestion}`);
const answerData: AnswerSubmissionToBackendType = {
roomName: roomName,
answer: answer,
username: username,
idQuestion: idQuestion
};
-
+ // localStorage.setItem(`Answer${idQuestion}`, JSON.stringify(answer));
+ setAnswers((prevAnswers) => {
+ console.log(`JoinRoom: handleOnSubmitAnswer: prevAnswers: ${JSON.stringify(prevAnswers)}`);
+ const newAnswers = [...prevAnswers]; // Create a copy of the previous answers array
+ newAnswers[idQuestion - 1] = answerData; // Update the specific answer
+ return newAnswers; // Return the new array
+ });
+ console.log(`JoinRoom: handleOnSubmitAnswer: answers: ${JSON.stringify(answers)}`);
webSocketService.submitAnswer(answerData);
};
@@ -152,6 +178,7 @@ const JoinRoom: React.FC = () => {
return (
@@ -161,6 +188,7 @@ const JoinRoom: React.FC = () => {
question && (
diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx
index 3a67c27..bcdc80d 100644
--- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx
+++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx
@@ -24,6 +24,7 @@ import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
import ApiService from '../../../services/ApiService';
import { QuestionType } from 'src/Types/QuestionType';
import { Button } from '@mui/material';
+import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
const ManageRoom: React.FC = () => {
const navigate = useNavigate();
@@ -131,7 +132,11 @@ const ManageRoom: React.FC = () => {
if (!quizStarted) return;
if (quizMode === 'teacher') {
- webSocketService.nextQuestion(formattedRoomName, currentQuestion);
+ webSocketService.nextQuestion(
+ {roomName: formattedRoomName,
+ questions: quizQuestions,
+ questionIndex: Number(currentQuestion?.question.id) - 1,
+ isLaunch: false});
} else if (quizMode === 'student') {
webSocketService.launchStudentModeQuiz(formattedRoomName, quizQuestions);
}
@@ -224,7 +229,10 @@ const ManageRoom: React.FC = () => {
if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
setCurrentQuestion(quizQuestions[nextQuestionIndex]);
- webSocketService.nextQuestion(formattedRoomName, quizQuestions[nextQuestionIndex]);
+ webSocketService.nextQuestion({roomName: formattedRoomName,
+ questions: quizQuestions,
+ questionIndex: nextQuestionIndex,
+ isLaunch: false});
};
const previousQuestion = () => {
@@ -234,7 +242,7 @@ const ManageRoom: React.FC = () => {
if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return;
setCurrentQuestion(quizQuestions[prevQuestionIndex]);
- webSocketService.nextQuestion(formattedRoomName, quizQuestions[prevQuestionIndex]);
+ webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: prevQuestionIndex, isLaunch: false});
};
const initializeQuizQuestion = () => {
@@ -262,7 +270,7 @@ const ManageRoom: React.FC = () => {
}
setCurrentQuestion(quizQuestions[0]);
- webSocketService.nextQuestion(formattedRoomName, quizQuestions[0]);
+ webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: 0, isLaunch: true});
};
const launchStudentMode = () => {
@@ -300,9 +308,8 @@ const ManageRoom: React.FC = () => {
const showSelectedQuestion = (questionIndex: number) => {
if (quiz?.content && quizQuestions) {
setCurrentQuestion(quizQuestions[questionIndex]);
-
if (quizMode === 'teacher') {
- webSocketService.nextQuestion(formattedRoomName, quizQuestions[questionIndex]);
+ webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false});
}
}
};
@@ -313,7 +320,7 @@ const ManageRoom: React.FC = () => {
};
function checkIfIsCorrect(
- answer: string | number | boolean,
+ answer: AnswerType,
idQuestion: number,
questions: QuestionType[]
): boolean {
@@ -453,6 +460,7 @@ const ManageRoom: React.FC = () => {
)}
diff --git a/client/src/services/WebsocketService.tsx b/client/src/services/WebsocketService.tsx
index 3cacf36..9262a59 100644
--- a/client/src/services/WebsocketService.tsx
+++ b/client/src/services/WebsocketService.tsx
@@ -1,18 +1,20 @@
import { io, Socket } from 'socket.io-client';
+import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
+import { QuestionType } from 'src/Types/QuestionType';
// Must (manually) sync these types to server/socket/socket.js
export type AnswerSubmissionToBackendType = {
roomName: string;
username: string;
- answer: string | number | boolean;
+ answer: AnswerType;
idQuestion: number;
};
export type AnswerReceptionFromBackendType = {
idUser: string;
username: string;
- answer: string | number | boolean;
+ answer: AnswerType;
idQuestion: number;
};
@@ -59,12 +61,19 @@ class WebSocketService {
// }
// }
- nextQuestion(roomName: string, question: unknown) {
- console.log('WebsocketService: nextQuestion', roomName, question);
- if (!question) {
+ nextQuestion(args: {roomName: string, questions: QuestionType[] | undefined, questionIndex: number, isLaunch: boolean}) {
+ // deconstruct args
+ const { roomName, questions, questionIndex, isLaunch } = args;
+ console.log('WebsocketService: nextQuestion', roomName, questions, questionIndex, isLaunch);
+ if (!questions || !questions[questionIndex]) {
throw new Error('WebsocketService: nextQuestion: question is null');
}
+
if (this.socket) {
+ if (isLaunch) {
+ this.socket.emit('launch-teacher-mode', { roomName, questions });
+ }
+ const question = questions[questionIndex];
this.socket.emit('next-question', { roomName, question });
}
}
diff --git a/server/__tests__/socket.test.js b/server/__tests__/socket.test.js
index 739d79d..2d84da4 100644
--- a/server/__tests__/socket.test.js
+++ b/server/__tests__/socket.test.js
@@ -109,17 +109,29 @@ describe("websocket server", () => {
});
});
- test("should send next question", (done) => {
- studentSocket.on("next-question", (question) => {
- expect(question).toEqual({ question: "question2" });
+ test("should launch teacher mode", (done) => {
+ studentSocket.on("launch-teacher-mode", (questions) => {
+ expect(questions).toEqual([
+ { question: "question1" },
+ { question: "question2" },
+ ]);
done();
});
- teacherSocket.emit("next-question", {
+ teacherSocket.emit("launch-teacher-mode", {
roomName: "ROOM1",
- question: { question: "question2" },
+ questions: [{ question: "question1" }, { question: "question2" }],
});
});
+ test("should send next question", (done) => {
+ studentSocket.on("next-question", ( question ) => {
+ expect(question).toBe("question2");
+ done();
+ });
+ teacherSocket.emit("next-question", { roomName: "ROOM1", question: 'question2'},
+ );
+ });
+
test("should send answer", (done) => {
teacherSocket.on("submit-answer-room", (answer) => {
expect(answer).toEqual({
diff --git a/server/package-lock.json b/server/package-lock.json
index e0fdb07..0f006f8 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -2498,6 +2498,7 @@
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.1"
},
diff --git a/server/socket/socket.js b/server/socket/socket.js
index 393135c..0adaf92 100644
--- a/server/socket/socket.js
+++ b/server/socket/socket.js
@@ -81,6 +81,10 @@ const setupWebsocket = (io) => {
socket.to(roomName).emit("next-question", question);
});
+ socket.on("launch-teacher-mode", ({ roomName, questions }) => {
+ socket.to(roomName).emit("launch-teacher-mode", questions);
+ });
+
socket.on("launch-student-mode", ({ roomName, questions }) => {
socket.to(roomName).emit("launch-student-mode", questions);
});