220 lines
5.3 KiB
JavaScript
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();
|
|
});
|
|
}
|
|
});
|