From fdabc4325a655a58a73700130a4a369f53cbce74 Mon Sep 17 00:00:00 2001 From: Evan <64989416+Ferotiq@users.noreply.github.com> Date: Sun, 27 Feb 2022 12:04:03 -0600 Subject: [PATCH] TypeScript FrontEnd: Add Test Files (#2540) * add funbox.ts * add layout-emulator.ts * add manual-restart-tracker.ts * add monkey.ts * add out-of-focus.ts * add pace-caret.ts * add pb-crown.ts * add poetry.ts * add practise-words.ts * add replay.ts * add test-timer.ts * add timer-progress.ts * add today-tracker.ts * add tts.ts * add weak-spot.ts and wordset.ts * add wikipedia.ts * fixes * add shift-tracker.ts; please help me * add result.ts * add test-ui.ts * add what I got * Revert "add what I got" This reverts commit 6c6b3d8b4b0b4390d906c35c6dc075f56250ce6e. * fix * merge or something --- frontend/package-lock.json | 93 +++++++++ frontend/package.json | 9 +- frontend/src/scripts/config.ts | 2 +- .../controllers/challenge-controller.ts | 2 +- .../scripts/controllers/input-controller.ts | 4 +- frontend/src/scripts/elements/keymap.ts | 146 ++++++++------- frontend/src/scripts/misc.ts | 48 ++--- frontend/src/scripts/modules.d.ts | 3 +- .../src/scripts/observables/config-event.ts | 2 +- frontend/src/scripts/pages/settings.ts | 4 +- .../popups/mobile-test-config-popup.ts | 2 +- .../src/scripts/settings/settings-group.ts | 2 +- .../src/scripts/test/{funbox.js => funbox.ts} | 64 ++++--- ...{layout-emulator.js => layout-emulator.ts} | 11 +- .../scripts/test/manual-restart-tracker.js | 13 -- .../scripts/test/manual-restart-tracker.ts | 13 ++ frontend/src/scripts/test/monkey.js | 69 ------- frontend/src/scripts/test/monkey.ts | 81 ++++++++ .../test/{out-of-focus.js => out-of-focus.ts} | 6 +- .../test/{pace-caret.js => pace-caret.ts} | 99 ++++++---- .../scripts/test/{pb-crown.js => pb-crown.ts} | 4 +- frontend/src/scripts/test/poetry.js | 61 ------ frontend/src/scripts/test/poetry.ts | 68 +++++++ .../{practise-words.js => practise-words.ts} | 39 ++-- .../src/scripts/test/{replay.js => replay.ts} | 113 +++++++---- .../src/scripts/test/{result.js => result.ts} | 137 +++++++------- .../{shift-tracker.js => shift-tracker.ts} | 95 ++++++---- frontend/src/scripts/test/test-input.ts | 2 +- frontend/src/scripts/test/test-logic.js | 6 +- frontend/src/scripts/test/test-stats.ts | 2 +- .../test/{test-timer.js => test-timer.ts} | 63 ++++--- .../scripts/test/{test-ui.js => test-ui.ts} | 176 ++++++++++-------- frontend/src/scripts/test/test-words.ts | 2 +- .../{timer-progress.js => timer-progress.ts} | 52 +++--- .../{today-tracker.js => today-tracker.ts} | 27 ++- frontend/src/scripts/test/{tts.js => tts.ts} | 23 ++- .../test/{weak-spot.js => weak-spot.ts} | 23 ++- .../test/{wikipedia.js => wikipedia.ts} | 50 +++-- .../scripts/test/{wordset.js => wordset.ts} | 34 ++-- frontend/src/scripts/types/types.d.ts | 24 ++- 40 files changed, 991 insertions(+), 683 deletions(-) rename frontend/src/scripts/test/{funbox.js => funbox.ts} (81%) rename frontend/src/scripts/test/{layout-emulator.js => layout-emulator.ts} (93%) delete mode 100644 frontend/src/scripts/test/manual-restart-tracker.js create mode 100644 frontend/src/scripts/test/manual-restart-tracker.ts delete mode 100644 frontend/src/scripts/test/monkey.js create mode 100644 frontend/src/scripts/test/monkey.ts rename frontend/src/scripts/test/{out-of-focus.js => out-of-focus.ts} (75%) rename frontend/src/scripts/test/{pace-caret.js => pace-caret.ts} (68%) rename frontend/src/scripts/test/{pb-crown.js => pb-crown.ts} (80%) delete mode 100644 frontend/src/scripts/test/poetry.js create mode 100644 frontend/src/scripts/test/poetry.ts rename frontend/src/scripts/test/{practise-words.js => practise-words.ts} (82%) rename frontend/src/scripts/test/{replay.js => replay.ts} (72%) rename frontend/src/scripts/test/{result.js => result.ts} (88%) rename frontend/src/scripts/test/{shift-tracker.js => shift-tracker.ts} (61%) rename frontend/src/scripts/test/{test-timer.js => test-timer.ts} (81%) rename frontend/src/scripts/test/{test-ui.js => test-ui.ts} (85%) rename frontend/src/scripts/test/{timer-progress.js => timer-progress.ts} (76%) rename frontend/src/scripts/test/{today-tracker.js => today-tracker.ts} (56%) rename frontend/src/scripts/test/{tts.js => tts.ts} (53%) rename frontend/src/scripts/test/{weak-spot.js => weak-spot.ts} (71%) rename frontend/src/scripts/test/{wikipedia.js => wikipedia.ts} (63%) rename frontend/src/scripts/test/{wordset.js => wordset.ts} (72%) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 69e49fbcc..c8c608c6f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -44,6 +44,7 @@ "gulp-eslint-new": "^1.3.0", "gulp-sass": "^5.0.0", "gulp-typescript": "^6.0.0-alpha.1", + "html2canvas": "^1.4.1", "madge": "5.0.1", "moment": "^2.29.1", "stream-browserify": "^3.0.0", @@ -3138,6 +3139,15 @@ "node": ">=0.10.0" } }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -4029,6 +4039,15 @@ "node": "*" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dev": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -6874,6 +6893,19 @@ "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz", "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg==" }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dev": true, + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-parser-js": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", @@ -10902,6 +10934,15 @@ "node": ">= 8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dev": true, + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11516,6 +11557,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dev": true, + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -14433,6 +14483,12 @@ } } }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "dev": true + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -15160,6 +15216,15 @@ "randomfill": "^1.0.3" } }, + "css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dev": true, + "requires": { + "utrie": "^1.0.2" + } + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -17441,6 +17506,16 @@ "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.3.tgz", "integrity": "sha512-QM0FFkw0LRX1PR8pNzJVAY25JhIWvbKMBFM4gqk+QdV+kPXOhleWGCB6AiAF/goGjIHK2e/nIElplvjQwhr0jg==" }, + "html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dev": true, + "requires": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + } + }, "http-parser-js": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.5.tgz", @@ -20516,6 +20591,15 @@ } } }, + "text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dev": true, + "requires": { + "utrie": "^1.0.2" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -21002,6 +21086,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dev": true, + "requires": { + "base64-arraybuffer": "^1.0.2" + } + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 84f0eb968..d98a69891 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,15 +16,15 @@ "npm": "8.1.2" }, "devDependencies": { - "@types/howler": "^2.2.5", - "@types/tinycolor2": "^1.4.3", - "@types/jquery": "^3.5.13", - "@types/select2": "^4.0.55", "@babel/core": "^7.17.2", "@babel/plugin-transform-modules-commonjs": "^7.16.8", "@babel/plugin-transform-runtime": "^7.17.0", "@babel/preset-env": "^7.16.11", "@types/grecaptcha": "^3.0.3", + "@types/howler": "^2.2.5", + "@types/jquery": "^3.5.13", + "@types/select2": "^4.0.55", + "@types/tinycolor2": "^1.4.3", "babel-loader": "^8.2.3", "buffer": "^6.0.3", "circular-dependency-plugin": "5.2.2", @@ -37,6 +37,7 @@ "gulp-eslint-new": "^1.3.0", "gulp-sass": "^5.0.0", "gulp-typescript": "^6.0.0-alpha.1", + "html2canvas": "^1.4.1", "madge": "5.0.1", "moment": "^2.29.1", "stream-browserify": "^3.0.0", diff --git a/frontend/src/scripts/config.ts b/frontend/src/scripts/config.ts index 61c874967..dbb097146 100644 --- a/frontend/src/scripts/config.ts +++ b/frontend/src/scripts/config.ts @@ -1002,7 +1002,7 @@ export function setTimeConfig( //quote length export function setQuoteLength( - len: MonkeyTypes.QuoteLengthArray | MonkeyTypes.QuoteLength, + len: MonkeyTypes.QuoteLength[] | MonkeyTypes.QuoteLength, nosave?: boolean, multipleMode?: boolean ): boolean { diff --git a/frontend/src/scripts/controllers/challenge-controller.ts b/frontend/src/scripts/controllers/challenge-controller.ts index 755b1414c..ca45fc174 100644 --- a/frontend/src/scripts/controllers/challenge-controller.ts +++ b/frontend/src/scripts/controllers/challenge-controller.ts @@ -215,7 +215,7 @@ export async function setup(challengeName: string): Promise { UpdateConfig.setTheme(challenge.parameters[1] as string); } if (challenge.parameters[2] != null) { - Funbox.activate(challenge.parameters[2]); + Funbox.activate(challenge.parameters[2]); } } else if (challenge.type === "accuracy") { UpdateConfig.setTimeConfig(0, true); diff --git a/frontend/src/scripts/controllers/input-controller.ts b/frontend/src/scripts/controllers/input-controller.ts index 0c76e692a..803a785b0 100644 --- a/frontend/src/scripts/controllers/input-controller.ts +++ b/frontend/src/scripts/controllers/input-controller.ts @@ -20,7 +20,7 @@ import * as PaceCaret from "../test/pace-caret"; import * as TimerProgress from "../test/timer-progress"; import * as Focus from "../test/focus"; import * as ShiftTracker from "../test/shift-tracker"; -import * as Replay from "../test/replay.js"; +import * as Replay from "../test/replay"; import * as MonkeyPower from "../elements/monkey-power"; import * as WeakSpot from "../test/weak-spot"; import * as Leaderboards from "../elements/leaderboards"; @@ -481,7 +481,7 @@ function handleChar(char: string, charIndex: number): void { TestWords.words.currentIndex === 0 ) { TestUI.setActiveWordTop( - document.querySelector("#words .active")?.offsetTop + (document.querySelector("#words .active"))?.offsetTop ); } diff --git a/frontend/src/scripts/elements/keymap.ts b/frontend/src/scripts/elements/keymap.ts index 3556e2ae4..baeb8d2ff 100644 --- a/frontend/src/scripts/elements/keymap.ts +++ b/frontend/src/scripts/elements/keymap.ts @@ -119,96 +119,98 @@ export async function refresh( let keymapElement = ""; - Object.keys(lts.keys).forEach((row, index) => { - const rowKeys = lts.keys[row]; - let rowElement = ""; - if (row === "row1" && !showTopRow) { - return; - } - - if ((row === "row2" || row === "row3" || row === "row4") && !isMatrix) { - rowElement += "
"; - } - - if (row === "row4" && lts.type !== "iso" && !isMatrix) { - rowElement += "
"; - } - - if (row === "row5") { - let layoutDisplay = layoutString.replace(/_/g, " "); - if (Config.keymapLegendStyle === "blank") { - layoutDisplay = ""; + (Object.keys(lts.keys) as (keyof MonkeyTypes.Keys)[]).forEach( + (row, index) => { + const rowKeys = lts.keys[row]; + let rowElement = ""; + if (row === "row1" && !showTopRow) { + return; } - rowElement += "
"; - rowElement += `
+ + if ((row === "row2" || row === "row3" || row === "row4") && !isMatrix) { + rowElement += "
"; + } + + if (row === "row4" && lts.type !== "iso" && !isMatrix) { + rowElement += "
"; + } + + if (row === "row5") { + let layoutDisplay = layoutString.replace(/_/g, " "); + if (Config.keymapLegendStyle === "blank") { + layoutDisplay = ""; + } + rowElement += "
"; + rowElement += `
${layoutDisplay}
`; - rowElement += `
`; - rowElement += `
+ rowElement += `
`; + rowElement += `
`; - } else { - for (let i = 0; i < rowKeys.length; i++) { - if (row === "row2" && i === 12) continue; - if ( - (Config.keymapStyle === "matrix" || - Config.keymapStyle === "split_matrix") && - i >= 10 - ) - continue; - const key = rowKeys[i]; - const bump = row === "row3" && (i === 3 || i === 6) ? true : false; - let keyDisplay = key[0]; - if (Config.keymapLegendStyle === "blank") { - keyDisplay = ""; - } else if (Config.keymapLegendStyle === "uppercase") { - keyDisplay = keyDisplay.toUpperCase(); - } - const keyElement = `
+ } else { + for (let i = 0; i < rowKeys.length; i++) { + if (row === "row2" && i === 12) continue; + if ( + (Config.keymapStyle === "matrix" || + Config.keymapStyle === "split_matrix") && + i >= 10 + ) + continue; + const key = rowKeys[i]; + const bump = row === "row3" && (i === 3 || i === 6) ? true : false; + let keyDisplay = key[0]; + if (Config.keymapLegendStyle === "blank") { + keyDisplay = ""; + } else if (Config.keymapLegendStyle === "uppercase") { + keyDisplay = keyDisplay.toUpperCase(); + } + const keyElement = `
${keyDisplay} ${bump ? "
" : ""}
`; - let splitSpacer = ""; - if ( - Config.keymapStyle === "split" || - Config.keymapStyle === "split_matrix" || - Config.keymapStyle === "alice" - ) { + let splitSpacer = ""; if ( - row === "row4" && - (Config.keymapStyle === "split" || - Config.keymapStyle === "alice") && - lts.type === "iso" + Config.keymapStyle === "split" || + Config.keymapStyle === "split_matrix" || + Config.keymapStyle === "alice" ) { - if (i === 6) { - splitSpacer += `
`; - } - } else { - if (i === 5) { - splitSpacer += `
`; + if ( + row === "row4" && + (Config.keymapStyle === "split" || + Config.keymapStyle === "alice") && + lts.type === "iso" + ) { + if (i === 6) { + splitSpacer += `
`; + } + } else { + if (i === 5) { + splitSpacer += `
`; + } } } - } - if (Config.keymapStyle === "alice" && row === "row4") { - if ( - (lts.type === "iso" && i === 6) || - (lts.type !== "iso" && i === 5) - ) { - splitSpacer += `
`; + if (Config.keymapStyle === "alice" && row === "row4") { + if ( + (lts.type === "iso" && i === 6) || + (lts.type !== "iso" && i === 5) + ) { + splitSpacer += `
`; + } } - } - rowElement += splitSpacer + keyElement; + rowElement += splitSpacer + keyElement; + } } - } - keymapElement += `
${rowElement}
`; - }); + keymapElement += `
${rowElement}
`; + } + ); $("#keymap").html(keymapElement); diff --git a/frontend/src/scripts/misc.ts b/frontend/src/scripts/misc.ts index 21f07ff83..a66bf7a0a 100644 --- a/frontend/src/scripts/misc.ts +++ b/frontend/src/scripts/misc.ts @@ -102,13 +102,14 @@ export async function getSortedThemesList(): Promise { } } -type Funbox = { name: string; type: string; info: string }; - -let funboxList: Funbox[] = []; -export async function getFunboxList(): Promise { +let funboxList: MonkeyTypes.FunboxObject[] = []; +export async function getFunboxList(): Promise { if (funboxList.length === 0) { return $.getJSON("funbox/_list.json", function (data) { - funboxList = data.sort(function (a: Funbox, b: Funbox) { + funboxList = data.sort(function ( + a: MonkeyTypes.FunboxObject, + b: MonkeyTypes.FunboxObject + ) { const nameA = a.name.toLowerCase(); const nameB = b.name.toLowerCase(); if (nameA < nameB) return -1; @@ -122,8 +123,10 @@ export async function getFunboxList(): Promise { } } -export async function getFunbox(funbox: string): Promise { - const list: Funbox[] = await getFunboxList(); +export async function getFunbox( + funbox: string +): Promise { + const list: MonkeyTypes.FunboxObject[] = await getFunboxList(); return list.find(function (element) { return element.name == funbox; }); @@ -274,15 +277,17 @@ export async function getLanguageGroups(): Promise< } } -type Language = { +interface LanguageObject { name: string; leftToRight: boolean; noLazyMode?: boolean; + ligatures?: boolean; words: string[]; -}; + bcp47?: string; +} -let currentLanguage: Language; -export async function getLanguage(lang: string): Promise { +let currentLanguage: LanguageObject; +export async function getLanguage(lang: string): Promise { try { if (currentLanguage == undefined || currentLanguage.name !== lang) { console.log("getting language json"); @@ -303,7 +308,7 @@ export async function getLanguage(lang: string): Promise { export async function getCurrentLanguage( languageName: string -): Promise { +): Promise { return await getLanguage(languageName); } @@ -810,9 +815,10 @@ export function canQuickRestart( } } -export function clearTimeouts(timeouts: number[]): void { +export function clearTimeouts(timeouts: (number | NodeJS.Timeout)[]): void { timeouts.forEach((to) => { - clearTimeout(to); + if (typeof to === "number") clearTimeout(to); + else clearTimeout(to); }); } @@ -936,19 +942,19 @@ export function getMode2( randomQuote: MonkeyTypes.Quote ): string { const mode = config.mode; - let mode2 = ""; if (mode === "time") { - mode2 = config.time.toString(); + return config.time.toString(); } else if (mode === "words") { - mode2 = config.words.toString(); + return config.words.toString(); } else if (mode === "custom") { - mode2 = "custom"; + return "custom"; } else if (mode === "zen") { - mode2 = "zen"; + return "zen"; } else if (mode === "quote") { - mode2 = randomQuote.id.toString(); + return randomQuote.id.toString(); } - return mode2; + + return ""; } export async function downloadResultsCSV( diff --git a/frontend/src/scripts/modules.d.ts b/frontend/src/scripts/modules.d.ts index 693c6aba7..7b9928f1e 100644 --- a/frontend/src/scripts/modules.d.ts +++ b/frontend/src/scripts/modules.d.ts @@ -1,3 +1,4 @@ -declare let firebase: any; //typeof import("firebase").default; +declare let firebase: any; // typeof import("firebase").default; declare let moment: typeof import("moment"); declare let grecaptcha: ReCaptchaV2.ReCaptcha; +declare let html2canvas: typeof import("html2canvas").default; diff --git a/frontend/src/scripts/observables/config-event.ts b/frontend/src/scripts/observables/config-event.ts index 8b3d0131f..859b06b55 100644 --- a/frontend/src/scripts/observables/config-event.ts +++ b/frontend/src/scripts/observables/config-event.ts @@ -3,7 +3,7 @@ type ConfigValues = | number | boolean | string[] - | MonkeyTypes.QuoteLengthArray + | MonkeyTypes.QuoteLength[] | MonkeyTypes.ResultFilters | MonkeyTypes.CustomBackgroundFilter | null diff --git a/frontend/src/scripts/pages/settings.ts b/frontend/src/scripts/pages/settings.ts index 2c58d80d7..e4fe7e831 100644 --- a/frontend/src/scripts/pages/settings.ts +++ b/frontend/src/scripts/pages/settings.ts @@ -877,8 +877,8 @@ $(document).on("click", ".pageSettings .section.minBurst .button.save", () => { //funbox $(document).on("click", ".pageSettings .section.funbox .button", (e) => { - const funbox = $(e.currentTarget).attr("funbox"); - const type = $(e.currentTarget).attr("type"); + const funbox = $(e.currentTarget).attr("funbox"); + const type = $(e.currentTarget).attr("type"); Funbox.setFunbox(funbox, type); setActiveFunboxButton(); }); diff --git a/frontend/src/scripts/popups/mobile-test-config-popup.ts b/frontend/src/scripts/popups/mobile-test-config-popup.ts index f958e8acc..e8a320ce4 100644 --- a/frontend/src/scripts/popups/mobile-test-config-popup.ts +++ b/frontend/src/scripts/popups/mobile-test-config-popup.ts @@ -133,7 +133,7 @@ el.find(".quoteGroup .button").on("click", (e) => { len = [0, 1, 2, 3]; } UpdateConfig.setQuoteLength( - len as MonkeyTypes.QuoteLength | MonkeyTypes.QuoteLengthArray, + len as MonkeyTypes.QuoteLength | MonkeyTypes.QuoteLength[], false, e.shiftKey ); diff --git a/frontend/src/scripts/settings/settings-group.ts b/frontend/src/scripts/settings/settings-group.ts index 04858daf6..536a7af4c 100644 --- a/frontend/src/scripts/settings/settings-group.ts +++ b/frontend/src/scripts/settings/settings-group.ts @@ -5,7 +5,7 @@ type ConfigValues = | number | boolean | string[] - | MonkeyTypes.QuoteLengthArray + | MonkeyTypes.QuoteLength[] | MonkeyTypes.ResultFilters | MonkeyTypes.CustomBackgroundFilter | null diff --git a/frontend/src/scripts/test/funbox.js b/frontend/src/scripts/test/funbox.ts similarity index 81% rename from frontend/src/scripts/test/funbox.js rename to frontend/src/scripts/test/funbox.ts index 8d6242648..124ee03fb 100644 --- a/frontend/src/scripts/test/funbox.js +++ b/frontend/src/scripts/test/funbox.ts @@ -6,29 +6,36 @@ import Config, * as UpdateConfig from "../config"; import * as TTS from "./tts"; import * as ModesNotice from "../elements/modes-notice"; -let modeSaved = null; -let memoryTimer = null; -let memoryInterval = null; +let modeSaved: MonkeyTypes.FunboxObjectType | null = null; +let memoryTimer: number | null = null; +let memoryInterval: NodeJS.Timeout | null = null; -let settingsMemory = {}; +type SetFunction = (...params: any[]) => any; -function rememberSetting(settingName, value, setFunction) { +let settingsMemory: { + [key: string]: { value: any; setFunction: SetFunction }; +} = {}; + +function rememberSetting( + settingName: string, + value: any, + setFunction: SetFunction +): void { settingsMemory[settingName] ??= { value, setFunction, }; } -function loadMemory() { +function loadMemory(): void { Notifications.add("Reverting funbox settings", 0); Object.keys(settingsMemory).forEach((setting) => { - setting = settingsMemory[setting]; - setting.setFunction(setting.value, true); + settingsMemory[setting].setFunction(settingsMemory[setting].value, true); }); settingsMemory = {}; } -function showMemoryTimer() { +function showMemoryTimer(): void { $("#typingTest #memoryTimer").stop(true, true).animate( { opacity: 1, @@ -37,7 +44,7 @@ function showMemoryTimer() { ); } -function hideMemoryTimer() { +function hideMemoryTimer(): void { $("#typingTest #memoryTimer").stop(true, true).animate( { opacity: 0, @@ -46,24 +53,28 @@ function hideMemoryTimer() { ); } -export function resetMemoryTimer() { - memoryInterval = clearInterval(memoryInterval); +export function resetMemoryTimer(): void { + if (memoryInterval !== null) { + clearInterval(memoryInterval); + memoryInterval = null; + } memoryTimer = null; hideMemoryTimer(); } -function updateMemoryTimer(sec) { +function updateMemoryTimer(sec: number): void { $("#typingTest #memoryTimer").text( `Timer left to memorise all words: ${sec}s` ); } -export function startMemoryTimer() { +export function startMemoryTimer(): void { resetMemoryTimer(); memoryTimer = Math.round(Math.pow(TestWords.words.length, 1.2)); updateMemoryTimer(memoryTimer); showMemoryTimer(); memoryInterval = setInterval(() => { + if (memoryTimer === null) return; memoryTimer -= 1; memoryTimer == 0 ? hideMemoryTimer() : updateMemoryTimer(memoryTimer); if (memoryTimer <= 0) { @@ -73,24 +84,27 @@ export function startMemoryTimer() { }, 1000); } -export function reset() { +export function reset(): void { resetMemoryTimer(); } -export function toggleScript(...params) { +export function toggleScript(...params: any[]): void { if (Config.funbox === "tts") { TTS.speak(params[0]); } } -export function setFunbox(funbox, mode) { +export function setFunbox( + funbox: string, + mode: MonkeyTypes.FunboxObjectType | null +): boolean { modeSaved = mode; UpdateConfig.setFunbox(funbox, false); if (funbox === "none") loadMemory(); return true; } -export async function clear() { +export async function clear(): Promise { $("#funBoxTheme").attr("href", ``); $("#words").removeClass("nospace"); $("#words").removeClass("arrows"); @@ -101,19 +115,19 @@ export async function clear() { return true; } -export async function activate(funbox) { +export async function activate(funbox: string): Promise { let mode = modeSaved; if (funbox === undefined || funbox === null) { funbox = Config.funbox; } - let funboxInfo = await Misc.getFunbox(funbox); + const funboxInfo = await Misc.getFunbox(funbox); $("#funBoxTheme").attr("href", ``); $("#words").removeClass("nospace"); $("#words").removeClass("arrows"); - if (await Misc.getCurrentLanguage(Config.language).ligatures) { + if ((await Misc.getCurrentLanguage(Config.language)).ligatures) { if (funbox == "choo_choo" || funbox == "earthquake") { Notifications.add( "Current language does not support this funbox mode", @@ -149,7 +163,7 @@ export async function activate(funbox) { (funbox !== "none" && mode === undefined) || (funbox !== "none" && mode === null) ) { - let list = await Misc.getFunboxList(); + const list = await Misc.getFunboxList(); mode = list.filter((f) => f.name === funbox)[0].type; } @@ -205,8 +219,8 @@ export async function activate(funbox) { return true; } -export async function rememberSettings() { - let funbox = Config.funbox; +export async function rememberSettings(): Promise { + const funbox = Config.funbox; let mode = modeSaved; if (funbox === "none" && mode === undefined) { mode = null; @@ -214,7 +228,7 @@ export async function rememberSettings() { (funbox !== "none" && mode === undefined) || (funbox !== "none" && mode === null) ) { - let list = await Misc.getFunboxList(); + const list = await Misc.getFunboxList(); mode = list.filter((f) => f.name === funbox)[0].type; } if (mode === "style") { diff --git a/frontend/src/scripts/test/layout-emulator.js b/frontend/src/scripts/test/layout-emulator.ts similarity index 93% rename from frontend/src/scripts/test/layout-emulator.js rename to frontend/src/scripts/test/layout-emulator.ts index 35950c672..a3e51f5d6 100644 --- a/frontend/src/scripts/test/layout-emulator.js +++ b/frontend/src/scripts/test/layout-emulator.ts @@ -2,15 +2,20 @@ import Config from "../config"; import * as Misc from "../misc"; import { capsState } from "./caps-warning"; -export async function getCharFromEvent(event) { - function emulatedLayoutShouldShiftKey(event, newKeyPreview) { +export async function getCharFromEvent( + event: JQuery.KeyDownEvent +): Promise { + function emulatedLayoutShouldShiftKey( + event: JQuery.KeyDownEvent, + newKeyPreview: string + ): boolean { if (capsState) return Misc.isASCIILetter(newKeyPreview) !== event.shiftKey; return event.shiftKey; } const layout = await Misc.getLayout(Config.layout); - let keyEventCodes = []; + let keyEventCodes: string[] = []; if (layout.type === "ansi") { keyEventCodes = [ diff --git a/frontend/src/scripts/test/manual-restart-tracker.js b/frontend/src/scripts/test/manual-restart-tracker.js deleted file mode 100644 index 037c43e3a..000000000 --- a/frontend/src/scripts/test/manual-restart-tracker.js +++ /dev/null @@ -1,13 +0,0 @@ -let state = false; - -export function set() { - state = true; -} - -export function reset() { - state = false; -} - -export function get() { - return state; -} diff --git a/frontend/src/scripts/test/manual-restart-tracker.ts b/frontend/src/scripts/test/manual-restart-tracker.ts new file mode 100644 index 000000000..16a95d9c8 --- /dev/null +++ b/frontend/src/scripts/test/manual-restart-tracker.ts @@ -0,0 +1,13 @@ +let state = false; + +export function set(): void { + state = true; +} + +export function reset(): void { + state = false; +} + +export function get(): boolean { + return state; +} diff --git a/frontend/src/scripts/test/monkey.js b/frontend/src/scripts/test/monkey.js deleted file mode 100644 index 85742d7f8..000000000 --- a/frontend/src/scripts/test/monkey.js +++ /dev/null @@ -1,69 +0,0 @@ -import { mapRange } from "../misc"; -import Config from "../config"; - -let left = false; -let right = false; -let elements = { - "00": document.querySelector("#monkey .up"), - 10: document.querySelector("#monkey .left"), - "01": document.querySelector("#monkey .right"), - 11: document.querySelector("#monkey .both"), -}; -let elementsFast = { - "00": document.querySelector("#monkey .fast .up"), - 10: document.querySelector("#monkey .fast .left"), - "01": document.querySelector("#monkey .fast .right"), - 11: document.querySelector("#monkey .fast .both"), -}; -let last = "right"; -// 0 up -// 1 down - -function update() { - if (!Config.monkey) return; - if (!document.querySelector("#monkey").classList.contains("hidden")) { - Object.keys(elements).forEach((key) => { - elements[key].classList.add("hidden"); - }); - Object.keys(elementsFast).forEach((key) => { - elementsFast[key].classList.add("hidden"); - }); - - let id = left ? "1" : "0"; - id += right ? "1" : "0"; - - elements[id].classList.remove("hidden"); - elementsFast[id].classList.remove("hidden"); - } -} - -export function updateFastOpacity(num) { - if (!Config.monkey) return; - let opacity = mapRange(num, 100, 200, 0, 1); - $("#monkey .fast").animate({ opacity: opacity }, 1000); - let animDuration = mapRange(num, 100, 200, 0.5, 0.01); - if (animDuration == 0.5) animDuration = 0; - $("#monkey").css({ animationDuration: animDuration + "s" }); -} - -export function type() { - if (!Config.monkey) return; - if (!left && last == "right") { - left = true; - last = "left"; - } else if (!right) { - right = true; - last = "right"; - } - update(); -} - -export function stop() { - if (!Config.monkey) return; - if (left) { - left = false; - } else if (right) { - right = false; - } - update(); -} diff --git a/frontend/src/scripts/test/monkey.ts b/frontend/src/scripts/test/monkey.ts new file mode 100644 index 000000000..d0d4e4efe --- /dev/null +++ b/frontend/src/scripts/test/monkey.ts @@ -0,0 +1,81 @@ +import { mapRange } from "../misc"; +import Config from "../config"; + +let left = false; +let right = false; + +// 0 hand up +// 1 hand down + +// 00 both hands up +// 01 right hand down +// 10 left hand down +// 11 both hands down + +const elements = { + "00": document.querySelector("#monkey .up"), + "01": document.querySelector("#monkey .right"), + "10": document.querySelector("#monkey .left"), + "11": document.querySelector("#monkey .both"), +}; + +const elementsFast = { + "00": document.querySelector("#monkey .fast .up"), + "01": document.querySelector("#monkey .fast .right"), + "10": document.querySelector("#monkey .fast .left"), + "11": document.querySelector("#monkey .fast .both"), +}; + +let last = "right"; + +function toBit(b: boolean): "1" | "0" { + return b ? "1" : "0"; +} + +function update(): void { + if (!Config.monkey) return; + if (!document.querySelector("#monkey")?.classList.contains("hidden")) { + (Object.keys(elements) as (keyof typeof elements)[]).forEach((key) => { + elements[key]?.classList.add("hidden"); + }); + (Object.keys(elementsFast) as (keyof typeof elements)[]).forEach((key) => { + elementsFast[key]?.classList.add("hidden"); + }); + + const id: keyof typeof elements = `${toBit(left)}${toBit(right)}`; + + elements[id]?.classList.remove("hidden"); + elementsFast[id]?.classList.remove("hidden"); + } +} + +export function updateFastOpacity(num: number): void { + if (!Config.monkey) return; + const opacity = mapRange(num, 100, 200, 0, 1); + $("#monkey .fast").animate({ opacity: opacity }, 1000); + let animDuration = mapRange(num, 100, 200, 0.5, 0.01); + if (animDuration == 0.5) animDuration = 0; + $("#monkey").css({ animationDuration: animDuration + "s" }); +} + +export function type(): void { + if (!Config.monkey) return; + if (!left && last == "right") { + left = true; + last = "left"; + } else if (!right) { + right = true; + last = "right"; + } + update(); +} + +export function stop(): void { + if (!Config.monkey) return; + if (left) { + left = false; + } else if (right) { + right = false; + } + update(); +} diff --git a/frontend/src/scripts/test/out-of-focus.js b/frontend/src/scripts/test/out-of-focus.ts similarity index 75% rename from frontend/src/scripts/test/out-of-focus.js rename to frontend/src/scripts/test/out-of-focus.ts index 14bb629b9..732dcb476 100644 --- a/frontend/src/scripts/test/out-of-focus.js +++ b/frontend/src/scripts/test/out-of-focus.ts @@ -1,14 +1,14 @@ import * as Misc from "../misc"; -let outOfFocusTimeouts = []; +const outOfFocusTimeouts: (number | NodeJS.Timeout)[] = []; -export function hide() { +export function hide(): void { $("#words").css("transition", "none").removeClass("blurred"); $(".outOfFocusWarning").addClass("hidden"); Misc.clearTimeouts(outOfFocusTimeouts); } -export function show() { +export function show(): void { outOfFocusTimeouts.push( setTimeout(() => { $("#words").css("transition", "0.25s").addClass("blurred"); diff --git a/frontend/src/scripts/test/pace-caret.js b/frontend/src/scripts/test/pace-caret.ts similarity index 68% rename from frontend/src/scripts/test/pace-caret.js rename to frontend/src/scripts/test/pace-caret.ts index fa28f7084..907a7eeb3 100644 --- a/frontend/src/scripts/test/pace-caret.js +++ b/frontend/src/scripts/test/pace-caret.ts @@ -9,25 +9,38 @@ import * as TestActive from "../states/test-active"; import * as TestState from "./test-state"; import * as ConfigEvent from "../observables/config-event"; -export let settings = null; +interface Settings { + wpm: number; + cps: number; + spc: number; + correction: number; + currentWordIndex: number; + currentLetterIndex: number; + wordsStatus: { [key: number]: any }; + timeout: NodeJS.Timeout | null; +} -function resetCaretPosition() { +export let settings: Settings | null = null; + +function resetCaretPosition(): void { if (Config.paceCaret === "off" && !TestState.isPaceRepeat) return; if (!$("#paceCaret").hasClass("hidden")) { $("#paceCaret").addClass("hidden"); } if (Config.mode === "zen") return; - let caret = $("#paceCaret"); - let firstLetter = document - ?.querySelector("#words .word") - ?.querySelector("letter"); + const caret = $("#paceCaret"); + const firstLetter = ( + document?.querySelector("#words .word")?.querySelector("letter") + ); - if (!firstLetter) return; + const firstLetterHeight = $(firstLetter).height(); + + if (firstLetter === undefined || firstLetterHeight === undefined) return; caret.stop(true, true).animate( { - top: firstLetter.offsetTop - $(firstLetter).height() / 4, + top: firstLetter.offsetTop - firstLetterHeight / 4, left: firstLetter.offsetLeft, }, 0, @@ -35,9 +48,12 @@ function resetCaretPosition() { ); } -export async function init() { +export async function init(): Promise { $("#paceCaret").addClass("hidden"); - let mode2 = Misc.getMode2(Config, TestWords.randomQuote); + const mode2 = Misc.getMode2( + Config, + TestWords.randomQuote + ) as MonkeyTypes.Mode2; let wpm; if (Config.paceCaret === "pb") { wpm = await DB.getLocalPB( @@ -50,7 +66,6 @@ export async function init() { Config.funbox ); } else if (Config.paceCaret === "average") { - let mode2 = Misc.getMode2(Config, TestWords.randomQuote); wpm = await DB.getUserAverageWpm10( Config.mode, mode2, @@ -65,14 +80,14 @@ export async function init() { } else if (TestState.isPaceRepeat == true) { wpm = TestStats.lastTestWpm; } - if (wpm < 1 || wpm == false || wpm == undefined || Number.isNaN(wpm)) { + if (wpm === undefined || wpm < 1 || Number.isNaN(wpm)) { settings = null; return; } - let characters = wpm * 5; - let cps = characters / 60; //characters per step - let spc = 60 / characters; //seconds per character + const characters = wpm * 5; + const cps = characters / 60; //characters per step + const spc = 60 / characters; //seconds per character settings = { wpm: wpm, @@ -87,7 +102,7 @@ export async function init() { resetCaretPosition(); } -export function update(expectedStepEnd) { +export function update(expectedStepEnd: number): void { if (settings === null || !TestActive.get() || TestUI.resultVisible) { return; } @@ -139,36 +154,49 @@ export function update(expectedStepEnd) { } try { - let caret = $("#paceCaret"); + const caret = $("#paceCaret"); let currentLetter; let newTop; let newLeft; try { - let newIndex = + const newIndex = settings.currentWordIndex - (TestWords.words.currentIndex - TestUI.currentWordElementIndex); - let word = document.querySelectorAll("#words .word")[newIndex]; + const word = document.querySelectorAll("#words .word")[newIndex]; if (settings.currentLetterIndex === -1) { - currentLetter = word.querySelectorAll("letter")[0]; + currentLetter = word.querySelectorAll("letter")[0]; } else { - currentLetter = - word.querySelectorAll("letter")[settings.currentLetterIndex]; + currentLetter = ( + word.querySelectorAll("letter")[settings.currentLetterIndex] + ); } - newTop = currentLetter.offsetTop - $(currentLetter).height() / 5; + + const currentLetterHeight = $(currentLetter).height(), + currentLetterWidth = $(currentLetter).width(), + caretWidth = caret.width(); + + if ( + currentLetterHeight === undefined || + currentLetterWidth === undefined || + caretWidth === undefined + ) + throw ``; + + newTop = currentLetter.offsetTop - currentLetterHeight / 5; newLeft; if (settings.currentLetterIndex === -1) { newLeft = currentLetter.offsetLeft; } else { newLeft = - currentLetter.offsetLeft + - $(currentLetter).width() - - caret.width() / 2; + currentLetter.offsetLeft + currentLetterWidth - caretWidth / 2; } caret.removeClass("hidden"); } catch (e) { caret.addClass("hidden"); } + if (newTop === undefined) return; + let smoothlinescroll = $("#words .smoothScroller").height(); if (smoothlinescroll === undefined) smoothlinescroll = 0; @@ -176,7 +204,7 @@ export function update(expectedStepEnd) { top: newTop - smoothlinescroll, }); - let duration = expectedStepEnd - performance.now(); + const duration = expectedStepEnd - performance.now(); if (Config.smoothCaret) { caret.stop(true, true).animate( @@ -197,7 +225,7 @@ export function update(expectedStepEnd) { } settings.timeout = setTimeout(() => { try { - update(expectedStepEnd + settings.spc * 1000); + update(expectedStepEnd + (settings?.spc ?? 0) * 1000); } catch (e) { settings = null; } @@ -208,12 +236,13 @@ export function update(expectedStepEnd) { } } -export function reset() { +export function reset(): void { + if (settings !== null && settings.timeout !== null) + clearTimeout(settings.timeout); settings = null; - if (settings !== null) clearTimeout(settings.timeout); } -export function handleSpace(correct, currentWord) { +export function handleSpace(correct: boolean, currentWord: string): void { if (correct) { if ( settings !== null && @@ -235,10 +264,10 @@ export function handleSpace(correct, currentWord) { } } -export function start() { - update(performance.now() + settings.spc * 1000); +export function start(): void { + update(performance.now() + (settings?.spc ?? 0) * 1000); } -ConfigEvent.subscribe((eventKey, eventValue, nosave) => { - if (eventKey === "paceCaret") init(nosave); +ConfigEvent.subscribe((eventKey) => { + if (eventKey === "paceCaret") init(); }); diff --git a/frontend/src/scripts/test/pb-crown.js b/frontend/src/scripts/test/pb-crown.ts similarity index 80% rename from frontend/src/scripts/test/pb-crown.js rename to frontend/src/scripts/test/pb-crown.ts index c47d88ba7..dc7433e4c 100644 --- a/frontend/src/scripts/test/pb-crown.js +++ b/frontend/src/scripts/test/pb-crown.ts @@ -1,8 +1,8 @@ -export function hide() { +export function hide(): void { $("#result .stats .wpm .crown").css("opacity", 0).addClass("hidden"); } -export function show() { +export function show(): void { $("#result .stats .wpm .crown") .removeClass("hidden") .css("opacity", "0") diff --git a/frontend/src/scripts/test/poetry.js b/frontend/src/scripts/test/poetry.js deleted file mode 100644 index 802a0f0c8..000000000 --- a/frontend/src/scripts/test/poetry.js +++ /dev/null @@ -1,61 +0,0 @@ -const bannedChars = ["—", "_", " "]; -const maxWords = 100; -const apiURL = "https://poetrydb.org/random"; - -export class Poem { - constructor(title, author, words) { - this.title = title; - this.author = author; - this.words = words; - - this.cleanUpText(); - } - - cleanUpText() { - let count = 0; - let scrubbedWords = []; - for (let i = 0; i < this.words.length; i++) { - let scrubbed = ""; - for (let j = 0; j < this.words[i].length; j++) { - if (!bannedChars.includes(this.words[i][j])) - scrubbed += this.words[i][j]; - } - - if (scrubbed == "") continue; - - scrubbedWords.push(scrubbed); - count++; - - if (count == maxWords) break; - } - - this.words = scrubbedWords; - } -} - -export async function getPoem() { - return new Promise((res, rej) => { - console.log("Getting poem"); - let poemReq = new XMLHttpRequest(); - poemReq.onload = () => { - if (poemReq.readyState == 4) { - if (poemReq.status == 200) { - let poemObj = JSON.parse(poemReq.responseText)[0]; - let words = []; - poemObj.lines.forEach((line) => { - line.split(" ").forEach((word) => { - words.push(word); - }); - }); - - let poem = new Poem(poemObj.title, poemObj.author, words); - res(poem); - } else { - rej(poemReq.status); - } - } - }; - poemReq.open("GET", apiURL); - poemReq.send(); - }); -} diff --git a/frontend/src/scripts/test/poetry.ts b/frontend/src/scripts/test/poetry.ts new file mode 100644 index 000000000..6aa1ba1b5 --- /dev/null +++ b/frontend/src/scripts/test/poetry.ts @@ -0,0 +1,68 @@ +import axios from "axios"; + +const bannedChars = ["—", "_", " "]; +const maxWords = 100; +const apiURL = "https://poetrydb.org/random"; + +export class Poem { + public title: string; + public author: string; + public words: string[]; + constructor(title: string, author: string, words: string[]) { + this.title = title; + this.author = author; + this.words = words; + + this.cleanUpText(); + } + + cleanUpText(): void { + let count = 0; + const scrubbedWords = []; + for (let i = 0; i < this.words.length; i++) { + let scrubbed = ""; + for (let j = 0; j < this.words[i].length; j++) { + if (!bannedChars.includes(this.words[i][j])) + scrubbed += this.words[i][j]; + } + + if (scrubbed == "") continue; + + scrubbedWords.push(scrubbed); + count++; + + if (count == maxWords) break; + } + + this.words = scrubbedWords; + } +} + +interface PoemObject { + lines: string[]; + title: string; + author: string; +} + +export async function getPoem(): Promise { + console.log("Getting poem"); + + const response = await axios.get(apiURL); + + try { + const poemObj: PoemObject = response.data[0]; + + const words: string[] = []; + + poemObj.lines.forEach((line) => { + line.split(/ +/).forEach((word) => { + words.push(word); + }); + }); + + return new Poem(poemObj.title, poemObj.author, words); + } catch (e) { + console.log(e); + return response.status; + } +} diff --git a/frontend/src/scripts/test/practise-words.js b/frontend/src/scripts/test/practise-words.ts similarity index 82% rename from frontend/src/scripts/test/practise-words.js rename to frontend/src/scripts/test/practise-words.ts index 39ddbc99d..122f22e94 100644 --- a/frontend/src/scripts/test/practise-words.js +++ b/frontend/src/scripts/test/practise-words.ts @@ -5,22 +5,30 @@ import * as CustomText from "./custom-text"; import * as TestInput from "./test-input"; import * as ConfigEvent from "../observables/config-event"; -export let before = { +interface Before { + mode: MonkeyTypes.Mode | null; + punctuation: boolean | null; + numbers: boolean | null; +} + +export const before: Before = { mode: null, punctuation: null, numbers: null, }; -export function init(missed, slow) { +export function init(missed: boolean, slow: boolean): void { if (Config.mode === "zen") return; let limit; if ((missed && !slow) || (!missed && slow)) { limit = 20; } else if (missed && slow) { limit = 10; + } else { + limit = 10; } - let sortableMissedWords = []; + let sortableMissedWords: [string, number][] = []; if (missed) { Object.keys(TestInput.missedWords).forEach((missedWord) => { sortableMissedWords.push([missedWord, TestInput.missedWords[missedWord]]); @@ -36,11 +44,12 @@ export function init(missed, slow) { return; } - let sortableSlowWords = []; + let sortableSlowWords: [string, number][] = []; if (slow) { - sortableSlowWords = TestWords.words.get().map(function (e, i) { - return [e, TestInput.burstHistory[i]]; - }); + sortableSlowWords = (TestWords.words.get() as string[]).map((e, i) => [ + e, + TestInput.burstHistory[i], + ]); sortableSlowWords.sort((a, b) => { return a[1] - b[1]; }); @@ -58,7 +67,7 @@ export function init(missed, slow) { return; } - let newCustomText = []; + const newCustomText: string[] = []; sortableMissedWords.forEach((missed) => { for (let i = 0; i < missed[1]; i++) { newCustomText.push(missed[0]); @@ -73,10 +82,10 @@ export function init(missed, slow) { // console.log(newCustomText); - let mode = before.mode === null ? Config.mode : before.mode; - let punctuation = + const mode = before.mode === null ? Config.mode : before.mode; + const punctuation = before.punctuation === null ? Config.punctuation : before.punctuation; - let numbers = before.numbers === null ? Config.numbers : before.numbers; + const numbers = before.numbers === null ? Config.numbers : before.numbers; UpdateConfig.setMode("custom"); CustomText.setText(newCustomText); @@ -90,13 +99,13 @@ export function init(missed, slow) { before.numbers = numbers; } -export function resetBefore() { +export function resetBefore(): void { before.mode = null; before.punctuation = null; before.numbers = null; } -export function showPopup(focus = false) { +export function showPopup(focus = false): void { if ($("#practiseWordsPopupWrapper").hasClass("hidden")) { if (Config.mode === "zen") { Notifications.add("Practice words is unsupported in zen mode", 0); @@ -115,7 +124,7 @@ export function showPopup(focus = false) { } } -export function hidePopup() { +export function hidePopup(): void { if (!$("#practiseWordsPopupWrapper").hasClass("hidden")) { $("#practiseWordsPopupWrapper") .stop(true, true) @@ -125,7 +134,7 @@ export function hidePopup() { opacity: 0, }, 100, - (e) => { + () => { $("#practiseWordsPopupWrapper").addClass("hidden"); } ); diff --git a/frontend/src/scripts/test/replay.js b/frontend/src/scripts/test/replay.ts similarity index 72% rename from frontend/src/scripts/test/replay.js rename to frontend/src/scripts/test/replay.ts index e4e0503a6..6ea5e6abc 100644 --- a/frontend/src/scripts/test/replay.js +++ b/frontend/src/scripts/test/replay.ts @@ -1,25 +1,42 @@ import config from "../config"; import * as Sound from "../controllers/sound-controller"; -let wordsList = []; -let replayData = []; +type ReplayAction = + | "correctLetter" + | "incorrectLetter" + | "backWord" + | "submitCorrectWord" + | "submitErrorWord" + | "setLetterIndex"; + +interface Replay { + action: ReplayAction; + value?: string | number; + time: number; +} + +let wordsList: string[] = []; +let replayData: Replay[] = []; let replayStartTime = 0; let replayRecording = true; let wordPos = 0; let curPos = 0; let targetWordPos = 0; let targetCurPos = 0; -let timeoutList = []; -let stopwatchList = []; +let timeoutList: NodeJS.Timeout[] = []; +let stopwatchList: NodeJS.Timeout[] = []; const toggleButton = document.getElementById("playpauseReplayButton") - .children[0]; + ?.children[0]; -function replayGetWordsList(wordsListFromScript) { +function replayGetWordsList(wordsListFromScript: string[]): void { wordsList = wordsListFromScript; } -function initializeReplayPrompt() { +function initializeReplayPrompt(): void { const replayWordsElement = document.getElementById("replayWords"); + + if (replayWordsElement === null) return; + replayWordsElement.innerHTML = ""; let wordCount = 0; replayData.forEach((item) => { @@ -35,10 +52,10 @@ function initializeReplayPrompt() { }); wordsList.forEach((item, i) => { if (i > wordCount) return; - let x = document.createElement("div"); + const x = document.createElement("div"); x.className = "word"; for (i = 0; i < item.length; i++) { - let letter = document.createElement("LETTER"); + const letter = document.createElement("letter"); letter.innerHTML = item[i]; x.appendChild(letter); } @@ -46,7 +63,7 @@ function initializeReplayPrompt() { }); } -export function pauseReplay() { +export function pauseReplay(): void { timeoutList.forEach((item) => { clearTimeout(item); }); @@ -57,11 +74,17 @@ export function pauseReplay() { stopwatchList = []; targetCurPos = curPos; targetWordPos = wordPos; + + if (toggleButton === undefined) return; + toggleButton.className = "fas fa-play"; - toggleButton.parentNode.setAttribute("aria-label", "Resume replay"); + (toggleButton.parentNode as Element)?.setAttribute( + "aria-label", + "Resume replay" + ); } -function playSound(error = false) { +function playSound(error = false): void { if (error) { if (config.playSoundOnError) { Sound.playError(); @@ -73,8 +96,11 @@ function playSound(error = false) { } } -function handleDisplayLogic(item, nosound = false) { - let activeWord = document.getElementById("replayWords").children[wordPos]; +function handleDisplayLogic(item: Replay, nosound = false): void { + let activeWord = document.getElementById("replayWords")?.children[wordPos]; + + if (activeWord === undefined) return; + if (item.action === "correctLetter") { if (!nosound) playSound(); activeWord.children[curPos].classList.add("correct"); @@ -86,13 +112,16 @@ function handleDisplayLogic(item, nosound = false) { //if letter is an extra myElement = document.createElement("letter"); myElement.classList.add("extra"); - myElement.innerHTML = item.value; + myElement.innerHTML = item.value?.toString() ?? ""; activeWord.appendChild(myElement); } myElement = activeWord.children[curPos]; myElement.classList.add("incorrect"); curPos++; - } else if (item.action === "setLetterIndex") { + } else if ( + item.action === "setLetterIndex" && + typeof item.value === "number" + ) { if (!nosound) playSound(); curPos = item.value; // remove all letters from cursor to end of word @@ -115,14 +144,18 @@ function handleDisplayLogic(item, nosound = false) { } else if (item.action === "backWord") { if (!nosound) playSound(); wordPos--; - activeWord = document.getElementById("replayWords").children[wordPos]; + + const replayWords = document.getElementById("replayWords"); + + if (replayWords !== null) activeWord = replayWords.children[wordPos]; + curPos = activeWord.children.length; while (activeWord.children[curPos - 1].className === "") curPos--; activeWord.classList.remove("error"); } } -function loadOldReplay() { +function loadOldReplay(): number { let startingIndex = 0; curPos = 0; wordPos = 0; @@ -141,7 +174,7 @@ function loadOldReplay() { return startingIndex; } -function toggleReplayDisplay() { +function toggleReplayDisplay(): void { if ($("#resultReplay").stop(true, true).hasClass("hidden")) { initializeReplayPrompt(); loadOldReplay(); @@ -162,7 +195,10 @@ function toggleReplayDisplay() { } } else { //hide - if (toggleButton.parentNode.getAttribute("aria-label") != "Start replay") { + if ( + (toggleButton?.parentNode as Element)?.getAttribute("aria-label") != + "Start replay" + ) { pauseReplay(); } $("#resultReplay").slideUp(250, () => { @@ -171,7 +207,7 @@ function toggleReplayDisplay() { } } -function startReplayRecording() { +function startReplayRecording(): void { if (!$("#resultReplay").stop(true, true).hasClass("hidden")) { //hide replay display if user left it open toggleReplayDisplay(); @@ -184,27 +220,33 @@ function startReplayRecording() { targetWordPos = 0; } -function stopReplayRecording() { +function stopReplayRecording(): void { replayRecording = false; } -function addReplayEvent(action, value) { +function addReplayEvent(action: ReplayAction, value?: number | string): void { if (!replayRecording) { return; } - let timeDelta = performance.now() - replayStartTime; + const timeDelta = performance.now() - replayStartTime; replayData.push({ action: action, value: value, time: timeDelta }); } -function playReplay() { +function playReplay(): void { curPos = 0; wordPos = 0; + + if (toggleButton === undefined) return; + toggleButton.className = "fas fa-pause"; - toggleButton.parentNode.setAttribute("aria-label", "Pause replay"); + (toggleButton.parentNode as Element)?.setAttribute( + "aria-label", + "Pause replay" + ); initializeReplayPrompt(); - let startingIndex = loadOldReplay(); - let lastTime = replayData[startingIndex].time; + const startingIndex = loadOldReplay(); + const lastTime = replayData[startingIndex].time; let swTime = Math.round(lastTime / 1000); //starting time const swEndTime = Math.round(replayData[replayData.length - 1].time / 1000); while (swTime <= swEndTime) { @@ -230,22 +272,25 @@ function playReplay() { targetCurPos = 0; targetWordPos = 0; toggleButton.className = "fas fa-play"; - toggleButton.parentNode.setAttribute("aria-label", "Start replay"); + (toggleButton.parentNode as Element).setAttribute( + "aria-label", + "Start replay" + ); }, replayData[replayData.length - 1].time - lastTime) ); } -function getReplayExport() { +function getReplayExport(): string { return JSON.stringify({ replayData: replayData, wordsList: wordsList, }); } -$(".pageTest #playpauseReplayButton").click(async (event) => { - if (toggleButton.className === "fas fa-play") { +$(".pageTest #playpauseReplayButton").click(() => { + if (toggleButton?.className === "fas fa-play") { playReplay(); - } else if (toggleButton.className === "fas fa-pause") { + } else if (toggleButton?.className === "fas fa-pause") { pauseReplay(); } }); @@ -255,7 +300,7 @@ $("#replayWords").on("click", "letter", (event) => { pauseReplay(); const replayWords = document.querySelector("#replayWords"); - const words = [...replayWords.children]; + const words = [...(replayWords?.children ?? [])]; targetWordPos = words.indexOf(event.target.parentNode); const letters = [...words[targetWordPos].children]; targetCurPos = letters.indexOf(event.target); diff --git a/frontend/src/scripts/test/result.js b/frontend/src/scripts/test/result.ts similarity index 88% rename from frontend/src/scripts/test/result.js rename to frontend/src/scripts/test/result.ts index f6dbeb63a..837bb0c6a 100644 --- a/frontend/src/scripts/test/result.js +++ b/frontend/src/scripts/test/result.ts @@ -13,19 +13,19 @@ import * as GlarsesMode from "../states/glarses-mode"; import * as TestInput from "./test-input"; import * as Notifications from "../elements/notifications"; -let result; -let maxChartVal; +let result: MonkeyTypes.Result; +let maxChartVal: number; let useUnsmoothedRaw = false; -export function toggleUnsmoothedRaw() { +export function toggleUnsmoothedRaw(): void { useUnsmoothedRaw = !useUnsmoothedRaw; Notifications.add(useUnsmoothedRaw ? "on" : "off", 1); } -async function updateGraph() { +async function updateGraph(): Promise { ChartController.result.options.annotation.annotations = []; - let labels = []; + const labels = []; for (let i = 1; i <= TestInput.wpmHistory.length; i++) { if (TestStats.lastSecondNotRound && i === TestInput.wpmHistory.length) { labels.push(Misc.roundTo2(result.testDuration).toString()); @@ -37,16 +37,19 @@ async function updateGraph() { ChartController.result.data.labels = labels; ChartController.result.options.scales.yAxes[0].scaleLabel.labelString = Config.alwaysShowCPM ? "Character per Minute" : "Words per Minute"; - let chartData1 = Config.alwaysShowCPM + const chartData1 = Config.alwaysShowCPM ? TestInput.wpmHistory.map((a) => a * 5) : TestInput.wpmHistory; - let chartData2; + let chartData2: number[]; + + if (result.chartData === "toolong") return; if (useUnsmoothedRaw) { - chartData2 = Config.alwaysShowCPM - ? result.chartData.unsmoothedRaw.map((a) => a * 5) - : result.chartData.unsmoothedRaw; + chartData2 = + (Config.alwaysShowCPM + ? result.chartData.unsmoothedRaw?.map((a) => a * 5) + : result.chartData.unsmoothedRaw) ?? []; } else { chartData2 = Config.alwaysShowCPM ? result.chartData.raw.map((a) => a * 5) @@ -62,7 +65,7 @@ async function updateGraph() { maxChartVal = Math.max(...[Math.max(...chartData2), Math.max(...chartData1)]); if (!Config.startGraphsAtZero) { - let minChartVal = Math.min( + const minChartVal = Math.min( ...[Math.min(...chartData2), Math.min(...chartData1)] ); ChartController.result.options.scales.yAxes[0].ticks.min = minChartVal; @@ -74,7 +77,7 @@ async function updateGraph() { ChartController.result.data.datasets[2].data = result.chartData.err; - let fc = await ThemeColors.get("sub"); + const fc = await ThemeColors.get("sub"); if (Config.funbox !== "none") { let content = Config.funbox; if (Config.funbox === "layoutfluid") { @@ -113,19 +116,21 @@ async function updateGraph() { ChartController.result.resize(); } -export async function updateGraphPBLine() { - let themecolors = await ThemeColors.getAll(); - let lpb = await DB.getLocalPB( +export async function updateGraphPBLine(): Promise { + const themecolors = await ThemeColors.getAll(); + const lpb = await DB.getLocalPB( result.mode, result.mode2, - result.punctuation, + result.punctuation ?? false, result.language, result.difficulty, - result.lazyMode, - result.funbox + result.lazyMode ?? false, + result.funbox ?? "none" ); if (lpb == 0) return; - let chartlpb = Misc.roundTo2(Config.alwaysShowCPM ? lpb * 5 : lpb).toFixed(2); + const chartlpb = Misc.roundTo2(Config.alwaysShowCPM ? lpb * 5 : lpb).toFixed( + 2 + ); ChartController.result.options.annotation.annotations.push({ enabled: false, type: "line", @@ -162,7 +167,7 @@ export async function updateGraphPBLine() { ChartController.result.update(); } -function updateWpmAndAcc() { +function updateWpmAndAcc(): void { let inf = false; if (result.wpm >= 1000) { inf = true; @@ -247,7 +252,7 @@ function updateWpmAndAcc() { } } -function updateConsistency() { +function updateConsistency(): void { if (Config.alwaysShowDecimalPlaces) { $("#result .stats .consistency .bottom").text( Misc.roundTo2(result.consistency).toFixed(2) + "%" @@ -267,8 +272,8 @@ function updateConsistency() { } } -function updateTime() { - let afkSecondsPercent = Misc.roundTo2( +function updateTime(): void { + const afkSecondsPercent = Misc.roundTo2( (result.afkDuration / result.testDuration) * 100 ); $("#result .stats .time .bottom .afk").text(""); @@ -300,11 +305,11 @@ function updateTime() { } } -export function updateTodayTracker() { +export function updateTodayTracker(): void { $("#result .stats .time .bottom .timeToday").text(TodayTracker.getString()); } -function updateKey() { +function updateKey(): void { $("#result .stats .key .bottom").text( result.charStats[0] + "/" + @@ -316,16 +321,16 @@ function updateKey() { ); } -export function showCrown() { +export function showCrown(): void { PbCrown.show(); } -export function hideCrown() { +export function hideCrown(): void { PbCrown.hide(); $("#result .stats .wpm .crown").attr("aria-label", ""); } -export async function updateCrown() { +export async function updateCrown(): Promise { let pbDiff = 0; const lpb = await DB.getLocalPB( Config.mode, @@ -343,10 +348,10 @@ export async function updateCrown() { ); } -function updateTags(dontSave) { - let activeTags = []; +function updateTags(dontSave: boolean): void { + const activeTags: MonkeyTypes.Tag[] = []; try { - DB.getSnapshot().tags.forEach((tag) => { + DB.getSnapshot().tags?.forEach((tag) => { if (tag.active === true) { activeTags.push(tag); } @@ -363,7 +368,7 @@ function updateTags(dontSave) { let annotationSide = "left"; let labelAdjust = 15; activeTags.forEach(async (tag) => { - let tpb = await DB.getLocalTagPB( + const tpb = await DB.getLocalTagPB( tag._id, Config.mode, result.mode2, @@ -400,7 +405,7 @@ function updateTags(dontSave) { ); // console.log("new pb for tag " + tag.name); } else { - let themecolors = await ThemeColors.getAll(); + const themecolors = await ThemeColors.getAll(); ChartController.result.options.annotation.annotations.push({ enabled: false, type: "line", @@ -439,28 +444,18 @@ function updateTags(dontSave) { }); } -function updateTestType() { +function updateTestType(randomQuote: MonkeyTypes.Quote): void { let testType = ""; - if (Config.mode === "quote") { - let qlen = ""; - if (Config.quoteLength === 0) { - qlen = "short "; - } else if (Config.quoteLength === 1) { - qlen = "medium "; - } else if (Config.quoteLength === 2) { - qlen = "long "; - } else if (Config.quoteLength === 3) { - qlen = "thicc "; - } - testType += qlen + Config.mode; - } else { - testType += Config.mode; - } - if (Config.mode == "time") { + testType += Config.mode; + + if (Config.mode === "time") { testType += " " + Config.time; - } else if (Config.mode == "words") { + } else if (Config.mode === "words") { testType += " " + Config.words; + } else if (Config.mode === "quote") { + if (randomQuote.group !== undefined) + testType += " " + ["short", "medium", "long", "thicc"][randomQuote.group]; } if ( Config.mode != "custom" && @@ -495,12 +490,12 @@ function updateTestType() { } function updateOther( - difficultyFailed, - failReason, - afkDetected, - isRepeated, - tooShort -) { + difficultyFailed: boolean, + failReason: string, + afkDetected: boolean, + isRepeated: boolean, + tooShort: boolean +): void { let otherText = ""; if (difficultyFailed) { otherText += `
failed (${failReason})`; @@ -543,9 +538,9 @@ function updateOther( } } -export function updateRateQuote(randomQuote) { +export function updateRateQuote(randomQuote: MonkeyTypes.Quote): void { if (Config.mode === "quote") { - let userqr = + const userqr = DB.getSnapshot().quoteRatings?.[randomQuote.language]?.[randomQuote.id]; if (userqr) { $(".pageTest #result #rateQuoteButton .icon") @@ -554,7 +549,7 @@ export function updateRateQuote(randomQuote) { } QuoteRatePopup.getQuoteStats(randomQuote).then((quoteStats) => { $(".pageTest #result #rateQuoteButton .rating").text( - quoteStats.average?.toFixed(1) ?? "" + quoteStats?.average?.toFixed(1) ?? "" ); $(".pageTest #result #rateQuoteButton") .css({ opacity: 0 }) @@ -564,7 +559,7 @@ export function updateRateQuote(randomQuote) { } } -function updateQuoteSource(randomQuote) { +function updateQuoteSource(randomQuote: MonkeyTypes.Quote): void { if (Config.mode === "quote") { $("#result .stats .source").removeClass("hidden"); $("#result .stats .source .bottom").html(randomQuote.source); @@ -574,15 +569,15 @@ function updateQuoteSource(randomQuote) { } export function update( - res, - difficultyFailed, - failReason, - afkDetected, - isRepeated, - tooShort, - randomQuote, - dontSave -) { + res: MonkeyTypes.Result, + difficultyFailed: boolean, + failReason: string, + afkDetected: boolean, + isRepeated: boolean, + tooShort: boolean, + randomQuote: MonkeyTypes.Quote, + dontSave: boolean +): void { result = res; $("#result #resultWordsHistory").addClass("hidden"); $("#retrySavingResultButton").addClass("hidden"); @@ -604,7 +599,7 @@ export function update( updateConsistency(); updateTime(); updateKey(); - updateTestType(); + updateTestType(randomQuote); updateQuoteSource(randomQuote); updateGraph(); updateGraphPBLine(); diff --git a/frontend/src/scripts/test/shift-tracker.js b/frontend/src/scripts/test/shift-tracker.ts similarity index 61% rename from frontend/src/scripts/test/shift-tracker.js rename to frontend/src/scripts/test/shift-tracker.ts index c8cb498d5..f145e8ca6 100644 --- a/frontend/src/scripts/test/shift-tracker.js +++ b/frontend/src/scripts/test/shift-tracker.ts @@ -6,16 +6,24 @@ export let leftState = false; export let rightState = false; let caseState = false; -let keymapStrings = { +interface KeymapStrings { + left: string[] | null; + right: string[] | null; + keymap: string | null; +} + +const keymapStrings: KeymapStrings = { left: null, right: null, keymap: null, }; -function dynamicKeymapLegendStyle(uppercase) { - const keymapKeys = [...document.getElementsByClassName("keymap-key")]; +function dynamicKeymapLegendStyle(uppercase: boolean): void { + const keymapKeys = [ + ...document.getElementsByClassName("keymap-key"), + ]; - const layoutKeys = keymapKeys.map((el) => el.dataset.key); + const layoutKeys = keymapKeys.map((el) => el.dataset["key"]); const keys = keymapKeys.map((el) => el.childNodes[1]); @@ -39,37 +47,52 @@ function dynamicKeymapLegendStyle(uppercase) { } } -async function buildKeymapStrings() { +async function buildKeymapStrings(): Promise { if (keymapStrings.keymap === Config.keymapLayout) return; - let layout = await Misc.getLayout(Config.keymapLayout).keys; + const layout = await Misc.getLayout(Config.keymapLayout); + + if (layout === undefined) return; + + const layoutKeys = layout.keys; + const layoutKeysEntries = Object.entries(layoutKeys) as [string, string[]][]; + + keymapStrings.keymap = Config.keymapLayout; if (!layout) { - keymapStrings = { - left: null, - right: null, - keymap: Config.keymapLayout, - }; + keymapStrings.left = null; + keymapStrings.right = null; } else { - keymapStrings.left = ( - layout.slice(0, 7).join(" ") + - " " + - layout.slice(13, 19).join(" ") + - " " + - layout.slice(26, 31).join(" ") + - " " + - layout.slice(38, 43).join(" ") - ).replace(/ /g, ""); - keymapStrings.right = ( - layout.slice(6, 13).join(" ") + - " " + - layout.slice(18, 26).join(" ") + - " " + - layout.slice(31, 38).join(" ") + - " " + - layout.slice(42, 48).join(" ") - ).replace(/ /g, ""); - keymapStrings.keymap = Config.keymapLayout; + keymapStrings.left = layoutKeysEntries + .map(([rowName, row]) => + row + // includes "6" and "y" (buttons on qwerty) into the left hand + .slice( + 0, + ["row1", "row2"].includes(rowName) + ? rowName === "row1" + ? 7 + : 6 + : 5 + ) + .map((key) => key.split("")) + ) + .flat(2); + + keymapStrings.right = layoutKeysEntries + .map(([rowName, row]) => + row + // includes "b" (buttons on qwerty) into the right hand + .slice( + ["row1", "row4"].includes(rowName) + ? rowName === "row1" + ? 6 + : 4 + : 5 + ) + .map((key) => key.split("")) + ) + .flat(2); } } @@ -98,12 +121,12 @@ $(document).keyup((e) => { } }); -export function reset() { +export function reset(): void { leftState = false; rightState = false; } -let leftSideKeys = [ +const leftSideKeys = [ "KeyQ", "KeyW", "KeyE", @@ -129,7 +152,7 @@ let leftSideKeys = [ "Digit5", ]; -let rightSideKeys = [ +const rightSideKeys = [ "KeyU", "KeyI", "KeyO", @@ -158,7 +181,9 @@ let rightSideKeys = [ "Slash", ]; -export async function isUsingOppositeShift(event) { +export async function isUsingOppositeShift( + event: JQuery.KeyDownEvent +): Promise { if (!leftState && !rightState) return null; if (Config.oppositeShiftMode === "on") { @@ -190,4 +215,6 @@ export async function isUsingOppositeShift(event) { return false; } } + + return true; } diff --git a/frontend/src/scripts/test/test-input.ts b/frontend/src/scripts/test/test-input.ts index 4767b9f34..1b06ed285 100644 --- a/frontend/src/scripts/test/test-input.ts +++ b/frontend/src/scripts/test/test-input.ts @@ -71,7 +71,7 @@ class Input { return ret; } - getHistory(i: number): string | string[] { + getHistory(i?: number): string | string[] { if (i === undefined) { return this.history; } else { diff --git a/frontend/src/scripts/test/test-logic.js b/frontend/src/scripts/test/test-logic.js index 3dafe1122..e596ea728 100644 --- a/frontend/src/scripts/test/test-logic.js +++ b/frontend/src/scripts/test/test-logic.js @@ -25,9 +25,9 @@ import * as TestTimer from "./test-timer"; import * as OutOfFocus from "./out-of-focus"; import * as AccountButton from "../elements/account-button"; import * as DB from "../db"; -import * as Replay from "./replay.js"; -import * as Poetry from "./poetry.js"; -import * as Wikipedia from "./wikipedia.js"; +import * as Replay from "./replay"; +import * as Poetry from "./poetry"; +import * as Wikipedia from "./wikipedia"; import * as TodayTracker from "./today-tracker"; import * as WeakSpot from "./weak-spot"; import * as Wordset from "./wordset"; diff --git a/frontend/src/scripts/test/test-stats.ts b/frontend/src/scripts/test/test-stats.ts index d0a494d1c..73fded71c 100644 --- a/frontend/src/scripts/test/test-stats.ts +++ b/frontend/src/scripts/test/test-stats.ts @@ -173,7 +173,7 @@ export function calculateTestSeconds(now?: number): number { } } -export function calculateWpmAndRaw(): { wpm: number; raw: number } { +export function calculateWpmAndRaw(): MonkeyTypes.WordsPerMinuteAndRaw { let chars = 0; let correctWordChars = 0; let spaces = 0; diff --git a/frontend/src/scripts/test/test-timer.js b/frontend/src/scripts/test/test-timer.ts similarity index 81% rename from frontend/src/scripts/test/test-timer.js rename to frontend/src/scripts/test/test-timer.ts index 2533a0eeb..0b4c9f09e 100644 --- a/frontend/src/scripts/test/test-timer.js +++ b/frontend/src/scripts/test/test-timer.ts @@ -18,28 +18,30 @@ import * as Time from "../states/time"; import * as TimerEvent from "../observables/timer-event"; let slowTimerCount = 0; -let timer = null; +let timer: NodeJS.Timeout | null = null; const interval = 1000; let expected = 0; let timerDebug = false; -export function enableTimerDebug() { +export function enableTimerDebug(): void { timerDebug = true; } -export function clear() { +export function clear(): void { Time.set(0); - clearTimeout(timer); + if (timer !== null) clearTimeout(timer); } -function premid() { +function premid(): void { if (timerDebug) console.time("premid"); - document.querySelector("#premidSecondsLeft").innerHTML = - Config.time - Time.get(); + const premidSecondsLeft = document.querySelector("#premidSecondsLeft"); + + if (premidSecondsLeft !== null) + premidSecondsLeft.innerHTML = (Config.time - Time.get()).toString(); if (timerDebug) console.timeEnd("premid"); } -function updateTimer() { +function updateTimer(): void { if (timerDebug) console.time("timer progress update"); if ( Config.mode === "time" || @@ -50,9 +52,9 @@ function updateTimer() { if (timerDebug) console.timeEnd("timer progress update"); } -function calculateWpmRaw() { +function calculateWpmRaw(): MonkeyTypes.WordsPerMinuteAndRaw { if (timerDebug) console.time("calculate wpm and raw"); - let wpmAndRaw = TestStats.calculateWpmAndRaw(); + const wpmAndRaw = TestStats.calculateWpmAndRaw(); if (timerDebug) console.timeEnd("calculate wpm and raw"); if (timerDebug) console.time("update live wpm"); LiveWpm.update(wpmAndRaw.wpm, wpmAndRaw.raw); @@ -64,20 +66,20 @@ function calculateWpmRaw() { return wpmAndRaw; } -function monkey(wpmAndRaw) { +function monkey(wpmAndRaw: MonkeyTypes.WordsPerMinuteAndRaw): void { if (timerDebug) console.time("update monkey"); Monkey.updateFastOpacity(wpmAndRaw.wpm); if (timerDebug) console.timeEnd("update monkey"); } -function calculateAcc() { +function calculateAcc(): number { if (timerDebug) console.time("calculate acc"); - let acc = Misc.roundTo2(TestStats.calculateAccuracy()); + const acc = Misc.roundTo2(TestStats.calculateAccuracy()); if (timerDebug) console.timeEnd("calculate acc"); return acc; } -function layoutfluid() { +function layoutfluid(): void { if (timerDebug) console.time("layoutfluid"); if (Config.funbox === "layoutfluid" && Config.mode === "time") { const layouts = Config.customLayoutfluid @@ -117,15 +119,18 @@ function layoutfluid() { if (timerDebug) console.timeEnd("layoutfluid"); } -function checkIfFailed(wpmAndRaw, acc) { +function checkIfFailed( + wpmAndRaw: MonkeyTypes.WordsPerMinuteAndRaw, + acc: number +): void { if (timerDebug) console.time("fail conditions"); TestInput.pushKeypressesToHistory(); if ( Config.minWpm === "custom" && - wpmAndRaw.wpm < parseInt(Config.minWpmCustomSpeed) && + wpmAndRaw.wpm < Config.minWpmCustomSpeed && TestWords.words.currentIndex > 3 ) { - clearTimeout(timer); + if (timer !== null) clearTimeout(timer); SlowTimer.clear(); slowTimerCount = 0; TimerEvent.dispatch("fail", "min wpm"); @@ -133,10 +138,10 @@ function checkIfFailed(wpmAndRaw, acc) { } if ( Config.minAcc === "custom" && - acc < parseInt(Config.minAccCustom) && + acc < Config.minAccCustom && TestWords.words.currentIndex > 3 ) { - clearTimeout(timer); + if (timer !== null) clearTimeout(timer); SlowTimer.clear(); slowTimerCount = 0; TimerEvent.dispatch("fail", "min accuracy"); @@ -145,7 +150,7 @@ function checkIfFailed(wpmAndRaw, acc) { if (timerDebug) console.timeEnd("fail conditions"); } -function checkIfTimeIsUp() { +function checkIfTimeIsUp(): void { if (timerDebug) console.time("times up check"); if ( Config.mode == "time" || @@ -160,7 +165,7 @@ function checkIfTimeIsUp() { Config.mode === "custom") ) { //times up - clearTimeout(timer); + if (timer !== null) clearTimeout(timer); Caret.hide(); TestInput.input.pushHistory(); TestInput.corrected.pushHistory(); @@ -175,19 +180,19 @@ function checkIfTimeIsUp() { // --------------------------------------- -let timerStats = []; +let timerStats: MonkeyTypes.TimerStats[] = []; -export function getTimerStats() { +export function getTimerStats(): MonkeyTypes.TimerStats[] { return timerStats; } -async function timerStep() { +async function timerStep(): Promise { if (timerDebug) console.time("timer step -----------------------------"); Time.increment(); premid(); updateTimer(); - let wpmAndRaw = calculateWpmRaw(); - let acc = calculateAcc(); + const wpmAndRaw = calculateWpmRaw(); + const acc = calculateAcc(); monkey(wpmAndRaw); layoutfluid(); checkIfFailed(wpmAndRaw, acc); @@ -195,12 +200,12 @@ async function timerStep() { if (timerDebug) console.timeEnd("timer step -----------------------------"); } -export async function start() { +export async function start(): Promise { SlowTimer.clear(); slowTimerCount = 0; timerStats = []; expected = TestStats.start + interval; - (function loop() { + (function loop(): void { const delay = expected - performance.now(); timerStats.push({ dateNow: Date.now(), @@ -238,7 +243,7 @@ export async function start() { // time++; if (!TestActive.get()) { - clearTimeout(timer); + if (timer !== null) clearTimeout(timer); SlowTimer.clear(); slowTimerCount = 0; return; diff --git a/frontend/src/scripts/test/test-ui.js b/frontend/src/scripts/test/test-ui.ts similarity index 85% rename from frontend/src/scripts/test/test-ui.js rename to frontend/src/scripts/test/test-ui.ts index a0016ab67..60e525bd7 100644 --- a/frontend/src/scripts/test/test-ui.js +++ b/frontend/src/scripts/test/test-ui.ts @@ -13,6 +13,7 @@ import * as SlowTimer from "../states/slow-timer"; import * as ConfigEvent from "../observables/config-event"; ConfigEvent.subscribe((eventKey, eventValue) => { + if (eventValue === undefined || typeof eventValue !== "boolean") return; if (eventKey === "flipTestColors") flipColors(eventValue); if (eventKey === "colorfulMode") colorful(eventValue); if (eventKey === "highlightMode") updateWordElement(eventValue); @@ -23,25 +24,25 @@ export let currentWordElementIndex = 0; export let resultVisible = false; export let activeWordTop = 0; export let testRestarting = false; -export let testRestartingPromise = null; +export let testRestartingPromise: Promise; export let lineTransition = false; export let currentTestLine = 0; export let resultCalculating = false; -export function setResultVisible(val) { +export function setResultVisible(val: boolean): void { resultVisible = val; } -export function setCurrentWordElementIndex(val) { +export function setCurrentWordElementIndex(val: number): void { currentWordElementIndex = val; } -export function setActiveWordTop(val) { +export function setActiveWordTop(val: number): void { activeWordTop = val; } -let restartingResolve; -export function setTestRestarting(val) { +let restartingResolve: null | ((value?: unknown) => void); +export function setTestRestarting(val: boolean): void { testRestarting = val; if (val === true) { testRestartingPromise = new Promise((resolve) => { @@ -53,25 +54,25 @@ export function setTestRestarting(val) { } } -export function setResultCalculating(val) { +export function setResultCalculating(val: boolean): void { resultCalculating = val; } -export function reset() { +export function reset(): void { currentTestLine = 0; currentWordElementIndex = 0; } -export function focusWords() { +export function focusWords(): void { if (!$("#wordsWrapper").hasClass("hidden")) { $("#wordsInput").focus(); } } -export function updateActiveElement(backspace) { - let active = document.querySelector("#words .active"); +export function updateActiveElement(backspace?: boolean): void { + const active = document.querySelector("#words .active"); if (Config.mode == "zen" && backspace) { - active.remove(); + active?.remove(); } else if (active !== null) { if (Config.highlightMode == "word") { active.querySelectorAll("letter").forEach((e) => { @@ -81,12 +82,12 @@ export function updateActiveElement(backspace) { active.classList.remove("active"); } try { - let activeWord = document.querySelectorAll("#words .word")[ - currentWordElementIndex - ]; + const activeWord = + document.querySelectorAll("#words .word")[currentWordElementIndex]; activeWord.classList.add("active"); activeWord.classList.remove("error"); - activeWordTop = document.querySelector("#words .active").offsetTop; + activeWordTop = (document.querySelector("#words .active")) + .offsetTop; if (Config.highlightMode == "word") { activeWord.querySelectorAll("letter").forEach((e) => { e.classList.add("correct"); @@ -95,7 +96,7 @@ export function updateActiveElement(backspace) { } catch (e) {} } -function getWordHTML(word) { +function getWordHTML(word: string): string { let newlineafter = false; let retval = `
`; for (let c = 0; c < word.length; c++) { @@ -126,13 +127,13 @@ function getWordHTML(word) { return retval; } -export function showWords() { +export function showWords(): void { $("#words").empty(); let wordsHTML = ""; if (Config.mode !== "zen") { for (let i = 0; i < TestWords.words.length; i++) { - wordsHTML += getWordHTML(TestWords.words.get(i)); + wordsHTML += getWordHTML(TestWords.words.get(i)); } } else { wordsHTML = @@ -142,8 +143,12 @@ export function showWords() { $("#words").html(wordsHTML); $("#wordsWrapper").removeClass("hidden"); - const wordHeight = $(document.querySelector(".word")).outerHeight(true); - const wordsHeight = $(document.querySelector("#words")).outerHeight(true); + const wordHeight = ( + $(document.querySelector(".word")).outerHeight(true) + ); + const wordsHeight = ( + $(document.querySelector("#words")).outerHeight(true) + ); console.log( `Showing words. wordHeight: ${wordHeight}, wordsHeight: ${wordsHeight}` ); @@ -155,6 +160,7 @@ export function showWords() { ) { $("#words").css("height", "auto"); $("#wordsWrapper").css("height", "auto"); + let nh = wordHeight * 3; if (nh > wordsHeight) { @@ -172,18 +178,18 @@ export function showWords() { } if (Config.mode === "zen") { - $(document.querySelector(".word")).remove(); + $(document.querySelector(".word")).remove(); } updateActiveElement(); Caret.updatePosition(); } -export function addWord(word) { +export function addWord(word: string): void { $("#words").append(getWordHTML(word)); } -export function flipColors(tf) { +export function flipColors(tf: boolean): void { if (tf) { $("#words").addClass("flipped"); } else { @@ -191,7 +197,7 @@ export function flipColors(tf) { } } -export function colorful(tc) { +export function colorful(tc: boolean): void { if (tc) { $("#words").addClass("colorfulMode"); } else { @@ -199,9 +205,9 @@ export function colorful(tc) { } } -export async function screenshot() { +export async function screenshot(): Promise { let revealReplay = false; - function revertScreenshot() { + function revertScreenshot(): void { $("#notificationCenter").removeClass("hidden"); $("#commandLineMobileButton").removeClass("hidden"); $(".pageTest .ssWatermark").addClass("hidden"); @@ -230,30 +236,31 @@ export async function screenshot() { ); } $(".pageTest .buttons").addClass("hidden"); - let src = $("#middle"); - let sourceX = src.position().left; /*X position from div#target*/ - let sourceY = src.position().top; /*Y position from div#target*/ - let sourceWidth = src.outerWidth( - true + const src = $("#middle"); + const sourceX = src.position().left; /*X position from div#target*/ + const sourceY = src.position().top; /*Y position from div#target*/ + const sourceWidth = ( + src.outerWidth(true) ); /*clientWidth/offsetWidth from div#target*/ - let sourceHeight = src.outerHeight( - true + const sourceHeight = ( + src.outerHeight(true) ); /*clientHeight/offsetHeight from div#target*/ $("#notificationCenter").addClass("hidden"); $("#commandLineMobileButton").addClass("hidden"); $(".pageTest .loginTip").addClass("hidden"); try { - let paddingX = 50; - let paddingY = 25; + const paddingX = 50; + const paddingY = 25; html2canvas(document.body, { backgroundColor: await ThemeColors.get("bg"), width: sourceWidth + paddingX * 2, height: sourceHeight + paddingY * 2, x: sourceX - paddingX, y: sourceY - paddingY, - }).then(function (canvas) { - canvas.toBlob(function (blob) { + }).then((canvas) => { + canvas.toBlob((blob) => { try { + if (blob === null) return; if (navigator.userAgent.toLowerCase().indexOf("firefox") > -1) { open(URL.createObjectURL(blob)); revertScreenshot(); @@ -272,7 +279,7 @@ export async function screenshot() { revertScreenshot(); }); } - } catch (e) { + } catch (e: any) { Notifications.add( "Error saving image to clipboard: " + e.message, -1 @@ -281,7 +288,7 @@ export async function screenshot() { } }); }); - } catch (e) { + } catch (e: any) { Notifications.add("Error creating image: " + e.message, -1); revertScreenshot(); } @@ -290,12 +297,10 @@ export async function screenshot() { }, 3000); } -export function updateWordElement(showError = !Config.blindMode) { - let input = TestInput.input.current; - let wordAtIndex; - let currentWord; - wordAtIndex = document.querySelector("#words .word.active"); - currentWord = TestWords.words.getCurrent(); +export function updateWordElement(showError = !Config.blindMode): void { + const input = TestInput.input.current; + const wordAtIndex = document.querySelector("#words .word.active"); + const currentWord = TestWords.words.getCurrent(); if (!currentWord && Config.mode !== "zen") return; let ret = ""; @@ -334,7 +339,7 @@ export function updateWordElement(showError = !Config.blindMode) { } for (let i = 0; i < input.length; i++) { - let charCorrect = currentWord[i] == input[i]; + const charCorrect = currentWord[i] == input[i]; let correctClass = "correct"; if (Config.highlightMode == "off") { @@ -467,21 +472,23 @@ export function updateWordElement(showError = !Config.blindMode) { if (newlineafter) $("#words").append("
"); } -export function lineJump(currentTop) { +export function lineJump(currentTop: number): void { //last word of the line if (currentTestLine > 0) { - let hideBound = currentTop; + const hideBound = currentTop; - let toHide = []; - let wordElements = $("#words .word"); + const toHide: JQuery[] = []; + const wordElements = $("#words .word"); for (let i = 0; i < currentWordElementIndex; i++) { if ($(wordElements[i]).hasClass("hidden")) continue; - let forWordTop = Math.floor(wordElements[i].offsetTop); + const forWordTop = Math.floor(wordElements[i].offsetTop); if (forWordTop < hideBound - 10) { toHide.push($($("#words .word")[i])); } } - const wordHeight = $(document.querySelector(".word")).outerHeight(true); + const wordHeight = ( + $(document.querySelector(".word")).outerHeight(true) + ); if (Config.smoothLineScroll && toHide.length > 0) { lineTransition = true; $("#words").prepend( @@ -498,7 +505,9 @@ export function lineJump(currentTop) { ); $("#paceCaret").animate( { - top: document.querySelector("#paceCaret").offsetTop - wordHeight, + top: + (document.querySelector("#paceCaret"))?.offsetTop - + wordHeight, }, SlowTimer.get() ? 0 : 125 ); @@ -508,7 +517,9 @@ export function lineJump(currentTop) { }, SlowTimer.get() ? 0 : 125, () => { - activeWordTop = document.querySelector("#words .active").offsetTop; + activeWordTop = (( + document.querySelector("#words .active") + )).offsetTop; currentWordElementIndex -= toHide.length; lineTransition = false; @@ -520,31 +531,33 @@ export function lineJump(currentTop) { toHide.forEach((el) => el.remove()); currentWordElementIndex -= toHide.length; $("#paceCaret").css({ - top: document.querySelector("#paceCaret").offsetTop - wordHeight, + top: + (document.querySelector("#paceCaret")).offsetTop - + wordHeight, }); } } currentTestLine++; } -export function arrangeCharactersRightToLeft() { +export function arrangeCharactersRightToLeft(): void { $("#words").addClass("rightToLeftTest"); $("#resultWordsHistory .words").addClass("rightToLeftTest"); $("#resultReplay .words").addClass("rightToLeftTest"); } -export function arrangeCharactersLeftToRight() { +export function arrangeCharactersLeftToRight(): void { $("#words").removeClass("rightToLeftTest"); $("#resultWordsHistory .words").removeClass("rightToLeftTest"); $("#resultReplay .words").removeClass("rightToLeftTest"); } -async function loadWordsHistory() { +async function loadWordsHistory(): Promise { $("#resultWordsHistory .words").empty(); let wordsHTML = ""; for (let i = 0; i < TestInput.input.history.length + 2; i++) { - let input = TestInput.input.getHistory(i); - let word = TestWords.words.get(i); + const input = TestInput.input.getHistory(i); + const word = TestWords.words.get(i); let wordEl = ""; try { if (input === "") throw new Error("empty input word"); @@ -565,12 +578,12 @@ async function loadWordsHistory() { } if (i === TestInput.input.history.length - 1) { //last word - let wordstats = { + const wordstats = { correct: 0, incorrect: 0, missed: 0, }; - let length = Config.mode == "zen" ? input.length : word.length; + const length = Config.mode == "zen" ? input.length : word.length; for (let c = 0; c < length; c++) { if (c < input.length) { //on char that still has a word list pair @@ -669,7 +682,7 @@ async function loadWordsHistory() { return true; } -export function toggleResultWords() { +export function toggleResultWords(): void { if (resultVisible) { if ($("#resultWordsHistory").stop(true, true).hasClass("hidden")) { //show @@ -710,7 +723,7 @@ export function toggleResultWords() { } } -export function applyBurstHeatmap() { +export function applyBurstHeatmap(): void { if (Config.burstHeatmap) { $("#resultWordsHistory .heatmapLegend").removeClass("hidden"); @@ -726,13 +739,13 @@ export function applyBurstHeatmap() { burstlist = burstlist.splice(0, burstlist.length - 1); } - let median = Misc.median(burstlist); - let adatm = []; + const median = Misc.median(burstlist); + const adatm: number[] = []; burstlist.forEach((burst) => { adatm.push(Math.abs(median - burst)); }); - let step = Misc.mean(adatm); - let steps = [ + const step = Misc.mean(adatm); + const steps = [ { val: 0, class: "heatmap-0", @@ -754,8 +767,8 @@ export function applyBurstHeatmap() { class: "heatmap-4", }, ]; - $("#resultWordsHistory .words .word").each((index, word) => { - let wordBurstVal = parseInt($(word).attr("burst")); + $("#resultWordsHistory .words .word").each((_, word) => { + const wordBurstVal = parseInt($(word).attr("burst")); let cls = ""; steps.forEach((step) => { if (wordBurstVal > step.val) cls = step.class; @@ -772,7 +785,7 @@ export function applyBurstHeatmap() { } } -export function highlightBadWord(index, showError) { +export function highlightBadWord(index: number, showError: boolean): void { if (!showError) return; $($("#words .word")[index]).addClass("error"); } @@ -781,18 +794,17 @@ $(document.body).on("click", "#saveScreenshotButton", () => { screenshot(); }); -$(document).on("click", "#testModesNotice .text-button.blind", (event) => { +$(document).on("click", "#testModesNotice .text-button.blind", () => { UpdateConfig.setBlindMode(!Config.blindMode); }); -$(".pageTest #copyWordsListButton").click(async (event) => { +$(".pageTest #copyWordsListButton").click(async () => { try { let words; if (Config.mode == "zen") { words = TestInput.input.history.join(" "); } else { - words = TestWords.words - .get() + words = (TestWords.words.get()) .slice(0, TestInput.input.history.length) .join(" "); } @@ -803,22 +815,22 @@ $(".pageTest #copyWordsListButton").click(async (event) => { } }); -$(".pageTest #toggleBurstHeatmap").click(async (event) => { +$(".pageTest #toggleBurstHeatmap").click(async () => { UpdateConfig.setBurstHeatmap(!Config.burstHeatmap); }); -$(document).on("mouseleave", "#resultWordsHistory .words .word", (e) => { +$(document).on("mouseleave", "#resultWordsHistory .words .word", () => { $(".wordInputAfter").remove(); }); -$("#wpmChart").on("mouseleave", (e) => { +$("#wpmChart").on("mouseleave", () => { $(".wordInputAfter").remove(); }); $(document).on("mouseenter", "#resultWordsHistory .words .word", (e) => { if (resultVisible) { - let input = $(e.currentTarget).attr("input"); - let burst = $(e.currentTarget).attr("burst"); + const input = $(e.currentTarget).attr("input"); + const burst = parseInt($(e.currentTarget).attr("burst")); if (input != undefined) $(e.currentTarget).append( `
@@ -843,7 +855,7 @@ $("#wordsInput").on("focus", () => { if (!resultVisible && Config.showOutOfFocusWarning) { OutOfFocus.hide(); } - Caret.show(TestInput.input.current); + Caret.show(); }); $("#wordsInput").on("focusout", () => { diff --git a/frontend/src/scripts/test/test-words.ts b/frontend/src/scripts/test/test-words.ts index 85d378623..431deeaaf 100644 --- a/frontend/src/scripts/test/test-words.ts +++ b/frontend/src/scripts/test/test-words.ts @@ -7,7 +7,7 @@ class Words { this.length = 0; this.currentIndex = 0; } - get(i: number, raw = false): string | string[] { + get(i?: number, raw = false): string | string[] { if (i === undefined) { return this.list; } else { diff --git a/frontend/src/scripts/test/timer-progress.js b/frontend/src/scripts/test/timer-progress.ts similarity index 76% rename from frontend/src/scripts/test/timer-progress.js rename to frontend/src/scripts/test/timer-progress.ts index 515e647b6..e70e2329d 100644 --- a/frontend/src/scripts/test/timer-progress.js +++ b/frontend/src/scripts/test/timer-progress.ts @@ -8,8 +8,8 @@ import * as SlowTimer from "../states/slow-timer"; import * as TestActive from "../states/test-active"; import * as ConfigEvent from "../observables/config-event"; -export function show() { - let op = Config.showTimerProgress ? Config.timerOpacity : 0; +export function show(): void { + const op = Config.showTimerProgress ? Config.timerOpacity : 0; if (Config.mode != "zen" && Config.timerStyle === "bar") { $("#timerWrapper").stop(true, true).removeClass("hidden").animate( { @@ -43,7 +43,7 @@ export function show() { } } -export function hide() { +export function hide(): void { $("#timerWrapper").stop(true, true).animate( { opacity: 0, @@ -69,7 +69,7 @@ export function hide() { ); } -export function restart() { +export function restart(): void { if (Config.timerStyle === "bar") { if (Config.mode === "time") { $("#timer").stop(true, true).animate( @@ -89,13 +89,13 @@ export function restart() { } } -let timerNumberElement = document.querySelector("#timerNumber"); -let miniTimerNumberElement = document.querySelector( +const timerNumberElement = document.querySelector("#timerNumber"); +const miniTimerNumberElement = document.querySelector( "#miniTimerAndLiveWpm .time" ); -export function update() { - let time = Time.get(); +export function update(): void { + const time = Time.get(); if ( Config.mode === "time" || (Config.mode === "custom" && CustomText.isTimeRandom) @@ -105,7 +105,7 @@ export function update() { maxtime = CustomText.time; } if (Config.timerStyle === "bar") { - let percent = 100 - ((time + 1) / maxtime) * 100; + const percent = 100 - ((time + 1) / maxtime) * 100; $("#timer") .stop(true, true) .animate( @@ -120,13 +120,15 @@ export function update() { if (maxtime === 0) { displayTime = Misc.secondsToString(time); } - timerNumberElement.innerHTML = "
" + displayTime + "
"; + if (timerNumberElement !== null) + timerNumberElement.innerHTML = "
" + displayTime + "
"; } else if (Config.timerStyle === "mini") { let displayTime = Misc.secondsToString(maxtime - time); if (maxtime === 0) { displayTime = Misc.secondsToString(time); } - miniTimerNumberElement.innerHTML = displayTime; + if (miniTimerNumberElement !== null) + miniTimerNumberElement.innerHTML = displayTime; } } else if ( Config.mode === "words" || @@ -148,7 +150,7 @@ export function update() { outof = TestWords.randomQuote?.textSplit?.length ?? 1; } if (Config.timerStyle === "bar") { - let percent = Math.floor( + const percent = Math.floor( ((TestWords.words.currentIndex + 1) / outof) * 100 ); $("#timer") @@ -161,30 +163,36 @@ export function update() { ); } else if (Config.timerStyle === "text") { if (outof === 0) { - timerNumberElement.innerHTML = - "
" + `${TestInput.input.history.length}` + "
"; + if (timerNumberElement !== null) + timerNumberElement.innerHTML = + "
" + `${TestInput.input.history.length}` + "
"; } else { - timerNumberElement.innerHTML = - "
" + `${TestInput.input.history.length}/${outof}` + "
"; + if (timerNumberElement !== null) + timerNumberElement.innerHTML = + "
" + `${TestInput.input.history.length}/${outof}` + "
"; } } else if (Config.timerStyle === "mini") { if (Config.words === 0) { - miniTimerNumberElement.innerHTML = `${TestInput.input.history.length}`; + if (miniTimerNumberElement !== null) + miniTimerNumberElement.innerHTML = `${TestInput.input.history.length}`; } else { - miniTimerNumberElement.innerHTML = `${TestInput.input.history.length}/${outof}`; + if (miniTimerNumberElement !== null) + miniTimerNumberElement.innerHTML = `${TestInput.input.history.length}/${outof}`; } } } else if (Config.mode == "zen") { if (Config.timerStyle === "text") { - timerNumberElement.innerHTML = - "
" + `${TestInput.input.history.length}` + "
"; + if (timerNumberElement !== null) + timerNumberElement.innerHTML = + "
" + `${TestInput.input.history.length}` + "
"; } else { - miniTimerNumberElement.innerHTML = `${TestInput.input.history.length}`; + if (miniTimerNumberElement !== null) + miniTimerNumberElement.innerHTML = `${TestInput.input.history.length}`; } } } -export function updateStyle() { +export function updateStyle(): void { if (!TestActive.get()) return; hide(); update(); diff --git a/frontend/src/scripts/test/today-tracker.js b/frontend/src/scripts/test/today-tracker.ts similarity index 56% rename from frontend/src/scripts/test/today-tracker.js rename to frontend/src/scripts/test/today-tracker.ts index 4984adc1d..7a4c62af9 100644 --- a/frontend/src/scripts/test/today-tracker.js +++ b/frontend/src/scripts/test/today-tracker.ts @@ -3,12 +3,11 @@ import * as DB from "../db"; let seconds = 0; let addedAllToday = false; -let dayToday = null; +let dayToday: number; -export function addSeconds(s) { +export function addSeconds(s: number): void { if (addedAllToday) { - let nowDate = new Date(); - nowDate = nowDate.getDate(); + const nowDate = new Date().getDate(); if (nowDate > dayToday) { seconds = s; return; @@ -17,33 +16,33 @@ export function addSeconds(s) { seconds += s; } -export function getString() { - let secString = Misc.secondsToString(Math.round(seconds), true, true); +export function getString(): string { + const secString = Misc.secondsToString(Math.round(seconds), true, true); return secString + (addedAllToday === true ? " today" : " session"); } -export async function addAllFromToday() { - let todayDate = new Date(); +export function addAllFromToday(): void { + const todayDate = new Date(); todayDate.setSeconds(0); todayDate.setMinutes(0); todayDate.setHours(0); todayDate.setMilliseconds(0); dayToday = todayDate.getDate(); - todayDate = todayDate.getTime(); + const todayDateMS = todayDate.getTime(); seconds = 0; - let results = await DB.getSnapshot().results; + const results = DB.getSnapshot().results; - results.forEach((result) => { - let resultDate = new Date(result.timestamp); + results?.forEach((result) => { + const resultDate = new Date(result.timestamp); resultDate.setSeconds(0); resultDate.setMinutes(0); resultDate.setHours(0); resultDate.setMilliseconds(0); - resultDate = resultDate.getTime(); + const resultDateMS = resultDate.getTime(); - if (resultDate >= todayDate) { + if (resultDateMS >= todayDateMS) { seconds += result.testDuration + result.incompleteTestSeconds - result.afkDuration; } diff --git a/frontend/src/scripts/test/tts.js b/frontend/src/scripts/test/tts.ts similarity index 53% rename from frontend/src/scripts/test/tts.js rename to frontend/src/scripts/test/tts.ts index 748616907..4408da09a 100644 --- a/frontend/src/scripts/test/tts.js +++ b/frontend/src/scripts/test/tts.ts @@ -2,29 +2,32 @@ import Config from "../config"; import * as Misc from "../misc"; import * as ConfigEvent from "../observables/config-event"; -let voice; +let voice: SpeechSynthesisUtterance | undefined; -export async function setLanguage(lang = Config.language) { +export async function setLanguage(lang = Config.language): Promise { if (!voice) return; - let language = await Misc.getLanguage(lang); - let bcp = language.bcp47 ? language.bcp47 : "en-US"; + const language = await Misc.getLanguage(lang); + const bcp = language.bcp47 ? language.bcp47 : "en-US"; voice.lang = bcp; } -export async function init() { +export async function init(): Promise { voice = new SpeechSynthesisUtterance(); setLanguage(); } -export function clear() { +export function clear(): void { voice = undefined; } -export function speak(text) { - if (!voice) init(); - voice.text = text; +export function speak(text: string): void { window.speechSynthesis.cancel(); - window.speechSynthesis.speak(voice); + if (voice === undefined) init(); + + if (voice !== undefined) { + voice.text = text; + window.speechSynthesis.speak(voice); + } } ConfigEvent.subscribe((eventKey, eventValue) => { diff --git a/frontend/src/scripts/test/weak-spot.js b/frontend/src/scripts/test/weak-spot.ts similarity index 71% rename from frontend/src/scripts/test/weak-spot.js rename to frontend/src/scripts/test/weak-spot.ts index bc8bee824..a51bfbf41 100644 --- a/frontend/src/scripts/test/weak-spot.js +++ b/frontend/src/scripts/test/weak-spot.ts @@ -1,4 +1,5 @@ import * as TestInput from "./test-input"; +import { Wordset } from "./wordset"; // Changes how quickly it 'learns' scores - very roughly the score for a char // is based on last perCharCount occurrences. Make it smaller to adjust faster. @@ -11,15 +12,17 @@ const wordSamples = 20; // Score penatly (in milliseconds) for getting a letter wrong. const incorrectPenalty = 5000; -let scores = {}; +const scores: { [char: string]: Score } = {}; class Score { + public average: number; + public count: number; constructor() { this.average = 0.0; this.count = 0; } - update(score) { + update(score: number): void { if (this.count < perCharCount) { this.count++; } @@ -29,9 +32,9 @@ class Score { } } -export function updateScore(char, isCorrect) { +export function updateScore(char: string, isCorrect: boolean): void { const timings = TestInput.keypressTimings.spacing.array; - if (timings.length == 0) { + if (timings.length === 0 || typeof timings === "string") { return; } let score = timings[timings.length - 1]; @@ -44,7 +47,7 @@ export function updateScore(char, isCorrect) { scores[char].update(score); } -function score(word) { +function score(word: string): number { let total = 0.0; let numChars = 0; for (const c of word) { @@ -56,13 +59,13 @@ function score(word) { return numChars == 0 ? 0.0 : total / numChars; } -export function getWord(wordset) { +export function getWord(wordset: Wordset): string { let highScore; - let randomWord; + let randomWord = ""; for (let i = 0; i < wordSamples; i++) { - let newWord = wordset.randomWord(); - let newScore = score(newWord); - if (i == 0 || newScore > highScore) { + const newWord = wordset.randomWord(); + const newScore = score(newWord); + if (i == 0 || highScore === undefined || newScore > highScore) { randomWord = newWord; highScore = newScore; } diff --git a/frontend/src/scripts/test/wikipedia.js b/frontend/src/scripts/test/wikipedia.ts similarity index 63% rename from frontend/src/scripts/test/wikipedia.js rename to frontend/src/scripts/test/wikipedia.ts index ee5316cd8..7253f70a6 100644 --- a/frontend/src/scripts/test/wikipedia.js +++ b/frontend/src/scripts/test/wikipedia.ts @@ -2,14 +2,19 @@ import * as Loader from "../elements/loader"; import * as Misc from "../misc"; export class Section { - constructor(title, author, words) { + public title: string; + public author: string; + public words: string[]; + constructor(title: string, author: string, words: string[]) { this.title = title; this.author = author; this.words = words; } } -export async function getTLD(languageGroup) { +export async function getTLD( + languageGroup: MonkeyTypes.LanguageGroup +): Promise<"en" | "es" | "fr" | "de" | "pt" | "it" | "nl"> { // language group to tld switch (languageGroup.name) { case "english": @@ -38,22 +43,34 @@ export async function getTLD(languageGroup) { } } -export async function getSection(language) { +interface Post { + title: string; + author: string; + pageid: number; +} + +interface SectionObject { + title: string; + author: string; +} + +export async function getSection(language: string): Promise
{ // console.log("Getting section"); Loader.show(); // get TLD for wikipedia according to language group let urlTLD = "en"; - let currentLanguageGroup = await Misc.findCurrentGroup(language); - urlTLD = await getTLD(currentLanguageGroup); + const currentLanguageGroup = await Misc.findCurrentGroup(language); + if (currentLanguageGroup !== undefined) + urlTLD = await getTLD(currentLanguageGroup); const randomPostURL = `https://${urlTLD}.wikipedia.org/api/rest_v1/page/random/summary`; - let sectionObj = {}; - let randomPostReq = await fetch(randomPostURL); + const sectionObj: SectionObject = { title: "", author: "" }; + const randomPostReq = await fetch(randomPostURL); let pageid = 0; if (randomPostReq.status == 200) { - let postObj = await randomPostReq.json(); + const postObj: Post = await randomPostReq.json(); sectionObj.title = postObj.title; sectionObj.author = postObj.author; pageid = postObj.pageid; @@ -67,14 +84,13 @@ export async function getSection(language) { const sectionURL = `https://${urlTLD}.wikipedia.org/w/api.php?action=query&format=json&pageids=${pageid}&prop=extracts&exintro=true&origin=*`; - let sectionReq = new XMLHttpRequest(); - sectionReq.onload = () => { + const sectionReq = new XMLHttpRequest(); + sectionReq.onload = (): void => { if (sectionReq.readyState == 4) { if (sectionReq.status == 200) { - let sectionText = JSON.parse(sectionReq.responseText).query.pages[ - pageid.toString() - ].extract; - let words = []; + let sectionText: string = JSON.parse(sectionReq.responseText).query + .pages[pageid.toString()].extract; + const words: string[] = []; // Remove double whitespaces and finally trailing whitespaces. sectionText = sectionText.replace(/<\/p>

+/g, " "); @@ -92,7 +108,11 @@ export async function getSection(language) { words.push(word); }); - let section = new Section(sectionObj.title, sectionObj.author, words); + const section = new Section( + sectionObj.title, + sectionObj.author, + words + ); Loader.hide(); res(section); } else { diff --git a/frontend/src/scripts/test/wordset.js b/frontend/src/scripts/test/wordset.ts similarity index 72% rename from frontend/src/scripts/test/wordset.js rename to frontend/src/scripts/test/wordset.ts index 5c857c049..7e9c21abb 100644 --- a/frontend/src/scripts/test/wordset.js +++ b/frontend/src/scripts/test/wordset.ts @@ -1,13 +1,15 @@ -let currentWordset = null; -let currentWordGenerator = null; +let currentWordset: Wordset | null = null; +let currentWordGenerator: WordGenerator | null = null; -class Wordset { - constructor(words) { +export class Wordset { + public words: string[]; + public length: number; + constructor(words: string[]) { this.words = words; this.length = this.words.length; } - randomWord() { + public randomWord(): string { return this.words[Math.floor(Math.random() * this.length)]; } } @@ -15,12 +17,14 @@ class Wordset { const prefixSize = 2; class CharDistribution { + public chars: { [char: string]: number }; + public count: number; constructor() { this.chars = {}; this.count = 0; } - addChar(char) { + public addChar(char: string): void { this.count++; if (char in this.chars) { this.chars[char]++; @@ -29,7 +33,7 @@ class CharDistribution { } } - randomChar() { + public randomChar(): string { const randomIndex = Math.floor(Math.random() * this.count); let runningCount = 0; for (const [char, charCount] of Object.entries(this.chars)) { @@ -38,16 +42,18 @@ class CharDistribution { return char; } } + + return Object.keys(this.chars)[0]; } } class WordGenerator extends Wordset { - constructor(words) { + public ngrams: { [prefix: string]: CharDistribution } = {}; + constructor(words: string[]) { super(words); // Can generate an unbounded number of words in theory. this.length = Infinity; - this.ngrams = {}; for (let word of words) { // Mark the end of each word with a space. word += " "; @@ -58,16 +64,16 @@ class WordGenerator extends Wordset { this.ngrams[prefix] = new CharDistribution(); } this.ngrams[prefix].addChar(c); - prefix = (prefix + c).substr(-prefixSize); + prefix = (prefix + c).substring(-prefixSize); } } } - randomWord() { + public override randomWord(): string { let word = ""; for (;;) { - const prefix = word.substr(-prefixSize); - let charDistribution = this.ngrams[prefix]; + const prefix = word.substring(-prefixSize); + const charDistribution = this.ngrams[prefix]; if (!charDistribution) { // This shouldn't happen if this.ngrams is complete. If it does // somehow, start generating a new word. @@ -86,7 +92,7 @@ class WordGenerator extends Wordset { } } -export function withWords(words, funbox) { +export function withWords(words: string[], funbox: string): Wordset { if (funbox == "pseudolang") { if (currentWordGenerator == null || words !== currentWordGenerator.words) { currentWordGenerator = new WordGenerator(words); diff --git a/frontend/src/scripts/types/types.d.ts b/frontend/src/scripts/types/types.d.ts index 0ab379ef3..fde38a48c 100644 --- a/frontend/src/scripts/types/types.d.ts +++ b/frontend/src/scripts/types/types.d.ts @@ -23,8 +23,6 @@ declare namespace MonkeyTypes { type QuoteLength = -1 | 0 | 1 | 2 | 3; - type QuoteLengthArray = QuoteLength[]; - type FontSize = "1" | "125" | "15" | "2" | "3" | "4"; type CaretStyle = @@ -122,6 +120,7 @@ declare namespace MonkeyTypes { name: string; type: FunboxObjectType; info: string; + affectsWordGeneration?: boolean; } interface CustomText { @@ -186,6 +185,7 @@ declare namespace MonkeyTypes { wpm: number[]; raw: number[]; err: number[]; + unsmoothedRaw?: number[]; } interface KeyStats { @@ -245,7 +245,7 @@ declare namespace MonkeyTypes { words: WordsModes; time: TimeModes; mode: Mode; - quoteLength: QuoteLengthArray; + quoteLength: QuoteLength[]; language: string; fontSize: FontSize; freedomMode: boolean; @@ -546,6 +546,7 @@ declare namespace MonkeyTypes { id: number; group?: number; language: string; + textSplit?: string; } interface PSA { @@ -573,7 +574,7 @@ declare namespace MonkeyTypes { interface Layout { keymapShowTopRow: boolean; - type: "iso" | "ansi" | "ortho"; + type: "iso" | "ansi" | "ortho" | "matrix"; keys: Keys; } @@ -581,11 +582,16 @@ declare namespace MonkeyTypes { [layout: string]: Layout; } interface Keys { - [row1: string]: string[]; - [row2: string]: string[]; - [row3: string]: string[]; - [row4: string]: string[]; - [row5: string]: string[]; + row1: string[]; + row2: string[]; + row3: string[]; + row4: string[]; + row5: string[]; + } + + interface WordsPerMinuteAndRaw { + wpm: number; + raw: number; } interface Challenge {