Merge branch 'v4-extend-v2'
Browse files- README.md +9 -4
- __pycache__/backend.cpython-312.pyc +0 -0
- app.py +52 -33
- assets/2024-10-21-17-07-42.png +0 -0
- assets/2024-10-21-17-08-13.png +0 -0
- assets/2024-10-21-17-09-15.png +0 -0
- backend.py +23 -0
- questions/CLF-C02-v1.json +0 -0
- static/script.js +66 -20
- static/style.css +26 -0
- templates/client.html +21 -2
- templates/host.html +21 -6
README.md
CHANGED
@@ -15,14 +15,19 @@ A Python-based real-time quiz application designed to mimic the functionality of
|
|
15 |
```bash
|
16 |
Real-Time-Quiz-Application/
|
17 |
β
|
|
|
18 |
βββ app.py # Main Flask application
|
|
|
19 |
βββ templates/
|
20 |
β βββ index.html # Main index page with client and host buttons
|
21 |
β βββ client.html # Client interface
|
22 |
β βββ host.html # Host interface
|
23 |
βββ static/
|
24 |
-
β βββ style.css #
|
25 |
β βββ script.js # JavaScript for real-time interactions
|
|
|
|
|
|
|
26 |
βββ requirements.txt # Python dependencies
|
27 |
βββ README.md # Project documentation
|
28 |
```
|
@@ -57,12 +62,12 @@ pip install -r requirements.txt
|
|
57 |
```bash
|
58 |
python app.py
|
59 |
```
|
60 |
-
![](assets/2024-10-21-
|
61 |
4. **Access the application**:
|
62 |
- **Host interface**: [http://127.0.0.1:5000/host](http://127.0.0.1:5000/host)
|
63 |
-
|
64 |
- **Client interface**: [http://127.0.0.1:5000/client](http://127.0.0.1:5000/client)
|
65 |
-
![](assets/2024-10-21-
|
66 |
## Application Overview
|
67 |
|
68 |
### Host Interface
|
|
|
15 |
```bash
|
16 |
Real-Time-Quiz-Application/
|
17 |
β
|
18 |
+
β
|
19 |
βββ app.py # Main Flask application
|
20 |
+
βββ backend.py # Backend logic for loading and managing exams
|
21 |
βββ templates/
|
22 |
β βββ index.html # Main index page with client and host buttons
|
23 |
β βββ client.html # Client interface
|
24 |
β βββ host.html # Host interface
|
25 |
βββ static/
|
26 |
+
β βββ style.css # Custom styles to mimic the OnVUE exam appearance
|
27 |
β βββ script.js # JavaScript for real-time interactions
|
28 |
+
βββ questions/ # Folder containing exam JSON files
|
29 |
+
β βββ exam1.json # Example JSON file for exam 1
|
30 |
+
β βββ exam2.json # Example JSON file for exam 2
|
31 |
βββ requirements.txt # Python dependencies
|
32 |
βββ README.md # Project documentation
|
33 |
```
|
|
|
62 |
```bash
|
63 |
python app.py
|
64 |
```
|
65 |
+
![](assets/2024-10-21-17-07-42.png)
|
66 |
4. **Access the application**:
|
67 |
- **Host interface**: [http://127.0.0.1:5000/host](http://127.0.0.1:5000/host)
|
68 |
+
![](assets/2024-10-21-17-08-13.png)
|
69 |
- **Client interface**: [http://127.0.0.1:5000/client](http://127.0.0.1:5000/client)
|
70 |
+
![](assets/2024-10-21-17-09-15.png)
|
71 |
## Application Overview
|
72 |
|
73 |
### Host Interface
|
__pycache__/backend.cpython-312.pyc
ADDED
Binary file (1.37 kB). View file
|
|
app.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
from flask import Flask, render_template, request
|
2 |
from flask_socketio import SocketIO, emit, join_room, leave_room
|
|
|
3 |
import matplotlib.pyplot as plt
|
4 |
import base64
|
5 |
from io import BytesIO
|
@@ -9,11 +10,8 @@ app = Flask(__name__)
|
|
9 |
app.config['SECRET_KEY'] = 'your_secret_key'
|
10 |
socketio = SocketIO(app)
|
11 |
|
12 |
-
|
13 |
-
|
14 |
-
{"question": "What is the largest planet?", "options": ["Earth", "Mars", "Jupiter", "Saturn"], "correct": "Jupiter"}
|
15 |
-
]
|
16 |
-
initial_questions = questions.copy() # Keep a copy of the original questions to reset later
|
17 |
current_question = {"index": 0, "answers": {}, "started": False}
|
18 |
participants = {}
|
19 |
|
@@ -27,12 +25,12 @@ def client():
|
|
27 |
|
28 |
@app.route('/host')
|
29 |
def host():
|
30 |
-
return render_template('host.html')
|
31 |
|
32 |
@socketio.on('join')
|
33 |
def on_join(data):
|
34 |
username = data['username']
|
35 |
-
user_id_number = random.randint(1000, 9999)
|
36 |
participants[request.sid] = {"user_id_number": user_id_number, "username": username, "score": 0}
|
37 |
join_room('quiz')
|
38 |
emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
|
@@ -47,28 +45,46 @@ def on_leave():
|
|
47 |
emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
|
48 |
print(f"{username} left the quiz.")
|
49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
@socketio.on('start_quiz')
|
51 |
def start_quiz():
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
emit('
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
@socketio.on('submit_answer')
|
60 |
def receive_answer(data):
|
61 |
username = participants[request.sid]["username"]
|
62 |
answer = data['answer']
|
63 |
current_question['answers'][username] = answer
|
64 |
-
|
65 |
-
emit('all_answers_received', room='quiz')
|
66 |
|
67 |
@socketio.on('check_answers')
|
68 |
def check_answers():
|
69 |
index = current_question['index']
|
70 |
-
if index < len(
|
71 |
-
question =
|
72 |
correct_answer = question['correct']
|
73 |
results = {
|
74 |
"question": question["question"],
|
@@ -76,11 +92,9 @@ def check_answers():
|
|
76 |
"correct_answer": correct_answer
|
77 |
}
|
78 |
|
79 |
-
# Generate the chart and encode it as base64
|
80 |
chart_base64 = generate_chart(current_question["answers"], question["options"])
|
81 |
emit('display_results', {"results": results, "chart": chart_base64}, room='quiz')
|
82 |
|
83 |
-
# Update scores based on user_id_number
|
84 |
for sid, participant in participants.items():
|
85 |
if current_question['answers'].get(participant["username"]) == correct_answer:
|
86 |
participants[sid]["score"] += 1
|
@@ -89,23 +103,29 @@ def check_answers():
|
|
89 |
def next_question():
|
90 |
current_question['index'] += 1
|
91 |
current_question['answers'] = {}
|
92 |
-
if current_question['index'] < len(
|
93 |
-
question =
|
94 |
-
emit('clear_results', room='quiz')
|
95 |
emit('new_question', question, room='quiz')
|
|
|
|
|
96 |
else:
|
97 |
final_results = calculate_final_results()
|
98 |
-
emit('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
99 |
|
100 |
-
@socketio.on('restart_quiz')
|
101 |
-
def restart_quiz():
|
102 |
-
reset_quiz()
|
103 |
-
emit('quiz_reset', room='quiz')
|
104 |
|
105 |
def generate_chart(answers, options):
|
|
|
106 |
counts = [list(answers.values()).count(option) for option in options]
|
107 |
plt.figure(figsize=(6, 4))
|
108 |
-
plt.bar(
|
109 |
plt.xlabel('Options')
|
110 |
plt.ylabel('Number of Votes')
|
111 |
plt.title('Results')
|
@@ -118,15 +138,14 @@ def generate_chart(answers, options):
|
|
118 |
return chart_base64
|
119 |
|
120 |
def calculate_final_results():
|
121 |
-
|
122 |
-
return
|
123 |
|
124 |
def reset_quiz():
|
125 |
-
global
|
126 |
-
questions = initial_questions.copy()
|
127 |
current_question = {"index": 0, "answers": {}, "started": False}
|
128 |
for participant in participants.values():
|
129 |
participant["score"] = 0
|
130 |
|
131 |
if __name__ == '__main__':
|
132 |
-
socketio.run(app, debug=True)
|
|
|
1 |
from flask import Flask, render_template, request
|
2 |
from flask_socketio import SocketIO, emit, join_room, leave_room
|
3 |
+
import backend # Import backend functions
|
4 |
import matplotlib.pyplot as plt
|
5 |
import base64
|
6 |
from io import BytesIO
|
|
|
10 |
app.config['SECRET_KEY'] = 'your_secret_key'
|
11 |
socketio = SocketIO(app)
|
12 |
|
13 |
+
exams = backend.load_question_sets() # Load available exams
|
14 |
+
selected_questions = [] # Global variable to store the selected questions
|
|
|
|
|
|
|
15 |
current_question = {"index": 0, "answers": {}, "started": False}
|
16 |
participants = {}
|
17 |
|
|
|
25 |
|
26 |
@app.route('/host')
|
27 |
def host():
|
28 |
+
return render_template('host.html', exams=exams)
|
29 |
|
30 |
@socketio.on('join')
|
31 |
def on_join(data):
|
32 |
username = data['username']
|
33 |
+
user_id_number = random.randint(1000, 9999)
|
34 |
participants[request.sid] = {"user_id_number": user_id_number, "username": username, "score": 0}
|
35 |
join_room('quiz')
|
36 |
emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
|
|
|
45 |
emit('update_participants', {"participants": participants, "count": len(participants)}, room='quiz')
|
46 |
print(f"{username} left the quiz.")
|
47 |
|
48 |
+
@socketio.on('load_quiz')
|
49 |
+
def load_quiz(data):
|
50 |
+
global selected_questions
|
51 |
+
exam_name = data['exam_name']
|
52 |
+
start_question = data['start_question'] - 1 # Adjust for 0-based indexing
|
53 |
+
selected_questions = backend.select_exam(exam_name)
|
54 |
+
if selected_questions:
|
55 |
+
num_questions = len(selected_questions)
|
56 |
+
current_question['index'] = start_question
|
57 |
+
emit('quiz_loaded', {"success": True, "num_questions": num_questions, "start_question": start_question + 1}, room=request.sid)
|
58 |
+
else:
|
59 |
+
emit('quiz_loaded', {"success": False}, room=request.sid)
|
60 |
+
|
61 |
@socketio.on('start_quiz')
|
62 |
def start_quiz():
|
63 |
+
if participants and selected_questions:
|
64 |
+
current_question['started'] = True
|
65 |
+
emit('new_question', selected_questions[current_question['index']], room='quiz')
|
66 |
+
# Also emit the question to the host
|
67 |
+
emit('new_question', selected_questions[current_question['index']], room=request.sid)
|
68 |
+
emit('enable_end_quiz', room=request.sid) # Enable "End Quiz" for the host
|
69 |
+
|
70 |
+
@socketio.on('restart_quiz')
|
71 |
+
def restart_quiz():
|
72 |
+
reset_quiz()
|
73 |
+
emit('quiz_reset', room='quiz')
|
74 |
+
start_quiz()
|
75 |
|
76 |
@socketio.on('submit_answer')
|
77 |
def receive_answer(data):
|
78 |
username = participants[request.sid]["username"]
|
79 |
answer = data['answer']
|
80 |
current_question['answers'][username] = answer
|
81 |
+
print(f"{username} submitted an answer: {answer}")
|
|
|
82 |
|
83 |
@socketio.on('check_answers')
|
84 |
def check_answers():
|
85 |
index = current_question['index']
|
86 |
+
if index < len(selected_questions):
|
87 |
+
question = selected_questions[index]
|
88 |
correct_answer = question['correct']
|
89 |
results = {
|
90 |
"question": question["question"],
|
|
|
92 |
"correct_answer": correct_answer
|
93 |
}
|
94 |
|
|
|
95 |
chart_base64 = generate_chart(current_question["answers"], question["options"])
|
96 |
emit('display_results', {"results": results, "chart": chart_base64}, room='quiz')
|
97 |
|
|
|
98 |
for sid, participant in participants.items():
|
99 |
if current_question['answers'].get(participant["username"]) == correct_answer:
|
100 |
participants[sid]["score"] += 1
|
|
|
103 |
def next_question():
|
104 |
current_question['index'] += 1
|
105 |
current_question['answers'] = {}
|
106 |
+
if current_question['index'] < len(selected_questions):
|
107 |
+
question = selected_questions[current_question['index']]
|
108 |
+
emit('clear_results', room='quiz')
|
109 |
emit('new_question', question, room='quiz')
|
110 |
+
# Also emit the question to the host
|
111 |
+
emit('new_question', question, room=request.sid)
|
112 |
else:
|
113 |
final_results = calculate_final_results()
|
114 |
+
emit('display_final_results', final_results, room='quiz')
|
115 |
+
|
116 |
+
@socketio.on('end_quiz')
|
117 |
+
def end_quiz():
|
118 |
+
if current_question['started']: # Ensure the quiz has started before ending it
|
119 |
+
final_results = calculate_final_results()
|
120 |
+
emit('display_final_results', final_results, room='quiz')
|
121 |
+
reset_quiz() # Reset the quiz state
|
122 |
|
|
|
|
|
|
|
|
|
123 |
|
124 |
def generate_chart(answers, options):
|
125 |
+
letters = [chr(65 + i) for i in range(len(options))] # Dynamically generate letters for options
|
126 |
counts = [list(answers.values()).count(option) for option in options]
|
127 |
plt.figure(figsize=(6, 4))
|
128 |
+
plt.bar(letters, counts)
|
129 |
plt.xlabel('Options')
|
130 |
plt.ylabel('Number of Votes')
|
131 |
plt.title('Results')
|
|
|
138 |
return chart_base64
|
139 |
|
140 |
def calculate_final_results():
|
141 |
+
sorted_scores = sorted(participants.values(), key=lambda x: x['score'], reverse=True)
|
142 |
+
return [{"username": p["username"], "score": p["score"]} for p in sorted_scores]
|
143 |
|
144 |
def reset_quiz():
|
145 |
+
global selected_questions, current_question
|
|
|
146 |
current_question = {"index": 0, "answers": {}, "started": False}
|
147 |
for participant in participants.values():
|
148 |
participant["score"] = 0
|
149 |
|
150 |
if __name__ == '__main__':
|
151 |
+
socketio.run(app, debug=True)
|
assets/2024-10-21-17-07-42.png
ADDED
assets/2024-10-21-17-08-13.png
ADDED
assets/2024-10-21-17-09-15.png
ADDED
backend.py
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
|
4 |
+
# Function to load question sets from the "questions" directory
|
5 |
+
def load_question_sets(directory='questions'):
|
6 |
+
question_sets = []
|
7 |
+
for root, dirs, files in os.walk(directory):
|
8 |
+
for file in files:
|
9 |
+
if file.endswith(".json"):
|
10 |
+
question_sets.append(file[:-5]) # Remove the ".json" extension
|
11 |
+
return question_sets
|
12 |
+
|
13 |
+
# Function to select and load the specified exam
|
14 |
+
def select_exam(exam_name):
|
15 |
+
file_path = os.path.join('questions', f'{exam_name}.json')
|
16 |
+
try:
|
17 |
+
with open(file_path, 'r') as f:
|
18 |
+
questions = json.load(f)
|
19 |
+
print(f"Loaded {len(questions)} questions from {exam_name}")
|
20 |
+
return questions
|
21 |
+
except FileNotFoundError:
|
22 |
+
print(f"File {file_path} not found.")
|
23 |
+
return [] # Return an empty list if the file is not found
|
questions/CLF-C02-v1.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
static/script.js
CHANGED
@@ -12,21 +12,33 @@ function joinQuiz() {
|
|
12 |
document.getElementById('join-title').style.display = 'none';
|
13 |
}
|
14 |
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
-
|
20 |
-
document.getElementById('
|
21 |
-
document.getElementById('question-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
27 |
|
28 |
-
function
|
29 |
-
|
|
|
|
|
30 |
}
|
31 |
|
32 |
function startQuiz() {
|
@@ -41,30 +53,64 @@ function nextQuestion() {
|
|
41 |
socket.emit('next_question');
|
42 |
}
|
43 |
|
|
|
|
|
|
|
|
|
44 |
function restartQuiz() {
|
45 |
socket.emit('restart_quiz');
|
46 |
}
|
47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
socket.on('display_results', (data) => {
|
49 |
const img = `<img src="data:image/png;base64,${data.chart}" alt="Results Chart" />`;
|
50 |
const resultText = `<p>Correct Answer: ${data.results.correct_answer}</p>`;
|
51 |
document.getElementById('results').innerHTML = img + resultText;
|
52 |
});
|
53 |
|
|
|
|
|
|
|
|
|
54 |
socket.on('clear_results', () => {
|
55 |
document.getElementById('results').innerHTML = '';
|
56 |
});
|
57 |
|
58 |
-
socket.on('
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
|
|
|
|
|
|
64 |
});
|
65 |
|
66 |
socket.on('quiz_reset', () => {
|
67 |
document.getElementById('results').innerHTML = '';
|
68 |
document.getElementById('question-text').innerText = '';
|
69 |
document.getElementById('options').innerHTML = '';
|
70 |
-
|
|
|
|
|
|
|
|
12 |
document.getElementById('join-title').style.display = 'none';
|
13 |
}
|
14 |
|
15 |
+
function submitForm(event) {
|
16 |
+
event.preventDefault();
|
17 |
+
const selectedOption = document.querySelector('input[name="answer"]:checked');
|
18 |
+
if (selectedOption) {
|
19 |
+
const answer = selectedOption.value;
|
20 |
+
socket.emit('submit_answer', { answer });
|
21 |
+
} else {
|
22 |
+
alert("Please select an option before submitting.");
|
23 |
+
}
|
24 |
+
}
|
25 |
|
26 |
+
function selectExam() {
|
27 |
+
const examName = document.getElementById('exam-selector').value;
|
28 |
+
const startQuestion = document.getElementById('start-question-number').value;
|
29 |
+
document.getElementById('question-start-display').textContent = `Starting from question ${startQuestion}.`;
|
30 |
+
}
|
31 |
+
|
32 |
+
function loadQuiz() {
|
33 |
+
const examName = document.getElementById('exam-selector').value;
|
34 |
+
const startQuestion = document.getElementById('start-question-number').value;
|
35 |
+
socket.emit('load_quiz', { exam_name: examName, start_question: parseInt(startQuestion) });
|
36 |
+
}
|
37 |
|
38 |
+
function updateSliderValue(value) {
|
39 |
+
document.getElementById('start-question').value = value;
|
40 |
+
document.getElementById('start-question-number').value = value;
|
41 |
+
document.getElementById('question-start-display').textContent = `Starting from question ${value}.`;
|
42 |
}
|
43 |
|
44 |
function startQuiz() {
|
|
|
53 |
socket.emit('next_question');
|
54 |
}
|
55 |
|
56 |
+
function endQuiz() {
|
57 |
+
socket.emit('end_quiz');
|
58 |
+
}
|
59 |
+
|
60 |
function restartQuiz() {
|
61 |
socket.emit('restart_quiz');
|
62 |
}
|
63 |
|
64 |
+
socket.on('quiz_loaded', (data) => {
|
65 |
+
if (data.success) {
|
66 |
+
alert(`Quiz loaded with ${data.num_questions} questions, starting from question ${data.start_question}.`);
|
67 |
+
} else {
|
68 |
+
alert(`Failed to load quiz.`);
|
69 |
+
}
|
70 |
+
});
|
71 |
+
|
72 |
+
socket.on('new_question', (data) => {
|
73 |
+
document.getElementById('waiting-message').style.display = 'none';
|
74 |
+
document.getElementById('question-text').innerText = data.question;
|
75 |
+
// Dynamically generate letters for options (up to 'h')
|
76 |
+
const letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
77 |
+
const options = data.options.map((opt, index) =>
|
78 |
+
`<input type="radio" id="${letters[index]}" name="answer" value="${opt}">
|
79 |
+
<label for="${letters[index]}">${letters[index]}) ${opt}</label><br>`
|
80 |
+
).join('');
|
81 |
+
document.getElementById('options').innerHTML = options;
|
82 |
+
});
|
83 |
+
|
84 |
socket.on('display_results', (data) => {
|
85 |
const img = `<img src="data:image/png;base64,${data.chart}" alt="Results Chart" />`;
|
86 |
const resultText = `<p>Correct Answer: ${data.results.correct_answer}</p>`;
|
87 |
document.getElementById('results').innerHTML = img + resultText;
|
88 |
});
|
89 |
|
90 |
+
socket.on('enable_end_quiz', () => {
|
91 |
+
document.getElementById('end-quiz').disabled = false; // Enable the "End Quiz" button
|
92 |
+
});
|
93 |
+
|
94 |
socket.on('clear_results', () => {
|
95 |
document.getElementById('results').innerHTML = '';
|
96 |
});
|
97 |
|
98 |
+
socket.on('display_final_results', (finalResults) => {
|
99 |
+
document.getElementById('quiz-content').style.display = 'none';
|
100 |
+
const resultsTable = document.getElementById('results-table');
|
101 |
+
resultsTable.innerHTML = '';
|
102 |
+
finalResults.forEach((participant) => {
|
103 |
+
const row = `<tr><td>${participant.username}</td><td>${participant.score}</td></tr>`;
|
104 |
+
resultsTable.innerHTML += row;
|
105 |
+
});
|
106 |
+
document.getElementById('final-results').style.display = 'block';
|
107 |
});
|
108 |
|
109 |
socket.on('quiz_reset', () => {
|
110 |
document.getElementById('results').innerHTML = '';
|
111 |
document.getElementById('question-text').innerText = '';
|
112 |
document.getElementById('options').innerHTML = '';
|
113 |
+
document.getElementById('final-results').style.display = 'none';
|
114 |
+
document.getElementById('quiz-content').style.display = 'block';
|
115 |
+
document.getElementById('waiting-message').style.display = 'block';
|
116 |
+
});
|
static/style.css
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body {
|
2 |
+
background-color: #f4f4f4;
|
3 |
+
font-family: Arial, sans-serif;
|
4 |
+
}
|
5 |
+
|
6 |
+
.container {
|
7 |
+
max-width: 800px;
|
8 |
+
margin: 0 auto;
|
9 |
+
background-color: #fff;
|
10 |
+
padding: 20px;
|
11 |
+
border-radius: 8px;
|
12 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
13 |
+
}
|
14 |
+
|
15 |
+
h2, p {
|
16 |
+
font-size: 18px;
|
17 |
+
}
|
18 |
+
|
19 |
+
input[type="radio"] {
|
20 |
+
margin-right: 10px;
|
21 |
+
}
|
22 |
+
|
23 |
+
input[type="submit"] {
|
24 |
+
display: block;
|
25 |
+
margin-top: 20px;
|
26 |
+
}
|
templates/client.html
CHANGED
@@ -5,6 +5,7 @@
|
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>Quiz Client</title>
|
7 |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
|
|
8 |
</head>
|
9 |
<body>
|
10 |
<div class="container">
|
@@ -14,10 +15,28 @@
|
|
14 |
<div id="quiz-content" style="display: none;">
|
15 |
<h3>Logged in as: <span id="logged-user"></span></h3>
|
16 |
<h3 id="waiting-message" style="display: none;">Waiting for the Host...</h3>
|
17 |
-
<
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
19 |
<div id="results" class="mt-4"></div>
|
20 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
</div>
|
22 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
|
23 |
<script src="/static/script.js"></script>
|
|
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>Quiz Client</title>
|
7 |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
8 |
+
<link rel="stylesheet" href="/static/style.css">
|
9 |
</head>
|
10 |
<body>
|
11 |
<div class="container">
|
|
|
15 |
<div id="quiz-content" style="display: none;">
|
16 |
<h3>Logged in as: <span id="logged-user"></span></h3>
|
17 |
<h3 id="waiting-message" style="display: none;">Waiting for the Host...</h3>
|
18 |
+
<div id="question-section" class="mt-4">
|
19 |
+
<form id="quiz-form" onsubmit="submitForm(event)">
|
20 |
+
<p id="question-text"></p>
|
21 |
+
<div id="options"></div>
|
22 |
+
<input type="submit" value="Submit" class="btn btn-primary mt-2">
|
23 |
+
</form>
|
24 |
+
</div>
|
25 |
<div id="results" class="mt-4"></div>
|
26 |
</div>
|
27 |
+
<div id="final-results" style="display: none;" class="mt-4">
|
28 |
+
<h3>And the Winners are:</h3>
|
29 |
+
<table class="table mt-2">
|
30 |
+
<thead>
|
31 |
+
<tr>
|
32 |
+
<th>Participant</th>
|
33 |
+
<th>Score</th>
|
34 |
+
</tr>
|
35 |
+
</thead>
|
36 |
+
<tbody id="results-table">
|
37 |
+
</tbody>
|
38 |
+
</table>
|
39 |
+
</div>
|
40 |
</div>
|
41 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
|
42 |
<script src="/static/script.js"></script>
|
templates/host.html
CHANGED
@@ -5,17 +5,32 @@
|
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>Quiz Host</title>
|
7 |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
|
|
8 |
</head>
|
9 |
<body>
|
10 |
<div class="container">
|
11 |
<h2>Quiz Host</h2>
|
12 |
<p>Participants connected: <span id="participant-count">0</span></p>
|
13 |
-
<
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
<div id="results" class="mt-4"></div>
|
20 |
</div>
|
21 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
|
|
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<title>Quiz Host</title>
|
7 |
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
|
8 |
+
<link rel="stylesheet" href="/static/style.css">
|
9 |
</head>
|
10 |
<body>
|
11 |
<div class="container">
|
12 |
<h2>Quiz Host</h2>
|
13 |
<p>Participants connected: <span id="participant-count">0</span></p>
|
14 |
+
<select id="exam-selector" class="form-control" onchange="selectExam()">
|
15 |
+
<option value="" disabled selected>Select an exam</option>
|
16 |
+
{% for exam in exams %}
|
17 |
+
<option value="{{ exam }}">{{ exam }}</option>
|
18 |
+
{% endfor %}
|
19 |
+
</select>
|
20 |
+
<label for="start-question" class="mt-3">Select starting question:</label>
|
21 |
+
<input type="range" id="start-question" min="1" max="10" value="1" class="form-range mt-2 mb-2" oninput="updateSliderValue(this.value)">
|
22 |
+
<input type="number" id="start-question-number" min="1" max="10" value="1" class="form-control" oninput="updateSliderValue(this.value)">
|
23 |
+
<p id="question-start-display" class="mt-2">Starting from question 1.</p>
|
24 |
+
<button onclick="loadQuiz()" class="btn btn-info mt-3">Load Quiz</button><br><br>
|
25 |
+
<button onclick="startQuiz()" class="btn btn-success mt-3">Start Quiz</button><br><br>
|
26 |
+
<button onclick="restartQuiz()" class="btn btn-warning mt-3">Restart</button><br><br>
|
27 |
+
<button onclick="checkAnswers()" class="btn btn-primary mt-2">Check Answers</button><br><br>
|
28 |
+
<button onclick="nextQuestion()" class="btn btn-secondary mt-2">Next Question</button><br><br>
|
29 |
+
<button onclick="endQuiz()" id="end-quiz" class="btn btn-danger mt-2" disabled>End Quiz</button>
|
30 |
+
<div id="question-section" class="mt-4">
|
31 |
+
<p id="question-text"></p>
|
32 |
+
<div id="options"></div>
|
33 |
+
</div>
|
34 |
<div id="results" class="mt-4"></div>
|
35 |
</div>
|
36 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.js"></script>
|