feat(funbox): add no quit (@miodec) (#6741)

Brrrr
This commit is contained in:
Jack 2025-07-16 15:53:36 +02:00 committed by GitHub
parent 8d5d27d314
commit 5e4478c97a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 120 additions and 18 deletions

View file

@ -33,6 +33,7 @@ import { migrateConfig } from "./utils/config";
import { roundTo1 } from "@monkeytype/util/numbers";
import { getDefaultConfig } from "./constants/default-config";
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
import * as TestState from "./test/test-state";
const configLS = new LocalStorageWithSchema({
key: "config",
@ -91,8 +92,20 @@ export function saveFullConfigToLocalStorage(noDbCheck = false): void {
ConfigEvent.dispatch("saveToLocalStorage", stringified);
}
function isConfigChangeBlocked(): boolean {
if (TestState.isActive && config.funbox.includes("no_quit")) {
Notifications.add("No quit funbox is active. Please finish the test.", 0, {
important: true,
});
return true;
}
return false;
}
//numbers
export function setNumbers(numb: boolean, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValidBoolean("numbers", numb)) return false;
if (!canSetConfigWithCurrentFunboxes("numbers", numb, config.funbox)) {
@ -111,6 +124,8 @@ export function setNumbers(numb: boolean, nosave?: boolean): boolean {
//punctuation
export function setPunctuation(punc: boolean, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValidBoolean("punctuation", punc)) return false;
if (!canSetConfigWithCurrentFunboxes("punctuation", punc, config.funbox)) {
@ -128,6 +143,8 @@ export function setPunctuation(punc: boolean, nosave?: boolean): boolean {
}
export function setMode(mode: Mode, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValid("mode", mode, ModeSchema)) {
return false;
}
@ -224,6 +241,8 @@ export function setDifficulty(
diff: ConfigSchemas.Difficulty,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValid("difficulty", diff, ConfigSchemas.DifficultySchema)) {
return false;
}
@ -260,6 +279,8 @@ export function setFunbox(
funbox: ConfigSchemas.Funbox,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValid("funbox", funbox, ConfigSchemas.FunboxSchema))
return false;
@ -277,6 +298,8 @@ export function setFunbox(
}
export function toggleFunbox(funbox: FunboxName, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!canSetFunboxWithConfig(funbox, config)) {
return false;
}
@ -346,6 +369,8 @@ export function setStopOnError(
soe: ConfigSchemas.StopOnError,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (
!isConfigValueValid("stop on error", soe, ConfigSchemas.StopOnErrorSchema)
) {
@ -484,6 +509,8 @@ export function setMinWpm(
minwpm: ConfigSchemas.MinimumWordsPerMinute,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (
!isConfigValueValid(
"min speed",
@ -505,6 +532,8 @@ export function setMinWpmCustomSpeed(
val: ConfigSchemas.MinWpmCustomSpeed,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (
!isConfigValueValid(
"min speed custom",
@ -527,6 +556,8 @@ export function setMinAcc(
min: ConfigSchemas.MinimumAccuracy,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValid("min acc", min, ConfigSchemas.MinimumAccuracySchema))
return false;
@ -541,6 +572,8 @@ export function setMinAccCustom(
val: ConfigSchemas.MinimumAccuracyCustom,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
//migrate legacy configs
if (val > 100) val = 100;
if (
@ -564,6 +597,8 @@ export function setMinBurst(
min: ConfigSchemas.MinimumBurst,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValid("min burst", min, ConfigSchemas.MinimumBurstSchema)) {
return false;
}
@ -579,6 +614,8 @@ export function setMinBurstCustomSpeed(
val: ConfigSchemas.MinimumBurstCustomSpeed,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (
!isConfigValueValid(
"min burst custom speed",
@ -732,6 +769,8 @@ export function setColorfulMode(extra: boolean, nosave?: boolean): boolean {
//strict space
export function setStrictSpace(val: boolean, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValidBoolean("strict space", val)) return false;
config.strictSpace = val;
@ -1087,6 +1126,8 @@ export function setTimeConfig(
time: ConfigSchemas.TimeConfig,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
time = isNaN(time) || time < 0 ? getDefaultConfig().time : time;
if (!isConfigValueValid("time", time, ConfigSchemas.TimeConfigSchema))
return false;
@ -1107,6 +1148,8 @@ export function setQuoteLength(
nosave?: boolean,
multipleMode?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (Array.isArray(len)) {
if (
!isConfigValueValid(
@ -1159,6 +1202,8 @@ export function setWordCount(
wordCount: ConfigSchemas.WordCount,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
wordCount =
wordCount < 0 || wordCount > 100000 ? getDefaultConfig().words : wordCount;
@ -1501,6 +1546,8 @@ export function setRandomTheme(
}
export function setBritishEnglish(val: boolean, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValidBoolean("british english", val)) return false;
if (!val) {
@ -1514,6 +1561,8 @@ export function setBritishEnglish(val: boolean, nosave?: boolean): boolean {
}
export function setLazyMode(val: boolean, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValidBoolean("lazy mode", val)) return false;
if (!val) {
@ -1566,6 +1615,8 @@ export function setCustomThemeColors(
}
export function setLanguage(language: Language, nosave?: boolean): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValid("language", language, LanguageSchema)) return false;
config.language = language;
@ -1743,6 +1794,8 @@ export function setLayout(
layout: ConfigSchemas.Layout,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
if (!isConfigValueValid("layout", layout, ConfigSchemas.LayoutSchema))
return false;
@ -1850,6 +1903,8 @@ export function setCustomLayoutfluid(
value: ConfigSchemas.CustomLayoutFluid,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
// Remove duplicates
const deduped = Array.from(new Set(value));
if (
@ -1873,6 +1928,8 @@ export function setCustomPolyglot(
value: ConfigSchemas.CustomPolyglot,
nosave?: boolean
): boolean {
if (isConfigChangeBlocked()) return false;
// remove duplicates
const deduped = Array.from(new Set(value));
if (

View file

@ -2,6 +2,9 @@ import * as PageController from "./page-controller";
import * as TestUI from "../test/test-ui";
import * as PageTransition from "../states/page-transition";
import { Auth, isAuthenticated } from "../firebase";
import { isFunboxActive } from "../test/funbox/list";
import * as TestState from "../test/test-state";
import * as Notifications from "../elements/notifications";
//source: https://www.youtube.com/watch?v=OstALBk-jTc
// https://www.youtube.com/watch?v=OstALBk-jTc
@ -157,6 +160,16 @@ export function navigate(
);
return;
}
const noQuit = isFunboxActive("no_quit");
if (TestState.isActive && noQuit) {
Notifications.add("No quit funbox is active. Please finish the test.", 0, {
important: true,
});
event?.preventDefault();
return;
}
url = url.replace(/\/$/, "");
if (url === "") url = "/";

View file

@ -89,6 +89,15 @@ export function isFunboxActiveWithProperty(property: FunboxProperty): boolean {
return getActiveFunboxesWithProperty(property).length > 0;
}
/**
* Check if the given funbox is active
* @param funbox funbox name
* @returns true if the funbox is active, false otherwise
*/
export function isFunboxActive(funbox: FunboxName): boolean {
return getActiveFunboxNames().includes(funbox);
}
type MandatoryFunboxFunction<F extends keyof FunboxFunctions> = Exclude<
FunboxFunctions[F],
undefined

View file

@ -69,6 +69,7 @@ import {
findSingleActiveFunboxWithFunction,
getActiveFunboxes,
getActiveFunboxesWithFunction,
isFunboxActive,
} from "./funbox/list";
import { getFunbox } from "@monkeytype/funbox";
import * as CompositionState from "../states/composition";
@ -163,6 +164,15 @@ export function restart(options = {} as RestartOptions): void {
options = { ...defaultOptions, ...options };
const animationTime = options.noAnim ? 0 : Misc.applyReducedMotion(125);
const noQuit = isFunboxActive("no_quit");
if (TestState.isActive && noQuit) {
Notifications.add("No quit funbox is active. Please finish the test.", 0, {
important: true,
});
event?.preventDefault();
return;
}
if (TestUI.testRestarting || TestUI.resultCalculating) {
event?.preventDefault();
return;
@ -1402,18 +1412,20 @@ $(".pageTest").on("click", "#testConfig .mode .textButton", (e) => {
if ($(e.currentTarget).hasClass("active")) return;
const mode = ($(e.currentTarget).attr("mode") ?? "time") as Mode;
if (mode === undefined) return;
UpdateConfig.setMode(mode);
ManualRestart.set();
restart();
if (UpdateConfig.setMode(mode)) {
ManualRestart.set();
restart();
}
});
$(".pageTest").on("click", "#testConfig .wordCount .textButton", (e) => {
if (TestUI.testRestarting) return;
const wrd = $(e.currentTarget).attr("wordCount") ?? "15";
if (wrd !== "custom") {
UpdateConfig.setWordCount(parseInt(wrd));
ManualRestart.set();
restart();
if (UpdateConfig.setWordCount(parseInt(wrd))) {
ManualRestart.set();
restart();
}
}
});
@ -1421,9 +1433,10 @@ $(".pageTest").on("click", "#testConfig .time .textButton", (e) => {
if (TestUI.testRestarting) return;
const mode = $(e.currentTarget).attr("timeConfig") ?? "10";
if (mode !== "custom") {
UpdateConfig.setTimeConfig(parseInt(mode));
ManualRestart.set();
restart();
if (UpdateConfig.setTimeConfig(parseInt(mode))) {
ManualRestart.set();
restart();
}
}
});
@ -1436,24 +1449,27 @@ $(".pageTest").on("click", "#testConfig .quoteLength .textButton", (e) => {
if (len === -1) {
len = [0, 1, 2, 3];
}
UpdateConfig.setQuoteLength(len, false, e.shiftKey);
ManualRestart.set();
restart();
if (UpdateConfig.setQuoteLength(len, false, e.shiftKey)) {
ManualRestart.set();
restart();
}
}
});
$(".pageTest").on("click", "#testConfig .punctuationMode.textButton", () => {
if (TestUI.testRestarting) return;
UpdateConfig.setPunctuation(!Config.punctuation);
ManualRestart.set();
restart();
if (UpdateConfig.setPunctuation(!Config.punctuation)) {
ManualRestart.set();
restart();
}
});
$(".pageTest").on("click", "#testConfig .numbersMode.textButton", () => {
if (TestUI.testRestarting) return;
UpdateConfig.setNumbers(!Config.numbers);
ManualRestart.set();
restart();
if (UpdateConfig.setNumbers(!Config.numbers)) {
ManualRestart.set();
restart();
}
});
$("header").on("click", "nav #startTestButton, #logo", () => {

View file

@ -287,6 +287,7 @@ export const FunboxNameSchema = z.enum([
"ALL_CAPS",
"polyglot",
"asl",
"no_quit",
]);
export type FunboxName = z.infer<typeof FunboxNameSchema>;

View file

@ -461,6 +461,12 @@ const list: Record<FunboxName, FunboxMetadata> = {
name: "asl",
cssModifications: ["words"],
},
no_quit: {
description: "You can't restart the test.",
canGetPb: true,
difficultyLevel: 0,
name: "no_quit",
},
};
export function getFunbox(name: FunboxName): FunboxMetadata;