mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-12-26 18:09:40 +08:00
Multiple funboxes (#3578) egorguslyan
* input-controller * result * Finishing logic * Numbers + layoutfluid * One interface * Filter results * tts error on undefined Extencions like NoScript can partly block scripts on the page. If speech synthesis is not loaded, notification shows up without freezing the page * Improved randomcase * Prevent dublicates in command line * Change filter logic * Prettier * Convert numbers * num * Quote and zen modes * withWords * Misc * Expand funboxes list for pb saving * Move list to backend * Move to constants * Async withWords, checkFunbox tweak * Prettier * Forbid nonexistent funboxes * Disable speech if language is ignored TtS's init() uses setLanguage() * canGetPb * Less circular imports * Ligatures typo * Simon says blocks word highlight * blockWordHighlight backend * Changed imports * usesLayout * JSON schema * Display notification instead of reseting * canGetPB * One getWordHtml * Dividing properties * No sync * blockedModes * forcedConfig * Infinitness parameter, list sync * applyConfig, memory Remove extra applyConfig somewhere; Memory in quotes and custom modes * I lost this code after merging * Remove arrowKeys * isFunboxCompatible * Fix logic * sync canGetPb * remove FunboxObjectType * baloons * moved cangetpb function to pb utils * updated the pb check to be easier to understand * Refactor isFunboxCompatible * Check modes conflicts * Strict highlightMode type * Only one allowed or blocked highlight mode * More checks * Undefined only, not false * Prettier * Highlight modes * added intersect helper function * reworked forced config - storing allowed modes as an array, not string - first value will be used if config is outside of the allowed values - instead of checking if highlight mode is allowed, checking if the whole config is available - removed the "Finite" forced config and replaced it with "noInfiniteDuration" property - config event listener now checks every config change, not just highlight mode. this will ensure any future forced configs will work straight out of the box * ManualRestart in commandline * fixed funbox commands not correctly showing which funbox is active * Upd list * Reduce list * split funbox into smaller files moved funbox files into its own folder * missing none command * added function to convert camel case to space separated words * changed config validation to be blocking the change rather than reacting to the change * reduced code duplication * allowing sub color flash * moved keymap key higlighting and flashing into an observable event * moved tts into a observable event * passing funbox into config validation funcitons * replaced getActive with get * only keeping functions structure in the list, moved the actual function bodies to funbox.ts done to remove a circular dependency still need to finish the rest of the funboxes * removed empty function definitions (typing issues) * removed unnecessary type * unnecessary check * moved mode checking to config validation * longer notification * checking funboxes before changing mode * moved more functions * fixed incorrect type * checking funboxes when setting punctuation and numbers * Rest of funboxes * fixed funbox commands showing tags text and icon * checking if funbox can be set with the current config * better error message * validating with setting time and words importing from a new file * added a function to capitalise the first letter of a string * using function from a new file new parameters * moved test length check to a function in a different file * moved some funbox validation into its own file * only showing notifications if the setWordCount returned true * moved funbox validation to its own file * setting manual restart when trying to set funbox to nonoe * moving this validation to before activating the funbox * returning forcedConfigs along side if current value is allowed moved infinite check to checkFunboxForcedConfigs * removed function, replaced by funox validation * removing duplicates * throwing if no intersection * wrong type * always allowing setting funbox sometimes it might be possible to update the config * checking forced configs first, and updating config if possible only setting funbox to none when couldnt update config * basic difficulty levels * xp funbox bonus * removed console logs * renamed import, renamed type * lowercase b for consistency across the codebase * renamed variable for readability * renamed for clarity * converted metadata to object * changed from beforesubgroup on the command to before list on the subgroup * using code suggested by bruce * renamed type * removed console log * merch banner fix * important animation * updating the icon of "none" funbox command * removed unnecessary import Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Miodec <jack@monkeytype.com>
This commit is contained in:
parent
d45b487192
commit
588d14a2b3
47 changed files with 2290 additions and 1142 deletions
|
|
@ -36,6 +36,8 @@ import { getDailyLeaderboard } from "../../utils/daily-leaderboards";
|
|||
import AutoRoleList from "../../constants/auto-roles";
|
||||
import * as UserDAL from "../../dal/user";
|
||||
import { buildMonkeyMail } from "../../utils/monkey-mail";
|
||||
import FunboxesMetadata from "../../constants/funbox";
|
||||
import _ from "lodash";
|
||||
import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard";
|
||||
|
||||
try {
|
||||
|
|
@ -509,10 +511,16 @@ async function calculateXp(
|
|||
charStats,
|
||||
punctuation,
|
||||
numbers,
|
||||
funbox,
|
||||
} = result;
|
||||
|
||||
const { enabled, gainMultiplier, maxDailyBonus, minDailyBonus } =
|
||||
xpConfiguration;
|
||||
const {
|
||||
enabled,
|
||||
gainMultiplier,
|
||||
maxDailyBonus,
|
||||
minDailyBonus,
|
||||
funboxBonus: funboxBonusConfiguration,
|
||||
} = xpConfiguration;
|
||||
|
||||
if (mode === "zen" || !enabled) {
|
||||
return {
|
||||
|
|
@ -556,6 +564,18 @@ async function calculateXp(
|
|||
}
|
||||
}
|
||||
|
||||
if (funboxBonusConfiguration > 0) {
|
||||
const funboxModifier = _.sumBy(funbox.split("#"), (funboxName) => {
|
||||
const funbox = FunboxesMetadata[funboxName as string];
|
||||
const difficultyLevel = funbox?.difficultyLevel ?? 0;
|
||||
return Math.max(difficultyLevel * funboxBonusConfiguration, 0);
|
||||
});
|
||||
if (funboxModifier > 0) {
|
||||
modifier += funboxModifier;
|
||||
breakdown["funbox"] = Math.round(baseXp * funboxModifier);
|
||||
}
|
||||
}
|
||||
|
||||
if (xpConfiguration.streak.enabled) {
|
||||
const streakModifier = parseFloat(
|
||||
mapRange(
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export const BASE_CONFIGURATION: MonkeyTypes.Configuration = {
|
|||
},
|
||||
xp: {
|
||||
enabled: false,
|
||||
funboxBonus: 0,
|
||||
gainMultiplier: 0,
|
||||
maxDailyBonus: 0,
|
||||
minDailyBonus: 0,
|
||||
|
|
@ -238,6 +239,10 @@ export const CONFIGURATION_FORM_SCHEMA: ObjectSchema = {
|
|||
type: "number",
|
||||
label: "Gain Multiplier",
|
||||
},
|
||||
funboxBonus: {
|
||||
type: "number",
|
||||
label: "Funbox Bonus",
|
||||
},
|
||||
maxDailyBonus: {
|
||||
type: "number",
|
||||
label: "Max Daily Bonus",
|
||||
|
|
|
|||
112
backend/src/constants/funbox.ts
Normal file
112
backend/src/constants/funbox.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
const Funboxes: Record<string, MonkeyTypes.FunboxMetadata> = {
|
||||
nausea: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 2,
|
||||
},
|
||||
round_round_baby: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
},
|
||||
simon_says: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
mirror: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
},
|
||||
tts: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
choo_choo: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 2,
|
||||
},
|
||||
arrows: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
rAnDoMcAsE: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 2,
|
||||
},
|
||||
capitals: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
layoutfluid: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
earthquake: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
space_balls: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
gibberish: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
"58008": {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
ascii: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
specials: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
plus_one: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
plus_two: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
read_ahead_easy: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
},
|
||||
read_ahead: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 2,
|
||||
},
|
||||
read_ahead_hard: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
},
|
||||
memory: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
},
|
||||
nospace: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
poetry: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
wikipedia: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
weakspot: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
pseudolang: {
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export default Funboxes;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import _ from "lodash";
|
||||
import { isUsernameValid } from "../utils/validation";
|
||||
import { updateUserEmail } from "../utils/auth";
|
||||
import { checkAndUpdatePb } from "../utils/pb";
|
||||
import { canFunboxGetPb, checkAndUpdatePb } from "../utils/pb";
|
||||
import * as db from "../init/db";
|
||||
import MonkeyError from "../utils/error";
|
||||
import { Collection, ObjectId, WithId, Long, UpdateFilter } from "mongodb";
|
||||
|
|
@ -346,11 +346,9 @@ export async function checkIfPb(
|
|||
user: MonkeyTypes.User,
|
||||
result: MonkeyTypes.Result<MonkeyTypes.Mode>
|
||||
): Promise<boolean> {
|
||||
const { mode, funbox } = result;
|
||||
const { mode } = result;
|
||||
|
||||
if (funbox !== "none" && funbox !== "plus_one" && funbox !== "plus_two") {
|
||||
return false;
|
||||
}
|
||||
if (!canFunboxGetPb(result)) return false;
|
||||
|
||||
if (mode === "quote") {
|
||||
return false;
|
||||
|
|
@ -396,15 +394,8 @@ export async function checkIfTagPb(
|
|||
return [];
|
||||
}
|
||||
|
||||
const { mode, tags: resultTags, funbox } = result;
|
||||
if (
|
||||
funbox !== undefined &&
|
||||
funbox !== "none" &&
|
||||
funbox !== "plus_one" &&
|
||||
funbox !== "plus_two"
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const { mode, tags: resultTags } = result;
|
||||
if (!canFunboxGetPb(result)) return [];
|
||||
|
||||
if (mode === "quote") {
|
||||
return [];
|
||||
|
|
|
|||
6
backend/src/types/types.d.ts
vendored
6
backend/src/types/types.d.ts
vendored
|
|
@ -43,6 +43,7 @@ declare namespace MonkeyTypes {
|
|||
};
|
||||
xp: {
|
||||
enabled: boolean;
|
||||
funboxBonus: number;
|
||||
gainMultiplier: number;
|
||||
maxDailyBonus: number;
|
||||
minDailyBonus: number;
|
||||
|
|
@ -468,4 +469,9 @@ declare namespace MonkeyTypes {
|
|||
ratings: number;
|
||||
totalRating: number;
|
||||
}
|
||||
|
||||
interface FunboxMetadata {
|
||||
canGetPb: boolean;
|
||||
difficultyLevel: number;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import _ from "lodash";
|
||||
import FunboxesMetadata from "../constants/funbox";
|
||||
|
||||
interface CheckAndUpdatePbResult {
|
||||
isPb: boolean;
|
||||
|
|
@ -8,6 +9,17 @@ interface CheckAndUpdatePbResult {
|
|||
|
||||
type Result = MonkeyTypes.Result<MonkeyTypes.Mode>;
|
||||
|
||||
export function canFunboxGetPb(
|
||||
result: MonkeyTypes.Result<MonkeyTypes.Mode>
|
||||
): boolean {
|
||||
const funbox = result.funbox;
|
||||
if (!funbox || funbox === "none") return true;
|
||||
|
||||
return funbox
|
||||
.split("#")
|
||||
.every((funboxName) => FunboxesMetadata[funboxName]?.canGetPb === true);
|
||||
}
|
||||
|
||||
export function checkAndUpdatePb(
|
||||
userPersonalBests: MonkeyTypes.PersonalBests,
|
||||
lbPersonalBests: MonkeyTypes.LbPersonalBests | undefined,
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ function validateOthers() {
|
|||
type: "object",
|
||||
properties: {
|
||||
name: { type: "string" },
|
||||
type: { type: "string" },
|
||||
info: { type: "string" },
|
||||
affectsWordGeneration: { type: "boolean" },
|
||||
canGetPb: { type: "boolean" },
|
||||
alias: { type: "string" },
|
||||
},
|
||||
required: ["name", "type", "info"],
|
||||
required: ["name", "info", "canGetPb"],
|
||||
},
|
||||
};
|
||||
const funboxValidator = JSONValidator.validate(funboxData, funboxSchema);
|
||||
|
|
|
|||
|
|
@ -66,16 +66,16 @@
|
|||
|
||||
@keyframes flashHighlight {
|
||||
0% {
|
||||
background-color: var(--bg-color);
|
||||
background-color: var(--bg-color) !important;
|
||||
}
|
||||
10% {
|
||||
background-color: var(--main-color);
|
||||
background-color: var(--main-color) !important;
|
||||
}
|
||||
40% {
|
||||
background-color: var(--main-color);
|
||||
background-color: var(--main-color) !important;
|
||||
}
|
||||
100% {
|
||||
background-color: var(--bg-color);
|
||||
background-color: var(--bg-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -651,7 +651,9 @@ $(".pageAccount .topFilters .button.currentConfigFilter").on("click", () => {
|
|||
if (Config.funbox === "none") {
|
||||
filters.funbox.none = true;
|
||||
} else {
|
||||
filters.funbox[Config.funbox] = true;
|
||||
for (const f of Config.funbox.split("#")) {
|
||||
filters.funbox[f] = true;
|
||||
}
|
||||
}
|
||||
|
||||
filters["tags"]["none"] = true;
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ import KeymapLayoutsCommands, {
|
|||
|
||||
import Config, * as UpdateConfig from "../config";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as TestLogic from "../test/test-logic";
|
||||
import { randomizeTheme } from "../controllers/theme-controller";
|
||||
import * as CustomTextPopup from "../popups/custom-text-popup";
|
||||
import * as Settings from "../pages/settings";
|
||||
|
|
@ -122,6 +121,11 @@ Misc.getLanguageList()
|
|||
Misc.getFunboxList()
|
||||
.then((funboxes) => {
|
||||
updateFunboxCommands(funboxes);
|
||||
if (FunboxCommands[0].subgroup) {
|
||||
FunboxCommands[0].subgroup.beforeList = (): void => {
|
||||
updateFunboxCommands(funboxes);
|
||||
};
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(
|
||||
|
|
@ -231,7 +235,6 @@ export const commands: MonkeyTypes.CommandsSubgroup = {
|
|||
UpdateConfig.setCustomLayoutfluid(
|
||||
input as MonkeyTypes.CustomLayoutFluidSpaces
|
||||
);
|
||||
if (Config.funbox === "layoutfluid") TestLogic.restart();
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -139,6 +139,9 @@ function updateSuggested(): void {
|
|||
.split(" ")
|
||||
.filter((s, i) => s || i == 0); //remove empty entries after first
|
||||
const list = CommandlineLists.current[CommandlineLists.current.length - 1];
|
||||
|
||||
if (list.beforeList) list.beforeList();
|
||||
|
||||
if (
|
||||
inputVal[0] === "" &&
|
||||
Config.singleListCommandLine === "on" &&
|
||||
|
|
@ -258,8 +261,8 @@ function trigger(command: string): void {
|
|||
showInput(obj.id, escaped, obj.defaultValue ? obj.defaultValue() : "");
|
||||
} else if (obj.subgroup) {
|
||||
subgroup = true;
|
||||
if (obj.beforeSubgroup) {
|
||||
obj.beforeSubgroup();
|
||||
if (obj.subgroup.beforeList) {
|
||||
obj.subgroup.beforeList();
|
||||
}
|
||||
CommandlineLists.current.push(
|
||||
obj.subgroup as MonkeyTypes.CommandsSubgroup
|
||||
|
|
@ -308,7 +311,7 @@ function addChildCommands(
|
|||
}
|
||||
if ((commandItem as MonkeyTypes.Command).subgroup) {
|
||||
const command = commandItem as MonkeyTypes.Command;
|
||||
if (command.beforeSubgroup) command.beforeSubgroup();
|
||||
if (command.subgroup?.beforeList) command.subgroup.beforeList();
|
||||
try {
|
||||
(
|
||||
(commandItem as MonkeyTypes.Command)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import * as ThemeController from "../../controllers/theme-controller";
|
|||
export const subgroup: MonkeyTypes.CommandsSubgroup = {
|
||||
title: "Custom themes list...",
|
||||
// configKey: "customThemeId",
|
||||
beforeList: (): void => update(),
|
||||
list: [],
|
||||
};
|
||||
|
||||
|
|
@ -15,7 +16,6 @@ const commands: MonkeyTypes.Command[] = [
|
|||
display: "Custom themes...",
|
||||
icon: "fa-palette",
|
||||
subgroup,
|
||||
beforeSubgroup: (): void => update(),
|
||||
available: (): boolean => {
|
||||
return !!Auth?.currentUser;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import * as Funbox from "../../test/funbox";
|
||||
import * as Funbox from "../../test/funbox/funbox";
|
||||
import * as TestLogic from "../../test/test-logic";
|
||||
import * as ManualRestart from "../../test/manual-restart-tracker";
|
||||
import Config from "../../config";
|
||||
|
||||
const subgroup: MonkeyTypes.CommandsSubgroup = {
|
||||
title: "Funbox...",
|
||||
|
|
@ -11,7 +13,7 @@ const subgroup: MonkeyTypes.CommandsSubgroup = {
|
|||
configValue: "none",
|
||||
alias: "off",
|
||||
exec: (): void => {
|
||||
if (Funbox.setFunbox("none", null)) {
|
||||
if (Funbox.setFunbox("none")) {
|
||||
TestLogic.restart();
|
||||
}
|
||||
},
|
||||
|
|
@ -29,16 +31,81 @@ const commands: MonkeyTypes.Command[] = [
|
|||
},
|
||||
];
|
||||
|
||||
function update(funboxes: MonkeyTypes.FunboxObject[]): void {
|
||||
function update(funboxes: MonkeyTypes.FunboxMetadata[]): void {
|
||||
subgroup.list = [];
|
||||
subgroup.list.push({
|
||||
id: "changeFunboxNone",
|
||||
display: "none",
|
||||
configValue: "none",
|
||||
alias: "off",
|
||||
exec: (): void => {
|
||||
ManualRestart.set();
|
||||
if (Funbox.setFunbox("none")) {
|
||||
TestLogic.restart();
|
||||
}
|
||||
},
|
||||
});
|
||||
funboxes.forEach((funbox) => {
|
||||
let dis;
|
||||
if (Config.funbox.includes(funbox.name)) {
|
||||
dis =
|
||||
'<i class="fas fa-fw fa-check"></i>' + funbox.name.replace(/_/g, " ");
|
||||
} else {
|
||||
dis = '<i class="fas fa-fw"></i>' + funbox.name.replace(/_/g, " ");
|
||||
}
|
||||
|
||||
subgroup.list.push({
|
||||
id: "changeFunbox" + funbox.name,
|
||||
display: funbox.name.replace(/_/g, " "),
|
||||
noIcon: true,
|
||||
display: dis,
|
||||
// visible: Funbox.isFunboxCompatible(funbox.name, funbox.type),
|
||||
sticky: true,
|
||||
alias: funbox.alias,
|
||||
configValue: funbox.name,
|
||||
exec: (): void => {
|
||||
if (Funbox.setFunbox(funbox.name, funbox.type)) {
|
||||
TestLogic.restart();
|
||||
Funbox.toggleFunbox(funbox.name);
|
||||
ManualRestart.set();
|
||||
TestLogic.restart();
|
||||
|
||||
for (let i = 0; i < funboxes.length; i++) {
|
||||
// subgroup.list[i].visible = Funbox.isFunboxCompatible(funboxes[i].name, funboxes[i].type);
|
||||
|
||||
let txt = funboxes[i].name.replace(/_/g, " ");
|
||||
if (Config.funbox.includes(funboxes[i].name)) {
|
||||
txt = '<i class="fas fa-fw fa-check"></i>' + txt;
|
||||
} else {
|
||||
txt = '<i class="fas fa-fw"></i>' + txt;
|
||||
}
|
||||
if ($("#commandLine").hasClass("allCommands")) {
|
||||
$(
|
||||
`#commandLine .suggestions .entry[command='changeFunbox${funboxes[i].name}']`
|
||||
).html(
|
||||
`<div class="icon"><i class="fas fa-fw fa-gamepad"></i></div><div>Funbox > ` +
|
||||
txt
|
||||
);
|
||||
} else {
|
||||
$(
|
||||
`#commandLine .suggestions .entry[command='changeFunbox${funboxes[i].name}']`
|
||||
).html(txt);
|
||||
}
|
||||
}
|
||||
if (funboxes.length > 0) {
|
||||
const noneTxt =
|
||||
Config.funbox === "none"
|
||||
? `<i class="fas fa-fw fa-check"></i>none`
|
||||
: `<i class="fas fa-fw"></i>none`;
|
||||
if ($("#commandLine").hasClass("allCommands")) {
|
||||
$(
|
||||
`#commandLine .suggestions .entry[command='changeFunboxNone']`
|
||||
).html(
|
||||
`<div class="icon"><i class="fas fa-fw fa-gamepad"></i></div><div>Funbox > ` +
|
||||
noneTxt
|
||||
);
|
||||
} else {
|
||||
$(
|
||||
`#commandLine .suggestions .entry[command='changeFunboxNone']`
|
||||
).html(noneTxt);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import { Auth } from "../../firebase";
|
|||
const subgroup: MonkeyTypes.CommandsSubgroup = {
|
||||
title: "Presets...",
|
||||
list: [],
|
||||
beforeList: (): void => {
|
||||
update();
|
||||
},
|
||||
};
|
||||
|
||||
const commands: MonkeyTypes.Command[] = [
|
||||
|
|
@ -17,9 +20,6 @@ const commands: MonkeyTypes.Command[] = [
|
|||
display: "Presets...",
|
||||
icon: "fa-sliders-h",
|
||||
subgroup,
|
||||
beforeSubgroup: (): void => {
|
||||
update();
|
||||
},
|
||||
available: (): boolean => {
|
||||
return !!Auth?.currentUser;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import { Auth } from "../../firebase";
|
|||
const subgroup: MonkeyTypes.CommandsSubgroup = {
|
||||
title: "Change tags...",
|
||||
list: [],
|
||||
beforeList: (): void => {
|
||||
update();
|
||||
},
|
||||
};
|
||||
|
||||
const commands: MonkeyTypes.Command[] = [
|
||||
|
|
@ -18,9 +21,6 @@ const commands: MonkeyTypes.Command[] = [
|
|||
display: "Tags...",
|
||||
icon: "fa-tag",
|
||||
subgroup,
|
||||
beforeSubgroup: (): void => {
|
||||
update();
|
||||
},
|
||||
available: (): boolean => {
|
||||
return !!Auth?.currentUser;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { Auth } from "./firebase";
|
|||
import * as AnalyticsController from "./controllers/analytics-controller";
|
||||
import * as AccountButton from "./elements/account-button";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { canSetConfigWithCurrentFunboxes } from "./test/funbox/funbox-validation";
|
||||
|
||||
export let localStorageConfig: MonkeyTypes.Config;
|
||||
export let dbConfigLoaded = false;
|
||||
|
|
@ -92,6 +93,10 @@ export async function saveFullConfigToLocalStorage(
|
|||
export function setNumbers(numb: boolean, nosave?: boolean): boolean {
|
||||
if (!isConfigValueValid("numbers", numb, ["boolean"])) return false;
|
||||
|
||||
if (!canSetConfigWithCurrentFunboxes("numbers", numb, config.funbox)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.mode === "quote") {
|
||||
numb = false;
|
||||
}
|
||||
|
|
@ -106,6 +111,10 @@ export function setNumbers(numb: boolean, nosave?: boolean): boolean {
|
|||
export function setPunctuation(punc: boolean, nosave?: boolean): boolean {
|
||||
if (!isConfigValueValid("punctuation", punc, ["boolean"])) return false;
|
||||
|
||||
if (!canSetConfigWithCurrentFunboxes("punctuation", punc, config.funbox)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.mode === "quote") {
|
||||
punc = false;
|
||||
}
|
||||
|
|
@ -125,10 +134,10 @@ export function setMode(mode: MonkeyTypes.Mode, nosave?: boolean): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (mode !== "words" && config.funbox === "memory") {
|
||||
Notifications.add("Memory funbox can only be used with words mode.", 0);
|
||||
if (!canSetConfigWithCurrentFunboxes("mode", mode, config.funbox)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const previous = config.mode;
|
||||
config.mode = mode;
|
||||
if (config.mode == "custom") {
|
||||
|
|
@ -235,6 +244,36 @@ export function setFunbox(funbox: string, nosave?: boolean): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function toggleFunbox(
|
||||
funbox: string,
|
||||
nosave?: boolean
|
||||
): number | boolean {
|
||||
if (!isConfigValueValid("funbox", funbox, ["string"])) return false;
|
||||
|
||||
let r;
|
||||
|
||||
const funboxArray = config.funbox.split("#");
|
||||
if (funboxArray[0] == "none") funboxArray.splice(0, 1);
|
||||
if (!funboxArray.includes(funbox)) {
|
||||
funboxArray.push(funbox);
|
||||
config.funbox = funboxArray.sort().join("#");
|
||||
r = funboxArray.indexOf(funbox);
|
||||
} else {
|
||||
r = funboxArray.indexOf(funbox);
|
||||
funboxArray.splice(r, 1);
|
||||
if (funboxArray.length == 0) {
|
||||
config.funbox = "none";
|
||||
} else {
|
||||
config.funbox = funboxArray.join("#");
|
||||
}
|
||||
r = -r - 1;
|
||||
}
|
||||
saveToLocalStorage("funbox", nosave);
|
||||
ConfigEvent.dispatch("funbox", config.funbox);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
export function setBlindMode(blind: boolean, nosave?: boolean): boolean {
|
||||
if (!isConfigValueValid("blind mode", blind, ["boolean"])) return false;
|
||||
|
||||
|
|
@ -812,16 +851,7 @@ export function setHighlightMode(
|
|||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
mode === "word" &&
|
||||
(config.funbox === "nospace" ||
|
||||
config.funbox === "read_ahead" ||
|
||||
config.funbox === "read_ahead_easy" ||
|
||||
config.funbox === "read_ahead_hard" ||
|
||||
config.funbox === "tts" ||
|
||||
config.funbox === "arrows")
|
||||
) {
|
||||
Notifications.add("Can't use word highlight with this funbox", 0);
|
||||
if (!canSetConfigWithCurrentFunboxes("highlightMode", mode, config.funbox)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -970,6 +1000,10 @@ export function setTimeConfig(
|
|||
): boolean {
|
||||
if (!isConfigValueValid("time", time, ["number"])) return false;
|
||||
|
||||
if (!canSetConfigWithCurrentFunboxes("words", time, config.funbox)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newTime = isNaN(time) || time < 0 ? DefaultConfig.time : time;
|
||||
|
||||
config.time = newTime;
|
||||
|
|
@ -1030,6 +1064,10 @@ export function setWordCount(
|
|||
): boolean {
|
||||
if (!isConfigValueValid("words", wordCount, ["number"])) return false;
|
||||
|
||||
if (!canSetConfigWithCurrentFunboxes("words", wordCount, config.funbox)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const newWordCount =
|
||||
wordCount < 0 || wordCount > 100000 ? DefaultConfig.words : wordCount;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import * as Misc from "../utils/misc";
|
|||
import * as Notifications from "../elements/notifications";
|
||||
import * as ManualRestart from "../test/manual-restart-tracker";
|
||||
import * as CustomText from "../test/custom-text";
|
||||
import * as Funbox from "../test/funbox";
|
||||
import * as Funbox from "../test/funbox/funbox";
|
||||
import Config, * as UpdateConfig from "../config";
|
||||
import * as TestUI from "../test/test-ui";
|
||||
import * as ConfigEvent from "../observables/config-event";
|
||||
|
|
@ -94,10 +94,29 @@ export function verify(
|
|||
}
|
||||
}
|
||||
} else if (requirementType == "funbox") {
|
||||
const funboxMode = requirementValue["exact"];
|
||||
const funboxMode = requirementValue["exact"]
|
||||
.toString()
|
||||
.split("#")
|
||||
.sort()
|
||||
.join("#");
|
||||
if (funboxMode != result.funbox) {
|
||||
requirementsMet = false;
|
||||
failReasons.push(`${funboxMode} funbox not active`);
|
||||
for (const f of funboxMode.split("#")) {
|
||||
if (
|
||||
result.funbox?.split("#").find((rf) => rf == f) == undefined
|
||||
) {
|
||||
failReasons.push(`${f} funbox not active`);
|
||||
}
|
||||
}
|
||||
if (result.funbox?.split("#")) {
|
||||
for (const f of result.funbox.split("#")) {
|
||||
if (
|
||||
funboxMode.split("#").find((rf) => rf == f) == undefined
|
||||
) {
|
||||
failReasons.push(`${f} funbox active`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (requirementType == "raw") {
|
||||
const rawMode = Object.keys(requirementValue)[0];
|
||||
|
|
|
|||
|
|
@ -2,18 +2,15 @@ import * as TestLogic from "../test/test-logic";
|
|||
import * as TestUI from "../test/test-ui";
|
||||
import * as TestStats from "../test/test-stats";
|
||||
import * as Monkey from "../test/monkey";
|
||||
import Config, * as UpdateConfig from "../config";
|
||||
import * as Keymap from "../elements/keymap";
|
||||
import Config from "../config";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as LiveAcc from "../test/live-acc";
|
||||
import * as LiveBurst from "../test/live-burst";
|
||||
import * as Funbox from "../test/funbox";
|
||||
import * as Funbox from "../test/funbox/funbox";
|
||||
import * as Sound from "./sound-controller";
|
||||
import * as Caret from "../test/caret";
|
||||
import * as ManualRestart from "../test/manual-restart-tracker";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as CustomText from "../test/custom-text";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as LayoutEmulator from "../test/layout-emulator";
|
||||
import * as PaceCaret from "../test/pace-caret";
|
||||
import * as TimerProgress from "../test/timer-progress";
|
||||
|
|
@ -30,6 +27,9 @@ import * as TestWords from "../test/test-words";
|
|||
import * as Hangul from "hangul-js";
|
||||
import * as CustomTextState from "../states/custom-text-name";
|
||||
import { navigate } from "../observables/navigate-event";
|
||||
import * as FunboxList from "../test/funbox/funbox-list";
|
||||
import * as Settings from "../pages/settings";
|
||||
import * as KeymapEvent from "../observables/keymap-event";
|
||||
import { IgnoredKeys } from "../constants/ignored-keys";
|
||||
|
||||
let dontInsertSpace = false;
|
||||
|
|
@ -56,7 +56,7 @@ function updateUI(): void {
|
|||
|
||||
if (Config.keymapMode === "next" && Config.mode !== "zen") {
|
||||
if (!Config.language.startsWith("korean")) {
|
||||
Keymap.highlightKey(
|
||||
KeymapEvent.highlight(
|
||||
TestWords.words
|
||||
.getCurrent()
|
||||
.charAt(TestInput.input.current.length)
|
||||
|
|
@ -86,13 +86,13 @@ function updateUI(): void {
|
|||
inputCharLength - koCurrWord[inputGroupLength].length
|
||||
];
|
||||
|
||||
Keymap.highlightKey(koChar);
|
||||
KeymapEvent.highlight(koChar);
|
||||
} catch (e) {
|
||||
Keymap.highlightKey("");
|
||||
KeymapEvent.highlight("");
|
||||
}
|
||||
} else {
|
||||
//for new words
|
||||
Keymap.highlightKey(koCurrWord[0][0]);
|
||||
KeymapEvent.highlight(koCurrWord[0][0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -123,7 +123,9 @@ function backspaceToPrevious(): void {
|
|||
|
||||
TestInput.input.current = TestInput.input.popHistory();
|
||||
TestInput.corrected.popHistory();
|
||||
if (Config.funbox === "nospace" || Config.funbox === "arrows") {
|
||||
if (
|
||||
FunboxList.get(Config.funbox).find((f) => f.properties?.includes("nospace"))
|
||||
) {
|
||||
TestInput.input.current = TestInput.input.current.slice(0, -1);
|
||||
setWordsInput(" " + TestInput.input.current + " ");
|
||||
}
|
||||
|
|
@ -148,36 +150,25 @@ function handleSpace(): void {
|
|||
}
|
||||
|
||||
const currentWord: string = TestWords.words.getCurrent();
|
||||
if (Config.funbox === "layoutfluid" && Config.mode !== "time") {
|
||||
// here I need to check if Config.customLayoutFluid exists because of my
|
||||
// scuffed solution of returning whenever value is undefined in the setCustomLayoutfluid function
|
||||
const layouts: string[] = Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")
|
||||
: ["qwerty", "dvorak", "colemak"];
|
||||
let index = 0;
|
||||
const outOf: number = TestWords.words.length;
|
||||
index = Math.floor(
|
||||
(TestInput.input.history.length + 1) / (outOf / layouts.length)
|
||||
);
|
||||
if (Config.layout !== layouts[index] && layouts[index] !== undefined) {
|
||||
Notifications.add(`--- !!! ${layouts[index]} !!! ---`, 0);
|
||||
|
||||
for (const f of FunboxList.get(Config.funbox)) {
|
||||
if (f.functions?.handleSpace) {
|
||||
f.functions.handleSpace();
|
||||
}
|
||||
UpdateConfig.setLayout(layouts[index]);
|
||||
UpdateConfig.setKeymapLayout(layouts[index]);
|
||||
Keymap.highlightKey(
|
||||
TestWords.words
|
||||
.getCurrent()
|
||||
.charAt(TestInput.input.current.length)
|
||||
.toString()
|
||||
);
|
||||
Settings.groups["layout"]?.updateInput();
|
||||
}
|
||||
Settings.groups["layout"]?.updateInput();
|
||||
|
||||
dontInsertSpace = true;
|
||||
|
||||
const burst: number = TestStats.calculateBurst();
|
||||
LiveBurst.update(Math.round(burst));
|
||||
TestInput.pushBurstToHistory(burst);
|
||||
|
||||
const nospace =
|
||||
FunboxList.get(Config.funbox).find((f) =>
|
||||
f.properties?.includes("nospace")
|
||||
) !== undefined;
|
||||
|
||||
//correct word or in zen mode
|
||||
const isWordCorrect: boolean =
|
||||
currentWord === TestInput.input.current || Config.mode == "zen";
|
||||
|
|
@ -193,12 +184,12 @@ function handleSpace(): void {
|
|||
Caret.updatePosition();
|
||||
TestInput.incrementKeypressCount();
|
||||
TestInput.pushKeypressWord(TestWords.words.currentIndex);
|
||||
if (Config.funbox !== "nospace" && Config.funbox !== "arrows") {
|
||||
if (!nospace) {
|
||||
Sound.playClick();
|
||||
}
|
||||
Replay.addReplayEvent("submitCorrectWord");
|
||||
} else {
|
||||
if (Config.funbox !== "nospace" && Config.funbox !== "arrows") {
|
||||
if (!nospace) {
|
||||
if (!Config.playSoundOnError || Config.blindMode) {
|
||||
Sound.playClick();
|
||||
} else {
|
||||
|
|
@ -303,7 +294,7 @@ function handleSpace(): void {
|
|||
} //end of line wrap
|
||||
|
||||
if (Config.keymapMode === "react") {
|
||||
Keymap.flashKey(" ", true);
|
||||
KeymapEvent.flash(" ", true);
|
||||
}
|
||||
if (
|
||||
Config.mode === "words" ||
|
||||
|
|
@ -356,21 +347,12 @@ function isCharCorrect(char: string, charIndex: number): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
if (Config.funbox === "arrows") {
|
||||
if ((char === "w" || char === "ArrowUp") && originalChar === "↑") {
|
||||
return true;
|
||||
}
|
||||
if ((char === "s" || char === "ArrowDown") && originalChar === "↓") {
|
||||
return true;
|
||||
}
|
||||
if ((char === "a" || char === "ArrowLeft") && originalChar === "←") {
|
||||
return true;
|
||||
}
|
||||
if ((char === "d" || char === "ArrowRight") && originalChar === "→") {
|
||||
return true;
|
||||
}
|
||||
const funbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.isCharCorrect
|
||||
);
|
||||
if (funbox?.functions?.isCharCorrect) {
|
||||
return funbox.functions.isCharCorrect(char, originalChar);
|
||||
}
|
||||
|
||||
if (
|
||||
(char === "’" || char === "‘" || char === "'") &&
|
||||
(originalChar === "’" || originalChar === "‘" || originalChar === "'")
|
||||
|
|
@ -415,12 +397,17 @@ function handleChar(
|
|||
return;
|
||||
}
|
||||
|
||||
if (char === "\n" && Config.funbox === "58008") {
|
||||
char = " ";
|
||||
for (const f of FunboxList.get(Config.funbox)) {
|
||||
if (f.functions?.handleChar) char = f.functions.handleChar(char);
|
||||
}
|
||||
|
||||
const nospace =
|
||||
FunboxList.get(Config.funbox).find((f) =>
|
||||
f.properties?.includes("nospace")
|
||||
) !== undefined;
|
||||
|
||||
if (char !== "\n" && char !== "\t" && /\s/.test(char)) {
|
||||
if (Config.funbox === "nospace" || Config.funbox === "arrows") return;
|
||||
if (nospace) return;
|
||||
handleSpace();
|
||||
|
||||
//insert space for expert and master or strict space,
|
||||
|
|
@ -528,7 +515,7 @@ function handleChar(
|
|||
|
||||
//keymap
|
||||
if (Config.keymapMode === "react") {
|
||||
Keymap.flashKey(char, thisCharCorrect);
|
||||
KeymapEvent.flash(char, thisCharCorrect);
|
||||
}
|
||||
|
||||
if (!correctShiftUsed && Config.difficulty != "master") return;
|
||||
|
|
@ -649,7 +636,7 @@ function handleChar(
|
|||
|
||||
//simulate space press in nospace funbox
|
||||
if (
|
||||
((Config.funbox === "nospace" || Config.funbox === "arrows") &&
|
||||
(nospace &&
|
||||
TestInput.input.current.length === TestWords.words.getCurrent().length) ||
|
||||
(char === "\n" && thisCharCorrect)
|
||||
) {
|
||||
|
|
@ -861,11 +848,6 @@ $(document).keydown(async (event) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (Config.funbox !== "arrows" && /Arrow/i.test(event.key)) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
Monkey.type();
|
||||
|
||||
if (event.key === "Backspace" && TestInput.input.current.length === 0) {
|
||||
|
|
@ -921,22 +903,22 @@ $(document).keydown(async (event) => {
|
|||
(await ShiftTracker.isUsingOppositeShift(event)) !== false;
|
||||
}
|
||||
|
||||
if (Config.funbox === "arrows") {
|
||||
let char: string = event.key;
|
||||
if (["ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown"].includes(char)) {
|
||||
if (char === "ArrowLeft") char = "a";
|
||||
if (char === "ArrowRight") char = "d";
|
||||
if (char === "ArrowDown") char = "s";
|
||||
if (char === "ArrowUp") char = "w";
|
||||
const funbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.preventDefaultEvent
|
||||
);
|
||||
if (funbox?.functions?.preventDefaultEvent) {
|
||||
if (await funbox.functions.preventDefaultEvent(event)) {
|
||||
event.preventDefault();
|
||||
handleChar(char, TestInput.input.current.length);
|
||||
handleChar(event.key, TestInput.input.current.length);
|
||||
updateUI();
|
||||
setWordsInput(" " + TestInput.input.current);
|
||||
if (Config.tapeMode !== "off") {
|
||||
TestUI.scrollTape();
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
}
|
||||
|
||||
if (
|
||||
Config.layout !== "default" &&
|
||||
!(
|
||||
event.ctrlKey ||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import DefaultConfig from "./constants/default-config";
|
|||
import { Auth } from "./firebase";
|
||||
import { defaultSnap } from "./constants/default-snapshot";
|
||||
import * as ConnectionState from "./states/connection";
|
||||
import { getFunboxList } from "./utils/misc";
|
||||
|
||||
let dbSnapshot: MonkeyTypes.Snapshot | undefined;
|
||||
|
||||
|
|
@ -511,7 +512,11 @@ export async function getLocalPB<M extends MonkeyTypes.Mode>(
|
|||
lazyMode: boolean,
|
||||
funbox: string
|
||||
): Promise<number> {
|
||||
if (funbox !== "none" && funbox !== "plus_one" && funbox !== "plus_two") {
|
||||
const funboxes = (await getFunboxList()).filter((fb) => {
|
||||
return funbox?.split("#").includes(fb.name);
|
||||
});
|
||||
|
||||
if (!funboxes.every((f) => f.canGetPb)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -293,6 +293,14 @@ async function animateXpBreakdown(
|
|||
|
||||
if (skipBreakdown) return;
|
||||
|
||||
if (breakdown["funbox"]) {
|
||||
await Misc.sleep(delay);
|
||||
await append(`funbox +${breakdown["funbox"]}`);
|
||||
total += breakdown["funbox"];
|
||||
}
|
||||
|
||||
if (skipBreakdown) return;
|
||||
|
||||
if (breakdown["streak"]) {
|
||||
await Misc.sleep(delay);
|
||||
await append(`streak +${breakdown["streak"]}`);
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import Config from "../config";
|
|||
import * as ThemeColors from "./theme-colors";
|
||||
import * as SlowTimer from "../states/slow-timer";
|
||||
import * as ConfigEvent from "../observables/config-event";
|
||||
import * as KeymapEvent from "../observables/keymap-event";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as Hangul from "hangul-js";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as ActivePage from "../states/active-page";
|
||||
|
||||
export function highlightKey(currentKey: string): void {
|
||||
function highlightKey(currentKey: string): void {
|
||||
if (Config.mode === "zen") return;
|
||||
if (currentKey === "") currentKey = " ";
|
||||
try {
|
||||
|
|
@ -37,7 +38,7 @@ export function highlightKey(currentKey: string): void {
|
|||
}
|
||||
}
|
||||
|
||||
export async function flashKey(key: string, correct: boolean): Promise<void> {
|
||||
async function flashKey(key: string, correct?: boolean): Promise<void> {
|
||||
if (key == undefined) return;
|
||||
//console.log("key", key);
|
||||
if (key == " ") {
|
||||
|
|
@ -51,41 +52,38 @@ export async function flashKey(key: string, correct: boolean): Promise<void> {
|
|||
const themecolors = await ThemeColors.getAll();
|
||||
|
||||
try {
|
||||
let css = {
|
||||
color: themecolors.bg,
|
||||
backgroundColor: themecolors.sub,
|
||||
borderColor: themecolors.sub,
|
||||
};
|
||||
|
||||
if (correct || Config.blindMode) {
|
||||
$(key)
|
||||
.stop(true, true)
|
||||
.css({
|
||||
color: themecolors.bg,
|
||||
backgroundColor: themecolors.main,
|
||||
borderColor: themecolors.main,
|
||||
})
|
||||
.animate(
|
||||
{
|
||||
color: themecolors.sub,
|
||||
backgroundColor: "transparent",
|
||||
borderColor: themecolors.sub,
|
||||
},
|
||||
SlowTimer.get() ? 0 : 500,
|
||||
"easeOutExpo"
|
||||
);
|
||||
css = {
|
||||
color: themecolors.bg,
|
||||
backgroundColor: themecolors.main,
|
||||
borderColor: themecolors.main,
|
||||
};
|
||||
} else {
|
||||
$(key)
|
||||
.stop(true, true)
|
||||
.css({
|
||||
color: themecolors.bg,
|
||||
backgroundColor: themecolors.error,
|
||||
borderColor: themecolors.error,
|
||||
})
|
||||
.animate(
|
||||
{
|
||||
color: themecolors.sub,
|
||||
backgroundColor: "transparent",
|
||||
borderColor: themecolors.sub,
|
||||
},
|
||||
SlowTimer.get() ? 0 : 500,
|
||||
"easeOutExpo"
|
||||
);
|
||||
css = {
|
||||
color: themecolors.bg,
|
||||
backgroundColor: themecolors.error,
|
||||
borderColor: themecolors.error,
|
||||
};
|
||||
}
|
||||
|
||||
$(key)
|
||||
.stop(true, true)
|
||||
.css(css)
|
||||
.animate(
|
||||
{
|
||||
color: themecolors.sub,
|
||||
backgroundColor: "transparent",
|
||||
borderColor: themecolors.sub,
|
||||
},
|
||||
SlowTimer.get() ? 0 : 500,
|
||||
"easeOutExpo"
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
|
|
@ -291,3 +289,12 @@ ConfigEvent.subscribe((eventKey) => {
|
|||
refresh();
|
||||
}
|
||||
});
|
||||
|
||||
KeymapEvent.subscribe((mode, key, correct) => {
|
||||
if (mode === "highlight") {
|
||||
highlightKey(key);
|
||||
}
|
||||
if (mode === "flash") {
|
||||
flashKey(key, correct);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -182,10 +182,9 @@ export async function update(): Promise<void> {
|
|||
|
||||
if (Config.funbox !== "none") {
|
||||
$(".pageTest #testModesNotice").append(
|
||||
`<div class="textButton" commands="funbox"><i class="fas fa-gamepad"></i>${Config.funbox.replace(
|
||||
/_/g,
|
||||
" "
|
||||
)}</div>`
|
||||
`<div class="textButton" commands="funbox"><i class="fas fa-gamepad"></i>${Config.funbox
|
||||
.replace(/_/g, " ")
|
||||
.replace(/#/g, ", ")}</div>`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
33
frontend/src/ts/observables/keymap-event.ts
Normal file
33
frontend/src/ts/observables/keymap-event.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
type SubscribeFunction = (
|
||||
mode: "highlight" | "flash",
|
||||
key: string,
|
||||
correct?: boolean
|
||||
) => void;
|
||||
|
||||
const subscribers: SubscribeFunction[] = [];
|
||||
|
||||
export function subscribe(fn: SubscribeFunction): void {
|
||||
subscribers.push(fn);
|
||||
}
|
||||
|
||||
export async function flash(key: string, correct?: boolean): Promise<void> {
|
||||
subscribers.forEach((fn) => {
|
||||
try {
|
||||
fn("flash", key, correct);
|
||||
} catch (e) {
|
||||
console.error("Keymap flash event subscriber threw an error");
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function highlight(key: string): Promise<void> {
|
||||
subscribers.forEach((fn) => {
|
||||
try {
|
||||
fn("highlight", key);
|
||||
} catch (e) {
|
||||
console.error("Keymap highlight event subscriber threw an error");
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
18
frontend/src/ts/observables/tts-event.ts
Normal file
18
frontend/src/ts/observables/tts-event.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
type SubscribeFunction = (text: string) => void;
|
||||
|
||||
const subscribers: SubscribeFunction[] = [];
|
||||
|
||||
export function subscribe(fn: SubscribeFunction): void {
|
||||
subscribers.push(fn);
|
||||
}
|
||||
|
||||
export async function dispatch(text: string): Promise<void> {
|
||||
subscribers.forEach((fn) => {
|
||||
try {
|
||||
fn(text);
|
||||
} catch (e) {
|
||||
console.error("TTS event subscriber threw an error");
|
||||
console.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -88,10 +88,12 @@ function loadMoreLines(lineIndex?: number): void {
|
|||
}
|
||||
|
||||
if (result.funbox !== "none" && result.funbox !== undefined) {
|
||||
icons += `<span aria-label="${result.funbox.replace(
|
||||
/_/g,
|
||||
" "
|
||||
)}" data-balloon-pos="up"><i class="fas fa-gamepad"></i></span>`;
|
||||
icons += `<span aria-label="${result.funbox
|
||||
.replace(/_/g, " ")
|
||||
.replace(
|
||||
/#/g,
|
||||
", "
|
||||
)}" data-balloon-pos="up"><i class="fas fa-gamepad"></i></span>`;
|
||||
}
|
||||
|
||||
if (result.chartData === undefined) {
|
||||
|
|
@ -429,7 +431,14 @@ function fillContent(): void {
|
|||
return;
|
||||
}
|
||||
} else {
|
||||
if (!ResultFilters.getFilter("funbox", result.funbox)) {
|
||||
let counter = 0;
|
||||
for (const f of result.funbox.split("#")) {
|
||||
if (ResultFilters.getFilter("funbox", f)) {
|
||||
counter++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (counter == 0) {
|
||||
if (filterDebug) {
|
||||
console.log(`skipping result due to funbox filter`, result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Config, * as UpdateConfig from "../config";
|
|||
import * as Sound from "../controllers/sound-controller";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as DB from "../db";
|
||||
import * as Funbox from "../test/funbox";
|
||||
import { toggleFunbox } from "../test/funbox/funbox";
|
||||
import * as TagController from "../controllers/tag-controller";
|
||||
import * as PresetController from "../controllers/preset-controller";
|
||||
import * as ThemePicker from "../settings/theme-picker";
|
||||
|
|
@ -16,6 +16,7 @@ import * as CookiePopup from "../popups/cookie-popup";
|
|||
import Page from "./page";
|
||||
import { Auth } from "../firebase";
|
||||
import Ape from "../ape";
|
||||
import { areFunboxesCompatible } from "../test/funbox/funbox-validation";
|
||||
|
||||
interface SettingsGroups {
|
||||
[key: string]: SettingsGroup;
|
||||
|
|
@ -526,9 +527,7 @@ export async function fillSettingsPage(): Promise<void> {
|
|||
funboxEl.append(
|
||||
`<div class="funbox button" funbox='${funbox.name}' aria-label="${
|
||||
funbox.info
|
||||
}" data-balloon-pos="up" data-balloon-length="fit" type="${
|
||||
funbox.type
|
||||
}" style="transform:scaleX(-1);">${funbox.name.replace(
|
||||
}" data-balloon-pos="up" data-balloon-length="fit" style="transform:scaleX(-1);">${funbox.name.replace(
|
||||
/_/g,
|
||||
" "
|
||||
)}</div>`
|
||||
|
|
@ -537,9 +536,10 @@ export async function fillSettingsPage(): Promise<void> {
|
|||
funboxEl.append(
|
||||
`<div class="funbox button" funbox='${funbox.name}' aria-label="${
|
||||
funbox.info
|
||||
}" data-balloon-pos="up" data-balloon-length="fit" type="${
|
||||
funbox.type
|
||||
}">${funbox.name.replace(/_/g, " ")}</div>`
|
||||
}" data-balloon-pos="up" data-balloon-length="fit">${funbox.name.replace(
|
||||
/_/g,
|
||||
" "
|
||||
)}</div>`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -691,9 +691,24 @@ export function updateAuthSections(): void {
|
|||
|
||||
function setActiveFunboxButton(): void {
|
||||
$(`.pageSettings .section.funbox .button`).removeClass("active");
|
||||
$(
|
||||
`.pageSettings .section.funbox .button[funbox='${Config.funbox}']`
|
||||
).addClass("active");
|
||||
$(`.pageSettings .section.funbox .button`).removeClass("disabled");
|
||||
Misc.getFunboxList().then((funboxModes) => {
|
||||
funboxModes.forEach((funbox) => {
|
||||
if (
|
||||
!areFunboxesCompatible(Config.funbox, funbox.name) &&
|
||||
!Config.funbox.split("#").includes(funbox.name)
|
||||
) {
|
||||
$(
|
||||
`.pageSettings .section.funbox .button[funbox='${funbox.name}']`
|
||||
).addClass("disabled");
|
||||
}
|
||||
});
|
||||
});
|
||||
Config.funbox.split("#").forEach((funbox) => {
|
||||
$(`.pageSettings .section.funbox .button[funbox='${funbox}']`).addClass(
|
||||
"active"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function refreshTagsSettingsSection(): void {
|
||||
|
|
@ -933,8 +948,7 @@ $(".pageSettings .section.minBurst").on("click", ".button.save", () => {
|
|||
//funbox
|
||||
$(".pageSettings .section.funbox").on("click", ".button", (e) => {
|
||||
const funbox = <string>$(e.currentTarget).attr("funbox");
|
||||
const type = <MonkeyTypes.FunboxObjectType>$(e.currentTarget).attr("type");
|
||||
Funbox.setFunbox(funbox, type);
|
||||
toggleFunbox(funbox);
|
||||
setActiveFunboxButton();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import Config from "../config";
|
||||
import * as TestStats from "../test/test-stats";
|
||||
import * as TestUI from "../test/test-ui";
|
||||
import * as ManualRestart from "../test/manual-restart-tracker";
|
||||
import * as TestLogic from "../test/test-logic";
|
||||
import * as Funbox from "../test/funbox";
|
||||
import * as Funbox from "../test/funbox/funbox";
|
||||
import Page from "./page";
|
||||
import { updateTestPageAds } from "../controllers/ad-controller";
|
||||
import * as ModesNotice from "../elements/modes-notice";
|
||||
import * as Keymap from "../elements/keymap";
|
||||
|
||||
export const page = new Page(
|
||||
|
|
@ -15,6 +15,7 @@ export const page = new Page(
|
|||
async () => {
|
||||
TestLogic.restart();
|
||||
Funbox.clear();
|
||||
ModesNotice.update();
|
||||
$("#wordsInput").focusout();
|
||||
},
|
||||
async () => {
|
||||
|
|
@ -27,7 +28,7 @@ export const page = new Page(
|
|||
TestLogic.restart({
|
||||
noAnim: true,
|
||||
});
|
||||
Funbox.activate(Config.funbox);
|
||||
Funbox.activate();
|
||||
Keymap.refresh();
|
||||
},
|
||||
async () => {
|
||||
|
|
|
|||
|
|
@ -36,17 +36,18 @@ function apply(): void {
|
|||
const val = parseInt($("#customWordAmountPopup input").val() as string);
|
||||
|
||||
if (val !== null && !isNaN(val) && val >= 0 && isFinite(val)) {
|
||||
UpdateConfig.setWordCount(val as MonkeyTypes.WordsModes);
|
||||
ManualRestart.set();
|
||||
TestLogic.restart();
|
||||
if (val > 2000) {
|
||||
Notifications.add("Stay safe and take breaks!", 0);
|
||||
} else if (val == 0) {
|
||||
Notifications.add(
|
||||
"Infinite words! Make sure to use Bail Out from the command line to save your result.",
|
||||
0,
|
||||
7
|
||||
);
|
||||
if (UpdateConfig.setWordCount(val as MonkeyTypes.WordsModes)) {
|
||||
ManualRestart.set();
|
||||
TestLogic.restart();
|
||||
if (val > 2000) {
|
||||
Notifications.add("Stay safe and take breaks!", 0);
|
||||
} else if (val == 0) {
|
||||
Notifications.add(
|
||||
"Infinite words! Make sure to use Bail Out from the command line to save your result.",
|
||||
0,
|
||||
7
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Notifications.add("Custom word amount must be at least 1", 0);
|
||||
|
|
|
|||
|
|
@ -1,348 +0,0 @@
|
|||
import * as TestWords from "./test-words";
|
||||
import * as Notifications from "../elements/notifications";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as ManualRestart from "./manual-restart-tracker";
|
||||
import Config, * as UpdateConfig from "../config";
|
||||
import * as TTS from "./tts";
|
||||
import * as ModesNotice from "../elements/modes-notice";
|
||||
|
||||
let modeSaved: MonkeyTypes.FunboxObjectType | null = null;
|
||||
let memoryTimer: number | null = null;
|
||||
let memoryInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
type SetFunction = (...params: any[]) => any;
|
||||
|
||||
let settingsMemory: {
|
||||
[key: string]: { value: any; setFunction: SetFunction };
|
||||
} = {};
|
||||
|
||||
function rememberSetting(
|
||||
settingName: string,
|
||||
value: any,
|
||||
setFunction: SetFunction
|
||||
): void {
|
||||
settingsMemory[settingName] ??= {
|
||||
value,
|
||||
setFunction,
|
||||
};
|
||||
}
|
||||
|
||||
function loadMemory(): void {
|
||||
Object.keys(settingsMemory).forEach((setting) => {
|
||||
settingsMemory[setting].setFunction(settingsMemory[setting].value, true);
|
||||
});
|
||||
settingsMemory = {};
|
||||
}
|
||||
|
||||
function showMemoryTimer(): void {
|
||||
$("#typingTest #memoryTimer").stop(true, true).animate(
|
||||
{
|
||||
opacity: 1,
|
||||
},
|
||||
125
|
||||
);
|
||||
}
|
||||
|
||||
function hideMemoryTimer(): void {
|
||||
$("#typingTest #memoryTimer").stop(true, true).animate(
|
||||
{
|
||||
opacity: 0,
|
||||
},
|
||||
125
|
||||
);
|
||||
}
|
||||
|
||||
export function resetMemoryTimer(): void {
|
||||
if (memoryInterval !== null) {
|
||||
clearInterval(memoryInterval);
|
||||
memoryInterval = null;
|
||||
}
|
||||
memoryTimer = null;
|
||||
hideMemoryTimer();
|
||||
}
|
||||
|
||||
function updateMemoryTimer(sec: number): void {
|
||||
$("#typingTest #memoryTimer").text(
|
||||
`Timer left to memorise all words: ${sec}s`
|
||||
);
|
||||
}
|
||||
|
||||
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) {
|
||||
resetMemoryTimer();
|
||||
$("#wordsWrapper").addClass("hidden");
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
export function reset(): void {
|
||||
resetMemoryTimer();
|
||||
}
|
||||
|
||||
export function toggleScript(...params: string[]): void {
|
||||
if (Config.funbox === "tts") {
|
||||
TTS.speak(params[0]);
|
||||
}
|
||||
}
|
||||
|
||||
export function setFunbox(
|
||||
funbox: string,
|
||||
mode: MonkeyTypes.FunboxObjectType | null
|
||||
): boolean {
|
||||
modeSaved = mode;
|
||||
loadMemory();
|
||||
UpdateConfig.setFunbox(funbox, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function clear(): Promise<boolean> {
|
||||
$("#funBoxTheme").attr("href", ``);
|
||||
$("#words").removeClass("nospace");
|
||||
$("#words").removeClass("arrows");
|
||||
reset();
|
||||
$("#wordsWrapper").removeClass("hidden");
|
||||
ManualRestart.set();
|
||||
ModesNotice.update();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function activate(funbox?: string): Promise<boolean | undefined> {
|
||||
let mode = modeSaved;
|
||||
|
||||
if (funbox === undefined || funbox === null) {
|
||||
funbox = Config.funbox;
|
||||
}
|
||||
let funboxInfo;
|
||||
try {
|
||||
funboxInfo = await Misc.getFunbox(funbox);
|
||||
} catch (e) {
|
||||
Notifications.add(
|
||||
Misc.createErrorMessage(e, "Failed to activate funbox"),
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
$("#funBoxTheme").attr("href", ``);
|
||||
$("#words").removeClass("nospace");
|
||||
$("#words").removeClass("arrows");
|
||||
|
||||
let language;
|
||||
try {
|
||||
language = await Misc.getCurrentLanguage(Config.language);
|
||||
} catch (e) {
|
||||
Notifications.add(
|
||||
Misc.createErrorMessage(e, "Failed to activate funbox"),
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (language.ligatures) {
|
||||
if (funbox == "choo_choo" || funbox == "earthquake") {
|
||||
Notifications.add(
|
||||
"Current language does not support this funbox mode",
|
||||
0
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (funbox !== "none" && (Config.mode === "zen" || Config.mode == "quote")) {
|
||||
if (funboxInfo?.affectsWordGeneration === true) {
|
||||
Notifications.add(
|
||||
`${Misc.capitalizeFirstLetterOfEachWord(
|
||||
Config.mode
|
||||
)} mode does not support the ${funbox} funbox`,
|
||||
0
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
// if (funbox === "none") {
|
||||
|
||||
reset();
|
||||
|
||||
$("#wordsWrapper").removeClass("hidden");
|
||||
// }
|
||||
if (funbox === "none" && mode === undefined) {
|
||||
mode = null;
|
||||
} else if (
|
||||
(funbox !== "none" && mode === undefined) ||
|
||||
(funbox !== "none" && mode === null)
|
||||
) {
|
||||
let list;
|
||||
try {
|
||||
list = await Misc.getFunboxList();
|
||||
} catch (e) {
|
||||
Notifications.add(
|
||||
Misc.createErrorMessage(e, "Failed to activate funbox"),
|
||||
-1
|
||||
);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
mode = list.filter((f) => f.name === funbox)[0].type;
|
||||
}
|
||||
|
||||
ManualRestart.set();
|
||||
if (mode === "style") {
|
||||
if (funbox != undefined) {
|
||||
$("#funBoxTheme").attr("href", `funbox/${funbox}.css`);
|
||||
}
|
||||
|
||||
if (funbox === "simon_says") {
|
||||
UpdateConfig.setKeymapMode("next", true);
|
||||
}
|
||||
|
||||
if (
|
||||
(funbox === "read_ahead" ||
|
||||
funbox === "read_ahead_easy" ||
|
||||
funbox === "read_ahead_hard") &&
|
||||
Config.highlightMode === "word"
|
||||
) {
|
||||
UpdateConfig.setHighlightMode("letter", true);
|
||||
}
|
||||
} else if (mode === "script") {
|
||||
if (funbox === "tts") {
|
||||
$("#funBoxTheme").attr("href", `funbox/simon_says.css`);
|
||||
UpdateConfig.setKeymapMode("off", true);
|
||||
UpdateConfig.setHighlightMode("letter", true);
|
||||
} else if (funbox === "layoutfluid") {
|
||||
UpdateConfig.setLayout(
|
||||
Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")[0]
|
||||
: "qwerty",
|
||||
true
|
||||
);
|
||||
UpdateConfig.setKeymapLayout(
|
||||
Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")[0]
|
||||
: "qwerty",
|
||||
true
|
||||
);
|
||||
} else if (funbox === "memory") {
|
||||
UpdateConfig.setMode("words", true);
|
||||
UpdateConfig.setShowAllLines(true, true);
|
||||
if (Config.keymapMode === "next") {
|
||||
UpdateConfig.setKeymapMode("react", true);
|
||||
}
|
||||
} else if (funbox === "nospace") {
|
||||
$("#words").addClass("nospace");
|
||||
UpdateConfig.setHighlightMode("letter", true);
|
||||
} else if (funbox === "arrows") {
|
||||
$("#words").addClass("arrows");
|
||||
UpdateConfig.setHighlightMode("letter", true);
|
||||
}
|
||||
}
|
||||
// ModesNotice.update();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function rememberSettings(): Promise<void> {
|
||||
const funbox = Config.funbox;
|
||||
let mode = modeSaved;
|
||||
if (funbox === "none" && mode === undefined) {
|
||||
mode = null;
|
||||
} else if (
|
||||
(funbox !== "none" && mode === undefined) ||
|
||||
(funbox !== "none" && mode === null)
|
||||
) {
|
||||
let list;
|
||||
try {
|
||||
list = await Misc.getFunboxList();
|
||||
} catch (e) {
|
||||
Notifications.add(
|
||||
Misc.createErrorMessage(e, "Failed to remember setting"),
|
||||
-1
|
||||
);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
mode = list.filter((f) => f.name === funbox)[0].type;
|
||||
}
|
||||
if (mode === "style") {
|
||||
if (funbox === "simon_says") {
|
||||
rememberSetting(
|
||||
"keymapMode",
|
||||
Config.keymapMode,
|
||||
UpdateConfig.setKeymapMode
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
funbox === "read_ahead" ||
|
||||
funbox === "read_ahead_easy" ||
|
||||
funbox === "read_ahead_hard"
|
||||
) {
|
||||
rememberSetting(
|
||||
"highlightMode",
|
||||
Config.highlightMode,
|
||||
UpdateConfig.setHighlightMode
|
||||
);
|
||||
}
|
||||
} else if (mode === "script") {
|
||||
if (funbox === "tts") {
|
||||
rememberSetting(
|
||||
"keymapMode",
|
||||
Config.keymapMode,
|
||||
UpdateConfig.setKeymapMode
|
||||
);
|
||||
} else if (funbox === "layoutfluid") {
|
||||
rememberSetting(
|
||||
"keymapMode",
|
||||
Config.keymapMode,
|
||||
UpdateConfig.setKeymapMode
|
||||
);
|
||||
rememberSetting("layout", Config.layout, UpdateConfig.setLayout);
|
||||
rememberSetting(
|
||||
"keymapLayout",
|
||||
Config.keymapLayout,
|
||||
UpdateConfig.setKeymapLayout
|
||||
);
|
||||
} else if (funbox === "memory") {
|
||||
rememberSetting("mode", Config.mode, UpdateConfig.setMode);
|
||||
rememberSetting(
|
||||
"showAllLines",
|
||||
Config.showAllLines,
|
||||
UpdateConfig.setShowAllLines
|
||||
);
|
||||
if (Config.keymapMode === "next") {
|
||||
rememberSetting(
|
||||
"keymapMode",
|
||||
Config.keymapMode,
|
||||
UpdateConfig.setKeymapMode
|
||||
);
|
||||
}
|
||||
} else if (funbox === "nospace") {
|
||||
rememberSetting(
|
||||
"highlightMode",
|
||||
Config.highlightMode,
|
||||
UpdateConfig.setHighlightMode
|
||||
);
|
||||
} else if (funbox === "arrows") {
|
||||
rememberSetting(
|
||||
"highlightMode",
|
||||
Config.highlightMode,
|
||||
UpdateConfig.setHighlightMode
|
||||
);
|
||||
} else if (funbox === "58008") {
|
||||
rememberSetting("numbers", Config.numbers, UpdateConfig.setNumbers);
|
||||
}
|
||||
}
|
||||
}
|
||||
206
frontend/src/ts/test/funbox/funbox-list.ts
Normal file
206
frontend/src/ts/test/funbox/funbox-list.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
const list: MonkeyTypes.FunboxMetadata[] = [
|
||||
{
|
||||
name: "nausea",
|
||||
info: "I think I'm gonna be sick.",
|
||||
},
|
||||
{
|
||||
name: "round_round_baby",
|
||||
info: "...right round, like a record baby. Right, round round round.",
|
||||
},
|
||||
{
|
||||
name: "simon_says",
|
||||
info: "Type what simon says.",
|
||||
properties: ["changesWordsVisibility", "usesLayout"],
|
||||
forcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mirror",
|
||||
info: "Everything is mirrored!",
|
||||
},
|
||||
{
|
||||
name: "tts",
|
||||
info: "Listen closely.",
|
||||
properties: ["changesWordsVisibility", "speaks"],
|
||||
forcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "choo_choo",
|
||||
info: "All the letters are spinning!",
|
||||
properties: ["noLigatures", "conflictsWithSymmetricChars"],
|
||||
},
|
||||
{
|
||||
name: "arrows",
|
||||
info: "Eurobeat Intensifies...",
|
||||
properties: [
|
||||
"ignoresLanguage",
|
||||
"ignoresLayout",
|
||||
"nospace",
|
||||
"noLetters",
|
||||
"symmetricChars",
|
||||
],
|
||||
forcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rAnDoMcAsE",
|
||||
info: "I kInDa LiKe HoW iNeFfIcIeNt QwErTy Is.",
|
||||
properties: ["changesCapitalisation"],
|
||||
},
|
||||
{
|
||||
name: "capitals",
|
||||
info: "Capitalize Every Word.",
|
||||
properties: ["changesCapitalisation"],
|
||||
},
|
||||
{
|
||||
name: "layoutfluid",
|
||||
info: "Switch between layouts specified below proportionately to the length of the test.",
|
||||
properties: ["changesLayout", "noInfiniteDuration"],
|
||||
},
|
||||
{
|
||||
name: "earthquake",
|
||||
info: "Everybody get down! The words are shaking!",
|
||||
properties: ["noLigatures"],
|
||||
},
|
||||
{
|
||||
name: "space_balls",
|
||||
info: "In a galaxy far far away.",
|
||||
},
|
||||
{
|
||||
name: "gibberish",
|
||||
info: "Anvbuefl dizzs eoos alsb?",
|
||||
properties: ["ignoresLanguage", "unspeakable"],
|
||||
},
|
||||
{
|
||||
name: "58008",
|
||||
alias: "numbers",
|
||||
info: "A special mode for accountants.",
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "noLetters"],
|
||||
forcedConfig: {
|
||||
numbers: [false],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ascii",
|
||||
info: "Where was the ampersand again?. Only ASCII characters.",
|
||||
properties: ["ignoresLanguage", "noLetters", "unspeakable"],
|
||||
forcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "specials",
|
||||
info: "!@#$%^&*. Only special characters.",
|
||||
properties: ["ignoresLanguage", "noLetters", "unspeakable"],
|
||||
forcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "plus_one",
|
||||
info: "React quickly! Only one future word is visible.",
|
||||
properties: ["changesWordsVisibility", "toPush:2", "noInfiniteDuration"],
|
||||
},
|
||||
{
|
||||
name: "plus_two",
|
||||
info: "Only two future words are visible.",
|
||||
properties: ["changesWordsVisibility", "toPush:3", "noInfiniteDuration"],
|
||||
},
|
||||
{
|
||||
name: "read_ahead_easy",
|
||||
info: "Only the current word is invisible.",
|
||||
properties: ["changesWordsVisibility"],
|
||||
forcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "read_ahead",
|
||||
info: "Current and the next word are invisible!",
|
||||
properties: ["changesWordsVisibility"],
|
||||
forcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "read_ahead_hard",
|
||||
info: "Current and the next two words are invisible!",
|
||||
properties: ["changesWordsVisibility"],
|
||||
forcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "memory",
|
||||
info: "Test your memory. Remember the words and type them blind.",
|
||||
properties: ["changesWordsVisibility", "noInfiniteDuration"],
|
||||
forcedConfig: {
|
||||
mode: ["words", "quote", "custom"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nospace",
|
||||
info: "Whoneedsspacesanyway?",
|
||||
properties: ["nospace"],
|
||||
forcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "poetry",
|
||||
info: "Practice typing some beautiful prose.",
|
||||
properties: ["noInfiniteDuration"],
|
||||
forcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wikipedia",
|
||||
info: "Practice typing wikipedia sections.",
|
||||
properties: ["noInfiniteDuration"],
|
||||
forcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "weakspot",
|
||||
info: "Focus on slow and mistyped letters.",
|
||||
},
|
||||
{
|
||||
name: "pseudolang",
|
||||
info: "Nonsense words that look like the current language.",
|
||||
properties: ["unspeakable"],
|
||||
},
|
||||
];
|
||||
|
||||
export function getAll(): MonkeyTypes.FunboxMetadata[] {
|
||||
return list;
|
||||
}
|
||||
|
||||
export function get(config: string): MonkeyTypes.FunboxMetadata[] {
|
||||
const funboxes: MonkeyTypes.FunboxMetadata[] = [];
|
||||
for (const i of config.split("#")) {
|
||||
const f = list.find((f) => f.name === i);
|
||||
if (f) funboxes.push(f);
|
||||
}
|
||||
return funboxes;
|
||||
}
|
||||
|
||||
export function setFunboxFunctions(
|
||||
name: string,
|
||||
obj: MonkeyTypes.FunboxFunctions
|
||||
): void {
|
||||
const fb = list.find((f) => f.name === name);
|
||||
if (!fb) throw new Error(`Funbox ${name} not found.`);
|
||||
fb.functions = obj;
|
||||
}
|
||||
23
frontend/src/ts/test/funbox/funbox-memory.ts
Normal file
23
frontend/src/ts/test/funbox/funbox-memory.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
type SetFunction = (...params: any[]) => any;
|
||||
|
||||
let settingsMemory: {
|
||||
[key: string]: { value: any; setFunction: SetFunction };
|
||||
} = {};
|
||||
|
||||
export function save(
|
||||
settingName: string,
|
||||
value: any,
|
||||
setFunction: SetFunction
|
||||
): void {
|
||||
settingsMemory[settingName] ??= {
|
||||
value,
|
||||
setFunction,
|
||||
};
|
||||
}
|
||||
|
||||
export function load(): void {
|
||||
Object.keys(settingsMemory).forEach((setting) => {
|
||||
settingsMemory[setting].setFunction(settingsMemory[setting].value, true);
|
||||
});
|
||||
settingsMemory = {};
|
||||
}
|
||||
298
frontend/src/ts/test/funbox/funbox-validation.ts
Normal file
298
frontend/src/ts/test/funbox/funbox-validation.ts
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
import * as FunboxList from "./funbox-list";
|
||||
import * as Notifications from "../../elements/notifications";
|
||||
import * as Misc from "../../utils/misc";
|
||||
|
||||
export function checkFunboxForcedConfigs(
|
||||
key: string,
|
||||
value: MonkeyTypes.ConfigValues,
|
||||
funbox: string
|
||||
): {
|
||||
result: boolean;
|
||||
forcedConfigs?: Array<MonkeyTypes.ConfigValues>;
|
||||
} {
|
||||
if (FunboxList.get(funbox).length === 0) return { result: true };
|
||||
|
||||
if (key === "words" || key === "time") {
|
||||
if (value == 0) {
|
||||
if (funbox === "nospace") {
|
||||
console.log("break");
|
||||
}
|
||||
const fb = FunboxList.get(funbox).filter((f) =>
|
||||
f.properties?.includes("noInfiniteDuration")
|
||||
);
|
||||
if (fb.length > 0) {
|
||||
return {
|
||||
result: false,
|
||||
forcedConfigs: [key === "words" ? 10 : 15],
|
||||
};
|
||||
} else {
|
||||
return { result: true };
|
||||
}
|
||||
} else {
|
||||
return { result: true };
|
||||
}
|
||||
} else {
|
||||
const forcedConfigs: Record<string, MonkeyTypes.ConfigValues[]> = {};
|
||||
// collect all forced configs
|
||||
for (const fb of FunboxList.get(funbox)) {
|
||||
if (fb.forcedConfig) {
|
||||
//push keys to forcedConfigs, if they don't exist. if they do, intersect the values
|
||||
for (const key in fb.forcedConfig) {
|
||||
if (forcedConfigs[key] === undefined) {
|
||||
forcedConfigs[key] = fb.forcedConfig[key];
|
||||
} else {
|
||||
forcedConfigs[key] = Misc.intersect(
|
||||
forcedConfigs[key],
|
||||
fb.forcedConfig[key],
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//check if the key is in forcedConfigs, if it is check the value, if its not, return true
|
||||
if (forcedConfigs[key] === undefined) {
|
||||
return { result: true };
|
||||
} else {
|
||||
if (forcedConfigs[key].length === 0) {
|
||||
throw new Error("No intersection of forced configs");
|
||||
}
|
||||
return {
|
||||
result: forcedConfigs[key].includes(<MonkeyTypes.ConfigValues>value),
|
||||
forcedConfigs: forcedConfigs[key],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// function: canSetConfigWithCurrentFunboxes
|
||||
// checks using checkFunboxForcedConfigs. if it returns true, return true
|
||||
// if it returns false, show a notification and return false
|
||||
export function canSetConfigWithCurrentFunboxes(
|
||||
key: string,
|
||||
value: MonkeyTypes.ConfigValues,
|
||||
funbox: string,
|
||||
noNotification = false
|
||||
): boolean {
|
||||
let errorCount = 0;
|
||||
if (key === "mode") {
|
||||
let fb: MonkeyTypes.FunboxMetadata[] = [];
|
||||
fb = fb.concat(
|
||||
FunboxList.get(funbox).filter(
|
||||
(f) =>
|
||||
f.forcedConfig?.["mode"] !== undefined &&
|
||||
!f.forcedConfig?.["mode"].includes(value)
|
||||
)
|
||||
);
|
||||
if (value === "zen") {
|
||||
fb = fb.concat(
|
||||
FunboxList.get(funbox).filter(
|
||||
(f) =>
|
||||
f.functions?.getWord ||
|
||||
f.functions?.pullSection ||
|
||||
f.functions?.alterText ||
|
||||
f.functions?.withWords ||
|
||||
f.properties?.includes("changesCapitalisation") ||
|
||||
f.properties?.includes("nospace") ||
|
||||
f.properties?.find((fp) => fp.startsWith("toPush:")) ||
|
||||
f.properties?.includes("changesWordsVisibility") ||
|
||||
f.properties?.includes("speaks") ||
|
||||
f.properties?.includes("changesLayout")
|
||||
)
|
||||
);
|
||||
}
|
||||
if (value === "quote" || value == "custom") {
|
||||
fb = fb.concat(
|
||||
FunboxList.get(funbox).filter(
|
||||
(f) =>
|
||||
f.functions?.getWord ||
|
||||
f.functions?.pullSection ||
|
||||
f.functions?.withWords
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (fb.length > 0) {
|
||||
errorCount += 1;
|
||||
}
|
||||
}
|
||||
if (key === "words" || key === "time") {
|
||||
if (!checkFunboxForcedConfigs(key, value, funbox).result) {
|
||||
if (!noNotification) {
|
||||
Notifications.add("Active funboxes do not support infinite tests", 0);
|
||||
return false;
|
||||
} else {
|
||||
errorCount += 1;
|
||||
}
|
||||
}
|
||||
} else if (!checkFunboxForcedConfigs(key, value, funbox).result) {
|
||||
errorCount += 1;
|
||||
}
|
||||
|
||||
if (errorCount > 0) {
|
||||
if (!noNotification) {
|
||||
Notifications.add(
|
||||
`You can't set ${Misc.camelCaseToWords(
|
||||
key
|
||||
)} to ${value} with currently active funboxes.`,
|
||||
0,
|
||||
5
|
||||
);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function canSetFunboxWithConfig(
|
||||
funbox: string,
|
||||
config: MonkeyTypes.Config
|
||||
): boolean {
|
||||
let funboxToCheck = config.funbox;
|
||||
if (funboxToCheck === "none") {
|
||||
funboxToCheck = funbox;
|
||||
} else {
|
||||
funboxToCheck += "#" + funbox;
|
||||
}
|
||||
let errorCount = 0;
|
||||
for (const [configKey, configValue] of Object.entries(config)) {
|
||||
if (
|
||||
!canSetConfigWithCurrentFunboxes(
|
||||
configKey,
|
||||
configValue,
|
||||
funboxToCheck,
|
||||
true
|
||||
)
|
||||
) {
|
||||
errorCount += 1;
|
||||
}
|
||||
}
|
||||
if (errorCount > 0) {
|
||||
Notifications.add(
|
||||
`You can't enable ${funbox.replace(
|
||||
/_/g,
|
||||
" "
|
||||
)} with currently active config.`,
|
||||
0,
|
||||
5
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function areFunboxesCompatible(
|
||||
funboxes: string,
|
||||
withFunbox?: string
|
||||
): boolean {
|
||||
if (withFunbox === "none" || funboxes === "none") return true;
|
||||
let funboxesToCheck = FunboxList.get(funboxes);
|
||||
if (withFunbox !== undefined) {
|
||||
funboxesToCheck = funboxesToCheck.concat(
|
||||
FunboxList.getAll().filter((f) => f.name == withFunbox)
|
||||
);
|
||||
}
|
||||
|
||||
const allFunboxesAreValid =
|
||||
FunboxList.get(funboxes).filter(
|
||||
(f) => funboxes.split("#").find((cf) => cf == f.name) !== undefined
|
||||
).length == funboxes.split("#").length;
|
||||
const oneWordModifierMax =
|
||||
funboxesToCheck.filter(
|
||||
(f) =>
|
||||
f.functions?.getWord ||
|
||||
f.functions?.pullSection ||
|
||||
f.functions?.withWords
|
||||
).length <= 1;
|
||||
const layoutUsability =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "changesLayout")
|
||||
).length == 0 ||
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "ignoresLayout" || fp == "usesLayout")
|
||||
).length == 0;
|
||||
const oneNospaceOrToPushMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "nospace" || fp.startsWith("toPush"))
|
||||
).length <= 1;
|
||||
const oneChangesWordsVisibilityMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "changesWordsVisibility")
|
||||
).length <= 1;
|
||||
const capitalisationChangePosibility =
|
||||
funboxesToCheck.filter((f) => f.properties?.find((fp) => fp == "noLetters"))
|
||||
.length == 0 ||
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "changesCapitalisation")
|
||||
).length == 0;
|
||||
const noConflictsWithSymmetricChars =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "conflictsWithSymmetricChars")
|
||||
).length == 0 ||
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "symmetricChars")
|
||||
).length == 0;
|
||||
const canSpeak =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "speaks" || fp == "unspeakable")
|
||||
).length <= 1;
|
||||
const hasLanguageToSpeak =
|
||||
funboxesToCheck.filter((f) => f.properties?.find((fp) => fp == "speaks"))
|
||||
.length == 0 ||
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp == "ignoresLanguage")
|
||||
).length == 0;
|
||||
const oneToPushOrPullSectionMax =
|
||||
funboxesToCheck.filter(
|
||||
(f) =>
|
||||
f.properties?.find((fp) => fp.startsWith("toPush:")) ||
|
||||
f.functions?.pullSection
|
||||
).length <= 1;
|
||||
const oneApplyCSSMax =
|
||||
funboxesToCheck.filter((f) => f.functions?.applyCSS).length <= 1;
|
||||
const onePunctuateWordMax =
|
||||
funboxesToCheck.filter((f) => f.functions?.punctuateWord).length <= 1;
|
||||
const oneCharCheckerMax =
|
||||
funboxesToCheck.filter((f) => f.functions?.isCharCorrect).length <= 1;
|
||||
const oneCharReplacerMax =
|
||||
funboxesToCheck.filter((f) => f.functions?.getWordHtml).length <= 1;
|
||||
const allowedConfig = {} as MonkeyTypes.FunboxForcedConfig;
|
||||
let noConfigConflicts = true;
|
||||
for (const f of funboxesToCheck) {
|
||||
if (!f.forcedConfig) continue;
|
||||
for (const key in f.forcedConfig) {
|
||||
if (allowedConfig[key]) {
|
||||
if (
|
||||
Misc.intersect(allowedConfig[key], f.forcedConfig[key], true)
|
||||
.length === 0
|
||||
) {
|
||||
noConfigConflicts = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
allowedConfig[key] = f.forcedConfig[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
allFunboxesAreValid &&
|
||||
oneWordModifierMax &&
|
||||
layoutUsability &&
|
||||
oneNospaceOrToPushMax &&
|
||||
oneChangesWordsVisibilityMax &&
|
||||
capitalisationChangePosibility &&
|
||||
noConflictsWithSymmetricChars &&
|
||||
canSpeak &&
|
||||
hasLanguageToSpeak &&
|
||||
oneToPushOrPullSectionMax &&
|
||||
oneApplyCSSMax &&
|
||||
onePunctuateWordMax &&
|
||||
oneCharCheckerMax &&
|
||||
oneCharReplacerMax &&
|
||||
noConfigConflicts
|
||||
);
|
||||
}
|
||||
646
frontend/src/ts/test/funbox/funbox.ts
Normal file
646
frontend/src/ts/test/funbox/funbox.ts
Normal file
|
|
@ -0,0 +1,646 @@
|
|||
import * as Notifications from "../../elements/notifications";
|
||||
import * as Misc from "../../utils/misc";
|
||||
import * as ManualRestart from "../manual-restart-tracker";
|
||||
import Config, * as UpdateConfig from "../../config";
|
||||
import * as MemoryTimer from "./memory-funbox-timer";
|
||||
import * as FunboxMemory from "./funbox-memory";
|
||||
import * as FunboxList from "./funbox-list";
|
||||
import { save } from "./funbox-memory";
|
||||
import * as TTSEvent from "../../observables/tts-event";
|
||||
import * as KeymapEvent from "../../observables/keymap-event";
|
||||
import * as TestWords from "../test-words";
|
||||
import * as TestInput from "../test-input";
|
||||
import * as WeakSpot from "../weak-spot";
|
||||
import { getPoem } from "../poetry";
|
||||
import { getSection } from "../wikipedia";
|
||||
import {
|
||||
areFunboxesCompatible,
|
||||
checkFunboxForcedConfigs,
|
||||
} from "./funbox-validation";
|
||||
|
||||
const prefixSize = 2;
|
||||
|
||||
class CharDistribution {
|
||||
public chars: { [char: string]: number };
|
||||
public count: number;
|
||||
constructor() {
|
||||
this.chars = {};
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
public addChar(char: string): void {
|
||||
this.count++;
|
||||
if (char in this.chars) {
|
||||
this.chars[char]++;
|
||||
} else {
|
||||
this.chars[char] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public randomChar(): string {
|
||||
const randomIndex = Misc.randomIntFromRange(0, this.count - 1);
|
||||
let runningCount = 0;
|
||||
for (const [char, charCount] of Object.entries(this.chars)) {
|
||||
runningCount += charCount;
|
||||
if (runningCount > randomIndex) {
|
||||
return char;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(this.chars)[0];
|
||||
}
|
||||
}
|
||||
|
||||
class PseudolangWordGenerator extends Misc.Wordset {
|
||||
public ngrams: { [prefix: string]: CharDistribution } = {};
|
||||
constructor(words: string[]) {
|
||||
super(words);
|
||||
// Can generate an unbounded number of words in theory.
|
||||
this.length = Infinity;
|
||||
|
||||
for (let word of words) {
|
||||
// Mark the end of each word with a space.
|
||||
word += " ";
|
||||
let prefix = "";
|
||||
for (const c of word) {
|
||||
// Add `c` to the distribution of chars that can come after `prefix`.
|
||||
if (!(prefix in this.ngrams)) {
|
||||
this.ngrams[prefix] = new CharDistribution();
|
||||
}
|
||||
this.ngrams[prefix].addChar(c);
|
||||
prefix = (prefix + c).substr(-prefixSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override randomWord(): string {
|
||||
let word = "";
|
||||
for (;;) {
|
||||
const prefix = word.substr(-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.
|
||||
word = "";
|
||||
continue;
|
||||
}
|
||||
// Pick a random char from the distribution that comes after `prefix`.
|
||||
const nextChar = charDistribution.randomChar();
|
||||
if (nextChar == " ") {
|
||||
// A space marks the end of the word, so stop generating and return.
|
||||
break;
|
||||
}
|
||||
word += nextChar;
|
||||
}
|
||||
return word;
|
||||
}
|
||||
}
|
||||
|
||||
FunboxList.setFunboxFunctions("nausea", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/nausea.css`);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("round_round_baby", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/round_round_baby.css`);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("simon_says", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/simon_says.css`);
|
||||
},
|
||||
applyConfig(): void {
|
||||
UpdateConfig.setKeymapMode("next", true);
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("keymapMode", Config.keymapMode, UpdateConfig.setKeymapMode);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("mirror", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/mirror.css`);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("tts", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/simon_says.css`);
|
||||
},
|
||||
applyConfig(): void {
|
||||
UpdateConfig.setKeymapMode("off", true);
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("keymapMode", Config.keymapMode, UpdateConfig.setKeymapMode);
|
||||
},
|
||||
toggleScript(params: string[]): void {
|
||||
if (window.speechSynthesis == undefined) {
|
||||
Notifications.add("Failed to load text-to-speech script", -1);
|
||||
return;
|
||||
}
|
||||
TTSEvent.dispatch(params[0]);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("choo_choo", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/choo_choo.css`);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("arrows", {
|
||||
getWord(): string {
|
||||
return Misc.getArrows();
|
||||
},
|
||||
applyConfig(): void {
|
||||
$("#words").addClass("arrows");
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("highlightMode", Config.highlightMode, UpdateConfig.setHighlightMode);
|
||||
},
|
||||
handleChar(char: string): string {
|
||||
if (char === "a" || char === "ArrowLeft") {
|
||||
return "←";
|
||||
}
|
||||
if (char === "s" || char === "ArrowDown") {
|
||||
return "↓";
|
||||
}
|
||||
if (char === "w" || char === "ArrowUp") {
|
||||
return "↑";
|
||||
}
|
||||
if (char === "d" || char === "ArrowRight") {
|
||||
return "→";
|
||||
}
|
||||
return char;
|
||||
},
|
||||
isCharCorrect(char: string, originalChar: string): boolean {
|
||||
if ((char === "a" || char === "ArrowLeft") && originalChar === "←") {
|
||||
return true;
|
||||
}
|
||||
if ((char === "s" || char === "ArrowDown") && originalChar === "↓") {
|
||||
return true;
|
||||
}
|
||||
if ((char === "w" || char === "ArrowUp") && originalChar === "↑") {
|
||||
return true;
|
||||
}
|
||||
if ((char === "d" || char === "ArrowRight") && originalChar === "→") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
async preventDefaultEvent(
|
||||
event: JQuery.KeyDownEvent<Document, null, Document, Document>
|
||||
): Promise<boolean> {
|
||||
// TODO What's better?
|
||||
// return /Arrow/i.test(event.key);
|
||||
return ["ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown"].includes(
|
||||
event.key
|
||||
);
|
||||
},
|
||||
getWordHtml(char: string, letterTag?: boolean): string {
|
||||
let retval = "";
|
||||
if (char === "↑") {
|
||||
if (letterTag) retval += `<letter>`;
|
||||
retval += `<i class="fas fa-arrow-up"></i>`;
|
||||
if (letterTag) retval += `</letter>`;
|
||||
}
|
||||
if (char === "↓") {
|
||||
if (letterTag) retval += `<letter>`;
|
||||
retval += `<i class="fas fa-arrow-down"></i>`;
|
||||
if (letterTag) retval += `</letter>`;
|
||||
}
|
||||
if (char === "←") {
|
||||
if (letterTag) retval += `<letter>`;
|
||||
retval += `<i class="fas fa-arrow-left"></i>`;
|
||||
if (letterTag) retval += `</letter>`;
|
||||
}
|
||||
if (char === "→") {
|
||||
if (letterTag) retval += `<letter>`;
|
||||
retval += `<i class="fas fa-arrow-right"></i>`;
|
||||
if (letterTag) retval += `</letter>`;
|
||||
}
|
||||
return retval;
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("rAnDoMcAsE", {
|
||||
alterText(word: string): string {
|
||||
let randomcaseword = word[0];
|
||||
for (let i = 1; i < word.length; i++) {
|
||||
if (randomcaseword[i - 1] == randomcaseword[i - 1].toUpperCase()) {
|
||||
randomcaseword += word[i].toLowerCase();
|
||||
} else {
|
||||
randomcaseword += word[i].toUpperCase();
|
||||
}
|
||||
}
|
||||
return randomcaseword;
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("capitals", {
|
||||
alterText(word: string): string {
|
||||
return Misc.capitalizeFirstLetterOfEachWord(word);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("layoutfluid", {
|
||||
applyConfig(): void {
|
||||
UpdateConfig.setLayout(
|
||||
Config.customLayoutfluid.split("#")[0]
|
||||
? Config.customLayoutfluid.split("#")[0]
|
||||
: "qwerty",
|
||||
true
|
||||
);
|
||||
UpdateConfig.setKeymapLayout(
|
||||
Config.customLayoutfluid.split("#")[0]
|
||||
? Config.customLayoutfluid.split("#")[0]
|
||||
: "qwerty",
|
||||
true
|
||||
);
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("keymapMode", Config.keymapMode, UpdateConfig.setKeymapMode);
|
||||
save("layout", Config.layout, UpdateConfig.setLayout);
|
||||
save("keymapLayout", Config.keymapLayout, UpdateConfig.setKeymapLayout);
|
||||
},
|
||||
handleSpace(): void {
|
||||
if (Config.mode !== "time") {
|
||||
// here I need to check if Config.customLayoutFluid exists because of my
|
||||
// scuffed solution of returning whenever value is undefined in the setCustomLayoutfluid function
|
||||
const layouts: string[] = Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")
|
||||
: ["qwerty", "dvorak", "colemak"];
|
||||
let index = 0;
|
||||
const outOf: number = TestWords.words.length;
|
||||
index = Math.floor(
|
||||
(TestInput.input.history.length + 1) / (outOf / layouts.length)
|
||||
);
|
||||
if (Config.layout !== layouts[index] && layouts[index] !== undefined) {
|
||||
Notifications.add(`--- !!! ${layouts[index]} !!! ---`, 0);
|
||||
}
|
||||
if (layouts[index]) {
|
||||
UpdateConfig.setLayout(layouts[index]);
|
||||
UpdateConfig.setKeymapLayout(layouts[index]);
|
||||
}
|
||||
KeymapEvent.highlight(
|
||||
TestWords.words
|
||||
.getCurrent()
|
||||
.charAt(TestInput.input.current.length)
|
||||
.toString()
|
||||
);
|
||||
}
|
||||
},
|
||||
getResultContent(): string {
|
||||
return Config.customLayoutfluid.replace(/#/g, " ");
|
||||
},
|
||||
restart(): void {
|
||||
if (this.applyConfig) this.applyConfig();
|
||||
KeymapEvent.highlight(
|
||||
TestWords.words
|
||||
.getCurrent()
|
||||
.substring(
|
||||
TestInput.input.current.length,
|
||||
TestInput.input.current.length + 1
|
||||
)
|
||||
.toString()
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("earthquake", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/earthquake.css`);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("space_balls", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/space_balls.css`);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("gibberish", {
|
||||
getWord(): string {
|
||||
return Misc.getGibberish();
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("58008", {
|
||||
getWord(): string {
|
||||
let num = Misc.getNumbers(7);
|
||||
if (Config.language.startsWith("kurdish")) {
|
||||
num = Misc.convertNumberToArabic(num);
|
||||
} else if (Config.language.startsWith("nepali")) {
|
||||
num = Misc.convertNumberToNepali(num);
|
||||
}
|
||||
return num;
|
||||
},
|
||||
punctuateWord(word: string): string {
|
||||
if (word.length > 3) {
|
||||
if (Math.random() < 0.5) {
|
||||
word = Misc.setCharAt(
|
||||
word,
|
||||
Misc.randomIntFromRange(1, word.length - 2),
|
||||
"."
|
||||
);
|
||||
}
|
||||
if (Math.random() < 0.75) {
|
||||
const index = Misc.randomIntFromRange(1, word.length - 2);
|
||||
if (
|
||||
word[index - 1] !== "." &&
|
||||
word[index + 1] !== "." &&
|
||||
word[index + 1] !== "0"
|
||||
) {
|
||||
const special = Misc.randomElementFromArray(["/", "*", "-", "+"]);
|
||||
word = Misc.setCharAt(word, index, special);
|
||||
}
|
||||
}
|
||||
}
|
||||
return word;
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("numbers", Config.numbers, UpdateConfig.setNumbers);
|
||||
},
|
||||
handleChar(char: string): string {
|
||||
if (char === "\n") {
|
||||
return " ";
|
||||
}
|
||||
return char;
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("ascii", {
|
||||
getWord(): string {
|
||||
return Misc.getASCII();
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("specials", {
|
||||
getWord(): string {
|
||||
return Misc.getSpecials();
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("read_ahead_easy", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/read_ahead_easy.css`);
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("highlightMode", Config.highlightMode, UpdateConfig.setHighlightMode);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("read_ahead", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/read_ahead.css`);
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("highlightMode", Config.highlightMode, UpdateConfig.setHighlightMode);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("read_ahead_hard", {
|
||||
applyCSS(): void {
|
||||
$("#funBoxTheme").attr("href", `funbox/read_ahead_hard.css`);
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("highlightMode", Config.highlightMode, UpdateConfig.setHighlightMode);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("memory", {
|
||||
applyConfig(): void {
|
||||
$("#wordsWrapper").addClass("hidden");
|
||||
UpdateConfig.setShowAllLines(true, true);
|
||||
if (Config.keymapMode === "next") {
|
||||
UpdateConfig.setKeymapMode("react", true);
|
||||
}
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("mode", Config.mode, UpdateConfig.setMode);
|
||||
save("showAllLines", Config.showAllLines, UpdateConfig.setShowAllLines);
|
||||
if (Config.keymapMode === "next") {
|
||||
save("keymapMode", Config.keymapMode, UpdateConfig.setKeymapMode);
|
||||
}
|
||||
},
|
||||
start(): void {
|
||||
MemoryTimer.reset();
|
||||
$("#wordsWrapper").addClass("hidden");
|
||||
},
|
||||
restart(): void {
|
||||
MemoryTimer.start();
|
||||
if (Config.keymapMode === "next") {
|
||||
UpdateConfig.setKeymapMode("react");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("nospace", {
|
||||
applyConfig(): void {
|
||||
$("#words").addClass("nospace");
|
||||
},
|
||||
rememberSettings(): void {
|
||||
save("highlightMode", Config.highlightMode, UpdateConfig.setHighlightMode);
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("poetry", {
|
||||
async pullSection(): Promise<Misc.Section | false> {
|
||||
return getPoem();
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("wikipedia", {
|
||||
async pullSection(lang?: string): Promise<Misc.Section | false> {
|
||||
return getSection(lang ? lang : "english");
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("weakspot", {
|
||||
getWord(wordset?: Misc.Wordset): string {
|
||||
if (wordset !== undefined) return WeakSpot.getWord(wordset);
|
||||
else return "";
|
||||
},
|
||||
});
|
||||
|
||||
FunboxList.setFunboxFunctions("pseudolang", {
|
||||
async withWords(words?: string[]): Promise<Misc.Wordset> {
|
||||
if (words !== undefined) return new PseudolangWordGenerator(words);
|
||||
return new Misc.Wordset([]);
|
||||
},
|
||||
});
|
||||
|
||||
export function toggleScript(...params: string[]): void {
|
||||
FunboxList.get(Config.funbox).forEach((funbox) => {
|
||||
if (funbox.functions?.toggleScript) funbox.functions.toggleScript(params);
|
||||
});
|
||||
}
|
||||
|
||||
export function setFunbox(funbox: string): boolean {
|
||||
FunboxMemory.load();
|
||||
UpdateConfig.setFunbox(funbox, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function toggleFunbox(funbox: string): boolean {
|
||||
if (funbox == "none") setFunbox("none");
|
||||
if (
|
||||
!areFunboxesCompatible(Config.funbox, funbox) &&
|
||||
!Config.funbox.split("#").includes(funbox)
|
||||
) {
|
||||
Notifications.add(
|
||||
`${Misc.capitalizeFirstLetter(
|
||||
funbox.replace(/_/g, " ")
|
||||
)} funbox is not compatible with the current funbox selection`,
|
||||
0
|
||||
);
|
||||
return true;
|
||||
}
|
||||
FunboxMemory.load();
|
||||
const e = UpdateConfig.toggleFunbox(funbox, false);
|
||||
if (e === false || e === true) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function clear(): Promise<boolean> {
|
||||
$("#funBoxTheme").attr("href", ``);
|
||||
$("#words").removeClass("nospace");
|
||||
$("#words").removeClass("arrows");
|
||||
$("#wordsWrapper").removeClass("hidden");
|
||||
MemoryTimer.reset();
|
||||
ManualRestart.set();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function activate(funbox?: string): Promise<boolean | undefined> {
|
||||
if (funbox === undefined || funbox === null) {
|
||||
funbox = Config.funbox;
|
||||
} else if (Config.funbox != funbox) {
|
||||
Config.funbox = funbox;
|
||||
}
|
||||
|
||||
// The configuration might be edited with dev tools,
|
||||
// so we need to double check its validity
|
||||
if (!areFunboxesCompatible(Config.funbox)) {
|
||||
Notifications.add(
|
||||
Misc.createErrorMessage(
|
||||
undefined,
|
||||
`Failed to activate funbox: funboxes ${Config.funbox.replace(
|
||||
/_/g,
|
||||
" "
|
||||
)} are not compatible`
|
||||
),
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
MemoryTimer.reset();
|
||||
$("#wordsWrapper").removeClass("hidden");
|
||||
$("#funBoxTheme").attr("href", ``);
|
||||
$("#words").removeClass("nospace");
|
||||
$("#words").removeClass("arrows");
|
||||
|
||||
let language;
|
||||
try {
|
||||
language = await Misc.getCurrentLanguage(Config.language);
|
||||
} catch (e) {
|
||||
Notifications.add(
|
||||
Misc.createErrorMessage(e, "Failed to activate funbox"),
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (language.ligatures) {
|
||||
if (
|
||||
FunboxList.get(Config.funbox).find((f) =>
|
||||
f.properties?.includes("noLigatures")
|
||||
)
|
||||
) {
|
||||
Notifications.add(
|
||||
"Current language does not support this funbox mode",
|
||||
0
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let canSetSoFar = true;
|
||||
|
||||
for (const [configKey, configValue] of Object.entries(Config)) {
|
||||
const check = checkFunboxForcedConfigs(
|
||||
configKey,
|
||||
configValue,
|
||||
Config.funbox
|
||||
);
|
||||
if (check.result === true) continue;
|
||||
if (check.result === false) {
|
||||
if (check.forcedConfigs && check.forcedConfigs.length > 0) {
|
||||
if (configKey === "mode") {
|
||||
UpdateConfig.setMode(check.forcedConfigs[0] as MonkeyTypes.Mode);
|
||||
}
|
||||
if (configKey === "words") {
|
||||
UpdateConfig.setWordCount(check.forcedConfigs[0] as number);
|
||||
}
|
||||
if (configKey === "time") {
|
||||
UpdateConfig.setTimeConfig(check.forcedConfigs[0] as number);
|
||||
}
|
||||
if (configKey === "punctuation") {
|
||||
UpdateConfig.setPunctuation(check.forcedConfigs[0] as boolean);
|
||||
}
|
||||
if (configKey === "numbers") {
|
||||
UpdateConfig.setNumbers(check.forcedConfigs[0] as boolean);
|
||||
}
|
||||
if (configKey === "highlightMode") {
|
||||
UpdateConfig.setHighlightMode(
|
||||
check.forcedConfigs[0] as MonkeyTypes.HighlightMode
|
||||
);
|
||||
}
|
||||
} else {
|
||||
canSetSoFar = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!canSetSoFar) {
|
||||
if (Config.funbox.includes("#")) {
|
||||
Notifications.add(
|
||||
`Failed to activate funboxes ${Config.funbox}: no intersecting forced configs. Disabling funbox`,
|
||||
-1
|
||||
);
|
||||
} else {
|
||||
Notifications.add(
|
||||
`Failed to activate funbox ${Config.funbox}: no forced configs. Disabling funbox`,
|
||||
-1
|
||||
);
|
||||
}
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
|
||||
ManualRestart.set();
|
||||
FunboxList.get(Config.funbox).forEach(async (funbox) => {
|
||||
if (funbox.functions?.applyCSS) funbox.functions.applyCSS();
|
||||
if (funbox.functions?.applyConfig) funbox.functions.applyConfig();
|
||||
});
|
||||
// ModesNotice.update();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function rememberSettings(): Promise<void> {
|
||||
FunboxList.get(Config.funbox).forEach(async (funbox) => {
|
||||
if (funbox.functions?.rememberSettings) funbox.functions.rememberSettings();
|
||||
});
|
||||
}
|
||||
53
frontend/src/ts/test/funbox/memory-funbox-timer.ts
Normal file
53
frontend/src/ts/test/funbox/memory-funbox-timer.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import * as TestWords from "../test-words";
|
||||
|
||||
let memoryTimer: number | null = null;
|
||||
let memoryInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
export function show(): void {
|
||||
$("#typingTest #memoryTimer").stop(true, true).animate(
|
||||
{
|
||||
opacity: 1,
|
||||
},
|
||||
125
|
||||
);
|
||||
}
|
||||
|
||||
export function hide(): void {
|
||||
$("#typingTest #memoryTimer").stop(true, true).animate(
|
||||
{
|
||||
opacity: 0,
|
||||
},
|
||||
125
|
||||
);
|
||||
}
|
||||
|
||||
export function reset(): void {
|
||||
if (memoryInterval !== null) {
|
||||
clearInterval(memoryInterval);
|
||||
memoryInterval = null;
|
||||
}
|
||||
memoryTimer = null;
|
||||
hide();
|
||||
}
|
||||
|
||||
export function start(): void {
|
||||
reset();
|
||||
memoryTimer = Math.round(Math.pow(TestWords.words.length, 1.2));
|
||||
update(memoryTimer);
|
||||
show();
|
||||
memoryInterval = setInterval(() => {
|
||||
if (memoryTimer === null) return;
|
||||
memoryTimer -= 1;
|
||||
memoryTimer == 0 ? hide() : update(memoryTimer);
|
||||
if (memoryTimer <= 0) {
|
||||
reset();
|
||||
$("#wordsWrapper").addClass("hidden");
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
export function update(sec: number): void {
|
||||
$("#typingTest #memoryTimer").text(
|
||||
`Timer left to memorise all words: ${sec}s`
|
||||
);
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import axios from "axios";
|
||||
import { Section } from "../utils/misc";
|
||||
|
||||
const bannedChars = ["—", "_", " "];
|
||||
const maxWords = 100;
|
||||
const apiURL = "https://poetrydb.org/random";
|
||||
|
||||
export class Poem {
|
||||
public title: string;
|
||||
public author: string;
|
||||
public words: string[];
|
||||
export class Poem extends Section {
|
||||
constructor(title: string, author: string, words: string[]) {
|
||||
super(title, author, words);
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
this.words = words;
|
||||
|
|
@ -45,7 +44,7 @@ interface PoemObject {
|
|||
author: string;
|
||||
}
|
||||
|
||||
export async function getPoem(): Promise<Poem | false> {
|
||||
export async function getPoem(): Promise<Section | false> {
|
||||
console.log("Getting poem");
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import * as TestConfig from "./test-config";
|
|||
import { Chart } from "chart.js";
|
||||
import { Auth } from "../firebase";
|
||||
import * as SlowTimer from "../states/slow-timer";
|
||||
import * as FunboxList from "./funbox/funbox-list";
|
||||
|
||||
// eslint-disable-next-line no-duplicate-imports -- need to ignore because eslint doesnt know what import type is
|
||||
import type { PluginChartOptions, ScaleChartOptions } from "chart.js";
|
||||
|
|
@ -99,10 +100,15 @@ async function updateGraph(): Promise<void> {
|
|||
|
||||
const fc = await ThemeColors.get("sub");
|
||||
if (Config.funbox !== "none") {
|
||||
let content = Config.funbox;
|
||||
if (Config.funbox === "layoutfluid") {
|
||||
content += " " + Config.customLayoutfluid.replace(/#/g, " ");
|
||||
let content = "";
|
||||
for (const f of FunboxList.get(Config.funbox)) {
|
||||
content += f.name;
|
||||
if (f.functions?.getResultContent) {
|
||||
content += "(" + f.functions.getResultContent() + ")";
|
||||
}
|
||||
content += " ";
|
||||
}
|
||||
content = content.trimEnd();
|
||||
resultAnnotation.push({
|
||||
display: true,
|
||||
id: "funbox-label",
|
||||
|
|
@ -524,12 +530,11 @@ function updateTestType(randomQuote: MonkeyTypes.Quote): void {
|
|||
testType += " " + ["short", "medium", "long", "thicc"][randomQuote.group];
|
||||
}
|
||||
}
|
||||
if (
|
||||
Config.mode != "custom" &&
|
||||
Config.funbox !== "gibberish" &&
|
||||
Config.funbox !== "ascii" &&
|
||||
Config.funbox !== "58008"
|
||||
) {
|
||||
const ignoresLanguage =
|
||||
FunboxList.get(Config.funbox).find((f) =>
|
||||
f.properties?.includes("ignoresLanguage")
|
||||
) !== undefined;
|
||||
if (Config.mode != "custom" && !ignoresLanguage) {
|
||||
testType += "<br>" + result.language.replace(/_/g, " ");
|
||||
}
|
||||
if (Config.punctuation) {
|
||||
|
|
@ -545,7 +550,7 @@ function updateTestType(randomQuote: MonkeyTypes.Quote): void {
|
|||
testType += "<br>lazy";
|
||||
}
|
||||
if (Config.funbox !== "none") {
|
||||
testType += "<br>" + Config.funbox.replace(/_/g, " ");
|
||||
testType += "<br>" + Config.funbox.replace(/_/g, " ").replace(/#/g, ", ");
|
||||
}
|
||||
if (Config.difficulty == "expert") {
|
||||
testType += "<br>expert";
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import * as TestStats from "./test-stats";
|
|||
import * as PractiseWords from "./practise-words";
|
||||
import * as ShiftTracker from "./shift-tracker";
|
||||
import * as Focus from "./focus";
|
||||
import * as Funbox from "./funbox";
|
||||
import * as Funbox from "./funbox/funbox";
|
||||
import * as Keymap from "../elements/keymap";
|
||||
import * as ThemeController from "../controllers/theme-controller";
|
||||
import * as PaceCaret from "./pace-caret";
|
||||
|
|
@ -28,10 +28,7 @@ import * as OutOfFocus from "./out-of-focus";
|
|||
import * as AccountButton from "../elements/account-button";
|
||||
import * as DB from "../db";
|
||||
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";
|
||||
import * as ChallengeContoller from "../controllers/challenge-controller";
|
||||
import * as QuoteRatePopup from "../popups/quote-rate-popup";
|
||||
|
|
@ -57,6 +54,9 @@ import { Auth } from "../firebase";
|
|||
import * as AdController from "../controllers/ad-controller";
|
||||
import * as TestConfig from "./test-config";
|
||||
import * as ConnectionState from "../states/connection";
|
||||
import * as FunboxList from "./funbox/funbox-list";
|
||||
import * as MemoryFunboxTimer from "./funbox/memory-funbox-timer";
|
||||
import * as KeymapEvent from "../observables/keymap-event";
|
||||
|
||||
let failReason = "";
|
||||
const koInputVisual = document.getElementById("koInputVisual") as HTMLElement;
|
||||
|
|
@ -92,229 +92,212 @@ export async function punctuateWord(
|
|||
|
||||
const lastChar = Misc.getLastChar(previousWord);
|
||||
|
||||
if (Config.funbox === "58008") {
|
||||
if (currentWord.length > 3) {
|
||||
if (Math.random() < 0.5) {
|
||||
word = Misc.setCharAt(
|
||||
word,
|
||||
Misc.randomIntFromRange(1, word.length - 2),
|
||||
"."
|
||||
);
|
||||
const funbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.punctuateWord
|
||||
);
|
||||
if (funbox?.functions?.punctuateWord) {
|
||||
return funbox.functions.punctuateWord(word);
|
||||
}
|
||||
if (
|
||||
currentLanguage != "code" &&
|
||||
currentLanguage != "georgian" &&
|
||||
(index == 0 || shouldCapitalize(lastChar))
|
||||
) {
|
||||
//always capitalise the first word or if there was a dot unless using a code alphabet or the Georgian language
|
||||
|
||||
word = Misc.capitalizeFirstLetterOfEachWord(word);
|
||||
|
||||
if (currentLanguage == "turkish") {
|
||||
word = word.replace(/I/g, "İ");
|
||||
}
|
||||
|
||||
if (currentLanguage == "spanish" || currentLanguage == "catalan") {
|
||||
const rand = Math.random();
|
||||
if (rand > 0.9) {
|
||||
word = "¿" + word;
|
||||
spanishSentenceTracker = "?";
|
||||
} else if (rand > 0.8) {
|
||||
word = "¡" + word;
|
||||
spanishSentenceTracker = "!";
|
||||
}
|
||||
if (Math.random() < 0.75) {
|
||||
const index = Misc.randomIntFromRange(1, word.length - 2);
|
||||
if (
|
||||
word[index - 1] !== "." &&
|
||||
word[index + 1] !== "." &&
|
||||
word[index + 1] !== "0"
|
||||
}
|
||||
} else if (
|
||||
(Math.random() < 0.1 &&
|
||||
lastChar != "." &&
|
||||
lastChar != "," &&
|
||||
index != maxindex - 2) ||
|
||||
index == maxindex - 1
|
||||
) {
|
||||
if (currentLanguage == "spanish" || currentLanguage == "catalan") {
|
||||
if (spanishSentenceTracker == "?" || spanishSentenceTracker == "!") {
|
||||
word += spanishSentenceTracker;
|
||||
spanishSentenceTracker = "";
|
||||
}
|
||||
} else {
|
||||
const rand = Math.random();
|
||||
if (rand <= 0.8) {
|
||||
if (currentLanguage == "kurdish") {
|
||||
word += ".";
|
||||
} else if (currentLanguage === "nepali") {
|
||||
word += "।";
|
||||
} else {
|
||||
word += ".";
|
||||
}
|
||||
} else if (rand > 0.8 && rand < 0.9) {
|
||||
if (currentLanguage == "french") {
|
||||
word = "?";
|
||||
} else if (
|
||||
currentLanguage == "arabic" ||
|
||||
currentLanguage == "persian" ||
|
||||
currentLanguage == "urdu" ||
|
||||
currentLanguage == "kurdish"
|
||||
) {
|
||||
const special = Misc.randomElementFromArray(["/", "*", "-", "+"]);
|
||||
word = Misc.setCharAt(word, index, special);
|
||||
word += "؟";
|
||||
} else if (currentLanguage == "greek") {
|
||||
word += ";";
|
||||
} else {
|
||||
word += "?";
|
||||
}
|
||||
} else {
|
||||
if (currentLanguage == "french") {
|
||||
word = "!";
|
||||
} else {
|
||||
word += "!";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
currentLanguage != "code" &&
|
||||
currentLanguage != "georgian" &&
|
||||
(index == 0 || shouldCapitalize(lastChar))
|
||||
) {
|
||||
//always capitalise the first word or if there was a dot unless using a code alphabet or the Georgian language
|
||||
|
||||
word = Misc.capitalizeFirstLetterOfEachWord(word);
|
||||
|
||||
if (currentLanguage == "turkish") {
|
||||
word = word.replace(/I/g, "İ");
|
||||
}
|
||||
|
||||
if (currentLanguage == "spanish" || currentLanguage == "catalan") {
|
||||
const rand = Math.random();
|
||||
if (rand > 0.9) {
|
||||
word = "¿" + word;
|
||||
spanishSentenceTracker = "?";
|
||||
} else if (rand > 0.8) {
|
||||
word = "¡" + word;
|
||||
spanishSentenceTracker = "!";
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
(Math.random() < 0.1 &&
|
||||
lastChar != "." &&
|
||||
lastChar != "," &&
|
||||
index != maxindex - 2) ||
|
||||
index == maxindex - 1
|
||||
) {
|
||||
if (currentLanguage == "spanish" || currentLanguage == "catalan") {
|
||||
if (spanishSentenceTracker == "?" || spanishSentenceTracker == "!") {
|
||||
word += spanishSentenceTracker;
|
||||
spanishSentenceTracker = "";
|
||||
}
|
||||
} else {
|
||||
const rand = Math.random();
|
||||
if (rand <= 0.8) {
|
||||
if (currentLanguage == "kurdish") {
|
||||
word += ".";
|
||||
} else if (currentLanguage === "nepali") {
|
||||
word += "।";
|
||||
} else {
|
||||
word += ".";
|
||||
}
|
||||
} else if (rand > 0.8 && rand < 0.9) {
|
||||
if (currentLanguage == "french") {
|
||||
word = "?";
|
||||
} else if (
|
||||
currentLanguage == "arabic" ||
|
||||
currentLanguage == "persian" ||
|
||||
currentLanguage == "urdu" ||
|
||||
currentLanguage == "kurdish"
|
||||
) {
|
||||
word += "؟";
|
||||
} else if (currentLanguage == "greek") {
|
||||
word += ";";
|
||||
} else {
|
||||
word += "?";
|
||||
}
|
||||
} else {
|
||||
if (currentLanguage == "french") {
|
||||
word = "!";
|
||||
} else {
|
||||
word += "!";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
Math.random() < 0.01 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
currentLanguage !== "russian"
|
||||
) {
|
||||
word = `"${word}"`;
|
||||
} else if (
|
||||
Math.random() < 0.011 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
currentLanguage !== "russian" &&
|
||||
currentLanguage !== "ukrainian"
|
||||
) {
|
||||
word = `'${word}'`;
|
||||
} else if (Math.random() < 0.012 && lastChar != "," && lastChar != ".") {
|
||||
if (currentLanguage == "code") {
|
||||
const r = Math.random();
|
||||
if (r < 0.25) {
|
||||
word = `(${word})`;
|
||||
} else if (r < 0.5) {
|
||||
word = `{${word}}`;
|
||||
} else if (r < 0.75) {
|
||||
word = `[${word}]`;
|
||||
} else {
|
||||
word = `<${word}>`;
|
||||
}
|
||||
} else {
|
||||
} else if (
|
||||
Math.random() < 0.01 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
currentLanguage !== "russian"
|
||||
) {
|
||||
word = `"${word}"`;
|
||||
} else if (
|
||||
Math.random() < 0.011 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
currentLanguage !== "russian" &&
|
||||
currentLanguage !== "ukrainian"
|
||||
) {
|
||||
word = `'${word}'`;
|
||||
} else if (Math.random() < 0.012 && lastChar != "," && lastChar != ".") {
|
||||
if (currentLanguage == "code") {
|
||||
const r = Math.random();
|
||||
if (r < 0.25) {
|
||||
word = `(${word})`;
|
||||
}
|
||||
} else if (
|
||||
Math.random() < 0.013 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
lastChar != ";" &&
|
||||
lastChar != "؛" &&
|
||||
lastChar != ":"
|
||||
) {
|
||||
if (currentLanguage == "french") {
|
||||
word = ":";
|
||||
} else if (currentLanguage == "greek") {
|
||||
word = "·";
|
||||
} else if (r < 0.5) {
|
||||
word = `{${word}}`;
|
||||
} else if (r < 0.75) {
|
||||
word = `[${word}]`;
|
||||
} else {
|
||||
word += ":";
|
||||
word = `<${word}>`;
|
||||
}
|
||||
} else if (
|
||||
Math.random() < 0.014 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
previousWord != "-"
|
||||
) {
|
||||
word = "-";
|
||||
} else if (
|
||||
Math.random() < 0.015 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
lastChar != ";" &&
|
||||
lastChar != "؛" &&
|
||||
lastChar != ":"
|
||||
) {
|
||||
if (currentLanguage == "french") {
|
||||
word = ";";
|
||||
} else if (currentLanguage == "greek") {
|
||||
word = "·";
|
||||
} else if (currentLanguage == "arabic" || currentLanguage == "kurdish") {
|
||||
word += "؛";
|
||||
} else {
|
||||
word += ";";
|
||||
}
|
||||
} else if (Math.random() < 0.2 && lastChar != ",") {
|
||||
if (
|
||||
currentLanguage == "arabic" ||
|
||||
currentLanguage == "urdu" ||
|
||||
currentLanguage == "persian" ||
|
||||
currentLanguage == "kurdish"
|
||||
) {
|
||||
word += "،";
|
||||
} else {
|
||||
word += ",";
|
||||
}
|
||||
} else if (Math.random() < 0.25 && currentLanguage == "code") {
|
||||
const specials = ["{", "}", "[", "]", "(", ")", ";", "=", "+", "%", "/"];
|
||||
const specialsC = [
|
||||
"{",
|
||||
"}",
|
||||
"[",
|
||||
"]",
|
||||
"(",
|
||||
")",
|
||||
";",
|
||||
"=",
|
||||
"+",
|
||||
"%",
|
||||
"/",
|
||||
"/*",
|
||||
"*/",
|
||||
"//",
|
||||
"!=",
|
||||
"==",
|
||||
"<=",
|
||||
">=",
|
||||
"||",
|
||||
"&&",
|
||||
"<<",
|
||||
">>",
|
||||
"%=",
|
||||
"&=",
|
||||
"*=",
|
||||
"++",
|
||||
"+=",
|
||||
"--",
|
||||
"-=",
|
||||
"/=",
|
||||
"^=",
|
||||
"|=",
|
||||
];
|
||||
|
||||
if (
|
||||
(Config.language.startsWith("code_c") &&
|
||||
!Config.language.startsWith("code_css")) ||
|
||||
Config.language.startsWith("code_arduino")
|
||||
) {
|
||||
word = Misc.randomElementFromArray(specialsC);
|
||||
} else {
|
||||
word = Misc.randomElementFromArray(specials);
|
||||
}
|
||||
} else if (
|
||||
Math.random() < 0.5 &&
|
||||
currentLanguage === "english" &&
|
||||
(await EnglishPunctuation.check(word))
|
||||
) {
|
||||
word = await applyEnglishPunctuationToWord(word);
|
||||
} else {
|
||||
word = `(${word})`;
|
||||
}
|
||||
} else if (
|
||||
Math.random() < 0.013 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
lastChar != ";" &&
|
||||
lastChar != "؛" &&
|
||||
lastChar != ":"
|
||||
) {
|
||||
if (currentLanguage == "french") {
|
||||
word = ":";
|
||||
} else if (currentLanguage == "greek") {
|
||||
word = "·";
|
||||
} else {
|
||||
word += ":";
|
||||
}
|
||||
} else if (
|
||||
Math.random() < 0.014 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
previousWord != "-"
|
||||
) {
|
||||
word = "-";
|
||||
} else if (
|
||||
Math.random() < 0.015 &&
|
||||
lastChar != "," &&
|
||||
lastChar != "." &&
|
||||
lastChar != ";" &&
|
||||
lastChar != "؛" &&
|
||||
lastChar != ":"
|
||||
) {
|
||||
if (currentLanguage == "french") {
|
||||
word = ";";
|
||||
} else if (currentLanguage == "greek") {
|
||||
word = "·";
|
||||
} else if (currentLanguage == "arabic" || currentLanguage == "kurdish") {
|
||||
word += "؛";
|
||||
} else {
|
||||
word += ";";
|
||||
}
|
||||
} else if (Math.random() < 0.2 && lastChar != ",") {
|
||||
if (
|
||||
currentLanguage == "arabic" ||
|
||||
currentLanguage == "urdu" ||
|
||||
currentLanguage == "persian" ||
|
||||
currentLanguage == "kurdish"
|
||||
) {
|
||||
word += "،";
|
||||
} else {
|
||||
word += ",";
|
||||
}
|
||||
} else if (Math.random() < 0.25 && currentLanguage == "code") {
|
||||
const specials = ["{", "}", "[", "]", "(", ")", ";", "=", "+", "%", "/"];
|
||||
const specialsC = [
|
||||
"{",
|
||||
"}",
|
||||
"[",
|
||||
"]",
|
||||
"(",
|
||||
")",
|
||||
";",
|
||||
"=",
|
||||
"+",
|
||||
"%",
|
||||
"/",
|
||||
"/*",
|
||||
"*/",
|
||||
"//",
|
||||
"!=",
|
||||
"==",
|
||||
"<=",
|
||||
">=",
|
||||
"||",
|
||||
"&&",
|
||||
"<<",
|
||||
">>",
|
||||
"%=",
|
||||
"&=",
|
||||
"*=",
|
||||
"++",
|
||||
"+=",
|
||||
"--",
|
||||
"-=",
|
||||
"/=",
|
||||
"^=",
|
||||
"|=",
|
||||
];
|
||||
|
||||
if (
|
||||
(Config.language.startsWith("code_c") &&
|
||||
!Config.language.startsWith("code_css")) ||
|
||||
Config.language.startsWith("code_arduino")
|
||||
) {
|
||||
word = Misc.randomElementFromArray(specialsC);
|
||||
} else {
|
||||
word = Misc.randomElementFromArray(specials);
|
||||
}
|
||||
} else if (
|
||||
Math.random() < 0.5 &&
|
||||
currentLanguage === "english" &&
|
||||
(await EnglishPunctuation.check(word))
|
||||
) {
|
||||
word = await applyEnglishPunctuationToWord(word);
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
|
@ -351,9 +334,8 @@ export function startTest(): boolean {
|
|||
TestTimer.clear();
|
||||
Monkey.show();
|
||||
|
||||
if (Config.funbox === "memory") {
|
||||
Funbox.resetMemoryTimer();
|
||||
$("#wordsWrapper").addClass("hidden");
|
||||
for (const f of FunboxList.get(Config.funbox)) {
|
||||
if (f.functions?.start) f.functions.start();
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -514,7 +496,7 @@ export function restart(options = {} as RestartOptions): void {
|
|||
|
||||
$("#showWordHistoryButton").removeClass("loaded");
|
||||
$("#restartTestButton").blur();
|
||||
Funbox.resetMemoryTimer();
|
||||
MemoryFunboxTimer.reset();
|
||||
QuoteRatePopup.clearQuoteStats();
|
||||
if (ActivePage.get() == "test" && window.scrollY > 0) {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
|
|
@ -573,34 +555,19 @@ export function restart(options = {} as RestartOptions): void {
|
|||
|
||||
await Funbox.rememberSettings();
|
||||
|
||||
if (Config.funbox === "arrows") {
|
||||
UpdateConfig.setPunctuation(false, true);
|
||||
UpdateConfig.setNumbers(false, true);
|
||||
} else if (Config.funbox === "58008") {
|
||||
UpdateConfig.setNumbers(false, true);
|
||||
} else if (Config.funbox === "specials") {
|
||||
UpdateConfig.setPunctuation(false, true);
|
||||
UpdateConfig.setNumbers(false, true);
|
||||
} else if (Config.funbox === "ascii") {
|
||||
UpdateConfig.setPunctuation(false, true);
|
||||
UpdateConfig.setNumbers(false, true);
|
||||
}
|
||||
if (
|
||||
options.withSameWordset &&
|
||||
(Config.funbox === "plus_one" || Config.funbox === "plus_two")
|
||||
) {
|
||||
const toPush = [];
|
||||
if (Config.funbox === "plus_one") {
|
||||
toPush.push(TestWords.words.get(0));
|
||||
toPush.push(TestWords.words.get(1));
|
||||
if (options.withSameWordset) {
|
||||
const funboxToPush = FunboxList.get(Config.funbox)
|
||||
.find((f) => f.properties?.find((fp) => fp.startsWith("toPush")))
|
||||
?.properties?.find((fp) => fp.startsWith("toPush:"));
|
||||
if (funboxToPush) {
|
||||
const toPushCount = +funboxToPush.split(":")[1];
|
||||
const toPush = [];
|
||||
for (let i = 0; i < toPushCount; i++) {
|
||||
toPush.push(TestWords.words.get(i));
|
||||
}
|
||||
TestWords.words.reset();
|
||||
toPush.forEach((word) => TestWords.words.push(word));
|
||||
}
|
||||
if (Config.funbox === "plus_two") {
|
||||
toPush.push(TestWords.words.get(0));
|
||||
toPush.push(TestWords.words.get(1));
|
||||
toPush.push(TestWords.words.get(2));
|
||||
}
|
||||
TestWords.words.reset();
|
||||
toPush.forEach((word) => TestWords.words.push(word));
|
||||
}
|
||||
if (!options.withSameWordset && !shouldQuoteRepeat) {
|
||||
TestState.setRepeated(false);
|
||||
|
|
@ -617,7 +584,7 @@ export function restart(options = {} as RestartOptions): void {
|
|||
TestInput.input.reset();
|
||||
TestUI.showWords();
|
||||
if (Config.keymapMode === "next" && Config.mode !== "zen") {
|
||||
Keymap.highlightKey(
|
||||
KeymapEvent.highlight(
|
||||
TestWords.words
|
||||
.getCurrent()
|
||||
.substring(
|
||||
|
|
@ -652,11 +619,8 @@ export function restart(options = {} as RestartOptions): void {
|
|||
(<HTMLElement>document.querySelector("#liveAcc")).innerHTML = "100%";
|
||||
(<HTMLElement>document.querySelector("#liveBurst")).innerHTML = "0";
|
||||
|
||||
if (Config.funbox === "memory") {
|
||||
Funbox.startMemoryTimer();
|
||||
if (Config.keymapMode === "next") {
|
||||
UpdateConfig.setKeymapMode("react");
|
||||
}
|
||||
for (const f of FunboxList.get(Config.funbox)) {
|
||||
if (f.functions?.restart) f.functions.restart();
|
||||
}
|
||||
|
||||
if (Config.showAverage !== "off") {
|
||||
|
|
@ -668,37 +632,13 @@ export function restart(options = {} as RestartOptions): void {
|
|||
const mode2 = Misc.getMode2(Config, TestWords.randomQuote);
|
||||
let fbtext = "";
|
||||
if (Config.funbox !== "none") {
|
||||
fbtext = " " + Config.funbox;
|
||||
fbtext = " " + Config.funbox.split("#").join(" ");
|
||||
}
|
||||
$(".pageTest #premidTestMode").text(
|
||||
`${Config.mode} ${mode2} ${Config.language.replace(/_/g, " ")}${fbtext}`
|
||||
);
|
||||
$(".pageTest #premidSecondsLeft").text(Config.time);
|
||||
|
||||
if (Config.funbox === "layoutfluid") {
|
||||
UpdateConfig.setLayout(
|
||||
Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")[0]
|
||||
: "qwerty",
|
||||
true
|
||||
);
|
||||
UpdateConfig.setKeymapLayout(
|
||||
Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")[0]
|
||||
: "qwerty",
|
||||
true
|
||||
);
|
||||
Keymap.highlightKey(
|
||||
TestWords.words
|
||||
.getCurrent()
|
||||
.substring(
|
||||
TestInput.input.current.length,
|
||||
TestInput.input.current.length + 1
|
||||
)
|
||||
.toString()
|
||||
);
|
||||
}
|
||||
|
||||
$("#result").addClass("hidden");
|
||||
$("#testModesNotice").removeClass("hidden").css({
|
||||
opacity: 1,
|
||||
|
|
@ -731,36 +671,21 @@ export function restart(options = {} as RestartOptions): void {
|
|||
);
|
||||
}
|
||||
|
||||
function applyFunboxesToWord(word: string, wordset?: Wordset.Wordset): string {
|
||||
if (Config.funbox === "rAnDoMcAsE") {
|
||||
let randomcaseword = "";
|
||||
for (let i = 0; i < word.length; i++) {
|
||||
if (i % 2 != 0) {
|
||||
randomcaseword += word[i].toUpperCase();
|
||||
} else {
|
||||
randomcaseword += word[i];
|
||||
}
|
||||
function getFunboxWord(word: string, wordset?: Misc.Wordset): string {
|
||||
const wordFunbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.getWord
|
||||
);
|
||||
if (wordFunbox?.functions?.getWord) {
|
||||
word = wordFunbox.functions.getWord(wordset);
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
function applyFunboxesToWord(word: string): string {
|
||||
for (const f of FunboxList.get(Config.funbox)) {
|
||||
if (f.functions?.alterText) {
|
||||
word = f.functions.alterText(word);
|
||||
}
|
||||
word = randomcaseword;
|
||||
} else if (Config.funbox === "capitals") {
|
||||
word = Misc.capitalizeFirstLetterOfEachWord(word);
|
||||
} else if (Config.funbox === "gibberish") {
|
||||
word = Misc.getGibberish();
|
||||
} else if (Config.funbox === "arrows") {
|
||||
word = Misc.getArrows();
|
||||
} else if (Config.funbox === "58008") {
|
||||
word = Misc.getNumbers(7);
|
||||
if (Config.language.startsWith("kurdish")) {
|
||||
word = Misc.convertNumberToArabic(word);
|
||||
} else if (Config.language.startsWith("nepali")) {
|
||||
word = Misc.convertNumberToNepali(word);
|
||||
}
|
||||
} else if (Config.funbox === "specials") {
|
||||
word = Misc.getSpecials();
|
||||
} else if (Config.funbox === "ascii") {
|
||||
word = Misc.getASCII();
|
||||
} else if (wordset !== undefined && Config.funbox === "weakspot") {
|
||||
word = WeakSpot.getWord(wordset);
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
|
@ -783,7 +708,7 @@ function applyLazyModeToWord(
|
|||
}
|
||||
|
||||
async function getNextWord(
|
||||
wordset: Wordset.Wordset,
|
||||
wordset: Misc.Wordset,
|
||||
language: MonkeyTypes.LanguageObject,
|
||||
wordsBound: number
|
||||
): Promise<string> {
|
||||
|
|
@ -846,7 +771,7 @@ async function getNextWord(
|
|||
randomWord = randomWord.replace(/ +/gm, " ");
|
||||
randomWord = randomWord.replace(/^ | $/gm, "");
|
||||
randomWord = applyLazyModeToWord(randomWord, language);
|
||||
randomWord = applyFunboxesToWord(randomWord, wordset);
|
||||
randomWord = getFunboxWord(randomWord, wordset);
|
||||
randomWord = await applyBritishEnglishToWord(randomWord);
|
||||
|
||||
if (Config.punctuation) {
|
||||
|
|
@ -869,6 +794,8 @@ async function getNextWord(
|
|||
}
|
||||
}
|
||||
|
||||
randomWord = applyFunboxesToWord(randomWord);
|
||||
|
||||
return randomWord;
|
||||
}
|
||||
|
||||
|
|
@ -948,13 +875,12 @@ export async function init(): Promise<void> {
|
|||
}
|
||||
|
||||
let wordsBound = 100;
|
||||
if (Config.funbox === "plus_one") {
|
||||
wordsBound = 2;
|
||||
if (Config.mode === "words" && Config.words < wordsBound) {
|
||||
wordsBound = Config.words;
|
||||
}
|
||||
} else if (Config.funbox === "plus_two") {
|
||||
wordsBound = 3;
|
||||
|
||||
const funboxToPush = FunboxList.get(Config.funbox)
|
||||
.find((f) => f.properties?.find((fp) => fp.startsWith("toPush")))
|
||||
?.properties?.find((fp) => fp.startsWith("toPush:"));
|
||||
if (funboxToPush) {
|
||||
wordsBound = +funboxToPush.split(":")[1];
|
||||
if (Config.mode === "words" && Config.words < wordsBound) {
|
||||
wordsBound = Config.words;
|
||||
}
|
||||
|
|
@ -1020,36 +946,32 @@ export async function init(): Promise<void> {
|
|||
if (Config.mode == "custom") {
|
||||
wordList = CustomText.text;
|
||||
}
|
||||
const wordset = Wordset.withWords(wordList, Config.funbox);
|
||||
const wordset = await Wordset.withWords(wordList);
|
||||
let wordCount = 0;
|
||||
|
||||
if (
|
||||
(Config.funbox == "wikipedia" || Config.funbox == "poetry") &&
|
||||
Config.mode != "custom"
|
||||
) {
|
||||
let wordCount = 0;
|
||||
|
||||
// If mode is words, get as many sections as you need until the wordCount is fullfilled
|
||||
const sectionFunbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.pullSection
|
||||
);
|
||||
if (sectionFunbox?.functions?.pullSection) {
|
||||
while (
|
||||
(Config.mode == "words" && Config.words >= wordCount) ||
|
||||
(Config.mode === "time" && wordCount < 100)
|
||||
) {
|
||||
const section =
|
||||
Config.funbox == "wikipedia"
|
||||
? await Wikipedia.getSection(Config.language)
|
||||
: await Poetry.getPoem();
|
||||
const section = await sectionFunbox.functions.pullSection(
|
||||
Config.language
|
||||
);
|
||||
|
||||
if (Config.funbox == "poetry" && section === false) {
|
||||
if (section === false) {
|
||||
Notifications.add(
|
||||
"Error while getting poetry. Please try again later",
|
||||
"Error while getting section. Please try again later",
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none");
|
||||
UpdateConfig.toggleFunbox(sectionFunbox.name);
|
||||
restart();
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === undefined) continue;
|
||||
if (section === false) continue;
|
||||
|
||||
for (const word of section.words) {
|
||||
if (wordCount >= Config.words && Config.mode == "words") {
|
||||
|
|
@ -1060,7 +982,9 @@ export async function init(): Promise<void> {
|
|||
TestWords.words.push(word);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
if (wordCount == 0) {
|
||||
for (let i = 0; i < wordsBound; i++) {
|
||||
const randomWord = await getNextWord(wordset, language, wordsBound);
|
||||
|
||||
|
|
@ -1220,7 +1144,7 @@ export async function init(): Promise<void> {
|
|||
// } else {
|
||||
TestUI.showWords();
|
||||
if (Config.keymapMode === "next" && Config.mode !== "zen") {
|
||||
Keymap.highlightKey(
|
||||
KeymapEvent.highlight(
|
||||
TestWords.words
|
||||
.getCurrent()
|
||||
.substring(
|
||||
|
|
@ -1236,8 +1160,11 @@ export async function init(): Promise<void> {
|
|||
|
||||
export async function addWord(): Promise<void> {
|
||||
let bound = 100;
|
||||
if (Config.funbox === "plus_one") bound = 1;
|
||||
if (Config.funbox === "plus_two") bound = 2;
|
||||
const funboxToPush = FunboxList.get(Config.funbox)
|
||||
.find((f) => f.properties?.find((fp) => fp.startsWith("toPush")))
|
||||
?.properties?.find((fp) => fp.startsWith("toPush:"));
|
||||
const toPushCount: string | undefined = funboxToPush?.split(":")[1];
|
||||
if (toPushCount) bound = +toPushCount - 1;
|
||||
if (
|
||||
TestWords.words.length - TestInput.input.history.length > bound ||
|
||||
(Config.mode === "words" &&
|
||||
|
|
@ -1257,25 +1184,26 @@ export async function addWord(): Promise<void> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (Config.funbox === "wikipedia" || Config.funbox == "poetry") {
|
||||
const sectionFunbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.pullSection
|
||||
);
|
||||
if (sectionFunbox?.functions?.pullSection) {
|
||||
if (TestWords.words.length - TestWords.words.currentIndex < 20) {
|
||||
const section =
|
||||
Config.funbox == "wikipedia"
|
||||
? await Wikipedia.getSection(Config.language)
|
||||
: await Poetry.getPoem();
|
||||
const section = await sectionFunbox.functions.pullSection(
|
||||
Config.language
|
||||
);
|
||||
|
||||
if (Config.funbox == "poetry" && section === false) {
|
||||
if (section === false) {
|
||||
Notifications.add(
|
||||
"Error while getting poetry. Please try again later",
|
||||
"Error while getting section. Please try again later",
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none");
|
||||
UpdateConfig.toggleFunbox(sectionFunbox.name);
|
||||
restart();
|
||||
return;
|
||||
}
|
||||
|
||||
if (section === undefined) return;
|
||||
if (section === false) return;
|
||||
|
||||
let wordCount = 0;
|
||||
for (const word of section.words) {
|
||||
|
|
@ -1286,8 +1214,6 @@ export async function addWord(): Promise<void> {
|
|||
TestWords.words.push(word);
|
||||
TestUI.addWord(word);
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1299,7 +1225,7 @@ export async function addWord(): Promise<void> {
|
|||
...(await Misc.getCurrentLanguage(Config.language)),
|
||||
words: CustomText.text,
|
||||
};
|
||||
const wordset = Wordset.withWords(language.words, Config.funbox);
|
||||
const wordset = await Wordset.withWords(language.words);
|
||||
|
||||
const randomWord = await getNextWord(wordset, language, bound);
|
||||
|
||||
|
|
@ -1551,6 +1477,7 @@ export async function finish(difficultyFailed = false): Promise<void> {
|
|||
TestTimer.clear();
|
||||
Funbox.clear();
|
||||
Monkey.hide();
|
||||
ModesNotice.update();
|
||||
|
||||
//need one more calculation for the last word if test auto ended
|
||||
if (TestInput.burstHistory.length !== TestInput.input.getHistory().length) {
|
||||
|
|
@ -2091,6 +2018,12 @@ ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
|
|||
if (eventKey === "showAllLines" && !nosave) restart();
|
||||
if (eventKey === "keymapMode" && !nosave) restart();
|
||||
if (eventKey === "tapeMode" && !nosave) restart();
|
||||
if (
|
||||
eventKey === "customLayoutFluid" &&
|
||||
Config.funbox.includes("layoutfluid")
|
||||
) {
|
||||
restart();
|
||||
}
|
||||
}
|
||||
if (eventKey === "lazyMode" && eventValue === false && !nosave) {
|
||||
rememberLazyMode = false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Config from "../config";
|
|||
import * as Misc from "../utils/misc";
|
||||
import * as TestInput from "./test-input";
|
||||
import * as TestWords from "./test-words";
|
||||
import * as FunboxList from "./funbox/funbox-list";
|
||||
|
||||
interface CharCount {
|
||||
spaces: number;
|
||||
|
|
@ -273,7 +274,9 @@ export function calculateWpmAndRaw(): MonkeyTypes.WordsPerMinuteAndRaw {
|
|||
correctWordChars += toAdd.correct;
|
||||
}
|
||||
}
|
||||
if (Config.funbox === "nospace" || Config.funbox === "arrows") {
|
||||
if (
|
||||
FunboxList.get(Config.funbox).find((f) => f.properties?.includes("nospace"))
|
||||
) {
|
||||
spaces = 0;
|
||||
}
|
||||
chars += currTestInput.length;
|
||||
|
|
@ -444,7 +447,9 @@ function countChars(): CharCount {
|
|||
spaces++;
|
||||
}
|
||||
}
|
||||
if (Config.funbox === "nospace" || Config.funbox === "arrows") {
|
||||
if (
|
||||
FunboxList.get(Config.funbox).find((f) => f.properties?.includes("nospace"))
|
||||
) {
|
||||
spaces = 0;
|
||||
correctspaces = 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,10 @@ function calculateAcc(): number {
|
|||
|
||||
function layoutfluid(): void {
|
||||
if (timerDebug) console.time("layoutfluid");
|
||||
if (Config.funbox === "layoutfluid" && Config.mode === "time") {
|
||||
if (
|
||||
Config.funbox.split("#").includes("layoutfluid") &&
|
||||
Config.mode === "time"
|
||||
) {
|
||||
const layouts = Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")
|
||||
: ["qwerty", "dvorak", "colemak"];
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import * as Hangul from "hangul-js";
|
|||
import format from "date-fns/format";
|
||||
import { Auth } from "../firebase";
|
||||
import { skipXpBreakdown } from "../elements/account-button";
|
||||
import * as FunboxList from "./funbox/funbox-list";
|
||||
|
||||
ConfigEvent.subscribe((eventKey, eventValue) => {
|
||||
if (eventValue === undefined || typeof eventValue !== "boolean") return;
|
||||
|
|
@ -104,20 +105,12 @@ export function updateActiveElement(backspace?: boolean): void {
|
|||
function getWordHTML(word: string): string {
|
||||
let newlineafter = false;
|
||||
let retval = `<div class='word'>`;
|
||||
const funbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.getWordHtml
|
||||
);
|
||||
for (let c = 0; c < word.length; c++) {
|
||||
if (Config.funbox === "arrows") {
|
||||
if (word.charAt(c) === "↑") {
|
||||
retval += `<letter><i class="fas fa-arrow-up"></i></letter>`;
|
||||
}
|
||||
if (word.charAt(c) === "↓") {
|
||||
retval += `<letter><i class="fas fa-arrow-down"></i></letter>`;
|
||||
}
|
||||
if (word.charAt(c) === "←") {
|
||||
retval += `<letter><i class="fas fa-arrow-left"></i></letter>`;
|
||||
}
|
||||
if (word.charAt(c) === "→") {
|
||||
retval += `<letter><i class="fas fa-arrow-right"></i></letter>`;
|
||||
}
|
||||
if (funbox?.functions?.getWordHtml) {
|
||||
retval += funbox.functions.getWordHtml(word.charAt(c), true);
|
||||
} else if (word.charAt(c) === "\t") {
|
||||
retval += `<letter class='tabChar'><i class="fas fa-long-arrow-alt-right"></i></letter>`;
|
||||
} else if (word.charAt(c) === "\n") {
|
||||
|
|
@ -425,6 +418,9 @@ export function updateWordElement(showError = !Config.blindMode): void {
|
|||
wordHighlightClassString = "correct";
|
||||
}
|
||||
|
||||
const funbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.getWordHtml
|
||||
);
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
const charCorrect = currentWord[i] == input[i];
|
||||
|
||||
|
|
@ -436,18 +432,10 @@ export function updateWordElement(showError = !Config.blindMode): void {
|
|||
let currentLetter = currentWord[i];
|
||||
let tabChar = "";
|
||||
let nlChar = "";
|
||||
if (Config.funbox === "arrows") {
|
||||
if (currentLetter === "↑") {
|
||||
currentLetter = `<i class="fas fa-arrow-up"></i>`;
|
||||
}
|
||||
if (currentLetter === "↓") {
|
||||
currentLetter = `<i class="fas fa-arrow-down"></i>`;
|
||||
}
|
||||
if (currentLetter === "←") {
|
||||
currentLetter = `<i class="fas fa-arrow-left"></i>`;
|
||||
}
|
||||
if (currentLetter === "→") {
|
||||
currentLetter = `<i class="fas fa-arrow-right"></i>`;
|
||||
if (funbox?.functions?.getWordHtml) {
|
||||
const cl = funbox.functions.getWordHtml(currentLetter);
|
||||
if (cl != "") {
|
||||
currentLetter = cl;
|
||||
}
|
||||
} else if (currentLetter === "\t") {
|
||||
tabChar = "tabChar";
|
||||
|
|
@ -510,19 +498,8 @@ export function updateWordElement(showError = !Config.blindMode): void {
|
|||
}
|
||||
|
||||
for (let i = input.length; i < currentWord.length; i++) {
|
||||
if (Config.funbox === "arrows") {
|
||||
if (currentWord[i] === "↑") {
|
||||
ret += `<letter><i class="fas fa-arrow-up"></i></letter>`;
|
||||
}
|
||||
if (currentWord[i] === "↓") {
|
||||
ret += `<letter><i class="fas fa-arrow-down"></i></letter>`;
|
||||
}
|
||||
if (currentWord[i] === "←") {
|
||||
ret += `<letter><i class="fas fa-arrow-left"></i></letter>`;
|
||||
}
|
||||
if (currentWord[i] === "→") {
|
||||
ret += `<letter><i class="fas fa-arrow-right"></i></letter>`;
|
||||
}
|
||||
if (funbox?.functions?.getWordHtml) {
|
||||
ret += funbox.functions.getWordHtml(currentWord[i], true);
|
||||
} else if (currentWord[i] === "\t") {
|
||||
ret += `<letter class='tabChar'><i class="fas fa-long-arrow-alt-right"></i></letter>`;
|
||||
} else if (currentWord[i] === "\n") {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import Config from "../config";
|
||||
import * as Misc from "../utils/misc";
|
||||
import * as ConfigEvent from "../observables/config-event";
|
||||
import * as TTSEvent from "../observables/tts-event";
|
||||
|
||||
let voice: SpeechSynthesisUtterance | undefined;
|
||||
|
||||
|
|
@ -38,5 +39,11 @@ ConfigEvent.subscribe((eventKey, eventValue) => {
|
|||
init();
|
||||
}
|
||||
}
|
||||
if (eventKey === "language" && Config.funbox === "tts") setLanguage();
|
||||
if (eventKey === "language" && Config.funbox.split("#").includes("tts")) {
|
||||
setLanguage();
|
||||
}
|
||||
});
|
||||
|
||||
TTSEvent.subscribe((text) => {
|
||||
speak(text);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as TestInput from "./test-input";
|
||||
import { Wordset } from "./wordset";
|
||||
import { Wordset } from "../utils/misc";
|
||||
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
import * as Loader from "../elements/loader";
|
||||
import * as Misc from "../utils/misc";
|
||||
|
||||
export class Section {
|
||||
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;
|
||||
}
|
||||
}
|
||||
import { Section } from "../utils/misc";
|
||||
|
||||
export async function getTLD(
|
||||
languageGroup: MonkeyTypes.LanguageGroup
|
||||
|
|
|
|||
|
|
@ -1,109 +1,18 @@
|
|||
import { randomElementFromArray, randomIntFromRange } from "../utils/misc";
|
||||
import * as FunboxList from "./funbox/funbox-list";
|
||||
import { Wordset } from "../utils/misc";
|
||||
import Config from "../config";
|
||||
|
||||
let currentWordset: Wordset | null = null;
|
||||
let currentWordGenerator: WordGenerator | null = null;
|
||||
|
||||
export class Wordset {
|
||||
public words: string[];
|
||||
public length: number;
|
||||
constructor(words: string[]) {
|
||||
this.words = words;
|
||||
this.length = this.words.length;
|
||||
export async function withWords(words: string[]): Promise<Wordset> {
|
||||
const wordFunbox = FunboxList.get(Config.funbox).find(
|
||||
(f) => f.functions?.withWords
|
||||
);
|
||||
if (wordFunbox?.functions?.withWords) {
|
||||
return wordFunbox.functions.withWords(words);
|
||||
}
|
||||
|
||||
public randomWord(): string {
|
||||
return randomElementFromArray(this.words);
|
||||
}
|
||||
}
|
||||
|
||||
const prefixSize = 2;
|
||||
|
||||
class CharDistribution {
|
||||
public chars: { [char: string]: number };
|
||||
public count: number;
|
||||
constructor() {
|
||||
this.chars = {};
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
public addChar(char: string): void {
|
||||
this.count++;
|
||||
if (char in this.chars) {
|
||||
this.chars[char]++;
|
||||
} else {
|
||||
this.chars[char] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public randomChar(): string {
|
||||
const randomIndex = randomIntFromRange(0, this.count - 1);
|
||||
let runningCount = 0;
|
||||
for (const [char, charCount] of Object.entries(this.chars)) {
|
||||
runningCount += charCount;
|
||||
if (runningCount > randomIndex) {
|
||||
return char;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(this.chars)[0];
|
||||
}
|
||||
}
|
||||
|
||||
class WordGenerator extends Wordset {
|
||||
public ngrams: { [prefix: string]: CharDistribution } = {};
|
||||
constructor(words: string[]) {
|
||||
super(words);
|
||||
// Can generate an unbounded number of words in theory.
|
||||
this.length = Infinity;
|
||||
|
||||
for (let word of words) {
|
||||
// Mark the end of each word with a space.
|
||||
word += " ";
|
||||
let prefix = "";
|
||||
for (const c of word) {
|
||||
// Add `c` to the distribution of chars that can come after `prefix`.
|
||||
if (!(prefix in this.ngrams)) {
|
||||
this.ngrams[prefix] = new CharDistribution();
|
||||
}
|
||||
this.ngrams[prefix].addChar(c);
|
||||
prefix = (prefix + c).substr(-prefixSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override randomWord(): string {
|
||||
let word = "";
|
||||
for (;;) {
|
||||
const prefix = word.substr(-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.
|
||||
word = "";
|
||||
continue;
|
||||
}
|
||||
// Pick a random char from the distribution that comes after `prefix`.
|
||||
const nextChar = charDistribution.randomChar();
|
||||
if (nextChar == " ") {
|
||||
// A space marks the end of the word, so stop generating and return.
|
||||
break;
|
||||
}
|
||||
word += nextChar;
|
||||
}
|
||||
return word;
|
||||
}
|
||||
}
|
||||
|
||||
export function withWords(words: string[], funbox: string): Wordset {
|
||||
if (funbox == "pseudolang") {
|
||||
if (currentWordGenerator == null || words !== currentWordGenerator.words) {
|
||||
currentWordGenerator = new WordGenerator(words);
|
||||
}
|
||||
return currentWordGenerator;
|
||||
} else {
|
||||
if (currentWordset == null || words !== currentWordset.words) {
|
||||
currentWordset = new Wordset(words);
|
||||
}
|
||||
return currentWordset;
|
||||
if (currentWordset == null || words !== currentWordset.words) {
|
||||
currentWordset = new Wordset(words);
|
||||
}
|
||||
return currentWordset;
|
||||
}
|
||||
|
|
|
|||
65
frontend/src/ts/types/types.d.ts
vendored
65
frontend/src/ts/types/types.d.ts
vendored
|
|
@ -1,3 +1,5 @@
|
|||
type typesSeparatedWithHash<T> = T | `${T}#${typesSeparatedWithHash<T>}`;
|
||||
|
||||
declare namespace MonkeyTypes {
|
||||
type Difficulty = "normal" | "expert" | "master";
|
||||
|
||||
|
|
@ -141,8 +143,6 @@ declare namespace MonkeyTypes {
|
|||
|
||||
type MinimumBurst = "off" | "fixed" | "flex";
|
||||
|
||||
type FunboxObjectType = "script" | "style";
|
||||
|
||||
type IndicateTypos = "off" | "below" | "replace";
|
||||
|
||||
type CustomLayoutFluid = `${string}#${string}#${string}`;
|
||||
|
|
@ -183,12 +183,65 @@ declare namespace MonkeyTypes {
|
|||
display?: string;
|
||||
}
|
||||
|
||||
interface FunboxObject {
|
||||
type FunboxProperty =
|
||||
| "symmetricChars"
|
||||
| "conflictsWithSymmetricChars"
|
||||
| "changesWordsVisibility"
|
||||
| "speaks"
|
||||
| "unspeakable"
|
||||
| "changesLayout"
|
||||
| "ignoresLayout"
|
||||
| "usesLayout"
|
||||
| "ignoresLanguage"
|
||||
| "noLigatures"
|
||||
| "noLetters"
|
||||
| "changesCapitalisation"
|
||||
| "nospace"
|
||||
| `toPush:${number}`
|
||||
| "noInfiniteDuration";
|
||||
|
||||
interface FunboxFunctions {
|
||||
getWord?: (wordset?: Misc.Wordset) => string;
|
||||
punctuateWord?: (word: string) => string;
|
||||
withWords?: (words?: string[]) => Promise<Misc.Wordset>;
|
||||
alterText?: (word: string) => string;
|
||||
applyCSS?: () => void;
|
||||
applyConfig?: () => void;
|
||||
rememberSettings?: () => void;
|
||||
toggleScript?: (params: string[]) => void;
|
||||
pullSection?: (language?: string) => Promise<Misc.Section | false>;
|
||||
handleSpace?: () => void;
|
||||
handleChar?: (char: string) => string;
|
||||
isCharCorrect?: (char: string, originalChar: string) => boolean;
|
||||
preventDefaultEvent?: (
|
||||
event: JQuery.KeyDownEvent<Document, null, Document, Document>
|
||||
) => Promise<boolean>;
|
||||
handleKeydown?: (
|
||||
event: JQuery.KeyDownEvent<Document, null, Document, Document>
|
||||
) => Promise<void>;
|
||||
getResultContent?: () => string;
|
||||
start?: () => void;
|
||||
restart?: () => void;
|
||||
getWordHtml?: (char: string, letterTag?: boolean) => string;
|
||||
}
|
||||
|
||||
interface FunboxForcedConfig {
|
||||
[key: string]: ConfigValues[];
|
||||
// punctuation?: boolean;
|
||||
// numbers?: boolean;
|
||||
// highlightMode?: typesSeparatedWithHash<HighlightMode>;
|
||||
// words?: FunboxModeDuration;
|
||||
// time?: FunboxModeDuration;
|
||||
}
|
||||
|
||||
interface FunboxMetadata {
|
||||
name: string;
|
||||
type: FunboxObjectType;
|
||||
info: string;
|
||||
canGetPb?: boolean;
|
||||
alias?: string;
|
||||
affectsWordGeneration?: boolean;
|
||||
forcedConfig?: MonkeyTypes.FunboxForcedConfig;
|
||||
properties?: FunboxProperty[];
|
||||
functions?: FunboxFunctions;
|
||||
}
|
||||
|
||||
interface CustomText {
|
||||
|
|
@ -686,7 +739,6 @@ declare namespace MonkeyTypes {
|
|||
exec?: (input?: string) => void;
|
||||
hover?: () => void;
|
||||
available?: () => void;
|
||||
beforeSubgroup?: () => void;
|
||||
shouldFocusTestUI?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -694,6 +746,7 @@ declare namespace MonkeyTypes {
|
|||
title: string;
|
||||
configKey?: keyof Config;
|
||||
list: Command[];
|
||||
beforeList?: () => void;
|
||||
}
|
||||
|
||||
interface Theme {
|
||||
|
|
|
|||
|
|
@ -151,15 +151,15 @@ export async function findCurrentGroup(
|
|||
return retgroup;
|
||||
}
|
||||
|
||||
let funboxList: MonkeyTypes.FunboxObject[] | undefined;
|
||||
export async function getFunboxList(): Promise<MonkeyTypes.FunboxObject[]> {
|
||||
let funboxList: MonkeyTypes.FunboxMetadata[] | undefined;
|
||||
export async function getFunboxList(): Promise<MonkeyTypes.FunboxMetadata[]> {
|
||||
if (!funboxList) {
|
||||
let list = await cachedFetchJson<MonkeyTypes.FunboxObject[]>(
|
||||
let list = await cachedFetchJson<MonkeyTypes.FunboxMetadata[]>(
|
||||
"/./funbox/_list.json"
|
||||
);
|
||||
list = list.sort(function (
|
||||
a: MonkeyTypes.FunboxObject,
|
||||
b: MonkeyTypes.FunboxObject
|
||||
a: MonkeyTypes.FunboxMetadata,
|
||||
b: MonkeyTypes.FunboxMetadata
|
||||
) {
|
||||
const nameA = a.name.toLowerCase();
|
||||
const nameB = b.name.toLowerCase();
|
||||
|
|
@ -176,8 +176,8 @@ export async function getFunboxList(): Promise<MonkeyTypes.FunboxObject[]> {
|
|||
|
||||
export async function getFunbox(
|
||||
funbox: string
|
||||
): Promise<MonkeyTypes.FunboxObject | undefined> {
|
||||
const list: MonkeyTypes.FunboxObject[] = await getFunboxList();
|
||||
): Promise<MonkeyTypes.FunboxMetadata | undefined> {
|
||||
const list: MonkeyTypes.FunboxMetadata[] = await getFunboxList();
|
||||
return list.find(function (element) {
|
||||
return element.name == funbox;
|
||||
});
|
||||
|
|
@ -403,6 +403,10 @@ export function capitalizeFirstLetterOfEachWord(str: string): string {
|
|||
.join(" ");
|
||||
}
|
||||
|
||||
export function capitalizeFirstLetter(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export function isASCIILetter(c: string): boolean {
|
||||
return c.length === 1 && /[a-z]/i.test(c);
|
||||
}
|
||||
|
|
@ -1285,6 +1289,30 @@ export function memoizeAsync<T extends (...args: any) => Promise<any>>(
|
|||
}) as T;
|
||||
}
|
||||
|
||||
export class Wordset {
|
||||
public words: string[];
|
||||
public length: number;
|
||||
constructor(words: string[]) {
|
||||
this.words = words;
|
||||
this.length = this.words.length;
|
||||
}
|
||||
|
||||
public randomWord(): string {
|
||||
return randomElementFromArray(this.words);
|
||||
}
|
||||
}
|
||||
|
||||
export class Section {
|
||||
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 function isPasswordStrong(password: string): boolean {
|
||||
const hasCapital = !!password.match(/[A-Z]/);
|
||||
const hasNumber = !!password.match(/[\d]/);
|
||||
|
|
@ -1301,8 +1329,24 @@ export function areSortedArraysEqual(a: unknown[], b: unknown[]): boolean {
|
|||
return a.length === b.length && a.every((v, i) => v === b[i]);
|
||||
}
|
||||
|
||||
export function intersect<T>(a: T[], b: T[], removeDuplicates = false): T[] {
|
||||
let t;
|
||||
if (b.length > a.length) (t = b), (b = a), (a = t); // indexOf to loop over shorter
|
||||
const filtered = a.filter(function (e) {
|
||||
return b.indexOf(e) > -1;
|
||||
});
|
||||
return removeDuplicates ? [...new Set(filtered)] : filtered;
|
||||
}
|
||||
|
||||
export function htmlToText(html: string): string {
|
||||
const el = document.createElement("div");
|
||||
el.innerHTML = html;
|
||||
return el.textContent || el.innerText || "";
|
||||
}
|
||||
|
||||
export function camelCaseToWords(str: string): string {
|
||||
return str
|
||||
.replace(/([A-Z])/g, " $1")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,148 +1,138 @@
|
|||
[
|
||||
{
|
||||
"name": "nausea",
|
||||
"type": "style",
|
||||
"info": "I think I'm gonna be sick."
|
||||
"info": "I think I'm gonna be sick.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "round_round_baby",
|
||||
"type": "style",
|
||||
"info": "...right round, like a record baby. Right, round round round."
|
||||
"info": "...right round, like a record baby. Right, round round round.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "simon_says",
|
||||
"type": "style",
|
||||
"info": "Type what simon says."
|
||||
"info": "Type what simon says.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "mirror",
|
||||
"type": "style",
|
||||
"info": "Everything is mirrored!"
|
||||
"info": "Everything is mirrored!",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "tts",
|
||||
"type": "script",
|
||||
"info": "Listen closely."
|
||||
"info": "Listen closely.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "choo_choo",
|
||||
"type": "style",
|
||||
"info": "All the letters are spinning!"
|
||||
"info": "All the letters are spinning!",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "arrows",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "Eurobeat Intensifies..."
|
||||
"info": "Eurobeat Intensifies...",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "rAnDoMcAsE",
|
||||
"type": "script",
|
||||
"info": "I kInDa LiKe HoW iNeFfIcIeNt QwErTy Is."
|
||||
"info": "I kInDa LiKe HoW iNeFfIcIeNt QwErTy Is.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "capitals",
|
||||
"type": "script",
|
||||
"info": "Capitalize Every Word."
|
||||
"info": "Capitalize Every Word.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "layoutfluid",
|
||||
"type": "script",
|
||||
"info": "Switch between layouts specified below proportionately to the length of the test."
|
||||
"info": "Switch between layouts specified below proportionately to the length of the test.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "earthquake",
|
||||
"type": "style",
|
||||
"info": "Everybody get down! The words are shaking!"
|
||||
"info": "Everybody get down! The words are shaking!",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "space_balls",
|
||||
"type": "style",
|
||||
"info": "In a galaxy far far away."
|
||||
"info": "In a galaxy far far away.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "gibberish",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "Anvbuefl dizzs eoos alsb?"
|
||||
"info": "Anvbuefl dizzs eoos alsb?",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "58008",
|
||||
"type": "script",
|
||||
"alias": "numbers",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "A special mode for accountants."
|
||||
"info": "A special mode for accountants.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "ascii",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "Where was the ampersand again?. Only ASCII characters."
|
||||
"info": "Where was the ampersand again?. Only ASCII characters.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "specials",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "!@#$%^&*. Only special characters."
|
||||
"info": "!@#$%^&*. Only special characters.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "plus_one",
|
||||
"type": "script",
|
||||
"info": "React quickly! Only one future word is visible."
|
||||
"info": "React quickly! Only one future word is visible.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "plus_two",
|
||||
"type": "script",
|
||||
"info": "Only two future words are visible."
|
||||
"info": "Only two future words are visible.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "read_ahead_easy",
|
||||
"type": "style",
|
||||
"info": "Only the current word is invisible."
|
||||
"info": "Only the current word is invisible.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "read_ahead",
|
||||
"type": "style",
|
||||
"info": "Current and the next word are invisible!"
|
||||
"info": "Current and the next word are invisible!",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "read_ahead_hard",
|
||||
"type": "style",
|
||||
"info": "Current and the next two words are invisible!"
|
||||
"info": "Current and the next two words are invisible!",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "memory",
|
||||
"type": "script",
|
||||
"info": "Test your memory. Remember the words and type them blind."
|
||||
"info": "Test your memory. Remember the words and type them blind.",
|
||||
"canGetPb": true
|
||||
},
|
||||
{
|
||||
"name": "nospace",
|
||||
"type": "script",
|
||||
"info": "Whoneedsspacesanyway?"
|
||||
"info": "Whoneedsspacesanyway?",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "poetry",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "Practice typing some beautiful prose."
|
||||
"info": "Practice typing some beautiful prose.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "wikipedia",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "Practice typing wikipedia sections."
|
||||
"info": "Practice typing wikipedia sections.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "weakspot",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "Focus on slow and mistyped letters."
|
||||
"info": "Focus on slow and mistyped letters.",
|
||||
"canGetPb": false
|
||||
},
|
||||
{
|
||||
"name": "pseudolang",
|
||||
"type": "script",
|
||||
"affectsWordGeneration": true,
|
||||
"info": "Nonsense words that look like the current language."
|
||||
"info": "Nonsense words that look like the current language.",
|
||||
"canGetPb": false
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue