mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-30 02:39:05 +08:00
refactor: make funbox settings an array (@fehmer) (#6487)
change funbox from "hash separated values" to array. --------- Co-authored-by: Miodec <jack@monkeytype.com>
This commit is contained in:
parent
b36bc9f39e
commit
212b8d38cb
51 changed files with 500 additions and 474 deletions
|
|
@ -626,7 +626,7 @@ describe("result controller test", () => {
|
|||
...result,
|
||||
difficulty: "normal",
|
||||
language: "english",
|
||||
funbox: "none",
|
||||
funbox: [],
|
||||
lazyMode: false,
|
||||
punctuation: false,
|
||||
numbers: false,
|
||||
|
|
@ -707,7 +707,7 @@ describe("result controller test", () => {
|
|||
chartData: { wpm: [1, 2, 3], raw: [50, 55, 56], err: [0, 2, 0] },
|
||||
consistency: 23.5,
|
||||
difficulty: "normal",
|
||||
funbox: "none",
|
||||
funbox: [],
|
||||
hash: "hash",
|
||||
incompleteTestSeconds: 2,
|
||||
incompleteTests: [{ acc: 75, seconds: 10 }],
|
||||
|
|
@ -831,7 +831,7 @@ describe("result controller test", () => {
|
|||
chartData: { wpm: [1, 2, 3], raw: [50, 55, 56], err: [0, 2, 0] },
|
||||
consistency: 23.5,
|
||||
difficulty: "normal",
|
||||
funbox: "none",
|
||||
funbox: [],
|
||||
hash: "hash",
|
||||
incompleteTestSeconds: 2,
|
||||
incompleteTests: [{ acc: 75, seconds: 10 }],
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@ import { ObjectId } from "mongodb";
|
|||
import * as UserDal from "../../src/dal/user";
|
||||
import { DBResult } from "../../src/utils/result";
|
||||
|
||||
let uid: string = "";
|
||||
let uid: string;
|
||||
const timestamp = Date.now() - 60000;
|
||||
|
||||
async function createDummyData(
|
||||
uid: string,
|
||||
count: number,
|
||||
timestamp: number,
|
||||
tag?: string
|
||||
modify?: Partial<DBResult>
|
||||
): Promise<void> {
|
||||
const dummyUser: UserDal.DBUser = {
|
||||
_id: new ObjectId(),
|
||||
|
|
@ -28,51 +27,53 @@ async function createDummyData(
|
|||
};
|
||||
|
||||
vi.spyOn(UserDal, "getUser").mockResolvedValue(dummyUser);
|
||||
const tags: string[] = [];
|
||||
if (tag !== undefined) tags.push(tag);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
await ResultDal.addResult(uid, {
|
||||
_id: new ObjectId(),
|
||||
wpm: i,
|
||||
rawWpm: i,
|
||||
charStats: [0, 0, 0, 0],
|
||||
acc: 0,
|
||||
mode: "time",
|
||||
mode2: "10" as never,
|
||||
quoteLength: 1,
|
||||
timestamp,
|
||||
restartCount: 0,
|
||||
incompleteTestSeconds: 0,
|
||||
incompleteTests: [],
|
||||
testDuration: 10,
|
||||
afkDuration: 0,
|
||||
tags,
|
||||
consistency: 100,
|
||||
keyConsistency: 100,
|
||||
chartData: { wpm: [], raw: [], err: [] },
|
||||
uid,
|
||||
keySpacingStats: { average: 0, sd: 0 },
|
||||
keyDurationStats: { average: 0, sd: 0 },
|
||||
difficulty: "normal",
|
||||
language: "english",
|
||||
isPb: false,
|
||||
name: "Test",
|
||||
} as DBResult);
|
||||
...{
|
||||
_id: new ObjectId(),
|
||||
wpm: i,
|
||||
rawWpm: i,
|
||||
charStats: [0, 0, 0, 0],
|
||||
acc: 0,
|
||||
mode: "time",
|
||||
mode2: "10" as never,
|
||||
quoteLength: 1,
|
||||
timestamp,
|
||||
restartCount: 0,
|
||||
incompleteTestSeconds: 0,
|
||||
incompleteTests: [],
|
||||
testDuration: 10,
|
||||
afkDuration: 0,
|
||||
tags: [],
|
||||
consistency: 100,
|
||||
keyConsistency: 100,
|
||||
chartData: { wpm: [], raw: [], err: [] },
|
||||
uid,
|
||||
keySpacingStats: { average: 0, sd: 0 },
|
||||
keyDurationStats: { average: 0, sd: 0 },
|
||||
difficulty: "normal",
|
||||
language: "english",
|
||||
isPb: false,
|
||||
name: "Test",
|
||||
funbox: ["58008", "read_ahead"],
|
||||
},
|
||||
...modify,
|
||||
});
|
||||
}
|
||||
}
|
||||
describe("ResultDal", () => {
|
||||
beforeEach(() => {
|
||||
uid = new ObjectId().toHexString();
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (uid) await ResultDal.deleteAll(uid);
|
||||
});
|
||||
describe("getResults", () => {
|
||||
beforeEach(() => {
|
||||
uid = new ObjectId().toHexString();
|
||||
});
|
||||
afterEach(async () => {
|
||||
if (uid) await ResultDal.deleteAll(uid);
|
||||
});
|
||||
|
||||
it("should read lastest 10 results ordered by timestamp", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 10, timestamp - 2000, "old");
|
||||
await createDummyData(uid, 20, timestamp, "current");
|
||||
await createDummyData(uid, 10, { timestamp: timestamp - 2000 });
|
||||
await createDummyData(uid, 20, { tags: ["current"] });
|
||||
|
||||
//WHEN
|
||||
const results = await ResultDal.getResults(uid, { limit: 10 });
|
||||
|
|
@ -88,8 +89,8 @@ describe("ResultDal", () => {
|
|||
});
|
||||
it("should read all if not limited", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 10, timestamp - 2000, "old");
|
||||
await createDummyData(uid, 20, timestamp, "current");
|
||||
await createDummyData(uid, 10, { timestamp: timestamp - 2000 });
|
||||
await createDummyData(uid, 20);
|
||||
|
||||
//WHEN
|
||||
const results = await ResultDal.getResults(uid, {});
|
||||
|
|
@ -99,8 +100,8 @@ describe("ResultDal", () => {
|
|||
});
|
||||
it("should read results onOrAfterTimestamp", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 10, timestamp - 2000, "old");
|
||||
await createDummyData(uid, 20, timestamp, "current");
|
||||
await createDummyData(uid, 10, { timestamp: timestamp - 2000 });
|
||||
await createDummyData(uid, 20, { tags: ["current"] });
|
||||
|
||||
//WHEN
|
||||
const results = await ResultDal.getResults(uid, {
|
||||
|
|
@ -115,8 +116,11 @@ describe("ResultDal", () => {
|
|||
});
|
||||
it("should read next 10 results", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 10, timestamp - 2000, "old");
|
||||
await createDummyData(uid, 20, timestamp, "current");
|
||||
await createDummyData(uid, 10, {
|
||||
timestamp: timestamp - 2000,
|
||||
tags: ["old"],
|
||||
});
|
||||
await createDummyData(uid, 20);
|
||||
|
||||
//WHEN
|
||||
const results = await ResultDal.getResults(uid, {
|
||||
|
|
@ -130,5 +134,84 @@ describe("ResultDal", () => {
|
|||
expect(it.tags).toContain("old");
|
||||
});
|
||||
});
|
||||
it("should convert legacy values", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 1, { funbox: "58008#read_ahead" as any });
|
||||
|
||||
//WHEN
|
||||
const results = await ResultDal.getResults(uid);
|
||||
|
||||
//THEN
|
||||
expect(results[0]?.funbox).toEqual(["58008", "read_ahead"]);
|
||||
});
|
||||
});
|
||||
describe("getResult", () => {
|
||||
it("should convert legacy values", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 1, { funbox: "58008#read_ahead" as any });
|
||||
const resultId = (await ResultDal.getLastResult(uid))._id.toHexString();
|
||||
|
||||
//WHEN
|
||||
const result = await ResultDal.getResult(uid, resultId);
|
||||
|
||||
//THEN
|
||||
expect(result?.funbox).toEqual(["58008", "read_ahead"]);
|
||||
});
|
||||
});
|
||||
describe("getLastResult", () => {
|
||||
it("should convert legacy values", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 1, { funbox: "58008#read_ahead" as any });
|
||||
|
||||
//WHEN
|
||||
const result = await ResultDal.getLastResult(uid);
|
||||
|
||||
//THEN
|
||||
expect(result?.funbox).toEqual(["58008", "read_ahead"]);
|
||||
});
|
||||
});
|
||||
describe("getResultByTimestamp", () => {
|
||||
it("should convert legacy values", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 1, { funbox: "58008#read_ahead" as any });
|
||||
|
||||
//WHEN
|
||||
const result = await ResultDal.getResultByTimestamp(uid, timestamp);
|
||||
|
||||
//THEN
|
||||
expect(result?.funbox).toEqual(["58008", "read_ahead"]);
|
||||
});
|
||||
});
|
||||
describe("converts legacy values", () => {
|
||||
it("should convert funbox as string", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 1, { funbox: "58008#read_ahead" as any });
|
||||
|
||||
//WHEN
|
||||
const read = await ResultDal.getLastResult(uid);
|
||||
|
||||
//THEN
|
||||
expect(read.funbox).toEqual(["58008", "read_ahead"]);
|
||||
});
|
||||
it("should convert funbox 'none'", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 1, { funbox: "none" as any });
|
||||
|
||||
//WHEN
|
||||
const read = await ResultDal.getLastResult(uid);
|
||||
|
||||
//THEN
|
||||
expect(read.funbox).toEqual([]);
|
||||
});
|
||||
it("should not convert funbox as array", async () => {
|
||||
//GIVEN
|
||||
await createDummyData(uid, 1, { funbox: ["58008", "read_ahead"] });
|
||||
|
||||
//WHEN
|
||||
const read = await ResultDal.getLastResult(uid);
|
||||
|
||||
//THEN
|
||||
expect(read.funbox).toEqual(["58008", "read_ahead"]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,27 +2,33 @@ import _ from "lodash";
|
|||
import * as pb from "../../src/utils/pb";
|
||||
import { Mode, PersonalBests } from "@monkeytype/contracts/schemas/shared";
|
||||
import { Result } from "@monkeytype/contracts/schemas/results";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
|
||||
describe("Pb Utils", () => {
|
||||
it("funboxCatGetPb", () => {
|
||||
const testCases = [
|
||||
{
|
||||
funbox: "plus_one",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
funbox: "none",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
funbox: "nausea#plus_one",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
funbox: "arrows",
|
||||
expected: false,
|
||||
},
|
||||
];
|
||||
const testCases: { funbox: FunboxName[] | undefined; expected: boolean }[] =
|
||||
[
|
||||
{
|
||||
funbox: ["plus_one"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
funbox: [],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
funbox: undefined,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
funbox: ["nausea", "plus_one"],
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
funbox: ["arrows"],
|
||||
expected: false,
|
||||
},
|
||||
];
|
||||
|
||||
_.each(testCases, (testCase) => {
|
||||
const { funbox, expected } = testCase;
|
||||
|
|
|
|||
|
|
@ -58,11 +58,7 @@ import {
|
|||
getStartOfDayTimestamp,
|
||||
} from "@monkeytype/util/date-and-time";
|
||||
import { MonkeyRequest } from "../types";
|
||||
import {
|
||||
getFunbox,
|
||||
checkCompatibility,
|
||||
stringToFunboxNames,
|
||||
} from "@monkeytype/funbox";
|
||||
import { getFunbox, checkCompatibility } from "@monkeytype/funbox";
|
||||
import { tryCatch } from "@monkeytype/util/trycatch";
|
||||
|
||||
try {
|
||||
|
|
@ -175,8 +171,8 @@ export async function updateTags(
|
|||
if (!(result.language ?? "")) {
|
||||
result.language = "english";
|
||||
}
|
||||
if (!(result.funbox ?? "")) {
|
||||
result.funbox = "none";
|
||||
if (result.funbox === undefined) {
|
||||
result.funbox = [];
|
||||
}
|
||||
if (!result.lazyMode) {
|
||||
result.lazyMode = false;
|
||||
|
|
@ -242,16 +238,11 @@ export async function addResult(
|
|||
Logger.warning("Object hash check is disabled, skipping hash check");
|
||||
}
|
||||
|
||||
if (completedEvent.funbox) {
|
||||
const funboxes = completedEvent.funbox.split("#");
|
||||
if (funboxes.length !== _.uniq(funboxes).length) {
|
||||
throw new MonkeyError(400, "Duplicate funboxes");
|
||||
}
|
||||
if (completedEvent.funbox.length !== _.uniq(completedEvent.funbox).length) {
|
||||
throw new MonkeyError(400, "Duplicate funboxes");
|
||||
}
|
||||
|
||||
const funboxNames = stringToFunboxNames(completedEvent.funbox ?? "");
|
||||
|
||||
if (!checkCompatibility(funboxNames)) {
|
||||
if (!checkCompatibility(completedEvent.funbox)) {
|
||||
throw new MonkeyError(400, "Impossible funbox combination");
|
||||
}
|
||||
|
||||
|
|
@ -732,15 +723,12 @@ async function calculateXp(
|
|||
}
|
||||
}
|
||||
|
||||
if (funboxBonusConfiguration > 0 && resultFunboxes !== "none") {
|
||||
const funboxModifier = _.sumBy(
|
||||
stringToFunboxNames(resultFunboxes),
|
||||
(funboxName) => {
|
||||
const funbox = getFunbox(funboxName);
|
||||
const difficultyLevel = funbox?.difficultyLevel ?? 0;
|
||||
return Math.max(difficultyLevel * funboxBonusConfiguration, 0);
|
||||
}
|
||||
);
|
||||
if (funboxBonusConfiguration > 0 && resultFunboxes.length !== 0) {
|
||||
const funboxModifier = _.sumBy(resultFunboxes, (funboxName) => {
|
||||
const funbox = getFunbox(funboxName);
|
||||
const difficultyLevel = funbox?.difficultyLevel ?? 0;
|
||||
return Math.max(difficultyLevel * funboxBonusConfiguration, 0);
|
||||
});
|
||||
if (funboxModifier > 0) {
|
||||
modifier += funboxModifier;
|
||||
breakdown.funbox = Math.round(baseXp * funboxModifier);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import * as db from "../init/db";
|
|||
|
||||
import { getUser, getTags } from "./user";
|
||||
import { DBResult } from "../utils/result";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
import { tryCatch } from "@monkeytype/util/trycatch";
|
||||
|
||||
export const getResultCollection = (): Collection<DBResult> =>
|
||||
|
|
@ -65,7 +66,7 @@ export async function getResult(uid: string, id: string): Promise<DBResult> {
|
|||
uid,
|
||||
});
|
||||
if (!result) throw new MonkeyError(404, "Result not found");
|
||||
return result;
|
||||
return convert(result);
|
||||
}
|
||||
|
||||
export async function getLastResult(uid: string): Promise<DBResult> {
|
||||
|
|
@ -75,14 +76,15 @@ export async function getLastResult(uid: string): Promise<DBResult> {
|
|||
.limit(1)
|
||||
.toArray();
|
||||
if (!lastResult) throw new MonkeyError(404, "No results found");
|
||||
return lastResult;
|
||||
return convert(lastResult);
|
||||
}
|
||||
|
||||
export async function getResultByTimestamp(
|
||||
uid: string,
|
||||
timestamp: number
|
||||
): Promise<DBResult | null> {
|
||||
return await getResultCollection().findOne({ uid, timestamp });
|
||||
const result = await getResultCollection().findOne({ uid, timestamp });
|
||||
return convert(result);
|
||||
}
|
||||
|
||||
type GetResultsOpts = {
|
||||
|
|
@ -125,5 +127,26 @@ export async function getResults(
|
|||
|
||||
const results = await query.toArray();
|
||||
if (results === undefined) throw new MonkeyError(404, "Result not found");
|
||||
return results;
|
||||
return convert(results);
|
||||
}
|
||||
|
||||
function convert<T extends DBResult | DBResult[] | null>(results: T): T {
|
||||
if (results === null) return results;
|
||||
|
||||
const migrate = (result: DBResult): DBResult => {
|
||||
if (typeof result.funbox === "string") {
|
||||
if (result.funbox === "none") {
|
||||
result.funbox = [];
|
||||
} else {
|
||||
result.funbox = (result.funbox as string).split("#") as FunboxName[];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
if (Array.isArray(results)) {
|
||||
return results.map(migrate) as T;
|
||||
} else {
|
||||
return migrate(results) as T;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
PersonalBests,
|
||||
} from "@monkeytype/contracts/schemas/shared";
|
||||
import { Result as ResultType } from "@monkeytype/contracts/schemas/results";
|
||||
import { getFunboxesFromString } from "@monkeytype/funbox";
|
||||
import { getFunbox } from "@monkeytype/funbox";
|
||||
|
||||
export type LbPersonalBests = {
|
||||
time: Record<number, Record<string, PersonalBest>>;
|
||||
|
|
@ -20,16 +20,9 @@ type CheckAndUpdatePbResult = {
|
|||
type Result = Omit<ResultType<Mode>, "_id" | "name">;
|
||||
|
||||
export function canFunboxGetPb(result: Result): boolean {
|
||||
const funboxString = result.funbox;
|
||||
if (
|
||||
funboxString === undefined ||
|
||||
funboxString === "" ||
|
||||
funboxString === "none"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (result.funbox === undefined || result.funbox.length === 0) return true;
|
||||
|
||||
return getFunboxesFromString(funboxString).every((f) => f.canGetPb);
|
||||
return getFunbox(result.funbox).every((f) => f.canGetPb);
|
||||
}
|
||||
|
||||
export function checkAndUpdatePb(
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export function incrementResult(res: CompletedEvent, isPb?: boolean): void {
|
|||
});
|
||||
|
||||
resultFunbox.inc({
|
||||
funbox: funbox || "none",
|
||||
funbox: (funbox ?? ["none"]).join("#"),
|
||||
});
|
||||
|
||||
resultWpm.observe(
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export function buildDbResult(
|
|||
if (!ce.blindMode) delete res.blindMode;
|
||||
if (!ce.lazyMode) delete res.lazyMode;
|
||||
if (ce.difficulty === "normal") delete res.difficulty;
|
||||
if (ce.funbox === "none") delete res.funbox;
|
||||
if (ce.funbox.length === 0) delete res.funbox;
|
||||
if (ce.language === "english") delete res.language;
|
||||
if (!ce.numbers) delete res.numbers;
|
||||
if (!ce.punctuation) delete res.punctuation;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import * as Config from "../../src/ts/config";
|
||||
|
||||
import { CustomThemeColors } from "@monkeytype/contracts/schemas/configs";
|
||||
import {
|
||||
CustomThemeColors,
|
||||
FunboxName,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
describe("Config", () => {
|
||||
|
|
@ -332,10 +335,10 @@ describe("Config", () => {
|
|||
expect(Config.setFavThemes([stringOfLength(51)])).toBe(false);
|
||||
});
|
||||
it("setFunbox", () => {
|
||||
expect(Config.setFunbox("mirror")).toBe(true);
|
||||
expect(Config.setFunbox("mirror#58008")).toBe(true);
|
||||
expect(Config.setFunbox(["mirror"])).toBe(true);
|
||||
expect(Config.setFunbox(["mirror", "58008"])).toBe(true);
|
||||
|
||||
expect(Config.setFunbox(stringOfLength(101))).toBe(false);
|
||||
expect(Config.setFunbox([stringOfLength(101) as FunboxName])).toBe(false);
|
||||
});
|
||||
it("setPaceCaretCustomSpeed", () => {
|
||||
expect(Config.setPaceCaretCustomSpeed(0)).toBe(true);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { canSetConfigWithCurrentFunboxes } from "../../../src/ts/test/funbox/funbox-validation";
|
||||
|
||||
import * as Notifications from "../../../src/ts/elements/notifications";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
describe("funbox-validation", () => {
|
||||
describe("canSetConfigWithCurrentFunboxes", () => {
|
||||
const addNotificationMock = vi.spyOn(Notifications, "add");
|
||||
|
|
@ -60,7 +61,7 @@ describe("funbox-validation", () => {
|
|||
`check $funbox with $key=$value`,
|
||||
({ key, value, funbox, error }) => {
|
||||
expect(
|
||||
canSetConfigWithCurrentFunboxes(key, value, funbox.join("#"))
|
||||
canSetConfigWithCurrentFunboxes(key, value, funbox as FunboxName[])
|
||||
).toBe(error === undefined);
|
||||
|
||||
if (error !== undefined) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import * as TestLogic from "../../src/ts/test/test-logic";
|
|||
import * as TestState from "../../src/ts/test/test-state";
|
||||
import * as Misc from "../../src/ts/utils/misc";
|
||||
import { loadTestSettingsFromUrl } from "../../src/ts/utils/url-handler";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
|
||||
//mock modules to avoid dependencies
|
||||
vi.mock("../../src/ts/test/test-logic", () => ({
|
||||
|
|
@ -167,6 +168,19 @@ describe("url-handler", () => {
|
|||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets funbox", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ funbox: ["crt", "choo_choo"] })
|
||||
);
|
||||
|
||||
//WHEN
|
||||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setFunboxMock).toHaveBeenCalledWith(["crt", "choo_choo"], true);
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("sets funbox legacy", () => {
|
||||
//GIVEN
|
||||
findGetParameterMock.mockReturnValue(
|
||||
urlData({ funbox: "crt#choo_choo" })
|
||||
|
|
@ -176,7 +190,7 @@ describe("url-handler", () => {
|
|||
loadTestSettingsFromUrl("");
|
||||
|
||||
//THEN
|
||||
expect(setFunboxMock).toHaveBeenCalledWith("crt#choo_choo", true);
|
||||
expect(setFunboxMock).toHaveBeenCalledWith(["crt", "choo_choo"], true);
|
||||
expect(restartTestMock).toHaveBeenCalled();
|
||||
});
|
||||
it("adds notification", () => {
|
||||
|
|
@ -195,7 +209,7 @@ describe("url-handler", () => {
|
|||
numbers: true,
|
||||
language: "english",
|
||||
difficulty: "master",
|
||||
funbox: "a#b",
|
||||
funbox: ["ascii", "crt"],
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -204,7 +218,7 @@ describe("url-handler", () => {
|
|||
|
||||
//THEN
|
||||
expect(addNotificationMock).toHaveBeenCalledWith(
|
||||
"Settings applied from URL:<br><br>mode: time<br>mode2: 60<br>custom text settings<br>punctuation: on<br>numbers: on<br>language: english<br>difficulty: master<br>funbox: a#b<br>",
|
||||
"Settings applied from URL:<br><br>mode: time<br>mode2: 60<br>custom text settings<br>punctuation: on<br>numbers: on<br>language: english<br>difficulty: master<br>funbox: ascii, crt<br>",
|
||||
1,
|
||||
{
|
||||
duration: 10,
|
||||
|
|
@ -246,7 +260,7 @@ describe("url-handler", () => {
|
|||
\"3\" Expected boolean, received string
|
||||
\"4\" Expected boolean, received string
|
||||
\"6\" Invalid enum value. Expected 'normal' | 'expert' | 'master', received 'invalid'
|
||||
\"7\" Expected string, received array`,
|
||||
\"7\" Invalid input`,
|
||||
0
|
||||
);
|
||||
});
|
||||
|
|
@ -262,7 +276,7 @@ const urlData = (
|
|||
numbers: boolean;
|
||||
language: string;
|
||||
difficulty: Difficulty;
|
||||
funbox: string;
|
||||
funbox: FunboxName[] | string;
|
||||
}>
|
||||
): string => {
|
||||
return compressToURI(
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ function validateOthers() {
|
|||
funbox: {
|
||||
type: "object",
|
||||
properties: {
|
||||
exact: { type: "string" },
|
||||
exact: { type: "array" },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -388,11 +388,7 @@ async function showCommands(): Promise<void> {
|
|||
} else if (configKey !== undefined) {
|
||||
let isActive;
|
||||
|
||||
if (command.configValueMode === "funbox") {
|
||||
isActive = (Config[configKey] as string)
|
||||
.split("#")
|
||||
.includes(command.configValue as string);
|
||||
} else if (command.configValueMode === "include") {
|
||||
if (command.configValueMode === "include") {
|
||||
isActive = (
|
||||
Config[configKey] as (
|
||||
| string
|
||||
|
|
@ -741,7 +737,7 @@ const modal = new AnimatedModal({
|
|||
}
|
||||
});
|
||||
|
||||
modalEl.addEventListener("mousemove", (e) => {
|
||||
modalEl.addEventListener("mousemove", (_e) => {
|
||||
mouseMode = true;
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const list: Command[] = [
|
|||
sticky: true,
|
||||
exec: (): void => {
|
||||
ManualRestart.set();
|
||||
if (Funbox.setFunbox("none")) {
|
||||
if (Funbox.setFunbox([])) {
|
||||
TestLogic.restart();
|
||||
}
|
||||
},
|
||||
|
|
@ -33,8 +33,7 @@ for (const funbox of getAllFunboxes()) {
|
|||
sticky: true,
|
||||
alias: funbox.alias,
|
||||
configValue: funbox.name,
|
||||
//todo remove funbox mode once Config.funbox is changed to an array
|
||||
configValueMode: "funbox",
|
||||
configValueMode: "include",
|
||||
exec: (): void => {
|
||||
Funbox.toggleFunbox(funbox.name);
|
||||
ManualRestart.set();
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export type Command = {
|
|||
defaultValue?: () => string;
|
||||
configKey?: keyof Config;
|
||||
configValue?: string | number | boolean | number[];
|
||||
configValueMode?: "include" | "funbox";
|
||||
configValueMode?: "include";
|
||||
exec?: (options: CommandExecOptions) => void;
|
||||
hover?: () => void;
|
||||
available?: () => boolean;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import {
|
|||
typedKeys,
|
||||
} from "./utils/misc";
|
||||
import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs";
|
||||
import { Config } from "@monkeytype/contracts/schemas/configs";
|
||||
import { Config, FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
import { Mode, ModeSchema } from "@monkeytype/contracts/schemas/shared";
|
||||
import { Language, LanguageSchema } from "@monkeytype/contracts/schemas/util";
|
||||
import { LocalStorageWithSchema } from "./utils/local-storage-with-schema";
|
||||
|
|
@ -262,52 +262,42 @@ export function setFunbox(
|
|||
if (!isConfigValueValid("funbox", funbox, ConfigSchemas.FunboxSchema))
|
||||
return false;
|
||||
|
||||
for (const funbox of config.funbox.split("#")) {
|
||||
for (const funbox of config.funbox) {
|
||||
if (!canSetFunboxWithConfig(funbox, config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const val = funbox || "none";
|
||||
config.funbox = val;
|
||||
config.funbox = funbox;
|
||||
saveToLocalStorage("funbox", nosave);
|
||||
ConfigEvent.dispatch("funbox", config.funbox);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function toggleFunbox(
|
||||
funbox: ConfigSchemas.Funbox,
|
||||
nosave?: boolean
|
||||
): number | boolean {
|
||||
if (!isConfigValueValid("funbox", funbox, ConfigSchemas.FunboxSchema))
|
||||
export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean {
|
||||
if (!canSetFunboxWithConfig(funbox, config)) {
|
||||
return false;
|
||||
|
||||
let r;
|
||||
|
||||
const funboxArray = config.funbox.split("#");
|
||||
if (funboxArray[0] === "none") funboxArray.splice(0, 1);
|
||||
if (!funboxArray.includes(funbox)) {
|
||||
if (!canSetFunboxWithConfig(funbox, config)) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
let newConfig: FunboxName[] = config.funbox;
|
||||
|
||||
if (newConfig.includes(funbox)) {
|
||||
newConfig = newConfig.filter((it) => it !== funbox);
|
||||
} else {
|
||||
newConfig.push(funbox);
|
||||
newConfig.sort();
|
||||
}
|
||||
|
||||
if (!isConfigValueValid("funbox", newConfig, ConfigSchemas.FunboxSchema)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config.funbox = newConfig;
|
||||
saveToLocalStorage("funbox", nosave);
|
||||
ConfigEvent.dispatch("funbox", config.funbox);
|
||||
|
||||
return r;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function setBlindMode(blind: boolean, nosave?: boolean): boolean {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const obj = {
|
|||
paceCaretStyle: "default",
|
||||
flipTestColors: false,
|
||||
layout: "default",
|
||||
funbox: "none",
|
||||
funbox: [],
|
||||
confidenceMode: "off",
|
||||
indicateTypos: "off",
|
||||
timerStyle: "mini",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { deepClone } from "../utils/misc";
|
|||
import { getDefaultConfig } from "./default-config";
|
||||
import { Mode } from "@monkeytype/contracts/schemas/shared";
|
||||
import { Result } from "@monkeytype/contracts/schemas/results";
|
||||
import { Config } from "@monkeytype/contracts/schemas/configs";
|
||||
import { Config, FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
import {
|
||||
ModifiableTestActivityCalendar,
|
||||
TestActivityCalendar,
|
||||
|
|
@ -42,7 +42,7 @@ export type SnapshotResult<M extends Mode> = Omit<
|
|||
blindMode: boolean;
|
||||
lazyMode: boolean;
|
||||
difficulty: string;
|
||||
funbox: string;
|
||||
funbox: FunboxName[];
|
||||
language: string;
|
||||
numbers: boolean;
|
||||
punctuation: boolean;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,11 @@ import {
|
|||
import {
|
||||
Config as ConfigType,
|
||||
Difficulty,
|
||||
FunboxName,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { Mode } from "@monkeytype/contracts/schemas/shared";
|
||||
import { CompletedEvent } from "@monkeytype/contracts/schemas/results";
|
||||
import { areUnsortedArraysEqual } from "../utils/arrays";
|
||||
import { tryCatch } from "@monkeytype/util/trycatch";
|
||||
|
||||
let challengeLoading = false;
|
||||
|
|
@ -36,7 +38,10 @@ export function clearActive(): void {
|
|||
|
||||
function verifyRequirement(
|
||||
result: CompletedEvent,
|
||||
requirements: Record<string, Record<string, string | number | boolean>>,
|
||||
requirements: Record<
|
||||
string,
|
||||
Record<string, string | number | boolean | FunboxName[]>
|
||||
>,
|
||||
requirementType: string
|
||||
): [boolean, string[]] {
|
||||
let requirementsMet = true;
|
||||
|
|
@ -93,29 +98,21 @@ function verifyRequirement(
|
|||
}
|
||||
}
|
||||
} else if (requirementType === "funbox") {
|
||||
const funboxMode = requirementValue["exact"]
|
||||
?.toString()
|
||||
.split("#")
|
||||
.sort()
|
||||
.join("#");
|
||||
|
||||
const funboxMode = requirementValue["exact"] as FunboxName[];
|
||||
if (funboxMode === undefined) {
|
||||
throw new Error("Funbox mode is undefined");
|
||||
}
|
||||
|
||||
if (funboxMode !== result.funbox) {
|
||||
if (!areUnsortedArraysEqual(funboxMode, result.funbox)) {
|
||||
requirementsMet = false;
|
||||
for (const f of funboxMode.split("#")) {
|
||||
if (
|
||||
result.funbox?.split("#").find((rf: string) => rf === f) === undefined
|
||||
) {
|
||||
for (const f of funboxMode) {
|
||||
if (!result.funbox?.includes(f)) {
|
||||
failReasons.push(`${f} funbox not active`);
|
||||
}
|
||||
}
|
||||
const funboxSplit = result.funbox?.split("#");
|
||||
if (funboxSplit !== undefined && funboxSplit.length > 0) {
|
||||
for (const f of funboxSplit) {
|
||||
if (funboxMode.split("#").find((rf) => rf === f) === undefined) {
|
||||
if (result.funbox !== undefined && result.funbox.length > 0) {
|
||||
for (const f of result.funbox) {
|
||||
if (!funboxMode.includes(f)) {
|
||||
failReasons.push(`${f} funbox active`);
|
||||
}
|
||||
}
|
||||
|
|
@ -218,7 +215,7 @@ export function verify(result: CompletedEvent): string | null {
|
|||
export async function setup(challengeName: string): Promise<boolean> {
|
||||
challengeLoading = true;
|
||||
|
||||
UpdateConfig.setFunbox("none");
|
||||
UpdateConfig.setFunbox([]);
|
||||
|
||||
const { data: list, error } = await tryCatch(JSONData.getChallengeList());
|
||||
if (error) {
|
||||
|
|
@ -288,14 +285,14 @@ export async function setup(challengeName: string): Promise<boolean> {
|
|||
UpdateConfig.setTheme(challenge.parameters[1] as string);
|
||||
}
|
||||
if (challenge.parameters[2] !== null) {
|
||||
void Funbox.activate(challenge.parameters[2] as string);
|
||||
void Funbox.activate(challenge.parameters[2] as FunboxName[]);
|
||||
}
|
||||
} else if (challenge.type === "accuracy") {
|
||||
UpdateConfig.setTimeConfig(0, true);
|
||||
UpdateConfig.setMode("time", true);
|
||||
UpdateConfig.setDifficulty("master", true);
|
||||
} else if (challenge.type === "funbox") {
|
||||
UpdateConfig.setFunbox(challenge.parameters[0] as string, true);
|
||||
UpdateConfig.setFunbox(challenge.parameters[0] as FunboxName[], true);
|
||||
UpdateConfig.setDifficulty("normal", true);
|
||||
if (challenge.parameters[1] === "words") {
|
||||
UpdateConfig.setWordCount(challenge.parameters[2] as number, true);
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ export async function getUserResults(offset?: number): Promise<boolean> {
|
|||
if (result.blindMode === undefined) result.blindMode = false;
|
||||
if (result.lazyMode === undefined) result.lazyMode = false;
|
||||
if (result.difficulty === undefined) result.difficulty = "normal";
|
||||
if (result.funbox === undefined) result.funbox = "none";
|
||||
if (result.funbox === undefined) result.funbox = [];
|
||||
if (result.language === undefined || result.language === null) {
|
||||
result.language = "english";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -645,10 +645,10 @@ $(".pageAccount .topFilters button.currentConfigFilter").on("click", () => {
|
|||
filters.language[Config.language] = true;
|
||||
}
|
||||
|
||||
if (Config.funbox === "none") {
|
||||
if (Config.funbox.length === 0) {
|
||||
filters.funbox["none"] = true;
|
||||
} else {
|
||||
for (const f of Config.funbox.split("#")) {
|
||||
for (const f of Config.funbox) {
|
||||
filters.funbox[f] = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,11 +214,11 @@ export async function update(): Promise<void> {
|
|||
);
|
||||
}
|
||||
|
||||
if (Config.funbox !== "none") {
|
||||
if (Config.funbox.length > 0) {
|
||||
$(".pageTest #testModesNotice").append(
|
||||
`<button class="textButton" commands="funbox"><i class="fas fa-gamepad"></i>${Config.funbox
|
||||
.replace(/_/g, " ")
|
||||
.replace(/#/g, ", ")}</button>`
|
||||
.map((it) => it.replace(/_/g, " "))
|
||||
.join(", ")}</button>`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ function fillData(): void {
|
|||
if (r.punctuation) tt += "<br>punctuation";
|
||||
if (r.blindMode) tt += "<br>blind";
|
||||
if (r.lazyMode) tt += "<br>lazy";
|
||||
if (r.funbox !== "none") {
|
||||
tt += "<br>" + r.funbox.replace(/_/g, " ").replace(/#/g, ", ");
|
||||
if (r.funbox.length > 0) {
|
||||
tt += "<br>" + r.funbox.map((it) => it.replace(/_/g, " ")).join(",");
|
||||
}
|
||||
if (r.difficulty !== "normal") tt += "<br>" + r.difficulty;
|
||||
if (r.tags.length > 0) tt += "<br>" + r.tags.length + " tags";
|
||||
|
|
@ -116,11 +116,11 @@ const modal = new AnimatedModal({
|
|||
setup: async (modalEl): Promise<void> => {
|
||||
modalEl
|
||||
.querySelector("button.save")
|
||||
?.addEventListener("click", async (e) => {
|
||||
?.addEventListener("click", async () => {
|
||||
void syncNotSignedInLastResult(Auth?.currentUser?.uid as string);
|
||||
hide();
|
||||
});
|
||||
modalEl.querySelector("button.discard")?.addEventListener("click", (e) => {
|
||||
modalEl.querySelector("button.discard")?.addEventListener("click", () => {
|
||||
TestLogic.clearNotSignedInResult();
|
||||
Notifications.add("Last test result discarded", 0);
|
||||
hide();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { getMode2 } from "../utils/misc";
|
|||
import * as CustomText from "../test/custom-text";
|
||||
import { compressToURI } from "lz-ts";
|
||||
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
|
||||
import { Difficulty } from "@monkeytype/contracts/schemas/configs";
|
||||
import { Difficulty, FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
import { Mode, Mode2 } from "@monkeytype/contracts/schemas/shared";
|
||||
|
||||
function getCheckboxValue(checkbox: string): boolean {
|
||||
|
|
@ -21,7 +21,7 @@ type SharedTestSettings = [
|
|||
boolean | null,
|
||||
string | null,
|
||||
Difficulty | null,
|
||||
string | null
|
||||
FunboxName[] | null
|
||||
];
|
||||
|
||||
function updateURL(): void {
|
||||
|
|
|
|||
|
|
@ -97,11 +97,10 @@ function loadMoreLines(lineIndex?: number): void {
|
|||
icons += `<span aria-label="lazy mode" data-balloon-pos="up"><i class="fas fa-fw fa-couch"></i></span>`;
|
||||
}
|
||||
|
||||
if (result.funbox !== "none" && result.funbox !== undefined) {
|
||||
if (result.funbox !== undefined && result.funbox.length > 0) {
|
||||
icons += `<span aria-label="${result.funbox
|
||||
.replace(/_/g, " ")
|
||||
.replace(
|
||||
/#/g,
|
||||
.map((it) => it.replace(/_/g, " "))
|
||||
.join(
|
||||
", "
|
||||
)}" data-balloon-pos="up"><i class="fas fa-gamepad"></i></span>`;
|
||||
}
|
||||
|
|
@ -414,7 +413,7 @@ async function fillContent(): Promise<void> {
|
|||
return;
|
||||
}
|
||||
|
||||
if (result.funbox === "none" || result.funbox === undefined) {
|
||||
if (result.funbox === undefined || result.funbox.length === 0) {
|
||||
if (!ResultFilters.getFilter("funbox", "none")) {
|
||||
if (filterDebug) {
|
||||
console.log(`skipping result due to funbox filter`, result);
|
||||
|
|
@ -423,7 +422,7 @@ async function fillContent(): Promise<void> {
|
|||
}
|
||||
} else {
|
||||
let counter = 0;
|
||||
for (const f of result.funbox.split("#")) {
|
||||
for (const f of result.funbox) {
|
||||
if (ResultFilters.getFilter("funbox", f)) {
|
||||
counter++;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -23,16 +23,14 @@ import * as CustomBackgroundFilter from "../elements/custom-background-filter";
|
|||
import {
|
||||
ConfigValue,
|
||||
CustomBackgroundSchema,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import {
|
||||
getAllFunboxes,
|
||||
FunboxName,
|
||||
checkCompatibility,
|
||||
} from "@monkeytype/funbox";
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { getAllFunboxes, checkCompatibility } from "@monkeytype/funbox";
|
||||
import { getActiveFunboxNames } from "../test/funbox/list";
|
||||
import { SnapshotPreset } from "../constants/default-snapshot";
|
||||
import { LayoutsList } from "../constants/layouts";
|
||||
import { DataArrayPartial, Optgroup } from "slim-select/store";
|
||||
import { areUnsortedArraysEqual } from "../utils/arrays";
|
||||
import { tryCatch } from "@monkeytype/util/trycatch";
|
||||
|
||||
type SettingsGroups<T extends ConfigValue> = Record<string, SettingsGroup<T>>;
|
||||
|
|
@ -708,7 +706,7 @@ async function fillSettingsPage(): Promise<void> {
|
|||
events: {
|
||||
afterChange: (newVal): void => {
|
||||
const customPolyglot = newVal.map((it) => it.value);
|
||||
if (customPolyglot.toSorted() !== Config.customPolyglot.toSorted()) {
|
||||
if (!areUnsortedArraysEqual(customPolyglot, Config.customPolyglot)) {
|
||||
void UpdateConfig.setCustomPolyglot(customPolyglot);
|
||||
}
|
||||
},
|
||||
|
|
@ -755,18 +753,18 @@ function setActiveFunboxButton(): void {
|
|||
getAllFunboxes().forEach((funbox) => {
|
||||
if (
|
||||
!checkCompatibility(getActiveFunboxNames(), funbox.name) &&
|
||||
!Config.funbox.split("#").includes(funbox.name)
|
||||
!Config.funbox.includes(funbox.name)
|
||||
) {
|
||||
$(
|
||||
`.pageSettings .section[data-config-name='funbox'] .button[data-config-value='${funbox.name}']`
|
||||
).addClass("disabled");
|
||||
}
|
||||
});
|
||||
Config.funbox.split("#").forEach((funbox) => {
|
||||
for (const funbox of Config.funbox) {
|
||||
$(
|
||||
`.pageSettings .section[data-config-name='funbox'] .button[data-config-value='${funbox}']`
|
||||
).addClass("active");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function refreshTagsSettingsSection(): void {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import * as Strings from "../../utils/strings";
|
|||
import { randomIntFromRange } from "@monkeytype/util/numbers";
|
||||
import * as Arrays from "../../utils/arrays";
|
||||
import { save } from "./funbox-memory";
|
||||
import { type FunboxName } from "@monkeytype/funbox";
|
||||
import * as TTSEvent from "../../observables/tts-event";
|
||||
import * as Notifications from "../../elements/notifications";
|
||||
import * as DDR from "../../utils/ddr";
|
||||
|
|
@ -23,7 +22,11 @@ import * as WeakSpot from "../weak-spot";
|
|||
import * as IPAddresses from "../../utils/ip-addresses";
|
||||
import * as TestState from "../test-state";
|
||||
import { WordGenError } from "../../utils/word-gen-error";
|
||||
import { KeymapLayout, Layout } from "@monkeytype/contracts/schemas/configs";
|
||||
import {
|
||||
FunboxName,
|
||||
KeymapLayout,
|
||||
Layout,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
|
||||
export type FunboxFunctions = {
|
||||
getWord?: (wordset?: Wordset, wordIndex?: number) => string;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import * as Notifications from "../../elements/notifications";
|
||||
import * as Strings from "../../utils/strings";
|
||||
import { Config, ConfigValue } from "@monkeytype/contracts/schemas/configs";
|
||||
import { FunboxMetadata, getFunboxesFromString } from "@monkeytype/funbox";
|
||||
import {
|
||||
Config,
|
||||
ConfigValue,
|
||||
FunboxName,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { FunboxMetadata, getFunbox } from "@monkeytype/funbox";
|
||||
import { intersect } from "@monkeytype/util/arrays";
|
||||
|
||||
export function checkForcedConfig(
|
||||
|
|
@ -73,19 +77,20 @@ export function checkForcedConfig(
|
|||
export function canSetConfigWithCurrentFunboxes(
|
||||
key: string,
|
||||
value: ConfigValue,
|
||||
funbox: string,
|
||||
funbox: FunboxName[] = [],
|
||||
noNotification = false
|
||||
): boolean {
|
||||
let errorCount = 0;
|
||||
const funboxes = getFunbox(funbox);
|
||||
if (key === "mode") {
|
||||
let fb = getFunboxesFromString(funbox).filter(
|
||||
let fb = getFunbox(funbox).filter(
|
||||
(f) =>
|
||||
f.frontendForcedConfig?.["mode"] !== undefined &&
|
||||
!(f.frontendForcedConfig["mode"] as ConfigValue[]).includes(value)
|
||||
);
|
||||
if (value === "zen") {
|
||||
fb = fb.concat(
|
||||
getFunboxesFromString(funbox).filter((f) => {
|
||||
funboxes.filter((f) => {
|
||||
const funcs = f.frontendFunctions ?? [];
|
||||
const props = f.properties ?? [];
|
||||
return (
|
||||
|
|
@ -106,7 +111,7 @@ export function canSetConfigWithCurrentFunboxes(
|
|||
}
|
||||
if (value === "quote" || value === "custom") {
|
||||
fb = fb.concat(
|
||||
getFunboxesFromString(funbox).filter((f) => {
|
||||
funboxes.filter((f) => {
|
||||
const funcs = f.frontendFunctions ?? [];
|
||||
const props = f.properties ?? [];
|
||||
return (
|
||||
|
|
@ -124,7 +129,7 @@ export function canSetConfigWithCurrentFunboxes(
|
|||
}
|
||||
}
|
||||
if (key === "words" || key === "time") {
|
||||
if (!checkForcedConfig(key, value, getFunboxesFromString(funbox)).result) {
|
||||
if (!checkForcedConfig(key, value, funboxes).result) {
|
||||
if (!noNotification) {
|
||||
Notifications.add("Active funboxes do not support infinite tests", 0);
|
||||
return false;
|
||||
|
|
@ -132,9 +137,7 @@ export function canSetConfigWithCurrentFunboxes(
|
|||
errorCount += 1;
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
!checkForcedConfig(key, value, getFunboxesFromString(funbox)).result
|
||||
) {
|
||||
} else if (!checkForcedConfig(key, value, funboxes).result) {
|
||||
errorCount += 1;
|
||||
}
|
||||
|
||||
|
|
@ -157,16 +160,12 @@ export function canSetConfigWithCurrentFunboxes(
|
|||
}
|
||||
|
||||
export function canSetFunboxWithConfig(
|
||||
funbox: string,
|
||||
funbox: FunboxName,
|
||||
config: Config
|
||||
): boolean {
|
||||
console.log("cansetfunboxwithconfig", funbox, config.mode);
|
||||
let funboxToCheck = config.funbox;
|
||||
if (funboxToCheck === "none") {
|
||||
funboxToCheck = funbox;
|
||||
} else {
|
||||
funboxToCheck += "#" + funbox;
|
||||
}
|
||||
let funboxToCheck = [...config.funbox, funbox];
|
||||
|
||||
const errors = [];
|
||||
for (const [configKey, configValue] of Object.entries(config)) {
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ 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 { HighlightMode } from "@monkeytype/contracts/schemas/configs";
|
||||
import {
|
||||
HighlightMode,
|
||||
FunboxName,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { Mode } from "@monkeytype/contracts/schemas/shared";
|
||||
import { FunboxName, checkCompatibility } from "@monkeytype/funbox";
|
||||
import { checkCompatibility } from "@monkeytype/funbox";
|
||||
import {
|
||||
getActiveFunboxes,
|
||||
getActiveFunboxNames,
|
||||
|
|
@ -21,15 +24,15 @@ import { checkForcedConfig } from "./funbox-validation";
|
|||
import { tryCatch } from "@monkeytype/util/trycatch";
|
||||
|
||||
export function toggleScript(...params: string[]): void {
|
||||
if (Config.funbox === "none") return;
|
||||
if (Config.funbox.length === 0) return;
|
||||
|
||||
for (const fb of getActiveFunboxesWithFunction("toggleScript")) {
|
||||
fb.functions.toggleScript(params);
|
||||
}
|
||||
}
|
||||
|
||||
export function setFunbox(funbox: string): boolean {
|
||||
if (funbox === "none") {
|
||||
export function setFunbox(funbox: FunboxName[]): boolean {
|
||||
if (funbox.length === 0) {
|
||||
for (const fb of getActiveFunboxesWithFunction("clearGlobal")) {
|
||||
fb.functions.clearGlobal();
|
||||
}
|
||||
|
|
@ -39,14 +42,10 @@ export function setFunbox(funbox: string): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function toggleFunbox(funbox: "none" | FunboxName): boolean {
|
||||
if (funbox === "none") setFunbox("none");
|
||||
export function toggleFunbox(funbox: FunboxName): void {
|
||||
if (
|
||||
!checkCompatibility(
|
||||
getActiveFunboxNames(),
|
||||
funbox === "none" ? undefined : funbox
|
||||
) &&
|
||||
!Config.funbox.split("#").includes(funbox)
|
||||
!checkCompatibility(getActiveFunboxNames(), funbox) &&
|
||||
!Config.funbox.includes(funbox)
|
||||
) {
|
||||
Notifications.add(
|
||||
`${Strings.capitalizeFirstLetter(
|
||||
|
|
@ -54,20 +53,16 @@ export function toggleFunbox(funbox: "none" | FunboxName): boolean {
|
|||
)} funbox is not compatible with the current funbox selection`,
|
||||
0
|
||||
);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
FunboxMemory.load();
|
||||
const e = UpdateConfig.toggleFunbox(funbox, false);
|
||||
UpdateConfig.toggleFunbox(funbox, false);
|
||||
|
||||
if (!getActiveFunboxNames().includes(funbox as FunboxName)) {
|
||||
get(funbox as FunboxName).functions?.clearGlobal?.();
|
||||
if (!getActiveFunboxNames().includes(funbox)) {
|
||||
get(funbox).functions?.clearGlobal?.();
|
||||
} else {
|
||||
get(funbox as FunboxName).functions?.applyGlobalCSS?.();
|
||||
get(funbox).functions?.applyGlobalCSS?.();
|
||||
}
|
||||
|
||||
//todo find out what the hell this means
|
||||
if (e === false || e === true) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function clear(): Promise<boolean> {
|
||||
|
|
@ -88,7 +83,9 @@ export async function clear(): Promise<boolean> {
|
|||
return true;
|
||||
}
|
||||
|
||||
export async function activate(funbox?: string): Promise<boolean | undefined> {
|
||||
export async function activate(
|
||||
funbox?: FunboxName[]
|
||||
): Promise<boolean | undefined> {
|
||||
if (funbox === undefined || funbox === null) {
|
||||
funbox = Config.funbox;
|
||||
} else if (Config.funbox !== funbox) {
|
||||
|
|
@ -101,14 +98,13 @@ export async function activate(funbox?: string): Promise<boolean | undefined> {
|
|||
Notifications.add(
|
||||
Misc.createErrorMessage(
|
||||
undefined,
|
||||
`Failed to activate funbox: funboxes ${Config.funbox.replace(
|
||||
/_/g,
|
||||
" "
|
||||
)} are not compatible`
|
||||
`Failed to activate funbox: funboxes ${Config.funbox
|
||||
.map((it) => it.replace(/_/g, " "))
|
||||
.join(", ")} are not compatible`
|
||||
),
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
UpdateConfig.setFunbox([], true);
|
||||
await clear();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -127,7 +123,7 @@ export async function activate(funbox?: string): Promise<boolean | undefined> {
|
|||
Misc.createErrorMessage(error, "Failed to activate funbox"),
|
||||
-1
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
UpdateConfig.setFunbox([], true);
|
||||
await clear();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -138,7 +134,7 @@ export async function activate(funbox?: string): Promise<boolean | undefined> {
|
|||
"Current language does not support this funbox mode",
|
||||
0
|
||||
);
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
UpdateConfig.setFunbox([], true);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
|
|
@ -183,7 +179,7 @@ export async function activate(funbox?: string): Promise<boolean | undefined> {
|
|||
}
|
||||
|
||||
if (!canSetSoFar) {
|
||||
if (Config.funbox.includes("#")) {
|
||||
if (Config.funbox.length > 1) {
|
||||
Notifications.add(
|
||||
`Failed to activate funboxes ${Config.funbox}: no intersecting forced configs. Disabling funbox`,
|
||||
-1
|
||||
|
|
@ -194,7 +190,7 @@ export async function activate(funbox?: string): Promise<boolean | undefined> {
|
|||
-1
|
||||
);
|
||||
}
|
||||
UpdateConfig.setFunbox("none", true);
|
||||
UpdateConfig.setFunbox([], true);
|
||||
await clear();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
import Config from "../../config";
|
||||
import {
|
||||
FunboxName,
|
||||
stringToFunboxNames,
|
||||
FunboxMetadata,
|
||||
getFunboxObject,
|
||||
FunboxProperty,
|
||||
} from "@monkeytype/funbox";
|
||||
|
||||
import { FunboxFunctions, getFunboxFunctions } from "./funbox-functions";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
|
||||
type FunboxMetadataWithFunctions = FunboxMetadata & {
|
||||
functions?: FunboxFunctions;
|
||||
|
|
@ -45,18 +44,12 @@ export function getAllFunboxes(): FunboxMetadataWithFunctions[] {
|
|||
return Object.values(metadataWithFunctions);
|
||||
}
|
||||
|
||||
export function getFromString(
|
||||
hashSeparatedFunboxes: string
|
||||
): FunboxMetadataWithFunctions[] {
|
||||
return get(stringToFunboxNames(hashSeparatedFunboxes));
|
||||
}
|
||||
|
||||
export function getActiveFunboxes(): FunboxMetadataWithFunctions[] {
|
||||
return get(stringToFunboxNames(Config.funbox));
|
||||
return get(getActiveFunboxNames());
|
||||
}
|
||||
|
||||
export function getActiveFunboxNames(): FunboxName[] {
|
||||
return stringToFunboxNames(Config.funbox);
|
||||
return Config.funbox ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -37,12 +37,8 @@ import type {
|
|||
} from "chartjs-plugin-annotation";
|
||||
import Ape from "../ape";
|
||||
import { CompletedEvent } from "@monkeytype/contracts/schemas/results";
|
||||
import {
|
||||
getActiveFunboxes,
|
||||
getFromString,
|
||||
isFunboxActiveWithProperty,
|
||||
} from "./funbox/list";
|
||||
import { getFunboxesFromString } from "@monkeytype/funbox";
|
||||
import { getActiveFunboxes, isFunboxActiveWithProperty } from "./funbox/list";
|
||||
import { getFunbox } from "@monkeytype/funbox";
|
||||
import { SnapshotUserTag } from "../constants/default-snapshot";
|
||||
|
||||
let result: CompletedEvent;
|
||||
|
|
@ -80,7 +76,6 @@ async function updateGraph(): Promise<void> {
|
|||
Numbers.roundTo2(typingSpeedUnit.fromWpm(a))
|
||||
),
|
||||
];
|
||||
|
||||
if (result.chartData === "toolong") return;
|
||||
|
||||
const chartData2 = [
|
||||
|
|
@ -129,7 +124,7 @@ async function updateGraph(): Promise<void> {
|
|||
ChartController.result.getDataset("error").data = result.chartData.err;
|
||||
|
||||
const fc = await ThemeColors.get("sub");
|
||||
if (Config.funbox !== "none") {
|
||||
if (Config.funbox.length > 0) {
|
||||
let content = "";
|
||||
for (const fb of getActiveFunboxes()) {
|
||||
content += fb.name;
|
||||
|
|
@ -184,7 +179,7 @@ export async function updateGraphPBLine(): Promise<void> {
|
|||
result.language,
|
||||
result.difficulty,
|
||||
result.lazyMode ?? false,
|
||||
getFunboxesFromString(result.funbox ?? "none")
|
||||
getFunbox(result.funbox)
|
||||
);
|
||||
const localPbWpm = localPb?.wpm ?? 0;
|
||||
if (localPbWpm === 0) return;
|
||||
|
|
@ -481,12 +476,11 @@ type CanGetPbObject = {
|
|||
};
|
||||
|
||||
async function resultCanGetPb(): Promise<CanGetPbObject> {
|
||||
const funboxes = result.funbox?.split("#") ?? [];
|
||||
const funboxObjects = getFromString(result.funbox);
|
||||
const funboxes = result.funbox;
|
||||
const funboxObjects = getFunbox(result.funbox);
|
||||
const allFunboxesCanGetPb = funboxObjects.every((f) => f?.canGetPb);
|
||||
|
||||
const funboxesOk =
|
||||
result.funbox === "none" || funboxes.length === 0 || allFunboxesCanGetPb;
|
||||
const funboxesOk = funboxes.length === 0 || allFunboxesCanGetPb;
|
||||
const notUsingStopOnLetter = Config.stopOnError !== "letter";
|
||||
const notBailedOut = !result.bailedOut;
|
||||
|
||||
|
|
@ -699,8 +693,9 @@ function updateTestType(randomQuote: Quote | null): void {
|
|||
if (Config.lazyMode) {
|
||||
testType += "<br>lazy";
|
||||
}
|
||||
if (Config.funbox !== "none") {
|
||||
testType += "<br>" + Config.funbox.replace(/_/g, " ").replace(/#/g, ", ");
|
||||
if (Config.funbox.length > 0) {
|
||||
testType +=
|
||||
"<br>" + Config.funbox.map((it) => it.replace(/_/g, " ")).join(", ");
|
||||
}
|
||||
if (Config.difficulty === "expert") {
|
||||
testType += "<br>expert";
|
||||
|
|
@ -796,7 +791,7 @@ export function updateRateQuote(randomQuote: Quote | null): void {
|
|||
quoteStats?.average?.toFixed(1) ?? ""
|
||||
);
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
.catch((_e: unknown) => {
|
||||
$(".pageTest #result #rateQuoteButton .rating").text("?");
|
||||
});
|
||||
$(".pageTest #result #rateQuoteButton")
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ import {
|
|||
getActiveFunboxes,
|
||||
getActiveFunboxesWithFunction,
|
||||
} from "./funbox/list";
|
||||
import { getFunboxesFromString } from "@monkeytype/funbox";
|
||||
import { getFunbox } from "@monkeytype/funbox";
|
||||
import * as CompositionState from "../states/composition";
|
||||
import { SnapshotResult } from "../constants/default-snapshot";
|
||||
import { WordGenError } from "../utils/word-gen-error";
|
||||
|
|
@ -1237,7 +1237,7 @@ async function saveResult(
|
|||
completedEvent.language,
|
||||
completedEvent.difficulty,
|
||||
completedEvent.lazyMode,
|
||||
getFunboxesFromString(completedEvent.funbox)
|
||||
getFunbox(completedEvent.funbox)
|
||||
);
|
||||
|
||||
if (localPb !== undefined) {
|
||||
|
|
|
|||
|
|
@ -91,10 +91,7 @@ function calculateAcc(): number {
|
|||
|
||||
function layoutfluid(): void {
|
||||
if (timerDebug) console.time("layoutfluid");
|
||||
if (
|
||||
Config.funbox.split("#").includes("layoutfluid") &&
|
||||
Config.mode === "time"
|
||||
) {
|
||||
if (Config.funbox.includes("layoutfluid") && Config.mode === "time") {
|
||||
const layouts = Config.customLayoutfluid
|
||||
? Config.customLayoutfluid.split("#")
|
||||
: ["qwerty", "dvorak", "colemak"];
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ const debouncedZipfCheck = debounce(250, async () => {
|
|||
ConfigEvent.subscribe((eventKey, eventValue, nosave) => {
|
||||
if (
|
||||
(eventKey === "language" || eventKey === "funbox") &&
|
||||
Config.funbox.split("#").includes("zipf")
|
||||
Config.funbox.includes("zipf")
|
||||
) {
|
||||
void debouncedZipfCheck();
|
||||
}
|
||||
|
|
@ -1160,8 +1160,8 @@ export async function scrollTape(): Promise<void> {
|
|||
export function updatePremid(): void {
|
||||
const mode2 = Misc.getMode2(Config, TestWords.currentQuote);
|
||||
let fbtext = "";
|
||||
if (Config.funbox !== "none") {
|
||||
fbtext = " " + Config.funbox.split("#").join(" ");
|
||||
if (Config.funbox.length > 0) {
|
||||
fbtext = " " + Config.funbox.join(" ");
|
||||
}
|
||||
$(".pageTest #premidTestMode").text(
|
||||
`${Config.mode} ${mode2} ${Strings.getLanguageDisplayString(
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ ConfigEvent.subscribe((eventKey, eventValue) => {
|
|||
void init();
|
||||
}
|
||||
}
|
||||
if (eventKey === "language" && Config.funbox.split("#").includes("tts")) {
|
||||
if (eventKey === "language" && Config.funbox.includes("tts")) {
|
||||
void setLanguage();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export function lastElementFromArray<T>(array: T[]): T | undefined {
|
|||
* @param b The second array.
|
||||
* @returns True if the arrays are equal, false otherwise.
|
||||
*/
|
||||
export function areUnsortedArraysEqual(a: unknown[], b: unknown[]): boolean {
|
||||
export function areUnsortedArraysEqual<T>(a: T[], b: T[]): boolean {
|
||||
return a.length === b.length && a.every((v) => b.includes(v));
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ export function areUnsortedArraysEqual(a: unknown[], b: unknown[]): boolean {
|
|||
* @param b The second array.
|
||||
* @returns True if the arrays are equal, false otherwise.
|
||||
*/
|
||||
export function areSortedArraysEqual(a: unknown[], b: unknown[]): boolean {
|
||||
export function areSortedArraysEqual<T>(a: T[], b: T[]): boolean {
|
||||
return a.length === b.length && a.every((v, i) => v === b[i]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
Config,
|
||||
ConfigValue,
|
||||
PartialConfig,
|
||||
FunboxName,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { typedKeys } from "./misc";
|
||||
import * as ConfigSchemas from "@monkeytype/contracts/schemas/configs";
|
||||
|
|
@ -112,5 +113,15 @@ export function replaceLegacyValues(
|
|||
configObj.soundVolume = parseFloat(configObj.soundVolume);
|
||||
}
|
||||
|
||||
if (typeof configObj.funbox === "string") {
|
||||
if (configObj.funbox === "none") {
|
||||
configObj.funbox = [];
|
||||
} else {
|
||||
configObj.funbox = (configObj.funbox as string).split(
|
||||
"#"
|
||||
) as FunboxName[];
|
||||
}
|
||||
}
|
||||
|
||||
return configObj;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
import { Accents } from "../test/lazy-mode";
|
||||
import { hexToHSL } from "./colors";
|
||||
|
||||
|
|
@ -306,7 +307,7 @@ export type Challenge = {
|
|||
display: string;
|
||||
autoRole: boolean;
|
||||
type: string;
|
||||
parameters: (string | number | boolean)[];
|
||||
parameters: (string | number | boolean | FunboxName[])[];
|
||||
message: string;
|
||||
requirements: Record<string, Record<string, string | number | boolean>>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import {
|
|||
CustomBackgroundSizeSchema,
|
||||
CustomThemeColors,
|
||||
CustomThemeColorsSchema,
|
||||
FunboxSchema,
|
||||
FunboxName,
|
||||
} from "@monkeytype/contracts/schemas/configs";
|
||||
import { z } from "zod";
|
||||
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
|
||||
|
|
@ -151,7 +153,7 @@ const TestSettingsSchema = z.tuple([
|
|||
z.boolean().nullable(), //numbers
|
||||
z.string().nullable(), //language
|
||||
DifficultySchema.nullable(),
|
||||
z.string().nullable(), //funbox
|
||||
FunboxSchema.or(z.string().nullable()), //funbox as array or legacy string as hash separated values
|
||||
]);
|
||||
|
||||
export function loadTestSettingsFromUrl(getOverride?: string): void {
|
||||
|
|
@ -247,8 +249,15 @@ export function loadTestSettingsFromUrl(getOverride?: string): void {
|
|||
}
|
||||
|
||||
if (de[7] !== null) {
|
||||
UpdateConfig.setFunbox(de[7], true);
|
||||
applied["funbox"] = de[7];
|
||||
let val: FunboxName[] = [];
|
||||
//convert legacy values
|
||||
if (typeof de[7] === "string") {
|
||||
val = de[7].split("#") as FunboxName[];
|
||||
} else {
|
||||
val = de[7];
|
||||
}
|
||||
UpdateConfig.setFunbox(val, true);
|
||||
applied["funbox"] = val.join(", ");
|
||||
}
|
||||
|
||||
restartTest({
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@
|
|||
"name": "inAGalaxyFarFarAway",
|
||||
"display": "In a galaxy far far away",
|
||||
"type": "script",
|
||||
"parameters": ["episode4.txt",null,"space_balls"],
|
||||
"parameters": ["episode4.txt",null,["space_balls"]],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
|
|
@ -269,7 +269,7 @@
|
|||
"name": "beepBoop",
|
||||
"display": "Beep Boop",
|
||||
"type": "script",
|
||||
"parameters": ["beepboop.txt",null,"nospace"],
|
||||
"parameters": ["beepboop.txt",null,["nospace"]],
|
||||
"message": "Mininum 45 WPM and 100% accuracy required.",
|
||||
"requirements": {
|
||||
"wpm": {
|
||||
|
|
@ -279,7 +279,7 @@
|
|||
"min": 100
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "nospace"
|
||||
"exact": ["nospace"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@
|
|||
"name": "whosYourDaddy",
|
||||
"display": "Who's your daddy?",
|
||||
"type": "script",
|
||||
"parameters": ["episode5.txt",null,"space_balls"],
|
||||
"parameters": ["episode5.txt",null,["space_balls"]],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
|
|
@ -298,7 +298,7 @@
|
|||
"name": "itsATrap",
|
||||
"display": "It's a trap!",
|
||||
"type": "script",
|
||||
"parameters": ["episode6.txt",null,"space_balls"],
|
||||
"parameters": ["episode6.txt",null,["space_balls"]],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
|
|
@ -399,7 +399,7 @@
|
|||
"name": "beLikeWater",
|
||||
"display": "Be like water",
|
||||
"type": "funbox",
|
||||
"parameters": ["layoutfluid","time",60],
|
||||
"parameters": [["layoutfluid"],"time",60],
|
||||
"message": "Remember: You need to achieve at least 50 wpm in each layout."
|
||||
}
|
||||
,{
|
||||
|
|
@ -407,13 +407,13 @@
|
|||
"display": "Rollercoaster",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["round_round_baby","time",3600],
|
||||
"parameters": [["round_round_baby"],"time",3600],
|
||||
"requirements" : {
|
||||
"time": {
|
||||
"min": 3600
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "round_round_baby"
|
||||
"exact": ["round_round_baby"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -422,13 +422,13 @@
|
|||
"display": "ɿoɿɿim ɿυoʜ ɘno",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["mirror","time",3600],
|
||||
"parameters": [["mirror"],"time",3600],
|
||||
"requirements" : {
|
||||
"time": {
|
||||
"min": 3600
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "mirror"
|
||||
"exact": ["mirror"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -437,13 +437,13 @@
|
|||
"display": "Choo choo",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["choo_choo","time",3600],
|
||||
"parameters": [["choo_choo"],"time",3600],
|
||||
"requirements" : {
|
||||
"time": {
|
||||
"min": 3600
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "choo_choo"
|
||||
"exact": ["choo_choo"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -451,7 +451,7 @@
|
|||
"name": "mnemonist",
|
||||
"display": "Mnemonist",
|
||||
"type": "funbox",
|
||||
"parameters": ["memory","words",25,"master"],
|
||||
"parameters": [["memory"],"words",25,"master"],
|
||||
"requirements": {
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
|
|
@ -463,13 +463,13 @@
|
|||
"display": "Earfquake",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["earthquake","time",3600],
|
||||
"parameters": [["earthquake"],"time",3600],
|
||||
"requirements" : {
|
||||
"time": {
|
||||
"min": 3600
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "earthquake"
|
||||
"exact": ["earthquake"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -478,13 +478,13 @@
|
|||
"display": "Simon Sez",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["simon_says","time",3600],
|
||||
"parameters": [["simon_says"],"time",3600],
|
||||
"requirements" : {
|
||||
"time": {
|
||||
"min": 3600
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "simon_says"
|
||||
"exact": ["simon_says"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -493,13 +493,13 @@
|
|||
"display": "Accountant",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["58008","time",3600],
|
||||
"parameters": [["58008"],"time",3600],
|
||||
"requirements" : {
|
||||
"time": {
|
||||
"min": 3600
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "58008"
|
||||
"exact": ["58008"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -508,7 +508,7 @@
|
|||
"display": "Hidden",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["read_ahead","time",60],
|
||||
"parameters": [["read_ahead"],"time",60],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -517,7 +517,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "read_ahead"
|
||||
"exact": ["read_ahead"]
|
||||
},
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
|
|
@ -529,7 +529,7 @@
|
|||
"display": "I can see the future",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["read_ahead_hard","time",60],
|
||||
"parameters": [["read_ahead_hard"],"time",60],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -538,7 +538,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "read_ahead_hard"
|
||||
"exact": ["read_ahead_hard"]
|
||||
},
|
||||
"config": {
|
||||
"tapeMode": "off"
|
||||
|
|
@ -550,7 +550,7 @@
|
|||
"display": "What are words at this point?",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["gibberish","time",3600],
|
||||
"parameters": [["gibberish"],"time",3600],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -559,7 +559,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "gibberish"
|
||||
"exact": ["gibberish"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -568,7 +568,7 @@
|
|||
"display": "Specials",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["specials","time",3600],
|
||||
"parameters": [["specials"],"time",3600],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -577,7 +577,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "specials"
|
||||
"exact": ["specials"]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -587,7 +587,7 @@
|
|||
"display": "Aeiou.",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["tts","time",3600],
|
||||
"parameters": [["tts"],"time",3600],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -596,7 +596,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "tts"
|
||||
"exact": ["tts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -605,7 +605,7 @@
|
|||
"display": "ASCII warrior",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["ascii","time",3600],
|
||||
"parameters": [["ascii"],"time",3600],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -614,7 +614,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "ascii"
|
||||
"exact": ["ascii"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -623,7 +623,7 @@
|
|||
"display": "I kInDa LiKe HoW iNeFfIcIeNt QwErTy Is",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["rAnDoMcAsE","time",3600],
|
||||
"parameters": [["rAnDoMcAsE"],"time",3600],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -632,7 +632,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "rAnDoMcAsE"
|
||||
"exact": ["rAnDoMcAsE"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -641,7 +641,7 @@
|
|||
"display": "One Nauseous Monkey",
|
||||
"autoRole": true,
|
||||
"type": "funbox",
|
||||
"parameters": ["nausea","time",3600],
|
||||
"parameters": [["nausea"],"time",3600],
|
||||
"requirements" : {
|
||||
"wpm": {
|
||||
"min": 100
|
||||
|
|
@ -650,7 +650,7 @@
|
|||
"min": 60
|
||||
},
|
||||
"funbox": {
|
||||
"exact": "nausea"
|
||||
"exact": ["nausea"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,5 +35,5 @@ export const contract = c.router({
|
|||
* Whenever there is a breaking change with old frontend clients increase this number.
|
||||
* This will inform the frontend to refresh.
|
||||
*/
|
||||
export const COMPATIBILITY_CHECK = 1;
|
||||
export const COMPATIBILITY_CHECK = 2;
|
||||
export const COMPATIBILITY_CHECK_HEADER = "X-Compatibility-Check";
|
||||
|
|
|
|||
|
|
@ -234,10 +234,56 @@ export type CustomThemeColors = z.infer<typeof CustomThemeColorsSchema>;
|
|||
export const FavThemesSchema = z.array(token().max(50));
|
||||
export type FavThemes = z.infer<typeof FavThemesSchema>;
|
||||
|
||||
export const FunboxSchema = z
|
||||
.string()
|
||||
.max(100)
|
||||
.regex(/[\w#]+/);
|
||||
export const FunboxNameSchema = z.enum([
|
||||
"58008",
|
||||
"mirror",
|
||||
"upside_down",
|
||||
"nausea",
|
||||
"round_round_baby",
|
||||
"simon_says",
|
||||
"tts",
|
||||
"choo_choo",
|
||||
"arrows",
|
||||
"rAnDoMcAsE",
|
||||
"capitals",
|
||||
"layout_mirror",
|
||||
"layoutfluid",
|
||||
"earthquake",
|
||||
"space_balls",
|
||||
"gibberish",
|
||||
"ascii",
|
||||
"specials",
|
||||
"plus_one",
|
||||
"plus_zero",
|
||||
"plus_two",
|
||||
"plus_three",
|
||||
"read_ahead_easy",
|
||||
"read_ahead",
|
||||
"read_ahead_hard",
|
||||
"memory",
|
||||
"nospace",
|
||||
"poetry",
|
||||
"wikipedia",
|
||||
"weakspot",
|
||||
"pseudolang",
|
||||
"IPv4",
|
||||
"IPv6",
|
||||
"binary",
|
||||
"hexadecimal",
|
||||
"zipf",
|
||||
"morse",
|
||||
"crt",
|
||||
"backwards",
|
||||
"ddoouubblleedd",
|
||||
"instant_messaging",
|
||||
"underscore_spaces",
|
||||
"ALL_CAPS",
|
||||
"polyglot",
|
||||
"asl",
|
||||
]);
|
||||
export type FunboxName = z.infer<typeof FunboxNameSchema>;
|
||||
|
||||
export const FunboxSchema = z.array(FunboxNameSchema).max(15);
|
||||
export type Funbox = z.infer<typeof FunboxSchema>;
|
||||
|
||||
export const PaceCaretCustomSpeedSchema = z.number().nonnegative();
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"moduleResolution": "Node",
|
||||
"module": "ES6",
|
||||
"target": "ES2015"
|
||||
"target": "ES6"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
import * as Util from "../src/util";
|
||||
|
||||
describe("util", () => {
|
||||
describe("stringToFunboxNames", () => {
|
||||
it("should get single funbox", () => {
|
||||
expect(Util.stringToFunboxNames("58008")).toEqual(["58008"]);
|
||||
});
|
||||
it("should fail for unknown funbox name", () => {
|
||||
expect(() => Util.stringToFunboxNames("unknown")).toThrowError(
|
||||
new Error("Invalid funbox name: unknown")
|
||||
);
|
||||
});
|
||||
it("should split multiple funboxes by hash", () => {
|
||||
expect(Util.stringToFunboxNames("58008#choo_choo")).toEqual([
|
||||
"58008",
|
||||
"choo_choo",
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -26,6 +26,9 @@
|
|||
"dependencies": {
|
||||
"@monkeytype/util": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@monkeytype/contracts": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
import { getList, getFunbox, getObject } from "./list";
|
||||
import { FunboxMetadata, FunboxName, FunboxProperty } from "./types";
|
||||
import { stringToFunboxNames } from "./util";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
import { getList, getFunbox, getObject, getFunboxNames } from "./list";
|
||||
import { FunboxMetadata, FunboxProperty } from "./types";
|
||||
import { checkCompatibility } from "./validation";
|
||||
|
||||
export type { FunboxName, FunboxMetadata, FunboxProperty };
|
||||
export { checkCompatibility, stringToFunboxNames, getFunbox };
|
||||
|
||||
export function getFunboxesFromString(names: string): FunboxMetadata[] {
|
||||
return getFunbox(stringToFunboxNames(names));
|
||||
}
|
||||
export type { FunboxMetadata, FunboxProperty };
|
||||
export { checkCompatibility, getFunbox, getFunboxNames };
|
||||
|
||||
export function getAllFunboxes(): FunboxMetadata[] {
|
||||
return getList();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { FunboxMetadata, FunboxName } from "./types";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
import { FunboxMetadata } from "./types";
|
||||
|
||||
const list: Record<FunboxName, FunboxMetadata> = {
|
||||
"58008": {
|
||||
|
|
@ -467,6 +468,7 @@ export function getFunbox(names: FunboxName[]): FunboxMetadata[];
|
|||
export function getFunbox(
|
||||
nameOrNames: FunboxName | FunboxName[]
|
||||
): FunboxMetadata | FunboxMetadata[] {
|
||||
if (nameOrNames === undefined) return [];
|
||||
if (Array.isArray(nameOrNames)) {
|
||||
const out = nameOrNames.map((name) => getObject()[name]);
|
||||
|
||||
|
|
@ -499,6 +501,6 @@ export function getList(): FunboxMetadata[] {
|
|||
return out;
|
||||
}
|
||||
|
||||
function getFunboxNames(): FunboxName[] {
|
||||
export function getFunboxNames(): FunboxName[] {
|
||||
return Object.keys(list) as FunboxName[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,4 @@
|
|||
export type FunboxName =
|
||||
| "58008"
|
||||
| "mirror"
|
||||
| "upside_down"
|
||||
| "nausea"
|
||||
| "round_round_baby"
|
||||
| "simon_says"
|
||||
| "tts"
|
||||
| "choo_choo"
|
||||
| "arrows"
|
||||
| "rAnDoMcAsE"
|
||||
| "capitals"
|
||||
| "layout_mirror"
|
||||
| "layoutfluid"
|
||||
| "earthquake"
|
||||
| "space_balls"
|
||||
| "gibberish"
|
||||
| "ascii"
|
||||
| "specials"
|
||||
| "plus_one"
|
||||
| "plus_zero"
|
||||
| "plus_two"
|
||||
| "plus_three"
|
||||
| "read_ahead_easy"
|
||||
| "read_ahead"
|
||||
| "read_ahead_hard"
|
||||
| "memory"
|
||||
| "nospace"
|
||||
| "poetry"
|
||||
| "wikipedia"
|
||||
| "weakspot"
|
||||
| "pseudolang"
|
||||
| "IPv4"
|
||||
| "IPv6"
|
||||
| "binary"
|
||||
| "hexadecimal"
|
||||
| "zipf"
|
||||
| "morse"
|
||||
| "crt"
|
||||
| "backwards"
|
||||
| "ddoouubblleedd"
|
||||
| "instant_messaging"
|
||||
| "underscore_spaces"
|
||||
| "ALL_CAPS"
|
||||
| "polyglot"
|
||||
| "asl";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
|
||||
export type FunboxForcedConfig = Record<string, string[] | boolean[]>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import { getList } from "./list";
|
||||
import { FunboxName } from "./types";
|
||||
|
||||
export function stringToFunboxNames(names: string): FunboxName[] {
|
||||
if (names === "none" || names === "") return [];
|
||||
const unsafeNames = names.split("#").map((name) => name.trim());
|
||||
const out: FunboxName[] = [];
|
||||
const list = new Set(getList().map((f) => f.name));
|
||||
for (const unsafeName of unsafeNames) {
|
||||
if (list.has(unsafeName as FunboxName)) {
|
||||
out.push(unsafeName as FunboxName);
|
||||
} else {
|
||||
throw new Error("Invalid funbox name: " + unsafeName);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { intersect } from "@monkeytype/util/arrays";
|
||||
import { FunboxForcedConfig, FunboxName } from "./types";
|
||||
import { FunboxForcedConfig } from "./types";
|
||||
import { getFunbox } from "./list";
|
||||
import { FunboxName } from "@monkeytype/contracts/schemas/configs";
|
||||
|
||||
export function checkCompatibility(
|
||||
funboxNames: FunboxName[],
|
||||
|
|
|
|||
40
pnpm-lock.yaml
generated
40
pnpm-lock.yaml
generated
|
|
@ -560,6 +560,9 @@ importers:
|
|||
|
||||
packages/funbox:
|
||||
dependencies:
|
||||
'@monkeytype/contracts':
|
||||
specifier: workspace:*
|
||||
version: link:../contracts
|
||||
'@monkeytype/util':
|
||||
specifier: workspace:*
|
||||
version: link:../util
|
||||
|
|
@ -668,7 +671,7 @@ importers:
|
|||
version: 0.16.7
|
||||
tsup:
|
||||
specifier: 8.4.0
|
||||
version: 8.4.0(postcss@8.5.1)(tsx@4.16.2)(typescript@5.5.4)(yaml@2.5.0)
|
||||
version: 8.4.0(postcss@8.5.3)(tsx@4.16.2)(typescript@5.5.4)(yaml@2.5.0)
|
||||
typescript:
|
||||
specifier: 5.5.4
|
||||
version: 5.5.4
|
||||
|
|
@ -18064,14 +18067,6 @@ snapshots:
|
|||
|
||||
possible-typed-array-names@1.0.0: {}
|
||||
|
||||
postcss-load-config@6.0.1(postcss@8.5.1)(tsx@4.16.2)(yaml@2.5.0):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
optionalDependencies:
|
||||
postcss: 8.5.1
|
||||
tsx: 4.16.2
|
||||
yaml: 2.5.0
|
||||
|
||||
postcss-load-config@6.0.1(postcss@8.5.3)(tsx@4.16.2)(yaml@2.5.0):
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
|
|
@ -19703,33 +19698,6 @@ snapshots:
|
|||
|
||||
tsscmp@1.0.6: {}
|
||||
|
||||
tsup@8.4.0(postcss@8.5.1)(tsx@4.16.2)(typescript@5.5.4)(yaml@2.5.0):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0(esbuild@0.25.0)
|
||||
cac: 6.7.14
|
||||
chokidar: 4.0.3
|
||||
consola: 3.4.0
|
||||
debug: 4.4.0
|
||||
esbuild: 0.25.0
|
||||
joycon: 3.1.1
|
||||
picocolors: 1.1.1
|
||||
postcss-load-config: 6.0.1(postcss@8.5.1)(tsx@4.16.2)(yaml@2.5.0)
|
||||
resolve-from: 5.0.0
|
||||
rollup: 4.34.8
|
||||
source-map: 0.8.0-beta.0
|
||||
sucrase: 3.35.0
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.12
|
||||
tree-kill: 1.2.2
|
||||
optionalDependencies:
|
||||
postcss: 8.5.1
|
||||
typescript: 5.5.4
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- supports-color
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
tsup@8.4.0(postcss@8.5.3)(tsx@4.16.2)(typescript@5.5.4)(yaml@2.5.0):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0(esbuild@0.25.0)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue