diff --git a/build.js b/build.js index 62adb7f..153d64e 100644 --- a/build.js +++ b/build.js @@ -2,12 +2,38 @@ import beautifier from 'js-beautify'; const { js: beautify } = beautifier; import UglifyJS from 'uglify-js'; import fs from 'fs'; +import webpack from 'webpack'; +import path from 'path'; +import applyPatches from './patches.js'; if (!fs.existsSync("./build")) fs.mkdirSync("./build"); fs.cpSync("./static/", "./build/", { recursive: true }); fs.cpSync("./assets/", "./build/assets/", { recursive: true }); -fs.cpSync("./src/fx_core.js", "./build/fx_core.js"); fs.writeFileSync("./build/index.html", fs.readFileSync("./build/index.html").toString().replace(/buildTimestamp/g, Date.now())); + +const buildClientCode = () => new Promise((resolve, reject) => { + webpack({ + mode: 'production', + entry: { fxClient: "./src/main.js" }, + output: { + path: path.resolve(import.meta.dirname, 'build'), + filename: 'fx.bundle.js', + }, + }, (err, stats) => { + if (err) { + if (err.details) console.error(err.details); + return reject(err); + } + const info = stats.toJson(); + if (stats.hasWarnings()) console.warn(info.warnings); + if (stats.hasErrors()) { + console.error(info.errors); + reject("Webpack compilation error"); + } + else resolve(); + }); +}); + let script = fs.readFileSync('./game/latest.js', { encoding: 'utf8' }).replace("\n", "").trim(); const exposeVarsToGlobalScope = true; @@ -127,11 +153,12 @@ rawCodeSegments.forEach(code => { matchDictionaryExpression(expression); }); -fs.writeFileSync("./build/fx_core.js", `const dictionary = ${JSON.stringify(dictionary)};\n` + fs.readFileSync("./build/fx_core.js").toString()); - -import applyPatches from './patches.js'; applyPatches({ replace, replaceOne, replaceRawCode, dictionary, matchOne, matchRawCode, escapeRegExp }); +await buildClientCode(); +// the dictionary should maybe get embedded into one of the files in the bundle +fs.writeFileSync("./build/fx.bundle.js", `const dictionary = ${JSON.stringify(dictionary)};\n` + fs.readFileSync("./build/fx.bundle.js").toString()); + console.log("Formatting code..."); script = beautify(script, { diff --git a/package-lock.json b/package-lock.json index c3a4f3a..87c6c50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,9 @@ "dependencies": { "js-beautify": "^1.14.11", "uglify-js": "^3.19.2" + }, + "devDependencies": { + "webpack": "^5.95.0" } }, "node_modules/@isaacs/cliui": { @@ -29,6 +32,64 @@ "node": ">=12" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@one-ini/wasm": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", @@ -43,6 +104,185 @@ "node": ">=14" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", @@ -51,6 +291,52 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, "node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -86,6 +372,73 @@ "balanced-match": "^1.0.0" } }, + "node_modules/browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001666", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", + "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -154,11 +507,109 @@ "node": ">=14" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.31", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz", + "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg==", + "dev": true + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -195,6 +646,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -230,6 +702,20 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/js-beautify": { "version": "1.14.11", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", @@ -249,6 +735,27 @@ "node": ">=14" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, "node_modules/lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", @@ -257,6 +764,33 @@ "node": "14 || >=16.14" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", @@ -279,6 +813,18 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, "node_modules/nopt": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", @@ -316,11 +862,73 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -346,6 +954,15 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -376,6 +993,25 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -464,6 +1100,88 @@ "node": ">=8" } }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, "node_modules/uglify-js": { "version": "3.19.2", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz", @@ -475,6 +1193,119 @@ "node": ">=0.8.0" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -593,6 +1424,55 @@ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true + }, + "@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "@one-ini/wasm": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", @@ -604,11 +1484,222 @@ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "optional": true }, + "@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "@types/node": { + "version": "22.7.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.4.tgz", + "integrity": "sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==", + "dev": true, + "requires": { + "undici-types": "~6.19.2" + } + }, + "@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, "abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==" }, + "acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true + }, + "acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "requires": {} + }, "ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -632,6 +1723,36 @@ "balanced-match": "^1.0.0" } }, + "browserslist": { + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001666", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001666.tgz", + "integrity": "sha512-gD14ICmoV5ZZM1OdzPWmpx+q4GyefaK06zi8hmfHV5xe4/2nOQX3+Dw5o+fSqOws2xVwL9j+anOPFwHzdEdV4g==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -685,11 +1806,90 @@ "semver": "^7.5.3" } }, + "electron-to-chromium": { + "version": "1.5.31", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.31.tgz", + "integrity": "sha512-QcDoBbQeYt0+3CWcK/rEbuHvwpbT/8SV9T3OSgs6cX1FlcUAkgrkqbg9zLnDrMM/rLamzQwal4LYFCiWk861Tg==", + "dev": true + }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, "foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -711,6 +1911,24 @@ "path-scurry": "^1.10.1" } }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -735,6 +1953,17 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + } + }, "js-beautify": { "version": "1.14.11", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", @@ -746,11 +1975,50 @@ "nopt": "^7.2.0" } }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true + }, "lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", @@ -764,6 +2032,18 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==" }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, "nopt": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", @@ -786,11 +2066,49 @@ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, + "picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -809,6 +2127,15 @@ } } }, + "serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -827,6 +2154,22 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -890,11 +2233,131 @@ } } }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "terser": { + "version": "5.34.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.34.1.tgz", + "integrity": "sha512-FsJZ7iZLd/BXkz+4xrRTGJ26o/6VTjQytUk8b8OxkwcD2I+79VPJlz7qss1+zE7h8GNIScFqXcDyJ/KqBYZFVA==", + "dev": true, + "requires": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + } + }, "uglify-js": { "version": "3.19.2", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.2.tgz", "integrity": "sha512-S8KA6DDI47nQXJSi2ctQ629YzwOVs+bQML6DAtvy0wgNdpi+0ySpQK0g2pxBq2xfF2z3YCscu7NNA8nXT9PlIQ==" }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.95.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz", + "integrity": "sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==", + "dev": true, + "requires": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + } + }, + "webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 43e623e..ec40bf5 100644 --- a/package.json +++ b/package.json @@ -21,5 +21,8 @@ "dependencies": { "js-beautify": "^1.14.11", "uglify-js": "^3.19.2" + }, + "devDependencies": { + "webpack": "^5.95.0" } } diff --git a/patches.js b/patches.js index 29e49db..ccf7551 100644 --- a/patches.js +++ b/patches.js @@ -15,7 +15,7 @@ export default ({ replace, replaceOne, replaceRawCode, dictionary, matchOne, mat // Add FX Client version info to the game version window replaceRawCode(`ar.oa(4,1,new s8(__L(),gameVersion+"
"+ah.aC5+"",`, `ar.oa(4,1,new s8(__L(),gameVersion+"
"+ah.aC5+"" -+ "

" + "FX Client v" + fx_version + " " + fx_update + "
FX Client Discord server" ++ "

" + "FX Client v" + __fx.version + "
FX Client Discord server" + "
Github repository
",`); // Add update information @@ -35,8 +35,8 @@ export default ({ replace, replaceOne, replaceRawCode, dictionary, matchOne, mat replaceOne(new RegExp(/(:(?\w+)<7\?\w+\.\w+\.\w+\(valuesArray\[\2\]\)):(\w+\.\w+\(valuesArray\[7\]\))}/ .source.replace(/valuesArray/g, valuesArray), "g"), '$1 : $ === 7 ? $3 ' - + `: $ === 8 ? utils.getMaxTroops(${dict.playerData}.${dict.playerTerritories}, ${playerId}) ` - + `: utils.getDensity(${playerId}) }`); + + `: $ === 8 ? __fx.utils.getMaxTroops(${dict.playerData}.${dict.playerTerritories}, ${playerId}) ` + + `: __fx.utils.getDensity(${playerId}) }`); // increase the size of the side panel by 25% to make the text easier to read replaceOne(/(this\.\w+=Math\.floor\(\(\w+\.\w+\.\w+\(\)\?\.1646:\.126\))\*(\w+\.\w+\),)/g, "$1 * 1.25 * $2"); } @@ -45,8 +45,8 @@ export default ({ replace, replaceOne, replaceRawCode, dictionary, matchOne, mat replaceRawCode(`=function(sE){o.ha(sE,2),b.h9<100?xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0):xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0)`, `=function(sE){ if (${playerId} === sE && !${gIsSingleplayer}) - wins_counter++, window.localStorage.setItem("fx_winCount", wins_counter), - xD(0,"Your Win Count is now " + wins_counter,3,sE,ad.gN,ad.kl,-1,!0); + __fx.wins.count++, window.localStorage.setItem("fx_winCount", __fx.wins.count), + xD(0,"Your Win Count is now " + __fx.wins.count,3,sE,ad.gN,ad.kl,-1,!0); o.ha(sE,2),b.h9<100?xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0):xD(0,__L([a8.jx[sE]]),3,sE,ad.gN,ad.kl,-1,!0)`); @@ -54,18 +54,18 @@ export default ({ replace, replaceOne, replaceRawCode, dictionary, matchOne, mat // add settings button replaceRawCode(`,new nQ("☰
"+__L(),function(){aD6(3)},aa.ks),new nQ("",function(){at.d5(12)},aa.kg,!1)]`, `,new nQ("☰
"+__L(),function(){aD6(3)},aa.ks),new nQ("",function(){at.d5(12)},aa.kg,!1), - new nQ("FX Client settings", function() { WindowManager.openWindow("settings"); }, "rgba(0, 0, 20, 0.5")]`) + new nQ("FX Client settings", function() { __fx.WindowManager.openWindow("settings"); }, "rgba(0, 0, 20, 0.5")]`) // set settings button position replaceRawCode(`aZ.g5.vO(aD3[3].button,x+a0S+gap,a3X+h+gap,a0S,h);`, `aZ.g5.vO(aD3[3].button,x+a0S+gap,a3X+h+gap,a0S,h); aZ.g5.vO(aD3[5].button, x, a3X + h * 2 + gap * 2, a0S * 2 + gap, h / 3);`); // render win count replaceRawCode(`if(_y.a4l(),_r.gI(),_m.gI(),aw.gI(),a0.g8()){ctx.imageSmoothingEnabled=!1;var iQ=a0.a4o("territorial.io"),kL=.84*aD4.gA/iQ.width;`, `if(_y.a4l(),_r.gI(),_m.gI(),aw.gI(),a0.g8()){ - if (settings.displayWinCounter) { + if (__fx.settings.displayWinCounter) { const size = Math.floor(aD4.gA * 0.03); ctx.font = ${dict.fontGeneratorFunction}(1, size); ctx.fillStyle = "#ffffff"; - const text = "Win count: " + wins_counter; + const text = "Win count: " + __fx.wins.count; const textLength = ctx.measureText(text).width; ctx.textAlign = "left"; ctx.textBaseline = "middle"; @@ -99,11 +99,11 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 // and also register the keybind handler functions replaceOne(/}(function \w+\((\w+)\){return!\(1<\2&&1===(?\w+)\|\|\(1<\2&&\2\*\3-\3<1\/1024\?\2=\(\3\+1\/1024\)\/\3:\2<1)/g, "} this.setAbsolutePercentage = function(newPercentage) { $ = newPercentage; }; " - + "keybindFunctions.setAbsolute = this.setAbsolutePercentage; " - + `keybindFunctions.setRelative = (arg1) => ${attackBarObject}.${setRelative}(arg1); $1`); + + "__fx.keybindFunctions.setAbsolute = this.setAbsolutePercentage; " + + `__fx.keybindFunctions.setRelative = (arg1) => ${attackBarObject}.${setRelative}(arg1); $1`); // insert keybind handling code into the keyDown handler function replaceOne(new RegExp(/(function \w+\((?\w+)\){)([^}]+matched)/g.source.replace(/matched/g, escapeRegExp(match)), "g"), - "$1 if (keybindHandler($.key)) return; $3"); + "$1 if (__fx.keybindHandler($.key)) return; $3"); } // Set the default font to Trebuchet MS @@ -111,53 +111,53 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 // Realistic bot names setting // matches c4[i] = c4[i].replace(a6U[dx], a6V[dx]) - replaceOne(/(((\w+)\[\w+\])=\2\.replace\(\w+(\[\w+\]),\w+\4\))/g, "$1; if (settings.realisticNames) $3 = realisticNames;") + replaceOne(/(((\w+)\[\w+\])=\2\.replace\(\w+(\[\w+\]),\w+\4\))/g, "$1; if (__fx.settings.realisticNames) $3 = realisticNames;") // Hide all links in main menu depending on settings //replaceOne(/(this\.\w+=function\(\){)((\w+\.\w+)\[2\]=\3\[3\]=\3\[4\]=(?!this\.\w+\.\w+),)/g, //"$1 if (settings.hideAllLinks) $3[0] = $3[1] = $; else $3[0] = $3[1] = true; $2") // Make the main canvas context have an alpha channel if a custom background is being used - replaceOne(/(document\.getElementById\("canvasA"\),\(\w+=\w+\.getContext\("2d",){alpha:!1}/g, "$1 {alpha: makeMainMenuTransparent}") + replaceOne(/(document\.getElementById\("canvasA"\),\(\w+=\w+\.getContext\("2d",){alpha:!1}/g, "$1 {alpha: __fx.makeMainMenuTransparent}") // Clear canvas background if a custom background is being used replaceRawCode(`,this.qk=function(){var a4n,a4m;aq.pd?(a4m=aL.gA/aq.eE,a4n=aL.gF/aq.eF,canvas.setTransform(a4m=a4n\w+)\(\){[^}]+?(?\w+)\.fillRect\(0,(?\w+),\w+,1\),(?:\3\.fillRect\([^()]+\),)+\3\.font=\w+,(\w+\.\w+)\.textBaseline\(\3,1\),\5\.textAlign\(\3,1\),\3\.fillText\(\w+\(\d+\),Math\.floor\()(\w+)\/2\),(Math\.floor\(\w+\+\w+\/2\)\));/g, - "$1($6 + $ - 22) / 2), $7; playerList.drawButton($, 12, 12, $ - 22);"); - const buttonBoundsCheck = `utils.isPointInRectangle($, $, ${uiOffset} + 12, ${uiOffset} + 12, ${topBarHeight} - 22, ${topBarHeight} - 22)` + "$1($6 + $ - 22) / 2), $7; __fx.playerList.drawButton($, 12, 12, $ - 22);"); + const buttonBoundsCheck = `__fx.utils.isPointInRectangle($, $, ${uiOffset} + 12, ${uiOffset} + 12, ${topBarHeight} - 22, ${topBarHeight} - 22)` // Handle player list button and leaderboard tabs mouseDown // and create a function for scrolling the leaderboard to the top replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){return!!\w+\(\2,\3\))&&(\(\w+=\w+\.\w+,[^}]+),!0\)/g, - `leaderboardFilter.scrollToTop = function(){position = 0;}, $1 && ((${buttonBoundsCheck} && playerList.display(${rawPlayerNames}), true) - && !($ - ${uiOffset} > leaderboardFilter.verticalClickThreshold && leaderboardFilter.handleMouseDown($ - ${uiOffset})) && $4),!0)`); + `__fx.leaderboardFilter.scrollToTop = function(){position = 0;}, $1 && ((${buttonBoundsCheck} && __fx.playerList.display(${rawPlayerNames}), true) + && !($ - ${uiOffset} > __fx.leaderboardFilter.verticalClickThreshold && __fx.leaderboardFilter.handleMouseDown($ - ${uiOffset})) && $4),!0)`); // Handle player list button and leaderboard tabs hover // and create a function for repainting the leaderboard replaceOne(/(this\.\w+=function\((?\w+),(?\w+)\){)(var \w+,\w+=\w+\(\3\);return \w+\?\(\w+=(\w+),\(\5=\w+\(0,\5\+=(?:[^}]+,(?\w+\.\w+=!0)){2})/g, - `leaderboardFilter.repaintLeaderboard = function() { ${drawFunction}(), $; }, - $1 if (${buttonBoundsCheck}) { playerList.hoveringOverButton === false && (playerList.hoveringOverButton = true, ${drawFunction}(), $); } - else { playerList.hoveringOverButton === true && (playerList.hoveringOverButton = false, ${drawFunction}(), $); } - if (leaderboardFilter.setHovering( - utils.isPointInRectangle($, $, ${uiOffset}, ${uiOffset} + leaderboardFilter.verticalClickThreshold, leaderboardFilter.windowWidth, leaderboardFilter.tabBarOffset), $ - ${uiOffset} + `__fx.leaderboardFilter.repaintLeaderboard = function() { ${drawFunction}(), $; }, + $1 if (${buttonBoundsCheck}) { __fx.playerList.hoveringOverButton === false && (__fx.playerList.hoveringOverButton = true, ${drawFunction}(), $); } + else { __fx.playerList.hoveringOverButton === true && (__fx.playerList.hoveringOverButton = false, ${drawFunction}(), $); } + if (__fx.leaderboardFilter.setHovering( + __fx.utils.isPointInRectangle($, $, ${uiOffset}, ${uiOffset} + __fx.leaderboardFilter.verticalClickThreshold, __fx.leaderboardFilter.windowWidth, __fx.leaderboardFilter.tabBarOffset), $ - ${uiOffset} )) return; $4`); } @@ -170,10 +170,10 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 `function a9V(ctx,i,fontSize,x,y,a9S){ var ___id = i; i=ac.jv.formatNumber(playerData.playerBalances[i]);a9S>>1&1?(ctx.lineWidth=.05*fontSize,ctx.strokeStyle=a9U(fontSize,a9S%2),ctx.strokeText(i,x,y)):(1\w+),(?\w+),(?\w+),(?\w+)\){)(\6\.fillText\((?\w+)\.(?\w+)\[\2\],\4,\5\)),(\2<(?\w+)\.(?\w+)&&2!==\8\.(?\w+)\[[^}]+)}/g, - `$1 var ___id = $2; $7, $10; ${settingsSwitchNameAndBalance} && settings.showPlayerDensity && (settings.coloredDensity && ($.fillStyle = utils.textStyleBasedOnDensity(___id)), $.fillText(utils.getDensity(___id), $, $ + $)); }`); + `$1 var ___id = $2; $7, $10; ${settingsSwitchNameAndBalance} && __fx.settings.showPlayerDensity && (__fx.settings.coloredDensity && ($.fillStyle = __fx.utils.textStyleBasedOnDensity(___id)), $.fillText(__fx.utils.getDensity(___id), $, $ + $)); }`); } { // Leaderboard filter @@ -182,13 +182,13 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 `var leaderboardHasChanged = true; this.playerPos = game.playerId; - leaderboardFilter.setUpdateFlag = () => leaderboardHasChanged = true; + __fx.leaderboardFilter.setUpdateFlag = () => leaderboardHasChanged = true; function updateFilteredLb() { if (!leaderboardHasChanged) return; - leaderboardFilter.filteredLeaderboard = leaderboardFilter.playersToInclude + __fx.leaderboardFilter.filteredLeaderboard = __fx.leaderboardFilter.playersToInclude .map(id => leaderboardPositionsById[id]).sort((a, b) => a - b); leaderboardHasChanged = false; - this.playerPos = leaderboardFilter.filteredLeaderboard.indexOf(leaderboardPositionsById[game.playerId]); + this.playerPos = __fx.leaderboardFilter.filteredLeaderboard.indexOf(leaderboardPositionsById[game.playerId]); } function drawFunction() { a0A.clearRect(0, 0, a04, y9), @@ -196,24 +196,24 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 a0A.fillRect(0, 0, a04, a0F), a0A.fillStyle = aZ.kZ, a0A.fillRect(0, a0F, a04, y9 - a0F); - if (leaderboardFilter.enabled) updateFilteredLb(); - var playerPos = (leaderboardFilter.enabled + if (__fx.leaderboardFilter.enabled) updateFilteredLb(); + var playerPos = (__fx.leaderboardFilter.enabled ? this.playerPos : leaderboardPositionsById[game.playerId] ); - if (leaderboardFilter.hoveringOverTabs) a0P = -1; - if (leaderboardFilter.enabled && a0P >= leaderboardFilter.filteredLeaderboard.length) a0P = -1; + if (__fx.leaderboardFilter.hoveringOverTabs) a0P = -1; + if (__fx.leaderboardFilter.enabled && a0P >= __fx.leaderboardFilter.filteredLeaderboard.length) a0P = -1; playerPos >= position && a0Z(playerPos - position, aZ.kw), 0 !== leaderboardPositionsById[game.playerId] && 0 === position && a0Z(0, aZ.lJ), -1 !== a0P && a0Z(a0P, aZ.kd), a0A.fillStyle = aZ.kZ, //console.log("drawing", a0P), - a0A.clearRect(0, y9 - leaderboardFilter.tabBarOffset, a04, leaderboardFilter.tabBarOffset); - a0A.fillRect(0, y9 - leaderboardFilter.tabBarOffset, a04, leaderboardFilter.tabBarOffset); + a0A.clearRect(0, y9 - __fx.leaderboardFilter.tabBarOffset, a04, __fx.leaderboardFilter.tabBarOffset); + a0A.fillRect(0, y9 - __fx.leaderboardFilter.tabBarOffset, a04, __fx.leaderboardFilter.tabBarOffset); a0A.fillStyle = aZ.gF, a0A.fillRect(0, a0F, a04, 1), - a0A.fillRect(0, y9 - leaderboardFilter.tabBarOffset, a04, 1), - leaderboardFilter.drawTabs(a0A, a04, y9 - leaderboardFilter.tabBarOffset, aZ.kw), + a0A.fillRect(0, y9 - __fx.leaderboardFilter.tabBarOffset, a04, 1), + __fx.leaderboardFilter.drawTabs(a0A, a04, y9 - __fx.leaderboardFilter.tabBarOffset, aZ.kw), a0A.fillRect(0, 0, a04, b0.ur), a0A.fillRect(0, 0, b0.ur, y9), a0A.fillRect(a04 - b0.ur, 0, b0.ur, y9), @@ -221,8 +221,8 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 replaceRawCode("var hZ,eh=leaderboardPositionsById[game.playerId]= result.length - windowHeight) position = (result.length > windowHeight ? result.length : windowHeight) - windowHeight; //if (position >= result.length) position = result.length - 1; @@ -245,7 +245,7 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 // in the leaderboard resize handler: make space for the tab buttons at the bottom of the leaderboard replaceRawCode(",a09.height=y9,a09_ctx=a09.getContext(\"2d\",{alpha:!0}),a0D=.025*a04,a06=.16*a04,a0E=0*a04,a0F=Math.floor(.45*a0D+a06),a0G=(y9-a06-2*a0D-a0E)/a08,a05=aY.g0.g1(1,Math.floor(.55*a06)),", `,a09.height=y9,a09_ctx=a09.getContext("2d",{alpha:!0}),a0D=.025*a04,a06=.16*a04,a0E=0*a04,a0F=Math.floor(.45*a0D+a06),a0G=(y9-a06-2*a0D-a0E)/a08, - a09.height = y9 += a0G, leaderboardFilter.tabBarOffset = Math.floor(a0G * 1.3), leaderboardFilter.verticalClickThreshold = y9 - leaderboardFilter.tabBarOffset, leaderboardFilter.windowWidth = a04, + a09.height = y9 += a0G, __fx.leaderboardFilter.tabBarOffset = Math.floor(a0G * 1.3), __fx.leaderboardFilter.verticalClickThreshold = y9 - __fx.leaderboardFilter.tabBarOffset, __fx.leaderboardFilter.windowWidth = a04, a05=aY.g0.g1(1,Math.floor(.55*a06)),`) // Set the leaderboardHasChanged flag on leaderboard updates replaceRawCode("for(var eM=a0q-1;0<=eM;eM--)a14[eM]=jR[eM],a15[eM]=a8.f8[jR[eM]];a14[a0q]=a0l[b.ed],a15[a0q]=a8.f8[b.ed]", @@ -254,16 +254,16 @@ canvas.font=aY.g0.g1(1,fontSize),canvas.fillStyle="rgba("+gR+","+tD+","+hj+",0.6 replaceRawCode("var a0p=a0q(fJ);return ag.tQ()&&-1!==a0P&&(a0P=-1,a0Y(),b3.d1=!0),b3.dY-a0Q<350&&a0T===a0p&&-1!==(a0p=(a0p=yr(-1,a0p,windowHeight))!==windowHeight&&vU(x,y)?a0p:-1)&&(x=leaderboardArray[a0p+position],a0p===windowHeight-1&&leaderboardPositionsById[game.playerId]>=position+windowHeight-1&&(x=game.playerId),", `var a0p = a0q(fJ); var isEmptySpace = false; - return ag.tQ() && -1 !== a0P && (a0P = -1, a0Y(), b3.d1 = !0), b3.dY - a0Q < 350 && a0T === a0p && -1 !== (a0p = (a0p = yr(-1, a0p, windowHeight)) !== windowHeight && vU(x, y) ? a0p : -1) && (x = (leaderboardFilter.enabled ? (updateFilteredLb(), leaderboardArray[leaderboardFilter.filteredLeaderboard[a0p + position] ?? (isEmptySpace = true, leaderboardPositionsById[game.playerId])]) : leaderboardArray[a0p + position]), a0p === windowHeight - 1 && (leaderboardFilter.enabled ? this.playerPos : leaderboardPositionsById[game.playerId]) >= + return ag.tQ() && -1 !== a0P && (a0P = -1, a0Y(), b3.d1 = !0), b3.dY - a0Q < 350 && a0T === a0p && -1 !== (a0p = (a0p = yr(-1, a0p, windowHeight)) !== windowHeight && vU(x, y) ? a0p : -1) && (x = (__fx.leaderboardFilter.enabled ? (updateFilteredLb(), leaderboardArray[__fx.leaderboardFilter.filteredLeaderboard[a0p + position] ?? (isEmptySpace = true, leaderboardPositionsById[game.playerId])]) : leaderboardArray[a0p + position]), a0p === windowHeight - 1 && (__fx.leaderboardFilter.enabled ? this.playerPos : leaderboardPositionsById[game.playerId]) >= position + windowHeight - 1 && (x = game.playerId), !isEmptySpace && `); // Get clan parsing function replaceRawCode(`this.uI=function(username){var uK,uJ=username.indexOf("[");return!(uJ<0)&&1<(uK=username.indexOf("]"))-uJ&&uK-uJ<=8?username.substring(uJ+1,uK).toUpperCase().trim():null},`, - `this.uI=function(username){var uK,uJ=username.indexOf("[");return!(uJ<0)&&1<(uK=username.indexOf("]"))-uJ&&uK-uJ<=8?username.substring(uJ+1,uK).toUpperCase().trim():null}, leaderboardFilter.parseClanFromPlayerName = this.uI;`); + `this.uI=function(username){var uK,uJ=username.indexOf("[");return!(uJ<0)&&1<(uK=username.indexOf("]"))-uJ&&uK-uJ<=8?username.substring(uJ+1,uK).toUpperCase().trim():null}, __fx.leaderboardFilter.parseClanFromPlayerName = this.uI;`); } { // Hovering tooltip replaceRawCode("this.click=function(gG,gH,uH){var fd=an.fe(gG),ff=an.fg(gH),fh=an.fi(fd,ff),fj=an.fk(fh);if(!an.fl(fd,ff))return!1;var fd=(bB.d3.isUIZoomEnabled()?.025:.0144)*aO.g4,ff=performance.now();if(Math.abs(gG-wK)>fd||Math.abs(gH-wL)>fd||dg+500fd||Math.abs(gH-wL)>fd||dg+500>1,gv=playerData.nv[i]+playerData.nx[i]+1-gv-2>>1,ctx.drawImage(km[game.gIsTeamGame?lV.i6[i]:i>1,y=playerData.nv[i]+playerData.nx[i]+1-gv-2>>1, highlight ? ctx.drawImage(km[game.gIsTeamGame?lV.i6[i]:iay.ak)||bK>ay.al||hQ<-es||bK<-es||(bI.setTransform(cz,0,0,cz,hQ,bK),", `function(a6,es,bK,hP,hQ,hR,ov){ - var highlight = settings.highlightClanSpawns && clanFilter.inOwnClan[a6]; + var highlight = __fx.settings.highlightClanSpawns && __fx.clanFilter.inOwnClan[a6]; if (highlight) es *= 2; 0===dV.eg[a6]||0===dV.ev[a6]||(hQ=ay.ak*((dV.nu[a6]+dV.nw[a6]+1)/2-bK)/(hQ-bK)-.5*es,bK=ay.al*((dV.nv[a6]+dV.nx[a6]+1)/2-hP)/(hR-hP)-.5*es,hQ>ay.ak)||bK>ay.al||hQ<-es||bK<-es||(bI.setTransform(highlight?cz*2:cz,0,0,highlight?cz*2:cz,hQ,bK),` ) diff --git a/src/clanFilters.js b/src/clanFilters.js new file mode 100644 index 0000000..9f128b3 --- /dev/null +++ b/src/clanFilters.js @@ -0,0 +1,102 @@ +import { getVar } from "./gameInterface.js"; + +export const leaderboardFilter = new (function() { + //this.playersToInclude = [0,1,8,20,24,30,32,42,50,69,200,400,500,510,511]; // for testing + this.playersToInclude = []; + this.tabLabels = ["ALL", "CLAN"]; + // these get populated by the modified game code + this.filteredLeaderboard = []; + this.tabBarOffset = 0; + this.windowWidth = 0; + this.verticalClickThreshold = 1000; + this.hoveringOverTabs = false; + this.scrollToTop = () => {}; + this.repaintLeaderboard = () => {}; + this.setUpdateFlag = () => {}; + this.parseClanFromPlayerName = () => { console.warn("parse function not set"); }; + + this.selectedTab = 0; + this.tabHovering = -1; + this.enabled = false; + //this.enabled = true; + this.drawTabs = function(canvas, totalWidth, verticalOffset, colorForSelectedTab) { + canvas.textBaseline = "middle"; + canvas.textAlign = "center"; + const tabWidth = totalWidth / this.tabLabels.length; + const textOffsetY = verticalOffset + this.tabBarOffset / 2; + //console.log(verticalOffset, this.tabBarOffset, textOffsetY); + this.tabLabels.forEach((label, index) => { + if (index !== 0) canvas.fillRect(tabWidth * index, verticalOffset, 1, this.tabBarOffset); + if (this.selectedTab === index) { + canvas.fillStyle = colorForSelectedTab; + canvas.fillRect(tabWidth * index, verticalOffset, tabWidth, this.tabBarOffset); + canvas.fillStyle = "rgb(255,255,255)"; + } + if (this.tabHovering === index) { + canvas.fillStyle = "rgba(255,255,255,0.3)"; + canvas.fillRect(tabWidth * index, verticalOffset, tabWidth, this.tabBarOffset); + canvas.fillStyle = "rgb(255,255,255)"; + } + canvas.fillText(label, tabWidth * index + tabWidth / 2, textOffsetY); + }); + } + this.setHovering = (isHovering, xRelative) => { + let repaintNeeded = false; + if (isHovering) { + const tab = Math.floor(xRelative / (this.windowWidth / this.tabLabels.length)); + if (this.tabHovering !== tab) { + this.tabHovering = tab; + repaintNeeded = true; + } + } + if (isHovering !== this.hoveringOverTabs) { + this.hoveringOverTabs = isHovering; + if (isHovering === false) this.tabHovering = -1; + if (!isHovering) repaintNeeded = true; + } + if (repaintNeeded) this.repaintLeaderboard(); + return isHovering; + } + this.handleMouseDown = (xRelative) => { + const tab = Math.floor(xRelative / (this.windowWidth / this.tabLabels.length)); + if (this.selectedTab !== tab) { + this.selectedTab = tab; + if (this.selectedTab === 0) this.clearFilter(); + else if (this.selectedTab === 1) { + this.filterByOwnClan(); + this.setUpdateFlag(); + } + this.repaintLeaderboard(); + } + return true; + }; + this.filterByOwnClan = () => { + this.playersToInclude = []; + const playerId = getVar("playerId"); + const ownClan = this.parseClanFromPlayerName(getVar("rawPlayerNames")[playerId]); + getVar("rawPlayerNames").forEach((name, id) => { + if (id === playerId || this.parseClanFromPlayerName(name) === ownClan) this.playersToInclude.push(id); + }); + this.enabled = true; + this.scrollToTop(); + }; + this.clearFilter = () => { this.enabled = false; } + this.reset = () => { + this.enabled = false; + this.selectedTab = 0; + clanFilter.refresh(); + } +}); + +export const clanFilter = new (function() { + this.inOwnClan = new Array(512); + this.inOwnClan.fill(false); + this.refresh = () => { + const gHumans = getVar("gHumans"); + const ownClan = leaderboardFilter.parseClanFromPlayerName(getVar("rawPlayerNames")[getVar("playerId")]); + if (ownClan === null) this.inOwnClan.fill(false); + else getVar("rawPlayerNames").forEach((name, id) => { + this.inOwnClan[id] = id < gHumans && leaderboardFilter.parseClanFromPlayerName(name) === ownClan; + }); + } +}); \ No newline at end of file diff --git a/src/donationsTracker.js b/src/donationsTracker.js new file mode 100644 index 0000000..aad8c42 --- /dev/null +++ b/src/donationsTracker.js @@ -0,0 +1,58 @@ +import WindowManager from "./windowManager.js"; +import { getVar } from "./gameInterface.js"; +import { escapeHtml } from "./utils.js"; + +WindowManager.add({ + name: "donationHistory", + element: document.querySelector("#donationhistory"), + beforeOpen: function(isSingleplayer) { + document.getElementById("donationhistory_note").style.display = (/*(settings.showBotDonations || isSingleplayer)*/ true ? "none" : "block"); + }, + onClose: function() { donationsTracker.openedWindowPlayerID = null; } +}); +const donationsTracker = new (function(){ + this.openedWindowPlayerID = null; + this.contentElement = document.querySelector("#donationhistory_content"); + this.donationHistory = Array(512); + // fill the array with empty arrays with length of 3 + //for (var i = 0; i < 512; i++) this.donationHistory.push([]); // not needed as .reset is called on game start + this.getHistoryOf = function(playerID) { + return this.donationHistory[playerID].toReversed(); + } + this.reset = function() { for (var i = 0; i < 512; i++) this.donationHistory[i] = []; }; + this.logDonation = function(senderID, receiverID, amount) { + const donationInfo = [senderID, receiverID, amount]; + this.donationHistory[receiverID].push(donationInfo); + this.donationHistory[senderID].push(donationInfo); + if (this.openedWindowPlayerID === senderID || this.openedWindowPlayerID === receiverID) { + const indexOfNewItem = this.donationHistory[this.openedWindowPlayerID === senderID ? senderID : receiverID].length; + this.contentElement.prepend(generateTableRowItem(donationInfo, indexOfNewItem, this.openedWindowPlayerID, true)); + } + }; + function generateTableRowItem(historyItem, index, playerID, isNew) { + const rawPlayerNames = getVar("rawPlayerNames"); + const row = document.createElement("tr"); + if (isNew) row.setAttribute("class", "new"); + let content = `${index}. `; + if (playerID === historyItem[1]) + content += `Received ${historyItem[2]} resources from ${escapeHtml(rawPlayerNames[historyItem[0]])}`; + else content += `Sent ${historyItem[2]} resources to ${escapeHtml(rawPlayerNames[historyItem[1]])}`; + content += ""; + row.innerHTML = content; + return row; + } + this.displayHistory = function displayDonationsHistory(playerID, playerNames = getVar("rawPlayerNames"), isSingleplayer = getVar("gIsSingleplayer")) { + var history = donationsTracker.getHistoryOf(playerID); + console.log("History for " + playerNames[playerID] + ":"); + console.log(history); + document.querySelector("#donationhistory h1").innerHTML = "Donation history for " + escapeHtml(playerNames[playerID]); + this.contentElement.innerHTML = ""; + if (history.length > 0) history.forEach((historyItem, index) => { + this.contentElement.appendChild(generateTableRowItem(historyItem, history.length - index, playerID)); + }); + else this.contentElement.innerText = "Nothing to display"; + this.openedWindowPlayerID = playerID; + WindowManager.openWindow("donationHistory", isSingleplayer); + } +}); +export default donationsTracker; \ No newline at end of file diff --git a/src/fx_core.js b/src/fx_core.js deleted file mode 100644 index 6f38a36..0000000 --- a/src/fx_core.js +++ /dev/null @@ -1,606 +0,0 @@ -const fx_version = '0.6.5.5'; // FX Client Version -const fx_update = 'Aug 26'; // FX Client Last Updated - -if (localStorage.getItem("fx_winCount") == undefined || localStorage.getItem("fx_winCount") == null) { - var wins_counter = 0; - console.log('Couldn\'t find a saved win data. creating one...'); -} else if (localStorage.getItem("fx_winCount") != undefined || localStorage.getItem("fx_winCount") != null) { - var wins_counter = localStorage.getItem("fx_winCount"); -} - -const playerDataProperties = ["playerTerritories", "playerBalances", "rawPlayerNames"]; -const gameObjectProperties = ["playerId", "gIsTeamGame", "gHumans", "gLobbyMaxJoin", "gameState", "gIsSingleplayer"]; -const getVar = varName => { - if (playerDataProperties.includes(varName)) return window[dictionary.playerData][dictionary[varName]]; - if (gameObjectProperties.includes(varName)) return window[dictionary.game][dictionary[varName]]; - return window[dictionary[varName]] -}; - -// https://stackoverflow.com/a/6234804 -function escapeHtml(unsafe) { - return unsafe.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); -} - -function KeybindsInput(containerElement) { - const header = document.createElement("p"); - header.innerText = "Attack Percentage Keybinds"; - const keybindContainer = document.createElement("div"); - keybindContainer.className = "arrayinput"; - const keybindAddButton = document.createElement("button"); - keybindAddButton.innerText = "Add"; - containerElement.append(header, keybindContainer, keybindAddButton); - this.container = keybindContainer; - this.keys = [ "key", "type", "value" ]; - this.objectArray = []; - this.addObject = function () { - this.objectArray.push({ key: "", type: "absolute", value: 0.8 }); - this.displayObjects(); - keybindAddButton.scrollIntoView(false); - }; - this.update = function () { - this.objectArray = settings.attackPercentageKeybinds; - this.displayObjects(); - } - keybindAddButton.addEventListener("click", this.addObject.bind(this)); - this.displayObjects = function () { - // Clear the content of the container - this.container.innerHTML = ""; - if (this.objectArray.length === 0) return this.container.innerText = "No custom attack percentage keybinds added"; - // Loop through the array and display input fields for each object - for (var i = 0; i < this.objectArray.length; i++) { - var objectDiv = document.createElement("div"); - // Create input fields for each key - this.keys.forEach(function (key) { - let inputField = document.createElement(key === "type" ? "select" : "input"); - if (key === "type") { - inputField.innerHTML = ''; - inputField.addEventListener("change", this.updateObject.bind(this, i, key)); - } else if (key === "key") { - inputField.type = "text"; - inputField.setAttribute("readonly", ""); - inputField.setAttribute("placeholder", "No key set"); - inputField.addEventListener("click", this.startKeyInput.bind(this, i, key)); - } else { // key === "value" - const isAbsolute = this.objectArray[i].type === "absolute"; - inputField.type = isAbsolute ? "text" : "number"; - if (isAbsolute) inputField.addEventListener("click", this.convertIntoNumberInput.bind(this, i, key), { once: true }); - else inputField.setAttribute("step", "0.1"); - inputField.addEventListener("input", this.updateObject.bind(this, i, key)); - } - if (key === "value" && this.objectArray[i].type === "absolute") - inputField.value = this.objectArray[i][key] * 100 + "%"; - else inputField.value = this.objectArray[i][key]; - // Append input field to the object div - objectDiv.appendChild(inputField); - }, this); - // Button to delete the object - var deleteButton = document.createElement("button"); - deleteButton.textContent = "Delete"; - deleteButton.addEventListener("click", this.deleteObject.bind(this, i)); - // Append delete button to the object div - objectDiv.appendChild(deleteButton); - // Append the object div to the container - this.container.appendChild(objectDiv); - } - }; - /** @param {PointerEvent} event */ - this.startKeyInput = function (index, property, event) { - event.target.value = "Press any key"; - const handler = this.updateObject.bind(this, index, property); - event.target.addEventListener('keydown', handler, { once: true }); - event.target.addEventListener("blur", () => { - event.target.removeEventListener('keydown', handler, { once: true }); - event.target.value = this.objectArray[index][property]; - //this.displayObjects(); - }, { once: true }); - }; - /** @param {PointerEvent} event */ - this.convertIntoNumberInput = function (index, property, event) { - event.target.value = event.target.value.slice(0, -1); - event.target.type = "number"; - event.target.addEventListener("blur", () => { - //event.target.value = this.objectArray[index][property]; - this.displayObjects(); - }, { once: true }); - }; - this.updateObject = function (index, property, event) { - if (index >= this.objectArray.length) return; - // Update the corresponding property of the object in the array - const value = property === "value" ? ( - this.objectArray[index].type === "absolute" ? parseFloat(event.target.value) / 100 : parseFloat(event.target.value) - ) : property === "key" ? event.key : event.target.value; - this.objectArray[index][property] = value; - if (property === "key") this.displayObjects(); - }; - this.deleteObject = function (index) { - // Remove the object from the array - this.objectArray.splice(index, 1); - // Display the updated input fields for objects - this.displayObjects(); - }; - return this; -} - -var settings = { - //"fontName": "Trebuchet MS", - //"showBotDonations": false, - "displayWinCounter": true, - "useFullscreenMode": false, - "hoveringTooltip": true, - //"hideAllLinks": false, - "realisticNames": false, - "showPlayerDensity": true, - "coloredDensity": true, - "densityDisplayStyle": "percentage", - "highlightClanSpawns": false, - //"customMapFileBtn": true - "customBackgroundUrl": "", - "attackPercentageKeybinds": [], -}; -const discontinuedSettings = [ "hideAllLinks", "fontName" ]; -let makeMainMenuTransparent = false; -var settingsManager = new (function() { - const settingsStructure = [ - { for: "displayWinCounter", type: "checkbox", label: "Display win counter", - note: "The win counter tracks multiplayer solo wins (not in team games)" }, - { type: "button", text: "Reset win counter", action: removeWins }, - { for: "useFullscreenMode", type: "checkbox", label: "Use fullscreen mode", - note: "Note: fullscreen mode will trigger after you click anywhere on the page due to browser policy restrictions." }, - { for: "hoveringTooltip", type: "checkbox", label: "Hovering tooltip", - note: "Display map territory info constantly (on mouse hover) instead of only when right clicking on the map" }, - //{ for: "hideAllLinks", type: "checkbox", label: "Hide Links option also hides app store links" }, - { for: "realisticNames", type: "checkbox", label: "Realistic Bot Names" }, - { for: "showPlayerDensity", type: "checkbox", label: "Show player density" }, - { for: "coloredDensity", type: "checkbox", label: "Colored density", note: "Display the density with a color between red and green depending on the density value" }, - { for: "densityDisplayStyle", type: "selectMenu", label: "Density value display style:", tooltip: "Controls how the territorial density value should be rendered", options: [ - { value: "percentage", label: "Percentage" }, - { value: "absoluteQuotient", label: "Value from 0 to 150 (BetterTT style)" } - ]}, - { for: "highlightClanSpawns", type: "checkbox", label: "Highlight clan spawnpoints", - note: "Increases the spawnpoint glow size for members of your clan" }, - { for: "customBackgroundUrl", type: "textInput", label: "Custom main menu background:", placeholder: "Enter an image URL here", tooltip: "A custom image to be shown as the main menu background instead of the currently selected map." }, - KeybindsInput - ]; - const settingsContainer = document.querySelector(".settings .scrollable"); - var inputFields = {}; // (includes select menus) - var checkboxFields = {}; - var customElements = []; - settingsStructure.forEach(item => { - if (typeof item === "function") { - const container = document.createElement("div"); - customElements.push(new item(container)); - return settingsContainer.append(container); - } - const label = document.createElement("label"); - if (item.tooltip) label.title = item.tooltip; - const isValueInput = item.type.endsWith("Input"); - const element = document.createElement(isValueInput || item.type === "checkbox" ? "input" : item.type === "selectMenu" ? "select" : "button"); - if (item.type === "textInput") element.type = "text"; - if (item.placeholder) element.placeholder = item.placeholder; - if (isValueInput || item.type === "selectMenu") inputFields[item.for] = element; - if (item.text) element.innerText = item.text; - if (item.action) element.addEventListener("click", item.action); - if (item.label) label.append(item.label + " "); - if (item.note) { - const note = document.createElement("small"); - note.innerText = item.note; - label.append(document.createElement("br"), note) - } - if (item.options) item.options.forEach(option => { - const optionElement = document.createElement("option"); - optionElement.setAttribute("value", option.value); - optionElement.innerText = option.label; - element.append(optionElement); - }); - label.append(element); - if (item.type === "checkbox") { - element.type = "checkbox"; - const checkmark = document.createElement("span"); - checkmark.className = "checkmark"; - label.className = "checkbox"; - label.append(checkmark); - checkboxFields[item.for] = element; - } else label.append(document.createElement("br")); - settingsContainer.append(label, document.createElement("br")); - }); - this.save = function() { - Object.keys(inputFields).forEach(function(key) { settings[key] = inputFields[key].value.trim(); }); - Object.keys(checkboxFields).forEach(function(key) { settings[key] = checkboxFields[key].checked; }); - this.applySettings(); - WindowManager.closeWindow("settings"); - discontinuedSettings.forEach(settingName => delete settings[settingName]); - localStorage.setItem("fx_settings", JSON.stringify(settings)); - // should probably firgure out a way to do this without reloading - // You can't do it, localstorages REQUIRE you to reload - window.location.reload(); - }; - - const fileInput = document.createElement("input"); - fileInput.type = "file"; - function handleFileSelect(event) { - const input = event.target; - /** @type {File} */ - const selectedFile = input.files[0]; - if (!selectedFile) return; - - input.removeEventListener("change", handleFileSelect); - input.value = ""; - if (!selectedFile.name.endsWith(".json")) return alert("Invalid file format"); - const fileReader = new FileReader(); - fileReader.onload = function() { - let result; - try { - result = JSON.parse(fileReader.result); - if (confirm("Warning: This will override all current settings, click \"OK\" to confirm")) settings = result; - localStorage.setItem("fx_settings", JSON.stringify(settings)); - window.location.reload(); - } catch (error) { - alert("Error\n" + error) - } - } - fileReader.readAsText(selectedFile); - } - this.importFromFile = function() { - fileInput.click(); - fileInput.addEventListener('change', handleFileSelect); - }; - // https://stackoverflow.com/a/34156339 - function saveFile(content, fileName, contentType) { - var a = document.createElement("a"); - var file = new Blob([content], {type: contentType}); - a.href = URL.createObjectURL(file); - a.download = fileName; - a.click(); - URL.revokeObjectURL(a.href); - } - this.exportToFile = function() { - saveFile(JSON.stringify(settings), 'FX_client_settings.json', 'application/json'); - }; - - this.syncFields = function() { - Object.keys(inputFields).forEach(function(key) { inputFields[key].value = settings[key]; }); - Object.keys(checkboxFields).forEach(function(key) { checkboxFields[key].checked = settings[key]; }); - customElements.forEach(element => element.update()); - }; - this.resetAll = function() { - if (!confirm("Are you Really SURE you want to RESET ALL SETTINGS back to the default?")) return; - localStorage.removeItem("fx_settings"); - window.location.reload(); - }; - this.applySettings = function() { - //setVarByName("bu", "px " + settings.fontName); - if (settings.useFullscreenMode && document.fullscreenEnabled) { - function tryEnterFullscreen() { - if (document.fullscreenElement !== null) return; - document.documentElement.requestFullscreen({ navigationUI: "hide" }) - .then(() => { console.log('Fullscreen mode activated'); }) - .catch((error) => { console.warn('Could not enter fullscreen mode:', error); }); - } - document.addEventListener('mousedown', tryEnterFullscreen, { once: true }); - document.addEventListener('click', tryEnterFullscreen, { once: true }); - } - if (settings.customBackgroundUrl !== "") { - document.body.style.backgroundImage = "url(" + settings.customBackgroundUrl + ")"; - document.body.style.backgroundSize = "cover"; - document.body.style.backgroundPosition = "center"; - } - makeMainMenuTransparent = settings.customBackgroundUrl !== ""; - }; -}); -function removeWins() { - var confirm1 = confirm('Do you really want to reset your Wins?'); - if (confirm1) { - wins_counter = 0; - localStorage.removeItem('fx_winCount'); - alert("Successfully reset wins"); - } -} -const openCustomBackgroundFilePicker = () => { - const fileInput = document.getElementById("customBackgroundFileInput"); - fileInput.click(); - fileInput.addEventListener('change', handleFileSelect); -} -function handleFileSelect(event) { - const fileInput = event.target; - const selectedFile = fileInput.files[0]; - console.log(fileInput.files); - console.log(fileInput.files[0]); - if (selectedFile) { - const fileUrl = URL.createObjectURL(selectedFile); - console.log("File URL:", fileUrl); - fileInput.value = ""; - fileInput.removeEventListener("change", handleFileSelect); - } -} - -var WindowManager = new (function() { - var windows = {}; - this.add = function(newWindow) { - windows[newWindow.name] = newWindow; - windows[newWindow.name].isOpen = false; - }; - this.openWindow = function(windowName, ...args) { - if (windows[windowName].isOpen === true) return; - if (windows[windowName].beforeOpen !== undefined) windows[windowName].beforeOpen(...args); - windows[windowName].isOpen = true; - windows[windowName].element.style.display = null; - }; - this.closeWindow = function(windowName) { - if (windows[windowName].isOpen === false) return; - windows[windowName].isOpen = false; - windows[windowName].element.style.display = "none"; - if (windows[windowName].onClose !== undefined) windows[windowName].onClose(); - }; - this.closeAll = function() { - Object.values(windows).forEach(function(windowObj) { - WindowManager.closeWindow(windowObj.name); - }); - }; -}); -WindowManager.add({ - name: "settings", - element: document.querySelector(".settings"), - beforeOpen: function() { settingsManager.syncFields(); } -}); -WindowManager.add({ - name: "donationHistory", - element: document.querySelector("#donationhistory"), - beforeOpen: function(isSingleplayer) { - document.getElementById("donationhistory_note").style.display = ((true || settings.showBotDonations || /*getVarByName("dt")*/ isSingleplayer) ? "none" : "block"); - }, - onClose: function() { donationsTracker.openedWindowPlayerID = null; } -}); -WindowManager.add({ - name: "playerList", - element: document.getElementById("playerlist"), - beforeOpen: function() {} -}); -document.getElementById("canvasA").addEventListener("mousedown", WindowManager.closeAll); -document.getElementById("canvasA").addEventListener("touchstart", WindowManager.closeAll, { passive: true }); -document.addEventListener("keydown", event => { if (event.key === "Escape") WindowManager.closeAll(); }); -var settingsGearIcon = document.createElement('img'); -settingsGearIcon.setAttribute('src', 'assets/geari_white.png'); - -const playerList = new (function () { - const playersIcon = document.createElement('img'); - playersIcon.setAttribute('src', 'assets/players_icon.png'); - document.getElementById("playerlist_content").addEventListener("click", event => { - const playerId = event.target.closest("tr[data-player-id]")?.getAttribute("data-player-id"); - if (!playerId) return; - if (getVar("gIsTeamGame")) WindowManager.closeWindow("playerList"), donationsTracker.displayHistory(playerId); - }); - this.display = function displayPlayerList(playerNames) { - const gHumans = getVar("gHumans"); - const gLobbyMaxJoin = getVar("gLobbyMaxJoin"); - let listContent = `

Players (${gHumans})

`; - for (let i = 0; i < gLobbyMaxJoin; i++) { - if (i === gHumans) listContent += `

Bots (${gLobbyMaxJoin - gHumans})

`; - listContent += `${i + 1}. ${escapeHtml(playerNames[i])}` - } - document.getElementById("playerlist_content").innerHTML = listContent; - document.getElementById("playerlist_content").setAttribute("class", getVar("gIsTeamGame") ? "clickable" : ""); - WindowManager.openWindow("playerList"); - } - this.hoveringOverButton = false; - this.drawButton = (canvas, x, y, size) => { - canvas.fillRect(x, y, size, size); - canvas.fillStyle = this.hoveringOverButton ? "#aaaaaaaa" : "#000000aa"; - canvas.clearRect(x + 1, y + 1, size - 2, size - 2); - canvas.fillRect(x + 1, y + 1, size - 2, size - 2); - canvas.fillStyle = "#ffffff"; - canvas.imageSmoothingEnabled = true; - canvas.drawImage(playersIcon, x + 2, y + 2, size - 4, size - 4); - canvas.imageSmoothingEnabled = false; - } -}); - -const leaderboardFilter = new (function() { - //this.playersToInclude = [0,1,8,20,24,30,32,42,50,69,200,400,500,510,511]; // for testing - this.playersToInclude = []; - this.tabLabels = ["ALL", "CLAN"]; - // these get populated by the modified game code - this.filteredLeaderboard = []; - this.tabBarOffset = 0; - this.windowWidth = 0; - this.verticalClickThreshold = 1000; - this.hoveringOverTabs = false; - this.scrollToTop = () => {}; - this.repaintLeaderboard = () => {}; - this.setUpdateFlag = () => {}; - this.parseClanFromPlayerName = () => { console.warn("parse function not set"); }; - - this.selectedTab = 0; - this.tabHovering = -1; - this.enabled = false; - //this.enabled = true; - this.drawTabs = function(canvas, totalWidth, verticalOffset, colorForSelectedTab) { - canvas.textBaseline = "middle"; - canvas.textAlign = "center"; - const tabWidth = totalWidth / this.tabLabels.length; - const textOffsetY = verticalOffset + this.tabBarOffset / 2; - //console.log(verticalOffset, this.tabBarOffset, textOffsetY); - this.tabLabels.forEach((label, index) => { - if (index !== 0) canvas.fillRect(tabWidth * index, verticalOffset, 1, this.tabBarOffset); - if (this.selectedTab === index) { - canvas.fillStyle = colorForSelectedTab; - canvas.fillRect(tabWidth * index, verticalOffset, tabWidth, this.tabBarOffset); - canvas.fillStyle = "rgb(255,255,255)"; - } - if (this.tabHovering === index) { - canvas.fillStyle = "rgba(255,255,255,0.3)"; - canvas.fillRect(tabWidth * index, verticalOffset, tabWidth, this.tabBarOffset); - canvas.fillStyle = "rgb(255,255,255)"; - } - canvas.fillText(label, tabWidth * index + tabWidth / 2, textOffsetY); - }); - } - this.setHovering = (isHovering, xRelative) => { - let repaintNeeded = false; - if (isHovering) { - const tab = Math.floor(xRelative / (this.windowWidth / this.tabLabels.length)); - if (this.tabHovering !== tab) { - this.tabHovering = tab; - repaintNeeded = true; - } - } - if (isHovering !== this.hoveringOverTabs) { - this.hoveringOverTabs = isHovering; - if (isHovering === false) this.tabHovering = -1; - if (!isHovering) repaintNeeded = true; - } - if (repaintNeeded) this.repaintLeaderboard(); - return isHovering; - } - this.handleMouseDown = (xRelative) => { - const tab = Math.floor(xRelative / (this.windowWidth / this.tabLabels.length)); - if (this.selectedTab !== tab) { - this.selectedTab = tab; - if (this.selectedTab === 0) this.clearFilter(); - else if (this.selectedTab === 1) { - this.filterByOwnClan(); - this.setUpdateFlag(); - } - this.repaintLeaderboard(); - } - return true; - }; - this.filterByOwnClan = () => { - this.playersToInclude = []; - const playerId = getVar("playerId"); - const ownClan = this.parseClanFromPlayerName(getVar("rawPlayerNames")[playerId]); - getVar("rawPlayerNames").forEach((name, id) => { - if (id === playerId || this.parseClanFromPlayerName(name) === ownClan) this.playersToInclude.push(id); - }); - this.enabled = true; - this.scrollToTop(); - }; - this.clearFilter = () => { this.enabled = false; } - this.reset = () => { - this.enabled = false; - this.selectedTab = 0; - clanFilter.refresh(); - } -}); - -const clanFilter = new (function() { - this.inOwnClan = new Array(512); - this.inOwnClan.fill(false); - this.refresh = () => { - const gHumans = getVar("gHumans"); - const ownClan = leaderboardFilter.parseClanFromPlayerName(getVar("rawPlayerNames")[getVar("playerId")]); - if (ownClan === null) this.inOwnClan.fill(false); - else getVar("rawPlayerNames").forEach((name, id) => { - this.inOwnClan[id] = id < gHumans && leaderboardFilter.parseClanFromPlayerName(name) === ownClan; - }); - } -}); - -const hoveringTooltip = new (function() { - let recentlyShown = false; - this.display = () => {}; // this gets populated by the modified game script - this.canvasPixelScale = 1; - function handler(e) { - if (!settings.hoveringTooltip || !getVar("gameState") || recentlyShown) return; - let x, y; - // https://stackoverflow.com/a/61732450 - if (e.type.includes(`touch`)) { - const { touches, changedTouches } = e.originalEvent ?? e; - const touch = touches[0] ?? changedTouches[0]; - x = touch.pageX; - y = touch.pageY; - } else if (e.type.includes(`mouse`)) { - x = e.clientX; - y = e.clientY; - } - - recentlyShown = true; - try { - this.display(this.canvasPixelScale * x, this.canvasPixelScale * y); - } catch (e) { console.error(e) } - // for better performance, reduce the tooltip display frequency to no more than once every 100 ms - setTimeout(() => recentlyShown = false, 100); - } - document.getElementById("canvasA").addEventListener("mousemove", handler.bind(this)); - document.getElementById("canvasA").addEventListener("touchstart", handler.bind(this)); -}); - -var donationsTracker = new (function(){ - this.openedWindowPlayerID = null; - this.contentElement = document.querySelector("#donationhistory_content"); - this.donationHistory = Array(512); - // fill the array with empty arrays with length of 3 - //for (var i = 0; i < 512; i++) this.donationHistory.push([]); // not needed as .reset is called on game start - this.getHistoryOf = function(playerID) { - return this.donationHistory[playerID].toReversed(); - } - this.reset = function() { for (var i = 0; i < 512; i++) this.donationHistory[i] = []; }; - this.logDonation = function(senderID, receiverID, amount) { - const donationInfo = [senderID, receiverID, amount]; - this.donationHistory[receiverID].push(donationInfo); - this.donationHistory[senderID].push(donationInfo); - if (this.openedWindowPlayerID === senderID || this.openedWindowPlayerID === receiverID) { - const indexOfNewItem = this.donationHistory[this.openedWindowPlayerID === senderID ? senderID : receiverID].length; - this.contentElement.prepend(generateTableRowItem(donationInfo, indexOfNewItem, this.openedWindowPlayerID, true)); - } - }; - function generateTableRowItem(historyItem, index, playerID, isNew) { - const rawPlayerNames = getVar("rawPlayerNames"); - const row = document.createElement("tr"); - if (isNew) row.setAttribute("class", "new"); - let content = `${index}. `; - if (playerID === historyItem[1]) - content += `Received ${historyItem[2]} resources from ${escapeHtml(rawPlayerNames[historyItem[0]])}`; - else content += `Sent ${historyItem[2]} resources to ${escapeHtml(rawPlayerNames[historyItem[1]])}`; - content += ""; - row.innerHTML = content; - return row; - } - this.displayHistory = function displayDonationsHistory(playerID, playerNames = getVar("rawPlayerNames"), isSingleplayer = getVar("gIsSingleplayer")) { - var history = donationsTracker.getHistoryOf(playerID); - console.log("History for " + playerNames[playerID] + ":"); - console.log(history); - document.querySelector("#donationhistory h1").innerHTML = "Donation history for " + escapeHtml(playerNames[playerID]); - this.contentElement.innerHTML = ""; - if (history.length > 0) history.forEach((historyItem, index) => { - this.contentElement.appendChild(generateTableRowItem(historyItem, history.length - index, playerID)); - }); - else this.contentElement.innerText = "Nothing to display"; - this.openedWindowPlayerID = playerID; - WindowManager.openWindow("donationHistory", isSingleplayer); - } -}); - -var utils = new (function() { - this.getMaxTroops = function(playerTerritories, playerID) { return (playerTerritories[playerID]*150).toString(); }; - this.getDensity = function(playerID, playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories")) { - if (settings.densityDisplayStyle === "percentage") return (((playerBalances[playerID] / ((playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID]) * 150)) * 100).toFixed(1) + "%"); - else return (playerBalances[playerID] / (playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID])).toFixed(1); - }; - this.isPointInRectangle = function(x, y, rectangleStartX, rectangleStartY, width, height) { - return x >= rectangleStartX && x <= rectangleStartX + width && y >= rectangleStartY && y <= rectangleStartY + height; - }; - /** @param {CanvasRenderingContext2D} canvas @param {string} text */ - this.fillTextMultiline = function(canvas, text, x, y, maxWidth) { - const lineHeight = parseInt(canvas.font.split(" ").find(part => part.endsWith("px")).slice(0, -2)); - text.split("\n").forEach((line, index) => canvas.fillText(line, x, y + index * lineHeight, maxWidth)); - } - this.textStyleBasedOnDensity = function(playerID) { - const playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories"); - return `hsl(${playerBalances[playerID] / (playerTerritories[playerID] * 1.5)}, 100%, 50%, 1)`; - } -}); - -const keybindFunctions = { setAbsolute: () => {}, setRelative: () => {} }; -const keybindHandler = key => { - const keybindData = settings.attackPercentageKeybinds.find(keybind => keybind.key === key); - if (keybindData === undefined) return false; - if (keybindData.type === "absolute") keybindFunctions.setAbsolute(keybindData.value); - else keybindFunctions.setRelative(keybindData.value); - return true; -}; - -if (localStorage.getItem("fx_settings") !== null) { - settings = {...settings, ...JSON.parse(localStorage.getItem("fx_settings"))}; -} -settingsManager.applySettings(); - -console.log('Successfully loaded FX Client'); \ No newline at end of file diff --git a/src/gameInterface.js b/src/gameInterface.js new file mode 100644 index 0000000..526b4ac --- /dev/null +++ b/src/gameInterface.js @@ -0,0 +1,7 @@ +const playerDataProperties = ["playerTerritories", "playerBalances", "rawPlayerNames"]; +const gameObjectProperties = ["playerId", "gIsTeamGame", "gHumans", "gLobbyMaxJoin", "gameState", "gIsSingleplayer"]; +export const getVar = varName => { + if (playerDataProperties.includes(varName)) return window[dictionary.playerData][dictionary[varName]]; + if (gameObjectProperties.includes(varName)) return window[dictionary.game][dictionary[varName]]; + return window[dictionary[varName]] +}; \ No newline at end of file diff --git a/src/gameScriptUtils.js b/src/gameScriptUtils.js new file mode 100644 index 0000000..e8f9b9a --- /dev/null +++ b/src/gameScriptUtils.js @@ -0,0 +1,23 @@ +import { getSettings } from "./settings.js"; +import { getVar } from "./gameInterface.js"; + +const utils = new (function() { + this.getMaxTroops = function(playerTerritories, playerID) { return (playerTerritories[playerID]*150).toString(); }; + this.getDensity = function(playerID, playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories")) { + if (getSettings().densityDisplayStyle === "percentage") return (((playerBalances[playerID] / ((playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID]) * 150)) * 100).toFixed(1) + "%"); + else return (playerBalances[playerID] / (playerTerritories[playerID] === 0 ? 1 : playerTerritories[playerID])).toFixed(1); + }; + this.isPointInRectangle = function(x, y, rectangleStartX, rectangleStartY, width, height) { + return x >= rectangleStartX && x <= rectangleStartX + width && y >= rectangleStartY && y <= rectangleStartY + height; + }; + /** @param {CanvasRenderingContext2D} canvas @param {string} text */ + this.fillTextMultiline = function(canvas, text, x, y, maxWidth) { + const lineHeight = parseInt(canvas.font.split(" ").find(part => part.endsWith("px")).slice(0, -2)); + text.split("\n").forEach((line, index) => canvas.fillText(line, x, y + index * lineHeight, maxWidth)); + } + this.textStyleBasedOnDensity = function(playerID) { + const playerBalances = getVar("playerBalances"), playerTerritories = getVar("playerTerritories"); + return `hsl(${playerBalances[playerID] / (playerTerritories[playerID] * 1.5)}, 100%, 50%, 1)`; + } +}); +export default utils \ No newline at end of file diff --git a/src/hoveringTooltip.js b/src/hoveringTooltip.js new file mode 100644 index 0000000..5a4e8a9 --- /dev/null +++ b/src/hoveringTooltip.js @@ -0,0 +1,32 @@ +import { getSettings } from "./settings.js"; +import { getVar } from "./gameInterface.js"; + +const hoveringTooltip = new (function() { + let recentlyShown = false; + this.display = () => {}; // this gets populated by the modified game script + this.canvasPixelScale = 1; + function handler(e) { + if (!getSettings().hoveringTooltip || !getVar("gameState") || recentlyShown) return; + let x, y; + // https://stackoverflow.com/a/61732450 + if (e.type.includes(`touch`)) { + const { touches, changedTouches } = e.originalEvent ?? e; + const touch = touches[0] ?? changedTouches[0]; + x = touch.pageX; + y = touch.pageY; + } else if (e.type.includes(`mouse`)) { + x = e.clientX; + y = e.clientY; + } + + recentlyShown = true; + try { + this.display(this.canvasPixelScale * x, this.canvasPixelScale * y); + } catch (e) { console.error(e) } + // for better performance, reduce the tooltip display frequency to no more than once every 100 ms + setTimeout(() => recentlyShown = false, 100); + } + document.getElementById("canvasA").addEventListener("mousemove", handler.bind(this)); + document.getElementById("canvasA").addEventListener("touchstart", handler.bind(this)); +}); +export default hoveringTooltip \ No newline at end of file diff --git a/src/keybinds.js b/src/keybinds.js new file mode 100644 index 0000000..e896661 --- /dev/null +++ b/src/keybinds.js @@ -0,0 +1,10 @@ +import { getSettings } from "./settings.js"; + +export const keybindFunctions = { setAbsolute: () => {}, setRelative: () => {} }; +export const keybindHandler = key => { + const keybindData = getSettings().attackPercentageKeybinds.find(keybind => keybind.key === key); + if (keybindData === undefined) return false; + if (keybindData.type === "absolute") keybindFunctions.setAbsolute(keybindData.value); + else keybindFunctions.setRelative(keybindData.value); + return true; +}; \ No newline at end of file diff --git a/src/keybindsInput.js b/src/keybindsInput.js new file mode 100644 index 0000000..4ce33c9 --- /dev/null +++ b/src/keybindsInput.js @@ -0,0 +1,97 @@ +export function KeybindsInput(containerElement) { + const header = document.createElement("p"); + header.innerText = "Attack Percentage Keybinds"; + const keybindContainer = document.createElement("div"); + keybindContainer.className = "arrayinput"; + const keybindAddButton = document.createElement("button"); + keybindAddButton.innerText = "Add"; + containerElement.append(header, keybindContainer, keybindAddButton); + this.container = keybindContainer; + this.keys = [ "key", "type", "value" ]; + this.objectArray = []; + this.addObject = function () { + this.objectArray.push({ key: "", type: "absolute", value: 0.8 }); + this.displayObjects(); + keybindAddButton.scrollIntoView(false); + }; + this.update = function (settings) { + this.objectArray = settings.attackPercentageKeybinds; + this.displayObjects(); + } + keybindAddButton.addEventListener("click", this.addObject.bind(this)); + this.displayObjects = function () { + // Clear the content of the container + this.container.innerHTML = ""; + if (this.objectArray.length === 0) return this.container.innerText = "No custom attack percentage keybinds added"; + // Loop through the array and display input fields for each object + for (var i = 0; i < this.objectArray.length; i++) { + var objectDiv = document.createElement("div"); + // Create input fields for each key + this.keys.forEach(function (key) { + let inputField = document.createElement(key === "type" ? "select" : "input"); + if (key === "type") { + inputField.innerHTML = ''; + inputField.addEventListener("change", this.updateObject.bind(this, i, key)); + } else if (key === "key") { + inputField.type = "text"; + inputField.setAttribute("readonly", ""); + inputField.setAttribute("placeholder", "No key set"); + inputField.addEventListener("click", this.startKeyInput.bind(this, i, key)); + } else { // key === "value" + const isAbsolute = this.objectArray[i].type === "absolute"; + inputField.type = isAbsolute ? "text" : "number"; + if (isAbsolute) inputField.addEventListener("click", this.convertIntoNumberInput.bind(this, i, key), { once: true }); + else inputField.setAttribute("step", "0.1"); + inputField.addEventListener("input", this.updateObject.bind(this, i, key)); + } + if (key === "value" && this.objectArray[i].type === "absolute") + inputField.value = this.objectArray[i][key] * 100 + "%"; + else inputField.value = this.objectArray[i][key]; + // Append input field to the object div + objectDiv.appendChild(inputField); + }, this); + // Button to delete the object + var deleteButton = document.createElement("button"); + deleteButton.textContent = "Delete"; + deleteButton.addEventListener("click", this.deleteObject.bind(this, i)); + // Append delete button to the object div + objectDiv.appendChild(deleteButton); + // Append the object div to the container + this.container.appendChild(objectDiv); + } + }; + this.startKeyInput = function (index, property, event) { + event.target.value = "Press any key"; + const handler = this.updateObject.bind(this, index, property); + event.target.addEventListener('keydown', handler, { once: true }); + event.target.addEventListener("blur", () => { + event.target.removeEventListener('keydown', handler); + event.target.value = this.objectArray[index][property]; + //this.displayObjects(); + }, { once: true }); + }; + this.convertIntoNumberInput = function (index, property, event) { + event.target.value = event.target.value.slice(0, -1); + event.target.type = "number"; + event.target.addEventListener("blur", () => { + //event.target.value = this.objectArray[index][property]; + this.displayObjects(); + }, { once: true }); + }; + this.updateObject = function (index, property, event) { + if (index >= this.objectArray.length) return; + // Update the corresponding property of the object in the array + const value = property === "value" ? ( + this.objectArray[index].type === "absolute" ? parseFloat(event.target.value) / 100 : parseFloat(event.target.value) + ) : property === "key" ? event.key : event.target.value; + this.objectArray[index][property] = value; + if (property === "key") this.displayObjects(); + }; + this.deleteObject = function (index) { + // Remove the object from the array + this.objectArray.splice(index, 1); + // Display the updated input fields for objects + this.displayObjects(); + }; + return this; +} \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..bd33681 --- /dev/null +++ b/src/main.js @@ -0,0 +1,30 @@ +const fx_version = '0.6.5.6'; // FX Client Version +const fx_update = 'Oct 3'; // FX Client Last Updated + +import settingsManager from './settings.js'; +import { clanFilter, leaderboardFilter } from "./clanFilters.js"; +import WindowManager from "./windowManager.js"; +import donationsTracker from "./donationsTracker.js"; +import winCounter from "./winCounter.js"; +import playerList from "./playerList.js"; +import gameScriptUtils from "./gameScriptUtils.js"; +import hoveringTooltip from "./hoveringTooltip.js"; +import { keybindFunctions, keybindHandler } from "./keybinds.js"; + +window.__fx = window.__fx || {}; +const __fx = window.__fx; +__fx.version = fx_version + " " + fx_update; + +__fx.settingsManager = settingsManager; +__fx.leaderboardFilter = leaderboardFilter; +__fx.utils = gameScriptUtils; +__fx.WindowManager = WindowManager; +__fx.keybindFunctions = keybindFunctions; +__fx.keybindHandler = keybindHandler; +__fx.donationsTracker = donationsTracker; +__fx.playerList = playerList; +__fx.hoveringTooltip = hoveringTooltip; +__fx.clanFilter = clanFilter; +__fx.wins = winCounter; + +console.log('Successfully loaded FX Client'); \ No newline at end of file diff --git a/src/playerList.js b/src/playerList.js new file mode 100644 index 0000000..ed4559b --- /dev/null +++ b/src/playerList.js @@ -0,0 +1,43 @@ +import { getVar } from "./gameInterface.js"; +import { escapeHtml } from "./utils.js"; +import donationsTracker from "./donationsTracker.js"; +import WindowManager from "./windowManager.js"; + +const playerList = new (function () { + const playersIcon = document.createElement('img'); + playersIcon.setAttribute('src', 'assets/players_icon.png'); + document.getElementById("playerlist_content").addEventListener("click", event => { + const playerId = event.target.closest("tr[data-player-id]")?.getAttribute("data-player-id"); + if (!playerId) return; + if (getVar("gIsTeamGame")) WindowManager.closeWindow("playerList"), donationsTracker.displayHistory(playerId); + }); + this.display = function displayPlayerList(playerNames) { + const gHumans = getVar("gHumans"); + const gLobbyMaxJoin = getVar("gLobbyMaxJoin"); + let listContent = `

Players (${gHumans})

`; + for (let i = 0; i < gLobbyMaxJoin; i++) { + if (i === gHumans) listContent += `

Bots (${gLobbyMaxJoin - gHumans})

`; + listContent += `${i + 1}. ${escapeHtml(playerNames[i])}` + } + document.getElementById("playerlist_content").innerHTML = listContent; + document.getElementById("playerlist_content").setAttribute("class", getVar("gIsTeamGame") ? "clickable" : ""); + WindowManager.openWindow("playerList"); + } + this.hoveringOverButton = false; + this.drawButton = (canvas, x, y, size) => { + canvas.fillRect(x, y, size, size); + canvas.fillStyle = this.hoveringOverButton ? "#aaaaaaaa" : "#000000aa"; + canvas.clearRect(x + 1, y + 1, size - 2, size - 2); + canvas.fillRect(x + 1, y + 1, size - 2, size - 2); + canvas.fillStyle = "#ffffff"; + canvas.imageSmoothingEnabled = true; + canvas.drawImage(playersIcon, x + 2, y + 2, size - 4, size - 4); + canvas.imageSmoothingEnabled = false; + } +}); +WindowManager.add({ + name: "playerList", + element: document.getElementById("playerlist") +}); + +export default playerList \ No newline at end of file diff --git a/src/settings.js b/src/settings.js new file mode 100644 index 0000000..a74575b --- /dev/null +++ b/src/settings.js @@ -0,0 +1,209 @@ +import { KeybindsInput } from "./keybindsInput.js"; +import winCounter from "./winCounter.js"; +import WindowManager from "./windowManager.js"; + +window.__fx = window.__fx || {}; +const __fx = window.__fx; + +var settings = { + //"fontName": "Trebuchet MS", + //"showBotDonations": false, + "displayWinCounter": true, + "useFullscreenMode": false, + "hoveringTooltip": true, + //"hideAllLinks": false, + "realisticNames": false, + "showPlayerDensity": true, + "coloredDensity": true, + "densityDisplayStyle": "percentage", + "highlightClanSpawns": false, + //"customMapFileBtn": true + "customBackgroundUrl": "", + "attackPercentageKeybinds": [], +}; +__fx.settings = settings; +const discontinuedSettings = [ "hideAllLinks", "fontName" ]; +__fx.makeMainMenuTransparent = false; + +/*var settingsGearIcon = document.createElement('img'); +settingsGearIcon.setAttribute('src', 'assets/geari_white.png');*/ + +const settingsManager = new (function() { + const settingsStructure = [ + { for: "displayWinCounter", type: "checkbox", label: "Display win counter", + note: "The win counter tracks multiplayer solo wins (not in team games)" }, + { type: "button", text: "Reset win counter", action: winCounter.removeWins }, + { for: "useFullscreenMode", type: "checkbox", label: "Use fullscreen mode", + note: "Note: fullscreen mode will trigger after you click anywhere on the page due to browser policy restrictions." }, + { for: "hoveringTooltip", type: "checkbox", label: "Hovering tooltip", + note: "Display map territory info constantly (on mouse hover) instead of only when right clicking on the map" }, + //{ for: "hideAllLinks", type: "checkbox", label: "Hide Links option also hides app store links" }, + { for: "realisticNames", type: "checkbox", label: "Realistic Bot Names" }, + { for: "showPlayerDensity", type: "checkbox", label: "Show player density" }, + { for: "coloredDensity", type: "checkbox", label: "Colored density", note: "Display the density with a color between red and green depending on the density value" }, + { for: "densityDisplayStyle", type: "selectMenu", label: "Density value display style:", tooltip: "Controls how the territorial density value should be rendered", options: [ + { value: "percentage", label: "Percentage" }, + { value: "absoluteQuotient", label: "Value from 0 to 150 (BetterTT style)" } + ]}, + { for: "highlightClanSpawns", type: "checkbox", label: "Highlight clan spawnpoints", + note: "Increases the spawnpoint glow size for members of your clan" }, + { for: "customBackgroundUrl", type: "textInput", label: "Custom main menu background:", placeholder: "Enter an image URL here", tooltip: "A custom image to be shown as the main menu background instead of the currently selected map." }, + KeybindsInput + ]; + const settingsContainer = document.querySelector(".settings .scrollable"); + var inputFields = {}; // (includes select menus) + var checkboxFields = {}; + var customElements = []; + settingsStructure.forEach(item => { + if (typeof item === "function") { + const container = document.createElement("div"); + customElements.push(new item(container)); + return settingsContainer.append(container); + } + const label = document.createElement("label"); + if (item.tooltip) label.title = item.tooltip; + const isValueInput = item.type.endsWith("Input"); + const element = document.createElement(isValueInput || item.type === "checkbox" ? "input" : item.type === "selectMenu" ? "select" : "button"); + if (item.type === "textInput") element.type = "text"; + if (item.placeholder) element.placeholder = item.placeholder; + if (isValueInput || item.type === "selectMenu") inputFields[item.for] = element; + if (item.text) element.innerText = item.text; + if (item.action) element.addEventListener("click", item.action); + if (item.label) label.append(item.label + " "); + if (item.note) { + const note = document.createElement("small"); + note.innerText = item.note; + label.append(document.createElement("br"), note) + } + if (item.options) item.options.forEach(option => { + const optionElement = document.createElement("option"); + optionElement.setAttribute("value", option.value); + optionElement.innerText = option.label; + element.append(optionElement); + }); + label.append(element); + if (item.type === "checkbox") { + element.type = "checkbox"; + const checkmark = document.createElement("span"); + checkmark.className = "checkmark"; + label.className = "checkbox"; + label.append(checkmark); + checkboxFields[item.for] = element; + } else label.append(document.createElement("br")); + settingsContainer.append(label, document.createElement("br")); + }); + this.save = function() { + Object.keys(inputFields).forEach(function(key) { settings[key] = inputFields[key].value.trim(); }); + Object.keys(checkboxFields).forEach(function(key) { settings[key] = checkboxFields[key].checked; }); + this.applySettings(); + WindowManager.closeWindow("settings"); + discontinuedSettings.forEach(settingName => delete settings[settingName]); + localStorage.setItem("fx_settings", JSON.stringify(settings)); + // should probably firgure out a way to do this without reloading - // You can't do it, localstorages REQUIRE you to reload + window.location.reload(); + }; + + const fileInput = document.createElement("input"); + fileInput.type = "file"; + function handleFileSelect(event) { + const input = event.target; + /** @type {File} */ + const selectedFile = input.files[0]; + if (!selectedFile) return; + + input.removeEventListener("change", handleFileSelect); + input.value = ""; + if (!selectedFile.name.endsWith(".json")) return alert("Invalid file format"); + const fileReader = new FileReader(); + fileReader.onload = function() { + let result; + try { + result = JSON.parse(fileReader.result); + if (confirm("Warning: This will override all current settings, click \"OK\" to confirm")) __fx.settings = settings = result; + localStorage.setItem("fx_settings", JSON.stringify(settings)); + window.location.reload(); + } catch (error) { + alert("Error\n" + error) + } + } + fileReader.readAsText(selectedFile); + } + this.importFromFile = function() { + fileInput.click(); + fileInput.addEventListener('change', handleFileSelect); + }; + // https://stackoverflow.com/a/34156339 + function saveFile(content, fileName, contentType) { + var a = document.createElement("a"); + var file = new Blob([content], {type: contentType}); + a.href = URL.createObjectURL(file); + a.download = fileName; + a.click(); + URL.revokeObjectURL(a.href); + } + this.exportToFile = function() { + saveFile(JSON.stringify(settings), 'FX_client_settings.json', 'application/json'); + }; + + this.syncFields = function() { + Object.keys(inputFields).forEach(function(key) { inputFields[key].value = settings[key]; }); + Object.keys(checkboxFields).forEach(function(key) { checkboxFields[key].checked = settings[key]; }); + customElements.forEach(element => element.update(settings)); + }; + this.resetAll = function() { + if (!confirm("Are you Really SURE you want to RESET ALL SETTINGS back to the default?")) return; + localStorage.removeItem("fx_settings"); + window.location.reload(); + }; + this.applySettings = function() { + //setVarByName("bu", "px " + settings.fontName); + if (settings.useFullscreenMode && document.fullscreenEnabled) { + function tryEnterFullscreen() { + if (document.fullscreenElement !== null) return; + document.documentElement.requestFullscreen({ navigationUI: "hide" }) + .then(() => { console.log('Fullscreen mode activated'); }) + .catch((error) => { console.warn('Could not enter fullscreen mode:', error); }); + } + document.addEventListener('mousedown', tryEnterFullscreen, { once: true }); + document.addEventListener('click', tryEnterFullscreen, { once: true }); + } + if (settings.customBackgroundUrl !== "") { + document.body.style.backgroundImage = "url(" + settings.customBackgroundUrl + ")"; + document.body.style.backgroundSize = "cover"; + document.body.style.backgroundPosition = "center"; + } + __fx.makeMainMenuTransparent = settings.customBackgroundUrl !== ""; + }; +}); + +const openCustomBackgroundFilePicker = () => { + const fileInput = document.getElementById("customBackgroundFileInput"); + fileInput.click(); + fileInput.addEventListener('change', handleFileSelect); +} +function handleFileSelect(event) { + const fileInput = event.target; + const selectedFile = fileInput.files[0]; + console.log(fileInput.files); + console.log(fileInput.files[0]); + if (selectedFile) { + const fileUrl = URL.createObjectURL(selectedFile); + console.log("File URL:", fileUrl); + fileInput.value = ""; + fileInput.removeEventListener("change", handleFileSelect); + } +} + +WindowManager.add({ + name: "settings", + element: document.querySelector(".settings"), + beforeOpen: function() { settingsManager.syncFields(); } +}); + +if (localStorage.getItem("fx_settings") !== null) { + __fx.settings = settings = {...settings, ...JSON.parse(localStorage.getItem("fx_settings"))}; +} +settingsManager.applySettings(); + +export default settingsManager; +export function getSettings() { return settings; }; \ No newline at end of file diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..467db0b --- /dev/null +++ b/src/utils.js @@ -0,0 +1,4 @@ +// https://stackoverflow.com/a/6234804 +export function escapeHtml(unsafe) { + return unsafe.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); +} \ No newline at end of file diff --git a/src/winCounter.js b/src/winCounter.js new file mode 100644 index 0000000..2b71144 --- /dev/null +++ b/src/winCounter.js @@ -0,0 +1,12 @@ +const winCounter = { count: 0, removeWins }; +if (localStorage.getItem("fx_winCount") !== null) winCounter.count = localStorage.getItem("fx_winCount"); + +function removeWins() { + if (confirm('Do you really want to reset your wins?')) { + winCounter.count = 0; + localStorage.removeItem('fx_winCount'); + alert("Successfully reset wins"); + } +} + +export default winCounter \ No newline at end of file diff --git a/src/windowManager.js b/src/windowManager.js new file mode 100644 index 0000000..7f7f17a --- /dev/null +++ b/src/windowManager.js @@ -0,0 +1,27 @@ +var windows = {}; +function add(newWindow) { + windows[newWindow.name] = newWindow; + windows[newWindow.name].isOpen = false; +}; +function openWindow(windowName, ...args) { + if (windows[windowName].isOpen === true) return; + if (windows[windowName].beforeOpen !== undefined) windows[windowName].beforeOpen(...args); + windows[windowName].isOpen = true; + windows[windowName].element.style.display = null; +}; +function closeWindow(windowName) { + if (windows[windowName].isOpen === false) return; + windows[windowName].isOpen = false; + windows[windowName].element.style.display = "none"; + if (windows[windowName].onClose !== undefined) windows[windowName].onClose(); +}; +function closeAll() { + Object.values(windows).forEach(function (windowObj) { + closeWindow(windowObj.name); + }); +}; +document.getElementById("canvasA").addEventListener("mousedown", closeAll); +document.getElementById("canvasA").addEventListener("touchstart", closeAll, { passive: true }); +document.addEventListener("keydown", event => { if (event.key === "Escape") closeAll(); }); + +export default { add, openWindow, closeWindow, closeAll } \ No newline at end of file diff --git a/static/index.html b/static/index.html index 9eedae2..a088cc0 100644 --- a/static/index.html +++ b/static/index.html @@ -64,10 +64,10 @@

- - - - + + + +
- +