feat: restructure client-side code

This commit is contained in:
2025-09-04 12:33:17 +02:00
parent edaf9ea94e
commit bbce3cbadf
21 changed files with 854 additions and 20 deletions

261
public/js/main.js Normal file
View File

@@ -0,0 +1,261 @@
import { $audio, $copyRoomCode, $leaveRoom, $nameDisplay, $nameLobby, $npArtist, $npTitle, $npYear, $pauseBtn, $placeBtn, $readyChk, $roomId, $roomCode, $slotSelect, $startGame, $volumeSlider, $playBtn, $nextBtn, $createRoom, $joinRoom, $lobby, $room, $setNameLobby } 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 = '';
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;
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';
}
}
// 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;
}
}
})();