Files
BattleForge/public/js/storage.js
T

220 lines
5.3 KiB
JavaScript

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();
});
}
});