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 6c6b3d8b4b.

* fix

* merge or something
This commit is contained in:
Evan 2022-02-27 12:04:03 -06:00 committed by GitHub
parent fdc392dc01
commit fdabc4325a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 991 additions and 683 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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 {

View file

@ -215,7 +215,7 @@ export async function setup(challengeName: string): Promise<boolean> {
UpdateConfig.setTheme(challenge.parameters[1] as string);
}
if (challenge.parameters[2] != null) {
Funbox.activate(challenge.parameters[2]);
Funbox.activate(<string>challenge.parameters[2]);
}
} else if (challenge.type === "accuracy") {
UpdateConfig.setTimeConfig(0, true);

View file

@ -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<HTMLElement>("#words .active")?.offsetTop
(<HTMLElement>document.querySelector("#words .active"))?.offsetTop
);
}

View file

@ -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 += "<div></div>";
}
if (row === "row4" && lts.type !== "iso" && !isMatrix) {
rowElement += "<div></div>";
}
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 += "<div></div>";
rowElement += `<div class="keymap-key key-space">
if ((row === "row2" || row === "row3" || row === "row4") && !isMatrix) {
rowElement += "<div></div>";
}
if (row === "row4" && lts.type !== "iso" && !isMatrix) {
rowElement += "<div></div>";
}
if (row === "row5") {
let layoutDisplay = layoutString.replace(/_/g, " ");
if (Config.keymapLegendStyle === "blank") {
layoutDisplay = "";
}
rowElement += "<div></div>";
rowElement += `<div class="keymap-key key-space">
<div class="letter">${layoutDisplay}</div>
</div>`;
rowElement += `<div class="keymap-split-spacer"></div>`;
rowElement += `<div class="keymap-key key-split-space">
rowElement += `<div class="keymap-split-spacer"></div>`;
rowElement += `<div class="keymap-key key-split-space">
<div class="letter"></div>
</div>`;
} 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 = `<div class="keymap-key" data-key="${key.replace(
'"',
"&quot;"
)}">
} 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 = `<div class="keymap-key" data-key="${key.replace(
'"',
"&quot;"
)}">
<span class="letter">${keyDisplay}</span>
${bump ? "<div class='bump'></div>" : ""}
</div>`;
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 += `<div class="keymap-split-spacer"></div>`;
}
} else {
if (i === 5) {
splitSpacer += `<div class="keymap-split-spacer"></div>`;
if (
row === "row4" &&
(Config.keymapStyle === "split" ||
Config.keymapStyle === "alice") &&
lts.type === "iso"
) {
if (i === 6) {
splitSpacer += `<div class="keymap-split-spacer"></div>`;
}
} else {
if (i === 5) {
splitSpacer += `<div class="keymap-split-spacer"></div>`;
}
}
}
}
if (Config.keymapStyle === "alice" && row === "row4") {
if (
(lts.type === "iso" && i === 6) ||
(lts.type !== "iso" && i === 5)
) {
splitSpacer += `<div class="extra-key"><span class="letter"></span></div>`;
if (Config.keymapStyle === "alice" && row === "row4") {
if (
(lts.type === "iso" && i === 6) ||
(lts.type !== "iso" && i === 5)
) {
splitSpacer += `<div class="extra-key"><span class="letter"></span></div>`;
}
}
}
rowElement += splitSpacer + keyElement;
rowElement += splitSpacer + keyElement;
}
}
}
keymapElement += `<div class="row r${index + 1}">${rowElement}</div>`;
});
keymapElement += `<div class="row r${index + 1}">${rowElement}</div>`;
}
);
$("#keymap").html(keymapElement);

View file

@ -102,13 +102,14 @@ export async function getSortedThemesList(): Promise<Theme[]> {
}
}
type Funbox = { name: string; type: string; info: string };
let funboxList: Funbox[] = [];
export async function getFunboxList(): Promise<Funbox[]> {
let funboxList: MonkeyTypes.FunboxObject[] = [];
export async function getFunboxList(): Promise<MonkeyTypes.FunboxObject[]> {
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<Funbox[]> {
}
}
export async function getFunbox(funbox: string): Promise<Funbox | undefined> {
const list: Funbox[] = await getFunboxList();
export async function getFunbox(
funbox: string
): Promise<MonkeyTypes.FunboxObject | undefined> {
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<Language> {
let currentLanguage: LanguageObject;
export async function getLanguage(lang: string): Promise<LanguageObject> {
try {
if (currentLanguage == undefined || currentLanguage.name !== lang) {
console.log("getting language json");
@ -303,7 +308,7 @@ export async function getLanguage(lang: string): Promise<Language> {
export async function getCurrentLanguage(
languageName: string
): Promise<Language> {
): Promise<LanguageObject> {
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(

View file

@ -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;

View file

@ -3,7 +3,7 @@ type ConfigValues =
| number
| boolean
| string[]
| MonkeyTypes.QuoteLengthArray
| MonkeyTypes.QuoteLength[]
| MonkeyTypes.ResultFilters
| MonkeyTypes.CustomBackgroundFilter
| null

View file

@ -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 = <string>$(e.currentTarget).attr("funbox");
const type = <MonkeyTypes.FunboxObjectType>$(e.currentTarget).attr("type");
Funbox.setFunbox(funbox, type);
setActiveFunboxButton();
});

View file

@ -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
);

View file

@ -5,7 +5,7 @@ type ConfigValues =
| number
| boolean
| string[]
| MonkeyTypes.QuoteLengthArray
| MonkeyTypes.QuoteLength[]
| MonkeyTypes.ResultFilters
| MonkeyTypes.CustomBackgroundFilter
| null

View file

@ -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<boolean> {
$("#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<boolean | undefined> {
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<void> {
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") {

View file

@ -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<string | null> {
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 = [

View file

@ -1,13 +0,0 @@
let state = false;
export function set() {
state = true;
}
export function reset() {
state = false;
}
export function get() {
return state;
}

View file

@ -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;
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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");

View file

@ -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 = <HTMLElement>(
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<void> {
$("#paceCaret").addClass("hidden");
let mode2 = Misc.getMode2(Config, TestWords.randomQuote);
const mode2 = Misc.getMode2(
Config,
TestWords.randomQuote
) as MonkeyTypes.Mode2<typeof Config.mode>;
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 = <HTMLElement>word.querySelectorAll("letter")[0];
} else {
currentLetter =
word.querySelectorAll("letter")[settings.currentLetterIndex];
currentLetter = <HTMLElement>(
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();
});

View file

@ -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")

View file

@ -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();
});
}

View file

@ -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<Poem | number> {
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;
}
}

View file

@ -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");
}
);

View file

@ -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);

View file

@ -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<MonkeyTypes.Mode>;
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<void> {
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<void> {
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<void> {
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 += `<br>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<MonkeyTypes.Mode>,
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();

View file

@ -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 = <HTMLElement[]>[
...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<void> {
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<boolean | null> {
if (!leftState && !rightState) return null;
if (Config.oppositeShiftMode === "on") {
@ -190,4 +215,6 @@ export async function isUsingOppositeShift(event) {
return false;
}
}
return true;
}

View file

@ -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 {

View file

@ -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";

View file

@ -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;

View file

@ -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<void> {
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<void> {
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;

View file

@ -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<unknown>;
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 = (<HTMLElement>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 = `<div class='word'>`;
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(<string>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 = <number>(
$(<Element>document.querySelector(".word")).outerHeight(true)
);
const wordsHeight = <number>(
$(<Element>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();
$(<Element>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<void> {
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 = <number>(
src.outerWidth(true)
); /*clientWidth/offsetWidth from div#target*/
let sourceHeight = src.outerHeight(
true
const sourceHeight = <number>(
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 = <Element>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("<div class='newline'></div>");
}
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<HTMLElement>[] = [];
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 = <number>(
$(<Element>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:
(<HTMLElement>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 = (<HTMLElement>(
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:
(<HTMLElement>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<boolean> {
$("#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 = <string>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(<string>$(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 = (<string[]>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(<string>$(e.currentTarget).attr("burst"));
if (input != undefined)
$(e.currentTarget).append(
`<div class="wordInputAfter">
@ -843,7 +855,7 @@ $("#wordsInput").on("focus", () => {
if (!resultVisible && Config.showOutOfFocusWarning) {
OutOfFocus.hide();
}
Caret.show(TestInput.input.current);
Caret.show();
});
$("#wordsInput").on("focusout", () => {

View file

@ -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 {

View file

@ -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 = "<div>" + displayTime + "</div>";
if (timerNumberElement !== null)
timerNumberElement.innerHTML = "<div>" + displayTime + "</div>";
} 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 =
"<div>" + `${TestInput.input.history.length}` + "</div>";
if (timerNumberElement !== null)
timerNumberElement.innerHTML =
"<div>" + `${TestInput.input.history.length}` + "</div>";
} else {
timerNumberElement.innerHTML =
"<div>" + `${TestInput.input.history.length}/${outof}` + "</div>";
if (timerNumberElement !== null)
timerNumberElement.innerHTML =
"<div>" + `${TestInput.input.history.length}/${outof}` + "</div>";
}
} 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 =
"<div>" + `${TestInput.input.history.length}` + "</div>";
if (timerNumberElement !== null)
timerNumberElement.innerHTML =
"<div>" + `${TestInput.input.history.length}` + "</div>";
} 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();

View file

@ -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;
}

View file

@ -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<void> {
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<void> {
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) => {

View file

@ -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;
}

View file

@ -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<Section> {
// 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><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 {

View file

@ -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);

View file

@ -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 {