moth/theme/index.mjs

181 lines
5.7 KiB
JavaScript

/**
* Functionality for index.html (Login / Puzzles list)
*/
import * as moth from "./moth.mjs"
import * as common from "./common.mjs"
class App {
constructor(basePath=".") {
this.configURL = new URL("config.json", location)
this.config = {}
this.server = new moth.Server(basePath)
let uuid = Math.floor(Math.random() * 1000000).toString(16)
this.fakeRegistration = {
TeamID: uuid,
TeamName: `Team ${uuid}`,
}
for (let form of document.querySelectorAll("form.login")) {
form.addEventListener("submit", event => this.handleLoginSubmit(event))
}
for (let e of document.querySelectorAll(".logout")) {
e.addEventListener("click", () => this.Logout())
}
setInterval(() => this.UpdateState(), common.Minute/3)
setInterval(() => this.UpdateConfig(), common.Minute* 5)
this.UpdateConfig()
.finally(() => this.UpdateState())
}
handleLoginSubmit(event) {
event.preventDefault()
console.log(event)
}
/**
* Attempt to log in to the server.
*
* @param {string} teamID
* @param {string} teamName
*/
async Login(teamID, teamName) {
try {
await this.server.Login(teamID, teamName)
common.Toast(`Logged in (team id = ${teamID})`)
this.UpdateState()
}
catch (error) {
common.Toast(error)
}
}
/**
* Log out of the server by clearing the saved Team ID.
*/
async Logout() {
try {
this.server.Reset()
common.Toast("Logged out")
this.UpdateState()
}
catch (error) {
common.Toast(error)
}
}
/**
* Update app configuration.
*
* Configuration can be updated less frequently than state, to reduce server
* load, since configuration should (hopefully) change less frequently.
*/
async UpdateConfig() {
let resp = await fetch(this.configURL)
this.config = await resp.json()
}
/**
* Update the entire page.
*
* Fetch a new state, and rebuild all dynamic elements on this bage based on
* what's returned. If we're in development mode and not logged in, auto
* login too.
*/
async UpdateState() {
this.state = await this.server.GetState()
for (let e of document.querySelectorAll(".messages")) {
e.innerHTML = this.state.Messages
}
// Update elements with data-track-solved
for (let e of document.querySelectorAll("[data-track-solved]")) {
// Only display if data-track-solved is the same as config.trackSolved
e.classList.toggle("hidden", common.Truthy(e.dataset.trackSolved) != this.config.TrackSolved)
}
for (let e of document.querySelectorAll(".login")) {
this.renderLogin(e, !this.server.LoggedIn())
}
for (let e of document.querySelectorAll(".puzzles")) {
this.renderPuzzles(e, this.server.LoggedIn())
}
if (this.state.DevelopmentMode() && !this.server.LoggedIn()) {
common.Toast("Automatically logging in to devel server")
console.info("Logging in with generated Team ID and Team Name", this.fakeRegistration)
return this.Login(this.fakeRegistration.TeamID, this.fakeRegistration.TeamName)
}
}
/**
* Render a login box.
*
* Just toggles visibility, there's nothing dynamic in a login box.
*/
renderLogin(element, visible) {
element.classList.toggle("hidden", !visible)
}
/**
* Render a puzzles box.
*
* Displays the list of open puzzles, and adds mothball download links
* if the server is in development mode.
*/
renderPuzzles(element, visible) {
element.classList.toggle("hidden", !visible)
while (element.firstChild) element.firstChild.remove()
for (let cat of this.state.Categories()) {
let pdiv = element.appendChild(document.createElement("div"))
pdiv.classList.add("category")
let h = pdiv.appendChild(document.createElement("h2"))
h.textContent = cat
// Extras if we're running a devel server
if (this.state.DevelopmentMode()) {
let a = h.appendChild(document.createElement('a'))
a.classList.add("mothball")
a.textContent = "📦"
a.href = this.server.URL(`mothballer/${cat}.mb`)
a.title = "Download a compiled puzzle for this category"
}
// List out puzzles in this category
let l = pdiv.appendChild(document.createElement("ul"))
for (let puzzle of this.state.Puzzles(cat)) {
let i = l.appendChild(document.createElement("li"))
let url = new URL("puzzle.html", window.location)
url.hash = `${puzzle.Category}:${puzzle.Points}`
let a = i.appendChild(document.createElement("a"))
a.textContent = puzzle.Points
a.href = url
a.target = "_blank"
if (this.config.TrackSolved) {
a.classList.toggle("solved", this.state.IsSolved(puzzle))
}
}
if (!this.state.ContainsUnsolved(cat)) {
l.appendChild(document.createElement("li")).textContent = "✿"
}
element.appendChild(pdiv)
}
}
}
function init() {
window.app = {
server: new App()
}
}
common.WhenDOMLoaded(init)