arena/src/ui/matchForm.js

162 lines
4.6 KiB
JavaScript

import { FIGHTER, SPAWN } from "../constants.js";
const STORAGE_KEYS = {
names: "arena.match.playerNames",
spawnPlacement: "arena.match.spawnPlacement",
};
export function createMatchForm() {
const form = getElement("#fighter-form");
const namesInput = getElement("#player-names");
const namesWarningNode = getElement("#player-names-warning");
const namesWarningTitleNode = getElement("[data-player-names-warning-title]");
const namesWarningCountNode = getElement("[data-player-names-warning-count]");
const namesWarningLimitNode = getElement("[data-player-names-warning-limit]");
const namesWarningReasonNode = getElement("[data-player-names-warning-reason]");
const appNode = document.querySelector("#app");
const statusNode = document.querySelector("#match-status");
const statusTextNodes = document.querySelectorAll("[data-status-text]");
const spawnPlacementInputs = getElements('input[name="spawnPlacement"]');
const setPlayerNamesWarning = (warning = null) => {
const hasWarning = Boolean(warning);
if (!hasWarning) {
namesWarningNode.hidden = true;
namesInput.removeAttribute("aria-invalid");
return;
}
namesWarningTitleNode.textContent = warning.title;
namesWarningCountNode.textContent = warning.fighterCount.toLocaleString("ko-KR");
namesWarningLimitNode.textContent = warning.maxFighterCount.toLocaleString("ko-KR");
namesWarningReasonNode.textContent = warning.reason ?? "";
namesWarningReasonNode.hidden = !warning.reason;
namesWarningNode.hidden = false;
namesInput.setAttribute("aria-invalid", "true");
};
const readMatchConfig = () => ({
names: nicknameValues(namesInput.value),
spawnPlacement: selectedSpawnPlacement(spawnPlacementInputs),
});
restoreSavedMatchSettings(namesInput, spawnPlacementInputs);
namesInput.addEventListener("input", () => {
setPlayerNamesWarning();
saveMatchSettings(namesInput, spawnPlacementInputs);
});
spawnPlacementInputs.forEach((input) => {
input.addEventListener("change", () => {
saveMatchSettings(namesInput, spawnPlacementInputs);
});
});
return {
onSubmit(handler) {
form.addEventListener("submit", (event) => {
event.preventDefault();
handler(readMatchConfig());
});
},
readMatchConfig,
setPlayerNamesWarning,
setStatus(message) {
if (statusNode) {
statusNode.setAttribute("aria-hidden", "false");
statusNode.title = message;
}
statusTextNodes.forEach((node) => {
node.textContent = message;
});
appNode?.classList.add("status-active");
},
};
}
function getElement(selector) {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Missing required element: ${selector}`);
}
return element;
}
function getElements(selector) {
const elements = [...document.querySelectorAll(selector)];
if (elements.length === 0) {
throw new Error(`Missing required elements: ${selector}`);
}
return elements;
}
function nicknameValues(value) {
return value
.split(/\r?\n|,/)
.map((name) => name.trim().slice(0, FIGHTER.NICKNAME_LENGTH))
.filter(Boolean);
}
function restoreSavedMatchSettings(namesInput, spawnPlacementInputs) {
const storage = getLocalStorage();
if (!storage) {
return;
}
try {
const savedNames = storage.getItem(STORAGE_KEYS.names);
const savedSpawnPlacement = storage.getItem(STORAGE_KEYS.spawnPlacement);
if (savedNames !== null) {
namesInput.value = savedNames;
}
setSpawnPlacement(spawnPlacementInputs, savedSpawnPlacement);
} catch {
// Storage may be unavailable in private or restricted browser contexts.
}
}
function saveMatchSettings(namesInput, spawnPlacementInputs) {
const storage = getLocalStorage();
if (!storage) {
return;
}
try {
storage.setItem(STORAGE_KEYS.names, namesInput.value);
storage.setItem(STORAGE_KEYS.spawnPlacement, selectedSpawnPlacement(spawnPlacementInputs));
} catch {
// Ignore storage failures so the match form remains usable.
}
}
function selectedSpawnPlacement(inputs) {
return inputs.find((input) => input.checked)?.value ?? SPAWN.DEFAULT_PLACEMENT;
}
function setSpawnPlacement(inputs, value) {
const savedInput = inputs.find((input) => input.value === value);
const defaultInput = inputs.find((input) => input.value === SPAWN.DEFAULT_PLACEMENT);
const nextInput = savedInput ?? defaultInput;
if (nextInput) {
nextInput.checked = true;
}
}
function getLocalStorage() {
try {
return window.localStorage;
} catch {
return null;
}
}