Massive feature update: Solo session recovery, immediate result saving, image proxy, player reconnection (Live), and UI/UX refinements
This commit is contained in:
+49
-22
@@ -250,6 +250,8 @@ function LiveSetupPage({ config, token, onSessionCreated }) {
|
||||
ws.onmessage = (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'session-created') {
|
||||
// IMPORTANT: Release WS control before handing off
|
||||
ws.onmessage = null;
|
||||
onSessionCreated(msg.code, ws, {
|
||||
code: msg.code,
|
||||
setName: msg.setName,
|
||||
@@ -305,7 +307,7 @@ function LiveSetupPage({ config, token, onSessionCreated }) {
|
||||
}
|
||||
|
||||
/* ================================================================== */
|
||||
/* Live Control Page (FIX: sends admin-join on mount) */
|
||||
/* Live Control Page */
|
||||
/* ================================================================== */
|
||||
|
||||
function LiveControlPage({ token, code, ws, initialSession }) {
|
||||
@@ -314,19 +316,31 @@ function LiveControlPage({ token, code, ws, initialSession }) {
|
||||
const [answerCount, setAnswerCount] = useState(0);
|
||||
const [result, setResult] = useState(null);
|
||||
const [leaderboardData, setLeaderboardData] = useState([]);
|
||||
const refreshRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ws) return;
|
||||
|
||||
const handler = (e) => {
|
||||
// Take over the WS completely with onmessage (no addEventListener confusion)
|
||||
ws.onmessage = (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
console.log('[admin-control] Received:', msg.type);
|
||||
console.log('[admin-control] Received:', msg.type, msg);
|
||||
|
||||
switch (msg.type) {
|
||||
case 'session-created':
|
||||
setSession({ code: msg.code, setName: msg.setName, questionCount: msg.questionCount, options: msg.options, players: [], playerCount: 0 });
|
||||
setSession({
|
||||
code: msg.code,
|
||||
setName: msg.setName,
|
||||
questionCount: msg.questionCount,
|
||||
options: msg.options,
|
||||
players: [],
|
||||
playerCount: 0,
|
||||
});
|
||||
setPhase('waiting');
|
||||
break;
|
||||
|
||||
case 'admin-joined':
|
||||
console.log('[admin-control] Session synced:', msg.session.players, 'count:', msg.session.playerCount);
|
||||
setSession(msg.session);
|
||||
setLeaderboardData(msg.session.leaderboard || []);
|
||||
if (msg.session.status === 'waiting') setPhase('waiting');
|
||||
@@ -334,69 +348,82 @@ function LiveControlPage({ token, code, ws, initialSession }) {
|
||||
else if (msg.session.status === 'showing-result') setPhase('result');
|
||||
else setPhase('question');
|
||||
break;
|
||||
|
||||
case 'player-joined':
|
||||
case 'player-left':
|
||||
console.log('[admin-control] Player update:', msg.players, 'count:', msg.playerCount);
|
||||
setSession((s) => {
|
||||
if (!s) return { code, players: msg.players, playerCount: msg.playerCount };
|
||||
return { ...s, players: msg.players, playerCount: msg.playerCount };
|
||||
const base = s || { code };
|
||||
return { ...base, players: msg.players, playerCount: msg.playerCount };
|
||||
});
|
||||
break;
|
||||
|
||||
case 'game-start':
|
||||
setPhase('active');
|
||||
break;
|
||||
|
||||
case 'question':
|
||||
setPhase('question');
|
||||
setAnswerCount(0);
|
||||
setResult(null);
|
||||
break;
|
||||
|
||||
case 'answer-count':
|
||||
setAnswerCount(msg.count);
|
||||
break;
|
||||
|
||||
case 'question-result':
|
||||
setResult(msg);
|
||||
setLeaderboardData(msg.leaderboard);
|
||||
setPhase('result');
|
||||
break;
|
||||
|
||||
case 'game-end':
|
||||
setLeaderboardData(msg.leaderboard);
|
||||
setPhase('ended');
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
console.error('Admin error:', msg.message);
|
||||
console.error('[admin-control] Error:', msg.message);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
ws.addEventListener('message', handler);
|
||||
// Sync current session state from server
|
||||
console.log('[admin-control] Sending admin-join for code:', code);
|
||||
ws.send(JSON.stringify({ type: 'admin-join', token, code }));
|
||||
|
||||
// Request current session state from server to sync up
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'admin-join', token, code }));
|
||||
} else {
|
||||
const onOpen = () => {
|
||||
// Safety net: periodically re-sync session state every 5 seconds while waiting
|
||||
refreshRef.current = setInterval(() => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'admin-join', token, code }));
|
||||
ws.removeEventListener('open', onOpen);
|
||||
};
|
||||
ws.addEventListener('open', onOpen);
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
return () => ws.removeEventListener('message', handler);
|
||||
return () => {
|
||||
clearInterval(refreshRef.current);
|
||||
ws.onmessage = null;
|
||||
};
|
||||
}, [ws, code, token]);
|
||||
|
||||
// Stop polling once game starts
|
||||
useEffect(() => {
|
||||
if (phase !== 'waiting') {
|
||||
clearInterval(refreshRef.current);
|
||||
}
|
||||
}, [phase]);
|
||||
|
||||
const start = () => {
|
||||
console.log('[admin-control] Sending admin-start');
|
||||
console.log('[admin-control] Starting game');
|
||||
ws.send(JSON.stringify({ type: 'admin-start', token, code }));
|
||||
};
|
||||
const showResultAction = () => {
|
||||
console.log('[admin-control] Sending admin-show-result');
|
||||
ws.send(JSON.stringify({ type: 'admin-show-result', token, code }));
|
||||
};
|
||||
const next = () => {
|
||||
console.log('[admin-control] Sending admin-next');
|
||||
ws.send(JSON.stringify({ type: 'admin-next', token, code }));
|
||||
};
|
||||
const end = () => {
|
||||
console.log('[admin-control] Sending admin-end');
|
||||
ws.send(JSON.stringify({ type: 'admin-end', token, code }));
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user