import { Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"; import { Scorecard, getStoreDAO } from "./models.js"; const storeDao = getStoreDAO(); export class FinskaScorecardController extends Controller { static targets = ["score1Input", "score2Input", "scoreTable", "team1Header", "team2Header"]; static values = { maxScore: Number, overflowScoreTo: Number } connect() { let rules = { maxScore: Math.floor(this.maxScoreValue), overflowScoreTo: Math.floor(this.overflowScoreToValue) }; this._undoStack = []; this._scorecard = storeDao.loadOrCreate(rules); this._loadTeamNames(); this.updateTable(); } _loadTeamNames() { const stored1 = localStorage.getItem('finska-team1-name'); const stored2 = localStorage.getItem('finska-team2-name'); this._team1Name = stored1 || 'Team A'; this._team2Name = stored2 || 'Team B'; this._renderTeamHeader(this.team1HeaderTarget, this._team1Name, 'editTeam1'); this._renderTeamHeader(this.team2HeaderTarget, this._team2Name, 'editTeam2'); } _renderTeamHeader(td, name, editAction) { td.innerHTML = ` ${this._escapeHtml(name)} `; } _renderEditHeader(td, currentName, teamNum) { td.innerHTML = ` `; td.querySelector('input').focus(); td.querySelector('input').select(); } editTeam1() { this._renderEditHeader(this.team1HeaderTarget, this._team1Name, '1'); } editTeam2() { this._renderEditHeader(this.team2HeaderTarget, this._team2Name, '2'); } confirmTeam1() { const input = this.team1HeaderTarget.querySelector('input'); const newName = input.value.trim() || 'Team A'; this._team1Name = newName; localStorage.setItem('finska-team1-name', newName); this._renderTeamHeader(this.team1HeaderTarget, this._team1Name, 'editTeam1'); } confirmTeam2() { const input = this.team2HeaderTarget.querySelector('input'); const newName = input.value.trim() || 'Team B'; this._team2Name = newName; localStorage.setItem('finska-team2-name', newName); this._renderTeamHeader(this.team2HeaderTarget, this._team2Name, 'editTeam2'); } cancelTeam1() { this._renderTeamHeader(this.team1HeaderTarget, this._team1Name, 'editTeam1'); } cancelTeam2() { this._renderTeamHeader(this.team2HeaderTarget, this._team2Name, 'editTeam2'); } editKeyDown1(e) { this._editKeyDown(e, this.confirmTeam1.bind(this), this.cancelTeam1.bind(this)); } editKeyDown2(e) { this._editKeyDown(e, this.confirmTeam2.bind(this), this.cancelTeam2.bind(this)); } _editKeyDown(e, confirmFn, cancelFn) { if (e.key === 'Enter') { e.preventDefault(); confirmFn(); } else if (e.key === 'Escape') { e.preventDefault(); cancelFn(); } } _escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; } _escapeAttr(str) { return str.replace(/&/g, '&').replace(/"/g, '"').replace(//g, '>'); } updateTable() { let tableBody = this.scoreTableTarget; let tableRows = tableBody.querySelectorAll("tr"); let pairs = this._scorecard.pairs(); for (let pairIndex = 0; pairIndex < pairs.length; pairIndex++) { let tableRow; if (pairIndex >= tableRows.length) { tableRow = this._appendRow(); } else { tableRow = tableRows[pairIndex]; } this._updateRow(tableRow, pairs[pairIndex]) } // Remove any extra rows for (let i = pairs.length; i < tableRows.length; i++) { tableBody.removeChild(tableRows[i]); } console.log(JSON.stringify(this._scorecard.toJson())); } _updateRow(tableRow, pair) { let tds = tableRow.querySelectorAll("td"); this._updateCell(pair.p1, tds[0], tds[1]); this._updateCell(pair.p2, tds[2], tds[3]); } _updateCell(score, scoreCell, totalCell) { scoreCell.classList.value = ""; totalCell.classList.value = ""; if (score != null) { scoreCell.textContent = score.score; totalCell.textContent = score.total; if (score.score === 0) { scoreCell.classList.add("score-foul"); totalCell.classList.add("score-foul"); } else if (score.wasOverflow) { scoreCell.classList.add("score-overflow"); totalCell.classList.add("score-overflow"); } else if (score.wasWin) { scoreCell.classList.add("score-win"); totalCell.classList.add("score-win"); } } else { scoreCell.textContent = ""; totalCell.textContent = ""; } } _appendRow() { let newRow = document.createElement("tr"); newRow.classList.add("score-entry"); for (let i = 0; i < 4; i++) { newRow.appendChild(document.createElement("td")); } this.scoreTableTarget.appendChild(newRow); return newRow; } addScore1() { this._addScore(this.score1InputTarget, this.score2InputTarget, this._scorecard.addPlayer1Score.bind(this._scorecard), this._scorecard.removeLastPlayer1Score.bind(this._scorecard)); } addScore2() { this._addScore(this.score2InputTarget, this.score1InputTarget, this._scorecard.addPlayer2Score.bind(this._scorecard), this._scorecard.removeLastPlayer2Score.bind(this._scorecard)); } _addScore(inputElem, focusToInputElem, addScoreFn, queueUndoFn) { let score = parseInt(inputElem.value); if (isNaN(score)) { score = 0; } addScoreFn(score); this._undoStack.push(queueUndoFn); inputElem.value = ""; storeDao.save(this._scorecard); this.updateTable(); focusToInputElem.focus(); } score1KeyDown(e) { this._handleKeyDown(e, this.addScore1.bind(this)); } score2KeyDown(e) { this._handleKeyDown(e, this.addScore2.bind(this)); } _handleKeyDown(e, addScoreFn) { if (e.key === "Enter") { e.preventDefault(); addScoreFn(); } } undoLast() { if (this._undoStack.length === 0) { return; } if (!confirm("Really undo last move?")) { return; } (this._undoStack.pop())(); storeDao.save(this._scorecard); this.updateTable(); } resetAll() { if (!confirm("Really reset?")) { return; } this._scorecard.reset(); storeDao.clear(); this._undoStack = []; this._team1Name = 'Team A'; this._team2Name = 'Team B'; localStorage.removeItem('finska-team1-name'); localStorage.removeItem('finska-team2-name'); this._renderTeamHeader(this.team1HeaderTarget, this._team1Name, 'editTeam1'); this._renderTeamHeader(this.team2HeaderTarget, this._team2Name, 'editTeam2'); this.updateTable(); } }