diff --git a/README.md b/README.md index da798ba..7af556e 100644 --- a/README.md +++ b/README.md @@ -287,12 +287,17 @@ None. ## Supported browser engines * Gecko (Firefox): works in v132, both with JS enabled (offline mode) and with JS disabled (plain old client-server mode). -* Blink (e.g. Chromium and Chromium-based browsers): not checked, should work in reasonably recent versions, - both with JS enabled (offline mode) and with JS disabled (plain old client-server mode). + Should work in offline mode starting with Firefox 63 (2018); should work in plain old client-server mode in all versions, + although the design in Firefox 51 (2017) and older will probably not look very good due to the lack of the recent CSS features. +* Blink (e.g. Chromium and Chromium-based browsers): superficially checked in Chromium 130, basic functionality seems to work. + Should work in offline mode starting with Chrome 67 (2018); should work in plain old client-server mode in all versions, + although the design in Chrome 56 (2017) and older will probably not look very good due to the lack of the recent CSS features. * WebKit (e.g. qutebrowser and notably Safari): not checked; should **not** work in offline mode, because WebKit does not fully implement decade-old standard which Gecko and Blink supported since 2018: https://github.com/WebKit/standards-positions/issues/97. - Everything should still work in online mode, falling back on client-server communication. + Everything should still work in online mode, falling back on client-server communication, + except that the design in Safari before 10.1 (2017) will not look very good (but will still be functional), + and will probably become disfunctional in Safari before 3.1 (2008). * Servo: checked, does **not** work in nightly as of 2024-11-20, and I don't have an opportunity to figure out why (it's not packaged for Alpine (...yet), their regular Linux builds don't work on Alpine, so I had to check it on someone else's Windows machine). @@ -315,10 +320,6 @@ None. * Implement error handling and handling of incorrect / malformed game states (since they come from the client). * Improve UI for large boards (disable autoplayers when board is too large, hide "enable autoplayer" buttons, provide clear indication to the user instead). -* Target older ES versions on the frontend, - so that offline-only game can work in all browsers fully supporting Web Components - (i.e. Blink-based and Firefox not older than 2018); - right now ES2022 is targeted. * Figure out better API for game board, `board.get(row, column)` and `board.get(row, column)` seemed to be a good way to encapsulate readonly state, but they became very inconvenient as the codebase grew and became more complex, diff --git a/src/frontend/components/board-game.ts b/src/frontend/components/board-game.ts index ac8f7b5..71df33d 100644 --- a/src/frontend/components/board-game.ts +++ b/src/frontend/components/board-game.ts @@ -24,20 +24,20 @@ export class BoardGameComponent extends HTMLElement { return; } - for (const playerNameElement of this.querySelectorAll(".current-player-name")) { + this.querySelectorAll(".current-player-name").forEach((playerNameElement) => { if ((playerNameElement as HTMLElement).innerText !== gameState.currentPlayerName) { (playerNameElement as HTMLElement).innerText = gameState.currentPlayerName; } - } + }); const buttonValues = getButtonValues(gameState); - for (const button of this.querySelectorAll("button")) { - for (const className of button.classList) { - if (Object.hasOwn(buttonValues, className)) { + this.querySelectorAll("button").forEach((button) => { + button.classList.forEach((className) => { + if (Object.prototype.hasOwnProperty.call(buttonValues, className)) { button.value = buttonValues[className as keyof ButtonValues].serialize(); } - } - } + }); + }); const currentOutcome = gameState.board ? rules.getBoardOutcome(gameState.board) : CurrentOutcome.Undecided; @@ -46,7 +46,7 @@ export class BoardGameComponent extends HTMLElement { this.classList.toggle(className, shouldEnable); } - for (const tbodyUntyped of this.querySelectorAll("tbody.game-board")) { + this.querySelectorAll("tbody.game-board").forEach((tbodyUntyped) => { const tbody = tbodyUntyped as HTMLTableSectionElement; while (gameState.rows < tbody.rows.length) { @@ -90,7 +90,7 @@ export class BoardGameComponent extends HTMLElement { row: rowNumber, column: columnNumber, }); - for (const button of cell.querySelectorAll("button")) { + cell.querySelectorAll("button").forEach((button) => { button.value = nextGameState.serialize(); if (button.disabled !== isDisabled) { button.disabled = isDisabled; @@ -101,10 +101,10 @@ export class BoardGameComponent extends HTMLElement { if (button.className !== className) { button.className = className; } - } + }); } } - } + }); } connectedCallback() { diff --git a/src/frontend/components/progressive-form.ts b/src/frontend/components/progressive-form.ts index e7f998f..90c797f 100644 --- a/src/frontend/components/progressive-form.ts +++ b/src/frontend/components/progressive-form.ts @@ -1,8 +1,18 @@ import { goToLocation } from "../utils/navigation-utils.ts"; import { updateWithQueryParams } from "../utils/url-utils.ts"; +// Needed for backwards compatibility with browsers that predate FormData as iterator +const getFormDataEntries = (formData: FormData) => { + const result: [string, FormDataEntryValue][] = []; + formData.forEach((value, key) => { + result.push([key, value]); + }); + + return result; +}; + const submitListener = function (this: HTMLFormElement, e: SubmitEvent) { - goToLocation(updateWithQueryParams(new URL(this.action), new FormData(this, e.submitter))); + goToLocation(updateWithQueryParams(new URL(this.action), getFormDataEntries(new FormData(this, e.submitter)))); e.preventDefault(); }; diff --git a/tsconfig.json b/tsconfig.json index 388bb0b..c3ba1be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,9 @@ "forceConsistentCasingInFileNames": true, "jsx": "react-jsx", "jsxImportSource": "preact", + "lib": ["DOM", "ES6"], "module": "NodeNext", "noEmit": true, - "target": "ES2022" + "target": "ES6" } }