const STORAGE_PREFIX = 'scenario:'; const MATCH_KEY = 'match:current'; function load(id) { try { const raw = localStorage.getItem(STORAGE_PREFIX + id); if (raw === null) { return null; } return JSON.parse(raw); } catch (e) { return null; } } function save(id, scenario) { const stamped = { ...scenario, lastModified: Date.now() }; localStorage.setItem(STORAGE_PREFIX + id, JSON.stringify(stamped)); } function remove(id) { localStorage.removeItem(STORAGE_PREFIX + id); } function listAll() { const items = []; for (let i = 0; i < localStorage.length; i += 1) { const key = localStorage.key(i); if (key !== null && key.startsWith(STORAGE_PREFIX)) { const id = key.slice(STORAGE_PREFIX.length); const scenario = load(id); if (scenario !== null) { items.push({ id, name: typeof scenario.name === 'string' ? scenario.name : id, lastModified: typeof scenario.lastModified === 'number' ? scenario.lastModified : 0, }); } } } items.sort((a, b) => b.lastModified - a.lastModified); return items; } function currentMatch() { try { const raw = localStorage.getItem(MATCH_KEY); if (raw === null) { return null; } return JSON.parse(raw); } catch (e) { return null; } } function setCurrentMatch(match) { localStorage.setItem(MATCH_KEY, JSON.stringify(match)); } function removeCurrentMatch() { localStorage.removeItem(MATCH_KEY); } function getCsrfToken() { const meta = document.querySelector('meta[name="csrf-token"]'); return meta ? meta.getAttribute('content') : ''; } function parsePath(name) { const match = name.match(/^(teamA|teamB)\[units\]\[(\d+)\]\[(\w+)\]$/); if (match) { const [, team, indexStr, key] = match; const teamKey = team === 'teamA' ? 'teamA' : 'teamB'; return [teamKey, 'units', Number(indexStr), key]; } const simple = [ 'id', 'name', 'battlefieldWidth', 'battlefieldHeight', 'victoryCondition', 'holdRoundsRequired', ]; if (simple.includes(name)) { return [name]; } return null; } function resolveField(scenario, path) { const parts = parsePath(path); if (parts === null) { return undefined; } let cursor = scenario; for (let i = 0; i < parts.length; i += 1) { if (cursor === null || cursor === undefined) { return undefined; } cursor = cursor[parts[i]]; } return cursor; } function populateRecentScenarios() { const root = document.getElementById('recent'); if (!root) { return; } while (root.firstChild) { root.removeChild(root.firstChild); } const items = listAll(); if (items.length === 0) { const p = document.createElement('p'); p.className = 'bf-empty'; p.appendChild(document.createTextNode('No saved scenarios yet.')); root.appendChild(p); return; } const ul = document.createElement('ul'); items.forEach((item) => { const li = document.createElement('li'); const a = document.createElement('a'); a.setAttribute('href', `/scenarios/${encodeURIComponent(item.id)}/edit/team`); a.appendChild(document.createTextNode(`${item.name} (${item.id})`)); li.appendChild(a); ul.appendChild(li); }); root.appendChild(ul); } // eslint-disable-next-line no-unused-vars async function fetchArchetypes() { const res = await fetch('/assets/archetypes.json'); return res.json(); } function prefillTeamEditor() { const form = document.querySelector('form[action*="/edit/team"]'); if (!form) { return; } const id = decodeURIComponent(window.location.pathname.split('/').slice(-3, -2)[0] || 'new'); if (id === 'new') { return; } const scenario = load(id); if (scenario === null) { return; } const fields = form.querySelectorAll('input[name], select[name]'); fields.forEach((field) => { const name = field.getAttribute('name'); if (!name) { return; } const value = resolveField(scenario, name); if (value !== undefined) { Object.assign(field, { value: String(value) }); } }); } async function startMatch() { const id = decodeURIComponent(window.location.pathname.split('/').slice(-3, -2)[0] || 'new'); if (id === 'new') { return; } const scenario = load(id); if (scenario === null) { return; } const csrf = getCsrfToken(); const res = await fetch(`/scenarios/${encodeURIComponent(id)}/start`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, }, body: JSON.stringify(scenario), }); if (!res.ok) { // eslint-disable-next-line no-alert alert(`Start match failed: HTTP ${res.status}`); return; } const data = await res.json(); if (data.match) { setCurrentMatch(data.match); window.location.href = '/'; } else { // eslint-disable-next-line no-alert alert(`Start match failed: ${data.errors ? data.errors.join(', ') : 'unknown'}`); } } window.bfStorage = { load, save, delete: remove, listAll, currentMatch, setCurrentMatch, removeCurrentMatch, }; document.addEventListener('DOMContentLoaded', () => { populateRecentScenarios(); prefillTeamEditor(); const startBtn = document.getElementById('bf-start-match'); if (startBtn) { startBtn.addEventListener('click', (event) => { event.preventDefault(); startMatch(); }); } });