All checks were successful
Build and Push Docker Image / docker (push) Successful in 9s
230 lines
6.6 KiB
JavaScript
230 lines
6.6 KiB
JavaScript
import {
|
||
$answerResult,
|
||
$audio,
|
||
$guessArtist,
|
||
$guessTitle,
|
||
$npArtist,
|
||
$npTitle,
|
||
$npYear,
|
||
} from './dom.js';
|
||
import { state } from './state.js';
|
||
import { cacheLastRoomId, cacheSessionId, sendMsg } from './ws.js';
|
||
import { renderRoom } from './render.js';
|
||
import { applySync } from './audio.js';
|
||
|
||
function updatePlayerIdFromRoom(r) {
|
||
try {
|
||
if (r?.players?.length === 1) {
|
||
const only = r.players[0];
|
||
if (only && only.id && only.id !== state.playerId) {
|
||
state.playerId = only.id;
|
||
try {
|
||
localStorage.setItem('playerId', only.id);
|
||
} catch {}
|
||
}
|
||
}
|
||
} catch {}
|
||
}
|
||
|
||
function shortName(id) {
|
||
if (!id) return '-';
|
||
const p = state.room?.players.find((x) => x.id === id);
|
||
return p ? p.name : id.slice(0, 4);
|
||
}
|
||
|
||
export function handleConnected(msg) {
|
||
console.debug('handleConnected', msg);
|
||
state.playerId = msg.playerId;
|
||
try {
|
||
if (msg.playerId) localStorage.setItem('playerId', msg.playerId);
|
||
} catch {}
|
||
if (msg.sessionId) {
|
||
const existing = localStorage.getItem('sessionId');
|
||
if (!existing) cacheSessionId(msg.sessionId);
|
||
}
|
||
|
||
// lazy import to avoid cycle
|
||
import('./session.js').then(({ reusePlayerName, reconnectLastRoom }) => {
|
||
reusePlayerName();
|
||
reconnectLastRoom();
|
||
});
|
||
|
||
if (state.room) {
|
||
try {
|
||
updatePlayerIdFromRoom(state.room);
|
||
renderRoom(state.room);
|
||
} catch {}
|
||
}
|
||
}
|
||
|
||
export function handleRoomUpdate(msg) {
|
||
if (msg?.room?.id) cacheLastRoomId(msg.room.id);
|
||
const r = msg.room;
|
||
updatePlayerIdFromRoom(r);
|
||
renderRoom(r);
|
||
}
|
||
|
||
export function handlePlayTrack(msg) {
|
||
const t = msg.track;
|
||
state.lastTrack = t;
|
||
state.revealed = false;
|
||
$npTitle.textContent = '???';
|
||
$npArtist.textContent = '';
|
||
$npYear.textContent = '';
|
||
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');
|
||
rd.src = '/hitstar.png';
|
||
}
|
||
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);
|
||
}
|
||
|
||
export function handleSync(msg) {
|
||
applySync(msg.startAt, msg.serverNow);
|
||
}
|
||
|
||
export 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');
|
||
}
|
||
}
|
||
|
||
export 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');
|
||
const rd = document.getElementById('recordDisc');
|
||
if (rd && track?.id) {
|
||
const coverUrl = `/cover/${encodeURIComponent(track.id)}`;
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
rd.src = coverUrl;
|
||
};
|
||
img.onerror = () => {
|
||
/* keep default logo */
|
||
};
|
||
img.src = `${coverUrl}?t=${Date.now()}`;
|
||
}
|
||
}
|
||
|
||
export function handleGameEnded(msg) {
|
||
alert(`Gewinner: ${shortName(msg.winner)}`);
|
||
}
|
||
|
||
export function onMessage(ev) {
|
||
const msg = JSON.parse(ev.data);
|
||
switch (msg.type) {
|
||
case 'resume_result': {
|
||
if (msg.ok) {
|
||
console.debug('handleResumeResult', msg);
|
||
if (msg.playerId) {
|
||
state.playerId = msg.playerId;
|
||
try {
|
||
localStorage.setItem('playerId', msg.playerId);
|
||
} catch {}
|
||
}
|
||
const code = msg.roomId || state.room?.id || localStorage.getItem('lastRoomId');
|
||
if (code) sendMsg({ type: 'join_room', code });
|
||
if (state.room) {
|
||
try {
|
||
renderRoom(state.room);
|
||
} catch {}
|
||
}
|
||
} else {
|
||
const code = state.room?.id || localStorage.getItem('lastRoomId');
|
||
if (code) sendMsg({ type: 'join_room', code });
|
||
}
|
||
return;
|
||
}
|
||
case 'connected':
|
||
return handleConnected(msg);
|
||
case 'room_update':
|
||
return handleRoomUpdate(msg);
|
||
case 'play_track':
|
||
return handlePlayTrack(msg);
|
||
case 'sync':
|
||
return handleSync(msg);
|
||
case 'control':
|
||
return handleControl(msg);
|
||
case 'reveal':
|
||
return handleReveal(msg);
|
||
case 'game_ended':
|
||
return handleGameEnded(msg);
|
||
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';
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
default:
|
||
return;
|
||
}
|
||
}
|