Files
hitstar/public/js/main.js
Elmar Kresse 33aa410c09
All checks were successful
Build and Push Docker Image / docker (push) Successful in 9s
refactor: enhance session management and room joining logic in WebSocket handling
2025-09-04 18:22:30 +02:00

315 lines
9.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { $audio, $copyRoomCode, $leaveRoom, $nameDisplay, $nameLobby, $npArtist, $npTitle, $npYear, $pauseBtn, $placeBtn, $readyChk, $roomId, $roomCode, $slotSelect, $startGame, $volumeSlider, $playBtn, $nextBtn, $createRoom, $joinRoom, $lobby, $room, $setNameLobby, $guessTitle, $guessArtist, $answerResult } from './dom.js';
import { state } from './state.js';
import { connectWS, sendMsg, cacheSessionId, cacheLastRoomId } from './ws.js';
import { renderRoom } from './render.js';
import { initAudioUI, applySync, stopAudioPlayback } from './audio.js';
function showToast(msg) {
const el = document.getElementById('toast');
if (!el) {
return;
}
el.textContent = msg;
el.style.opacity = '1';
setTimeout(() => {
el.style.opacity = '0';
}, 1200);
}
function handleConnected(msg) {
state.playerId = msg.playerId;
if (msg.sessionId) {
cacheSessionId(msg.sessionId);
}
const stored = localStorage.getItem('playerName');
if (stored) {
if ($nameLobby && $nameLobby.value !== stored) {
$nameLobby.value = stored;
}
if ($nameDisplay) {
$nameDisplay.textContent = stored;
}
sendMsg({ type: 'set_name', name: stored });
}
const last = state.room?.id || localStorage.getItem('lastRoomId');
if (last && !localStorage.getItem('sessionId')) {
sendMsg({ type: 'join_room', code: last });
}
}
function handleRoomUpdate(msg) {
if (msg?.room?.id) cacheLastRoomId(msg.room.id);
renderRoom(msg.room);
}
function handlePlayTrack(msg) {
const t = msg.track;
state.lastTrack = t;
state.revealed = false;
$npTitle.textContent = '???';
$npArtist.textContent = '';
$npYear.textContent = '';
// reset answer UI
if ($guessTitle) $guessTitle.value = '';
if ($guessArtist) $guessArtist.value = '';
if ($answerResult) { $answerResult.textContent=''; $answerResult.className='mt-1 text-sm'; }
try {
$audio.preload = 'auto';
} catch {}
$audio.src = t.url;
const pf = document.getElementById('progressFill');
if (pf) {
pf.style.width = '0%';
}
const rd = document.getElementById('recordDisc');
if (rd) {
rd.classList.remove('spin-record');
}
const { startAt, serverNow } = msg;
const now = Date.now();
const offsetMs = startAt - serverNow;
const localStart = now + offsetMs;
const delay = Math.max(0, localStart - now);
setTimeout(() => {
$audio.currentTime = 0;
$audio.play().catch(() => {});
const disc = document.getElementById('recordDisc');
if (disc) {
disc.classList.add('spin-record');
}
}, delay);
if (state.room) {
renderRoom(state.room);
}
}
function handleSync(msg) {
applySync(msg.startAt, msg.serverNow);
}
function handleControl(msg) {
const { action, startAt, serverNow } = msg;
if (action === 'pause') {
$audio.pause();
const disc = document.getElementById('recordDisc');
if (disc) {
disc.classList.remove('spin-record');
}
$audio.playbackRate = 1.0;
} else if (action === 'play') {
if (startAt && serverNow) {
const now = Date.now();
const elapsed = (now - startAt) / 1000;
$audio.currentTime = Math.max(0, elapsed);
}
$audio.play().catch(() => {});
const disc = document.getElementById('recordDisc');
if (disc) {
disc.classList.add('spin-record');
}
}
}
function handleReveal(msg) {
const { result, track } = msg;
$npTitle.textContent = track.title || track.id || 'Track';
$npArtist.textContent = track.artist ? ` ${track.artist}` : '';
$npYear.textContent = track.year ? ` (${track.year})` : '';
state.revealed = true;
const $rb = document.getElementById('revealBanner');
if ($rb) {
if (result.correct) {
$rb.textContent = 'Richtig!';
$rb.className = 'inline-block rounded-md bg-emerald-600 text-white px-3 py-1 text-sm font-medium';
} else {
$rb.textContent = 'Falsch!';
$rb.className = 'inline-block rounded-md bg-rose-600 text-white px-3 py-1 text-sm font-medium';
}
}
const $placeArea = document.getElementById('placeArea');
if ($placeArea) {
$placeArea.classList.add('hidden');
}
}
function handleGameEnded(msg) {
alert(`Gewinner: ${shortName(msg.winner)}`);
}
function onMessage(ev) {
const msg = JSON.parse(ev.data);
switch (msg.type) {
case 'resume_result':
if (msg.ok) {
if (msg.playerId) state.playerId = msg.playerId;
const code = msg.roomId || state.room?.id || localStorage.getItem('lastRoomId');
if (code) sendMsg({ type: 'join_room', code });
} else {
const code = state.room?.id || localStorage.getItem('lastRoomId');
if (code) sendMsg({ type: 'join_room', code });
}
break;
case 'connected':
handleConnected(msg);
break;
case 'room_update':
handleRoomUpdate(msg);
break;
case 'play_track':
handlePlayTrack(msg);
break;
case 'sync':
handleSync(msg);
break;
case 'control':
handleControl(msg);
break;
case 'reveal':
handleReveal(msg);
break;
case 'game_ended':
handleGameEnded(msg);
break;
case 'answer_result': {
if ($answerResult) {
if (!msg.ok) {
$answerResult.textContent = '⛔ Eingabe ungültig oder gerade nicht möglich';
$answerResult.className = 'mt-1 text-sm text-rose-600';
} else {
const okBoth = !!(msg.correctTitle && msg.correctArtist);
const parts = [];
parts.push(msg.correctTitle ? 'Titel ✓' : 'Titel ✗');
parts.push(msg.correctArtist ? 'Künstler ✓' : 'Künstler ✗');
let coin = '';
if (msg.awarded) coin = ' +1 Token';
else if (msg.alreadyAwarded) coin = ' (bereits erhalten)';
$answerResult.textContent = `${parts.join(' · ')}${coin}`;
$answerResult.className = okBoth ? 'mt-1 text-sm text-emerald-600' : 'mt-1 text-sm text-amber-600';
}
}
break;
}
default:
break;
}
}
function shortName(id) {
if (!id) return '-';
const p = state.room?.players.find((x) => x.id === id);
return p ? p.name : id.slice(0, 4);
}
function wire(el, type, handler) {
if (el) {
el.addEventListener(type, handler);
}
}
function wireUi() {
initAudioUI();
wire($setNameLobby, 'click', () => {
const name = $nameLobby.value.trim();
if (!name) return;
localStorage.setItem('playerName', name);
if ($nameDisplay) {
$nameDisplay.textContent = name;
}
sendMsg({ type: 'set_name', name });
});
wire($createRoom, 'click', () => sendMsg({ type: 'create_room' }));
wire($joinRoom, 'click', () => {
const code = $roomCode.value.trim();
if (code) {
sendMsg({ type: 'join_room', code });
}
});
wire($leaveRoom, 'click', () => {
sendMsg({ type: 'leave_room' });
stopAudioPlayback();
state.room = null;
$lobby.classList.remove('hidden');
$room.classList.add('hidden');
});
wire($startGame, 'click', () => sendMsg({ type: 'start_game' }));
wire($readyChk, 'change', (e) => {
const val = !!e.target.checked;
state.pendingReady = val;
sendMsg({ type: 'set_ready', ready: val });
});
wire($placeBtn, 'click', () => {
const slot = parseInt($slotSelect.value, 10);
sendMsg({ type: 'place_guess', slot });
});
wire($playBtn, 'click', () => sendMsg({ type: 'player_control', action: 'play' }));
wire($pauseBtn, 'click', () => sendMsg({ type: 'player_control', action: 'pause' }));
wire($nextBtn, 'click', () => sendMsg({ type: 'next_track' }));
if ($volumeSlider && $audio) {
try {
$volumeSlider.value = String($audio.volume ?? 1);
} catch {}
$volumeSlider.addEventListener('input', () => {
$audio.volume = parseFloat($volumeSlider.value);
});
}
if ($copyRoomCode) {
$copyRoomCode.style.display = 'inline-block';
wire($copyRoomCode, 'click', () => {
if (state.room?.id) {
navigator.clipboard.writeText(state.room.id).then(() => {
$copyRoomCode.textContent = '✔️';
showToast('Code kopiert!');
setTimeout(() => {
$copyRoomCode.textContent = '📋';
}, 1200);
});
}
});
}
if ($roomId) {
wire($roomId, 'click', () => {
if (state.room?.id) {
navigator.clipboard.writeText(state.room.id).then(() => {
$roomId.title = 'Kopiert!';
showToast('Code kopiert!');
setTimeout(() => {
$roomId.title = 'Klicken zum Kopieren';
}, 1200);
});
}
});
$roomId.style.cursor = 'pointer';
}
// Answer submit
const form = document.getElementById('answerForm');
if (form) {
form.addEventListener('submit', (e) => {
e.preventDefault();
const title = ($guessTitle?.value || '').trim();
const artist = ($guessArtist?.value || '').trim();
if (!title || !artist) {
if ($answerResult) { $answerResult.textContent = 'Bitte Titel und Künstler eingeben'; $answerResult.className = 'mt-1 text-sm text-amber-600'; }
return;
}
sendMsg({ type: 'submit_answer', guess: { title, artist } });
});
}
}
// boot
wireUi();
connectWS(onMessage);
// restore name immediately if present
(() => {
const saved = localStorage.getItem('playerName');
if (saved) {
if ($nameLobby && $nameLobby.value !== saved) {
$nameLobby.value = saved;
}
if ($nameDisplay) {
$nameDisplay.textContent = saved;
}
}
})();