299 lines
8.6 KiB
JavaScript
299 lines
8.6 KiB
JavaScript
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 } from './ws.js';
|
||
import { renderRoom } from './render.js';
|
||
import { initAudioUI, applySync } 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;
|
||
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 });
|
||
}
|
||
if (state.room?.id) {
|
||
sendMsg({ type: 'join_room', code: state.room.id });
|
||
}
|
||
}
|
||
|
||
function handleRoomUpdate(msg) {
|
||
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 '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' });
|
||
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;
|
||
}
|
||
}
|
||
})();
|