refactor: migrate UI to Preact components and remove legacy Astro/JS
- Replaced all .astro components with .jsx Preact components and added corresponding CSS modules. - Updated index.astro to use the new App Preact component; removed legacy script and Astro imports. - Deleted obsolete .astro component files and main JS logic (src/scripts/index.js, public/scripts/index.js). - Updated astro.config.mjs for Preact integration. - Updated package.json and package-lock.json to include @astrojs/preact and preact. - Updated tsconfig.json for Preact JSX support. - Refactored index.css to keep only global resets and utility styles. - All changes relate to Gitea issue #1 (refactor to astro app). Migrates the UI from Astro/vanilla JS to a modular Preact component architecture, removing all legacy code and aligning the project with modern best practices. Refs #1
This commit is contained in:
@@ -1,423 +0,0 @@
|
||||
// src/scripts/index.js
|
||||
// Modularized logic from original index.astro
|
||||
|
||||
// --- State ---
|
||||
let games = [];
|
||||
let currentGameId = null;
|
||||
let playerNameHistory = [];
|
||||
|
||||
// --- DOM Selectors ---
|
||||
const screens = {
|
||||
'new-game-screen': document.getElementById('new-game-screen'),
|
||||
'game-list-screen': document.getElementById('game-list-screen'),
|
||||
'game-detail-screen': document.getElementById('game-detail-screen'),
|
||||
'game-history-screen': document.getElementById('game-history-screen')
|
||||
};
|
||||
|
||||
// --- Screen Management ---
|
||||
function showScreen(screenId) {
|
||||
const currentScreen = document.querySelector('.screen.active');
|
||||
const newScreen = document.getElementById(screenId);
|
||||
if (currentScreen) {
|
||||
currentScreen.classList.remove('active');
|
||||
currentScreen.classList.add('slide-out');
|
||||
setTimeout(() => {
|
||||
currentScreen.classList.remove('slide-out');
|
||||
newScreen.classList.add('active');
|
||||
newScreen.classList.add('slide-in');
|
||||
setTimeout(() => {
|
||||
newScreen.classList.remove('slide-in');
|
||||
}, 300);
|
||||
}, 300);
|
||||
} else {
|
||||
newScreen.classList.add('active');
|
||||
}
|
||||
if (screenId === 'new-game-screen') {
|
||||
updateNameHistory();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Loading Overlay ---
|
||||
function showLoading() {
|
||||
document.querySelector('.loading-overlay').style.display = 'block';
|
||||
document.querySelector('.loading-indicator').style.display = 'block';
|
||||
}
|
||||
function hideLoading() {
|
||||
document.querySelector('.loading-overlay').style.display = 'none';
|
||||
document.querySelector('.loading-indicator').style.display = 'none';
|
||||
}
|
||||
|
||||
// --- Game Data Management ---
|
||||
function loadGames() {
|
||||
const savedGames = localStorage.getItem('bscscore_games');
|
||||
if (savedGames) {
|
||||
games = JSON.parse(savedGames);
|
||||
renderGames();
|
||||
}
|
||||
}
|
||||
function saveGames() {
|
||||
localStorage.setItem('bscscore_games', JSON.stringify(games));
|
||||
}
|
||||
|
||||
// --- Player Name History ---
|
||||
function updateNameHistory() {
|
||||
const allNames = games.flatMap(game => [game.player1, game.player2, game.player3].filter(Boolean));
|
||||
const nameLastUsed = {};
|
||||
games.forEach(game => {
|
||||
if (game.player1) nameLastUsed[game.player1] = Math.max(nameLastUsed[game.player1] || 0, new Date(game.updatedAt).getTime());
|
||||
if (game.player2) nameLastUsed[game.player2] = Math.max(nameLastUsed[game.player2] || 0, new Date(game.updatedAt).getTime());
|
||||
if (game.player3) nameLastUsed[game.player3] = Math.max(nameLastUsed[game.player3] || 0, new Date(game.updatedAt).getTime());
|
||||
});
|
||||
playerNameHistory = [...new Set(Object.keys(nameLastUsed))].sort((a, b) => nameLastUsed[b] - nameLastUsed[a]);
|
||||
updateNameSelects();
|
||||
}
|
||||
function updateNameSelects() {
|
||||
const player1Select = document.getElementById('player1-select');
|
||||
const player2Select = document.getElementById('player2-select');
|
||||
const player3Select = document.getElementById('player3-select');
|
||||
while (player1Select.options.length > 1) player1Select.remove(1);
|
||||
while (player2Select.options.length > 1) player2Select.remove(1);
|
||||
while (player3Select.options.length > 1) player3Select.remove(1);
|
||||
playerNameHistory.forEach(name => {
|
||||
const option1 = new Option(name, name);
|
||||
const option2 = new Option(name, name);
|
||||
const option3 = new Option(name, name);
|
||||
player1Select.add(option1);
|
||||
player2Select.add(option2);
|
||||
player3Select.add(option3);
|
||||
});
|
||||
}
|
||||
function updatePlayerInput(playerId) {
|
||||
const select = document.getElementById(`${playerId}-select`);
|
||||
const input = document.getElementById(playerId);
|
||||
if (select.value) {
|
||||
input.value = select.value;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Validation Modal ---
|
||||
function showValidationModal(message) {
|
||||
const modal = document.getElementById('validation-modal');
|
||||
const modalMessage = document.getElementById('validation-modal-message');
|
||||
modalMessage.textContent = message;
|
||||
modal.classList.add('show');
|
||||
}
|
||||
function closeValidationModal() {
|
||||
const modal = document.getElementById('validation-modal');
|
||||
modal.classList.remove('show');
|
||||
}
|
||||
|
||||
// --- New Game Creation ---
|
||||
function createNewGame() {
|
||||
const player1Name = document.getElementById('player1').value.trim();
|
||||
const player2Name = document.getElementById('player2').value.trim();
|
||||
const player3Name = document.getElementById('player3').value.trim();
|
||||
const gameType = document.getElementById('game-type').value;
|
||||
const raceTo = document.getElementById('race-to').value;
|
||||
if (!player1Name || !player2Name) {
|
||||
showValidationModal('Bitte Namen für beide Spieler eingeben');
|
||||
return;
|
||||
}
|
||||
const newGame = {
|
||||
id: Date.now(),
|
||||
player1: player1Name,
|
||||
player2: player2Name,
|
||||
player3: player3Name || null,
|
||||
score1: 0,
|
||||
score2: 0,
|
||||
score3: 0,
|
||||
gameType: gameType,
|
||||
raceTo: raceTo ? parseInt(raceTo) : null,
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
};
|
||||
games.push(newGame);
|
||||
saveGames();
|
||||
updateNameHistory();
|
||||
renderGames();
|
||||
showGameDetail(newGame.id);
|
||||
}
|
||||
|
||||
// --- Score Update ---
|
||||
function updateScore(gameId, player, change) {
|
||||
const game = games.find(g => g.id === gameId);
|
||||
if (!game || game.status === 'completed') return;
|
||||
if (player === 1) {
|
||||
game.score1 = Math.max(0, game.score1 + change);
|
||||
} else if (player === 2) {
|
||||
game.score2 = Math.max(0, game.score2 + change);
|
||||
} else if (player === 3) {
|
||||
game.score3 = Math.max(0, game.score3 + change);
|
||||
}
|
||||
game.updatedAt = new Date().toISOString();
|
||||
if (game.raceTo && (game.score1 >= game.raceTo || game.score2 >= game.raceTo || (game.player3 && game.score3 >= game.raceTo))) {
|
||||
showGameCompletionModal();
|
||||
}
|
||||
saveGames();
|
||||
renderGames();
|
||||
if (document.getElementById('game-detail-screen').classList.contains('active')) {
|
||||
document.getElementById('score1').textContent = game.score1;
|
||||
document.getElementById('score2').textContent = game.score2;
|
||||
document.getElementById('score3').textContent = game.score3;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Game Deletion ---
|
||||
function deleteGame(gameId) {
|
||||
const game = games.find(g => g.id === gameId);
|
||||
if (!game) return;
|
||||
currentGameId = gameId;
|
||||
const modal = document.getElementById('modal');
|
||||
const modalTitle = document.getElementById('modal-title');
|
||||
const modalMessage = document.getElementById('modal-message');
|
||||
modalTitle.textContent = 'Spiel löschen';
|
||||
modalMessage.textContent = `Möchten Sie das Spiel zwischen ${game.player1} und ${game.player2} wirklich löschen?`;
|
||||
modal.classList.add('show');
|
||||
}
|
||||
function closeModal() {
|
||||
const modal = document.getElementById('modal');
|
||||
modal.classList.remove('show');
|
||||
currentGameId = null;
|
||||
}
|
||||
function confirmDeleteGame(gameId) {
|
||||
if (!gameId) return;
|
||||
showLoading();
|
||||
setTimeout(() => {
|
||||
const gameIndex = games.findIndex(g => g.id === gameId);
|
||||
if (gameIndex !== -1) {
|
||||
games.splice(gameIndex, 1);
|
||||
saveGames();
|
||||
renderGames();
|
||||
if (currentGameId === gameId) {
|
||||
showScreen('game-list-screen');
|
||||
}
|
||||
}
|
||||
hideLoading();
|
||||
closeModal();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// --- Game Rendering ---
|
||||
function renderGames(filter = 'all') {
|
||||
const gamesContainer = document.getElementById('games-container');
|
||||
const filteredGames = games
|
||||
.filter(game => {
|
||||
if (filter === 'active') return game.status === 'active';
|
||||
if (filter === 'completed') return game.status === 'completed';
|
||||
return true;
|
||||
})
|
||||
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
||||
if (filteredGames.length === 0) {
|
||||
gamesContainer.innerHTML = '<div class="empty-state">Keine Spiele vorhanden</div>';
|
||||
return;
|
||||
}
|
||||
gamesContainer.innerHTML = filteredGames.map(game => {
|
||||
const playerNames = game.player3
|
||||
? `${game.player1} vs ${game.player2} vs ${game.player3}`
|
||||
: `${game.player1} vs ${game.player2}`;
|
||||
const scores = game.player3
|
||||
? `${game.score1} - ${game.score2} - ${game.score3}`
|
||||
: `${game.score1} - ${game.score2}`;
|
||||
return `
|
||||
<div class="game-item ${game.status === 'completed' ? 'completed' : 'active'}">
|
||||
<div class="game-info" onclick="showGameDetail(${game.id})">
|
||||
<div class="game-type">${game.gameType}${game.raceTo ? ` | ${game.raceTo}` : ''}</div>
|
||||
<div class="player-names">${playerNames}</div>
|
||||
<div class="game-scores">${scores}</div>
|
||||
</div>
|
||||
<button class="delete-button" onclick="deleteGame(${game.id})"></button>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// --- Game Detail ---
|
||||
function showGameDetail(gameId) {
|
||||
const game = games.find(g => g.id === gameId);
|
||||
if (!game) return;
|
||||
currentGameId = gameId;
|
||||
document.getElementById('game-title').textContent = `${game.gameType}${game.raceTo ? ` | Race to ${game.raceTo}` : ''}`;
|
||||
document.getElementById('player1-name').textContent = game.player1;
|
||||
document.getElementById('player2-name').textContent = game.player2;
|
||||
const player3Score = document.getElementById('player3-score');
|
||||
if (game.player3) {
|
||||
document.getElementById('player3-name').textContent = game.player3;
|
||||
document.getElementById('score3').textContent = game.score3;
|
||||
player3Score.style.display = 'flex';
|
||||
} else {
|
||||
player3Score.style.display = 'none';
|
||||
}
|
||||
document.getElementById('score1').textContent = game.score1;
|
||||
document.getElementById('score2').textContent = game.score2;
|
||||
const player1Container = document.querySelector('.player-score:first-child');
|
||||
const player2Container = document.querySelector('.player-score:nth-child(2)');
|
||||
const player3Container = document.getElementById('player3-score');
|
||||
player1Container.classList.toggle('franky', game.player1 === 'Fränky');
|
||||
player2Container.classList.toggle('franky', game.player2 === 'Fränky');
|
||||
player3Container.classList.toggle('franky', game.player3 === 'Fränky');
|
||||
const controlButton = document.getElementById('game-control');
|
||||
if (game.status === 'completed') {
|
||||
controlButton.textContent = 'Zurück zur Liste';
|
||||
controlButton.onclick = () => showScreen('game-list-screen');
|
||||
controlButton.classList.add('disabled');
|
||||
} else {
|
||||
controlButton.textContent = 'Spiel beenden';
|
||||
controlButton.onclick = () => finishGame();
|
||||
controlButton.classList.remove('disabled');
|
||||
}
|
||||
const scoreButtons = document.querySelectorAll('.score-button');
|
||||
scoreButtons.forEach(button => {
|
||||
button.disabled = game.status === 'completed';
|
||||
});
|
||||
showScreen('game-detail-screen');
|
||||
}
|
||||
|
||||
// --- Game Completion Modal ---
|
||||
function showGameCompletionModal() {
|
||||
const game = games.find(g => g.id === currentGameId);
|
||||
if (!game) return;
|
||||
const modal = document.getElementById('game-completion-modal');
|
||||
const finalScores = modal.querySelector('.final-scores');
|
||||
const winnerAnnouncement = modal.querySelector('.winner-announcement');
|
||||
let scoreHtml = '';
|
||||
scoreHtml += `
|
||||
<div class="final-score">
|
||||
<span class="player-name">${game.player1}</span>
|
||||
<span class="score">${game.score1}</span>
|
||||
</div>
|
||||
`;
|
||||
scoreHtml += `
|
||||
<div class="final-score">
|
||||
<span class="player-name">${game.player2}</span>
|
||||
<span class="score">${game.score2}</span>
|
||||
</div>
|
||||
`;
|
||||
if (game.player3) {
|
||||
scoreHtml += `
|
||||
<div class="final-score">
|
||||
<span class="player-name">${game.player3}</span>
|
||||
<span class="score">${game.score3}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
finalScores.innerHTML = scoreHtml;
|
||||
const scores = [game.score1, game.score2];
|
||||
if (game.player3) scores.push(game.score3);
|
||||
const maxScore = Math.max(...scores);
|
||||
const winners = [];
|
||||
if (game.score1 === maxScore) winners.push(game.player1);
|
||||
if (game.score2 === maxScore) winners.push(game.player2);
|
||||
if (game.player3 && game.score3 === maxScore) winners.push(game.player3);
|
||||
const winnerText = winners.length > 1
|
||||
? `Unentschieden zwischen ${winners.join(' und ')}`
|
||||
: `${winners[0]} hat gewonnen!`;
|
||||
winnerAnnouncement.innerHTML = `<h3>${winnerText}</h3>`;
|
||||
modal.classList.add('show');
|
||||
}
|
||||
function closeGameCompletionModal() {
|
||||
document.getElementById('game-completion-modal').classList.remove('show');
|
||||
}
|
||||
function confirmGameCompletion() {
|
||||
const game = games.find(g => g.id === currentGameId);
|
||||
if (!game) return;
|
||||
game.status = 'completed';
|
||||
game.updatedAt = new Date().toISOString();
|
||||
saveGames();
|
||||
renderGames();
|
||||
showGameDetail(currentGameId);
|
||||
closeGameCompletionModal();
|
||||
}
|
||||
|
||||
// --- Game Finish ---
|
||||
function finishGame() {
|
||||
const game = games.find(g => g.id === currentGameId);
|
||||
if (!game) return;
|
||||
showGameCompletionModal();
|
||||
}
|
||||
|
||||
// --- Filter Games ---
|
||||
function filterGames(filter) {
|
||||
document.querySelectorAll('.filter-button').forEach(button => {
|
||||
button.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`.filter-button[onclick=\"filterGames('${filter}')\"]`).classList.add('active');
|
||||
renderGames(filter);
|
||||
}
|
||||
|
||||
// --- Fullscreen ---
|
||||
function toggleFullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen().catch(err => {
|
||||
console.log(`Error attempting to enable fullscreen: ${err.message}`);
|
||||
});
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
}
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
const button = document.getElementById('fullscreen-toggle');
|
||||
if (document.fullscreenElement) {
|
||||
button.innerHTML = `<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>`;
|
||||
} else {
|
||||
button.innerHTML = `<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>`;
|
||||
}
|
||||
});
|
||||
|
||||
// --- Event Listeners ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadGames();
|
||||
// Attach event listeners for navigation, game creation, modals, etc.
|
||||
document.querySelectorAll('.nav-button').forEach(btn => {
|
||||
if (btn.textContent.includes('Neues Spiel')) btn.onclick = () => showScreen('new-game-screen');
|
||||
if (btn.textContent.includes('Abbrechen')) btn.onclick = () => showScreen('game-list-screen');
|
||||
if (btn.textContent.includes('Zurück zur Liste')) btn.onclick = () => showScreen('game-list-screen');
|
||||
});
|
||||
document.querySelectorAll('.filter-button').forEach((btn, idx) => {
|
||||
if (btn.textContent === 'Alle') btn.onclick = () => filterGames('all');
|
||||
if (btn.textContent === 'Aktiv') btn.onclick = () => filterGames('active');
|
||||
if (btn.textContent === 'Abgeschlossen') btn.onclick = () => filterGames('completed');
|
||||
});
|
||||
// Attach fullscreen toggle event
|
||||
const fullscreenBtn = document.getElementById('fullscreen-toggle');
|
||||
if (fullscreenBtn) fullscreenBtn.addEventListener('click', toggleFullscreen);
|
||||
// Modal buttons
|
||||
const modal = document.getElementById('modal');
|
||||
if (modal) {
|
||||
modal.querySelector('.close-button').onclick = closeModal;
|
||||
modal.querySelector('.modal-button.cancel').onclick = closeModal;
|
||||
modal.querySelector('.modal-button.confirm').onclick = () => confirmDeleteGame(currentGameId);
|
||||
}
|
||||
const validationModal = document.getElementById('validation-modal');
|
||||
if (validationModal) {
|
||||
validationModal.querySelector('.close-button').onclick = closeValidationModal;
|
||||
validationModal.querySelector('.modal-button.cancel').onclick = closeValidationModal;
|
||||
}
|
||||
const gameCompletionModal = document.getElementById('game-completion-modal');
|
||||
if (gameCompletionModal) {
|
||||
gameCompletionModal.querySelector('.btn.btn--warning').onclick = confirmGameCompletion;
|
||||
gameCompletionModal.querySelector('.btn:not(.btn--warning)').onclick = closeGameCompletionModal;
|
||||
}
|
||||
// New game creation
|
||||
const startGameBtn = document.querySelector('.nav-button');
|
||||
if (startGameBtn && startGameBtn.textContent.includes('Spiel starten')) {
|
||||
startGameBtn.onclick = createNewGame;
|
||||
}
|
||||
// Player name selects
|
||||
['player1', 'player2', 'player3'].forEach(pid => {
|
||||
const select = document.getElementById(`${pid}-select`);
|
||||
if (select) select.onchange = () => updatePlayerInput(pid);
|
||||
});
|
||||
// Score buttons
|
||||
document.querySelectorAll('.score-button').forEach((btn, idx) => {
|
||||
btn.onclick = () => {
|
||||
const player = Math.floor(idx / 2) + 1;
|
||||
const change = idx % 2 === 0 ? -1 : 1;
|
||||
updateScore(currentGameId, player, change);
|
||||
};
|
||||
});
|
||||
});
|
||||
// Initial screen
|
||||
showScreen('game-list-screen');
|
||||
|
||||
window.toggleFullscreen = toggleFullscreen;
|
||||
Reference in New Issue
Block a user