feat: Add real-time quiz lobby functionality

- Implement WebSocket-based lobby system for quiz participants
- Add lobby page with real-time participant updates
- Create LobbyConsumer for WebSocket communication
- Add routing configuration for WebSocket connections
- Update requirements with WebSocket dependencies
- Add development and production server documentation

This change enables real-time quiz lobbies where participants can
join and wait for the quiz to start, with instant updates for all
connected users.
This commit is contained in:
Juhn-Vitus Saß
2025-04-05 16:22:30 +02:00
parent 8d1489f513
commit 0068e162f7
12 changed files with 319 additions and 31 deletions

View File

@@ -2,20 +2,73 @@
{% block content %}
<div class="container mx-auto px-4">
<h1>{{ quiz.name }}</h1>
<h3>{{ quiz.name }}</h3>
<h1 class="text-center text-4xl font-bold mb-4">{{ quiz_game.join_code }}</h1>
{% if participant %}
<div class="mt-4 mb-4 text-center">
<h3>Sichtbar als <b>{{ participant.display_name }}</b></h3>
</div>
<div id="player-list">
<ul>
<li>Noch keine Spieler gefunden.</li>
{% endif %}
<div class="mb-4">
<h3 class="font-bold mb-2">Teilnehmer:</h3>
<ul id="participants-list" class="space-y-2">
<li class="text-gray-500">Warte auf Teilnehmer...</li>
</ul>
{{debug}}
</div>
<button class="w-full p-3 rounded-full bg-blue-600 text-white font-black hover:scale-105 transition duration-200" type="submit">Starten</button>
{% if host_id %}
<button id="start-button" class="w-full p-3 rounded-full bg-blue-600 text-white font-black hover:scale-105 transition duration-200" type="button">Starten</button>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
<script>
// TODO: Websocket Verbindung aufbauen und Teilnehmerliste bei beitreten updaten
const joinCode = '{{ quiz_game.join_code }}';
const wsScheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
const lobbySocket = new WebSocket(
wsScheme + '://' + window.location.host + '/ws/game/' + joinCode + '/'
);
lobbySocket.onmessage = function(e) {
const data = JSON.parse(e.data);
if (data.type === 'participant_list' || data.type === 'participant_list_update') {
updateParticipantsList(data.participants);
}
};
lobbySocket.onclose = function(e) {
console.error('Lobby socket closed unexpectedly');
};
{% if participant %}
// Send heartbeat every 10 seconds
setInterval(() => {
if (lobbySocket.readyState === WebSocket.OPEN) {
lobbySocket.send(JSON.stringify({
'type': 'heartbeat',
'participant_id': '{{ participant.participant_id }}'
}));
}
}, 10000);
{% endif %}
function updateParticipantsList(participants) {
const list = document.getElementById('participants-list');
if (participants.length === 0) {
list.innerHTML = '<li class="text-gray-500">Noch keine Teilnehmer...</li>';
return;
}
list.innerHTML = participants
.map(p => `<li class="p-2 bg-gray-100 rounded">${p.name}</li>`)
.join('');
}
// Request initial participants list
lobbySocket.onopen = function(e) {
lobbySocket.send(JSON.stringify({
'type': 'update_participants'
}));
};
</script>
{% endblock %}