mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-03-10 05:35:05 +08:00
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:
parent
fdc392dc01
commit
fdabc4325a
40 changed files with 991 additions and 683 deletions
93
frontend/package-lock.json
generated
93
frontend/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
'"',
|
||||
"""
|
||||
)}">
|
||||
} 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(
|
||||
'"',
|
||||
"""
|
||||
)}">
|
||||
<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);
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
3
frontend/src/scripts/modules.d.ts
vendored
3
frontend/src/scripts/modules.d.ts
vendored
|
@ -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;
|
||||
|
|
|
@ -3,7 +3,7 @@ type ConfigValues =
|
|||
| number
|
||||
| boolean
|
||||
| string[]
|
||||
| MonkeyTypes.QuoteLengthArray
|
||||
| MonkeyTypes.QuoteLength[]
|
||||
| MonkeyTypes.ResultFilters
|
||||
| MonkeyTypes.CustomBackgroundFilter
|
||||
| null
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ type ConfigValues =
|
|||
| number
|
||||
| boolean
|
||||
| string[]
|
||||
| MonkeyTypes.QuoteLengthArray
|
||||
| MonkeyTypes.QuoteLength[]
|
||||
| MonkeyTypes.ResultFilters
|
||||
| MonkeyTypes.CustomBackgroundFilter
|
||||
| null
|
||||
|
|
|
@ -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") {
|
|
@ -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 = [
|
|
@ -1,13 +0,0 @@
|
|||
let state = false;
|
||||
|
||||
export function set() {
|
||||
state = true;
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
state = false;
|
||||
}
|
||||
|
||||
export function get() {
|
||||
return state;
|
||||
}
|
13
frontend/src/scripts/test/manual-restart-tracker.ts
Normal file
13
frontend/src/scripts/test/manual-restart-tracker.ts
Normal 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;
|
||||
}
|
|
@ -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();
|
||||
}
|
81
frontend/src/scripts/test/monkey.ts
Normal file
81
frontend/src/scripts/test/monkey.ts
Normal 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();
|
||||
}
|
|
@ -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");
|
|
@ -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();
|
||||
});
|
|
@ -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")
|
|
@ -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();
|
||||
});
|
||||
}
|
68
frontend/src/scripts/test/poetry.ts
Normal file
68
frontend/src/scripts/test/poetry.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
);
|
|
@ -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);
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
|
@ -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", () => {
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
|
@ -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) => {
|
|
@ -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;
|
||||
}
|
|
@ -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 {
|
|
@ -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);
|
24
frontend/src/scripts/types/types.d.ts
vendored
24
frontend/src/scripts/types/types.d.ts
vendored
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue