This commit is contained in:
15
packages/funbox/src/index.ts
Normal file
15
packages/funbox/src/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { FunboxName } from "@monkeytype/schemas/configs";
|
||||
import { getList, getFunbox, getObject, getFunboxNames } from "./list";
|
||||
import { FunboxMetadata, FunboxProperty } from "./types";
|
||||
import { checkCompatibility, checkForcedConfig } from "./validation";
|
||||
|
||||
export type { FunboxMetadata, FunboxProperty };
|
||||
export { checkCompatibility, checkForcedConfig, getFunbox, getFunboxNames };
|
||||
|
||||
export function getAllFunboxes(): FunboxMetadata[] {
|
||||
return getList();
|
||||
}
|
||||
|
||||
export function getFunboxObject(): Record<FunboxName, FunboxMetadata> {
|
||||
return getObject();
|
||||
}
|
||||
528
packages/funbox/src/list.ts
Normal file
528
packages/funbox/src/list.ts
Normal file
@@ -0,0 +1,528 @@
|
||||
import { FunboxName } from "@monkeytype/schemas/configs";
|
||||
import { FunboxMetadata } from "./types";
|
||||
|
||||
const list: Record<FunboxName, FunboxMetadata> = {
|
||||
"58008": {
|
||||
description: "A special mode for accountants.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "noLetters"],
|
||||
frontendForcedConfig: {
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: [
|
||||
"getWord",
|
||||
"punctuateWord",
|
||||
"rememberSettings",
|
||||
"getEmulatedChar",
|
||||
],
|
||||
name: "58008",
|
||||
alias: "numbers",
|
||||
},
|
||||
mirror: {
|
||||
name: "mirror",
|
||||
description: "Everything is mirrored!",
|
||||
properties: ["hasCssFile"],
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
cssModifications: ["main"],
|
||||
},
|
||||
upside_down: {
|
||||
name: "upside_down",
|
||||
description: "Everything is upside down!",
|
||||
properties: ["hasCssFile"],
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
cssModifications: ["main"],
|
||||
},
|
||||
nausea: {
|
||||
name: "nausea",
|
||||
description: "I think I'm gonna be sick.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 2,
|
||||
properties: ["hasCssFile", "ignoreReducedMotion"],
|
||||
cssModifications: ["typingTest"],
|
||||
},
|
||||
round_round_baby: {
|
||||
name: "round_round_baby",
|
||||
description:
|
||||
"...right round, like a record baby. Right, round round round.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
properties: ["hasCssFile", "ignoreReducedMotion"],
|
||||
cssModifications: ["typingTest"],
|
||||
},
|
||||
simon_says: {
|
||||
name: "simon_says",
|
||||
description: "Type what simon says.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["hasCssFile", "changesWordsVisibility", "usesLayout"],
|
||||
frontendForcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
frontendFunctions: ["applyConfig", "rememberSettings"],
|
||||
},
|
||||
tts: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["hasCssFile", "changesWordsVisibility", "speaks"],
|
||||
frontendForcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
frontendFunctions: ["applyConfig", "rememberSettings", "toggleScript"],
|
||||
name: "tts",
|
||||
description: "Listen closely.",
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
choo_choo: {
|
||||
canGetPb: true,
|
||||
difficultyLevel: 2,
|
||||
properties: [
|
||||
"hasCssFile",
|
||||
"noLigatures",
|
||||
"conflictsWithSymmetricChars",
|
||||
"ignoreReducedMotion",
|
||||
],
|
||||
name: "choo_choo",
|
||||
description: "All the letters are spinning!",
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
arrows: {
|
||||
description: "Play it on a pad!",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: [
|
||||
"ignoresLanguage",
|
||||
"ignoresLayout",
|
||||
"nospace",
|
||||
"noLetters",
|
||||
"symmetricChars",
|
||||
],
|
||||
frontendForcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
frontendFunctions: [
|
||||
"getWord",
|
||||
"rememberSettings",
|
||||
"getEmulatedChar",
|
||||
"isCharCorrect",
|
||||
"getWordHtml",
|
||||
],
|
||||
name: "arrows",
|
||||
},
|
||||
rAnDoMcAsE: {
|
||||
description: "raNdomIze ThE CApitaLizatIon Of EveRY LeTtEr.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 2,
|
||||
properties: ["changesCapitalisation"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "rAnDoMcAsE",
|
||||
},
|
||||
sPoNgEcAsE: {
|
||||
description: "I kInDa LiKe HoW iNeFfIcIeNt QwErTy Is.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 2,
|
||||
properties: ["changesCapitalisation"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "sPoNgEcAsE",
|
||||
},
|
||||
capitals: {
|
||||
description: "Capitalize Every Word.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["changesCapitalisation"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "capitals",
|
||||
},
|
||||
layout_mirror: {
|
||||
description: "Mirror the keyboard layout",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
properties: ["changesLayout"],
|
||||
frontendFunctions: ["applyConfig", "rememberSettings"],
|
||||
name: "layout_mirror",
|
||||
},
|
||||
layoutfluid: {
|
||||
description:
|
||||
"Switch between layouts specified below proportionately to the length of the test.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["changesLayout", "noInfiniteDuration"],
|
||||
frontendFunctions: [
|
||||
"applyConfig",
|
||||
"rememberSettings",
|
||||
"handleSpace",
|
||||
"getResultContent",
|
||||
],
|
||||
name: "layoutfluid",
|
||||
},
|
||||
earthquake: {
|
||||
description: "Everybody get down! The words are shaking!",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["hasCssFile", "noLigatures", "ignoreReducedMotion"],
|
||||
name: "earthquake",
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
space_balls: {
|
||||
description: "In a galaxy far far away.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
properties: ["hasCssFile", "ignoreReducedMotion"],
|
||||
name: "space_balls",
|
||||
cssModifications: ["body"],
|
||||
},
|
||||
gibberish: {
|
||||
description: "Anvbuefl dizzs eoos alsb?",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "unspeakable"],
|
||||
frontendFunctions: ["getWord"],
|
||||
name: "gibberish",
|
||||
},
|
||||
ascii: {
|
||||
description: "Where was the ampersand again?. Only ASCII characters.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "noLetters", "unspeakable"],
|
||||
frontendForcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: ["getWord"],
|
||||
name: "ascii",
|
||||
},
|
||||
specials: {
|
||||
description: "!@#$%^&*. Only special characters.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "noLetters", "unspeakable"],
|
||||
frontendForcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: ["getWord"],
|
||||
name: "specials",
|
||||
},
|
||||
plus_one: {
|
||||
description: "Only one future word is visible.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
properties: ["changesWordsVisibility", "toPush:2", "noInfiniteDuration"],
|
||||
name: "plus_one",
|
||||
},
|
||||
plus_zero: {
|
||||
description: "React quickly! Only the current word is visible.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["changesWordsVisibility", "toPush:1", "noInfiniteDuration"],
|
||||
name: "plus_zero",
|
||||
},
|
||||
plus_two: {
|
||||
description: "Only two future words are visible.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
properties: ["changesWordsVisibility", "toPush:3", "noInfiniteDuration"],
|
||||
name: "plus_two",
|
||||
},
|
||||
plus_three: {
|
||||
description: "Only three future words are visible.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
properties: ["changesWordsVisibility", "toPush:4", "noInfiniteDuration"],
|
||||
name: "plus_three",
|
||||
},
|
||||
read_ahead_easy: {
|
||||
description: "Only the current word is invisible.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["changesWordsVisibility", "hasCssFile"],
|
||||
frontendForcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
frontendFunctions: ["rememberSettings", "handleKeydown"],
|
||||
name: "read_ahead_easy",
|
||||
},
|
||||
read_ahead: {
|
||||
description: "Current and the next word are invisible!",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 2,
|
||||
properties: ["changesWordsVisibility", "hasCssFile"],
|
||||
frontendForcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
frontendFunctions: ["rememberSettings", "handleKeydown"],
|
||||
name: "read_ahead",
|
||||
},
|
||||
read_ahead_hard: {
|
||||
description: "Current and the next two words are invisible!",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
properties: ["changesWordsVisibility", "hasCssFile"],
|
||||
frontendForcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
frontendFunctions: ["rememberSettings", "handleKeydown"],
|
||||
name: "read_ahead_hard",
|
||||
},
|
||||
memory: {
|
||||
description: "Test your memory. Remember the words and type them blind.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
properties: ["changesWordsVisibility", "noInfiniteDuration"],
|
||||
frontendForcedConfig: {
|
||||
mode: ["words", "quote", "custom"],
|
||||
},
|
||||
frontendFunctions: ["applyConfig", "rememberSettings", "start", "restart"],
|
||||
name: "memory",
|
||||
},
|
||||
nospace: {
|
||||
description: "Whoneedsspacesanyway?",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
properties: ["nospace"],
|
||||
frontendForcedConfig: {
|
||||
highlightMode: ["letter", "off"],
|
||||
},
|
||||
frontendFunctions: ["rememberSettings"],
|
||||
name: "nospace",
|
||||
},
|
||||
poetry: {
|
||||
description: "Practice typing some beautiful prose.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
properties: ["noInfiniteDuration", "ignoresLanguage"],
|
||||
frontendForcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: ["pullSection"],
|
||||
name: "poetry",
|
||||
},
|
||||
wikipedia: {
|
||||
description: "Practice typing wikipedia sections.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
properties: ["noInfiniteDuration", "ignoresLanguage"],
|
||||
frontendForcedConfig: {
|
||||
punctuation: [false],
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: ["pullSection"],
|
||||
name: "wikipedia",
|
||||
},
|
||||
weakspot: {
|
||||
description: "Focus on slow and mistyped letters.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
properties: ["changesWordsFrequency"],
|
||||
frontendFunctions: ["getWord"],
|
||||
name: "weakspot",
|
||||
},
|
||||
pseudolang: {
|
||||
description: "Nonsense words that look like the current language.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
properties: ["unspeakable", "ignoresLanguage"],
|
||||
frontendFunctions: ["withWords"],
|
||||
name: "pseudolang",
|
||||
},
|
||||
IPv4: {
|
||||
alias: "network",
|
||||
description: "For sysadmins.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "noLetters"],
|
||||
frontendForcedConfig: {
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: ["getWord", "punctuateWord", "rememberSettings"],
|
||||
name: "IPv4",
|
||||
},
|
||||
IPv6: {
|
||||
alias: "network",
|
||||
description: "For sysadmins with a long beard.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "noLetters"],
|
||||
frontendForcedConfig: {
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: ["getWord", "punctuateWord", "rememberSettings"],
|
||||
name: "IPv6",
|
||||
},
|
||||
binary: {
|
||||
description:
|
||||
"01000010 01100101 01100101 01110000 00100000 01100010 01101111 01101111 01110000 00101110",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "noLetters"],
|
||||
frontendForcedConfig: {
|
||||
numbers: [false],
|
||||
punctuation: [false],
|
||||
},
|
||||
frontendFunctions: ["getWord"],
|
||||
name: "binary",
|
||||
},
|
||||
hexadecimal: {
|
||||
description:
|
||||
"0x38 0x20 0x74 0x69 0x6D 0x65 0x73 0x20 0x6D 0x6F 0x72 0x65 0x20 0x62 0x6F 0x6F 0x70 0x21",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "noLetters"],
|
||||
frontendForcedConfig: {
|
||||
numbers: [false],
|
||||
},
|
||||
frontendFunctions: ["getWord", "punctuateWord", "rememberSettings"],
|
||||
name: "hexadecimal",
|
||||
},
|
||||
zipf: {
|
||||
description:
|
||||
"Words are generated according to Zipf's law. (not all languages will produce Zipfy results, use with caution)",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
properties: ["changesWordsFrequency"],
|
||||
frontendFunctions: ["getWordsFrequencyMode"],
|
||||
name: "zipf",
|
||||
},
|
||||
morse: {
|
||||
description: "-.../././.--./ -.../---/---/.--./-.-.--/ ",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "noLetters", "nospace"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "morse",
|
||||
},
|
||||
crt: {
|
||||
description: "Go back to the 1980s",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
properties: ["hasCssFile", "noLigatures"],
|
||||
frontendFunctions: ["applyGlobalCSS", "clearGlobal"],
|
||||
name: "crt",
|
||||
cssModifications: ["body"],
|
||||
},
|
||||
backwards: {
|
||||
description: "...sdrawkcab epyt ot yrt woN",
|
||||
name: "backwards",
|
||||
properties: [
|
||||
"hasCssFile",
|
||||
"conflictsWithSymmetricChars",
|
||||
"wordOrder:reverse",
|
||||
"reverseDirection",
|
||||
],
|
||||
canGetPb: true,
|
||||
frontendFunctions: ["alterText"],
|
||||
difficultyLevel: 3,
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
ddoouubblleedd: {
|
||||
description: "TTyyppee eevveerryytthhiinngg ttwwiiccee..",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["noLigatures"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "ddoouubblleedd",
|
||||
},
|
||||
instant_messaging: {
|
||||
description: "Who needs shift anyway?",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 0,
|
||||
properties: ["changesCapitalisation"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "instant_messaging",
|
||||
},
|
||||
underscore_spaces: {
|
||||
description: "Underscores_are_better.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage", "ignoresLayout", "nospace"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "underscore_spaces",
|
||||
},
|
||||
ALL_CAPS: {
|
||||
description: "WHY ARE WE SHOUTING?",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["changesCapitalisation"],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "ALL_CAPS",
|
||||
},
|
||||
polyglot: {
|
||||
description: "Use words from multiple languages in a single test.",
|
||||
canGetPb: false,
|
||||
difficultyLevel: 1,
|
||||
properties: ["ignoresLanguage"],
|
||||
frontendFunctions: ["withWords"],
|
||||
name: "polyglot",
|
||||
},
|
||||
asl: {
|
||||
description: "Practice american sign language.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: ["hasCssFile", "noLigatures"],
|
||||
name: "asl",
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
rot13: {
|
||||
description: "Vg znl abg or frpher, ohg vg vf sha gb glcr!",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 1,
|
||||
properties: [],
|
||||
frontendFunctions: ["alterText"],
|
||||
name: "rot13",
|
||||
},
|
||||
no_quit: {
|
||||
description: "You can't restart the test.",
|
||||
canGetPb: true,
|
||||
difficultyLevel: 0,
|
||||
name: "no_quit",
|
||||
},
|
||||
};
|
||||
|
||||
export function getObject(): Record<FunboxName, FunboxMetadata> {
|
||||
return list;
|
||||
}
|
||||
|
||||
export function getFunboxNames(): FunboxName[] {
|
||||
return Object.keys(list) as FunboxName[];
|
||||
}
|
||||
|
||||
export function getList(): FunboxMetadata[] {
|
||||
const out: FunboxMetadata[] = [];
|
||||
for (const name of getFunboxNames()) {
|
||||
out.push(list[name]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export function getFunbox(name: FunboxName): FunboxMetadata;
|
||||
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]);
|
||||
|
||||
//@ts-expect-error sanity check
|
||||
if (out.includes(undefined)) {
|
||||
throw new Error(
|
||||
"One of the funboxes is invalid: " + nameOrNames.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
return out;
|
||||
} else {
|
||||
const out = getObject()[nameOrNames];
|
||||
|
||||
if (out === undefined) {
|
||||
throw new Error("Invalid funbox name: " + nameOrNames);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
||||
39
packages/funbox/src/types.ts
Normal file
39
packages/funbox/src/types.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { FunboxName } from "@monkeytype/schemas/configs";
|
||||
|
||||
export type FunboxForcedConfig = Record<string, string[] | boolean[]>;
|
||||
|
||||
export type FunboxProperty =
|
||||
| "hasCssFile"
|
||||
| "ignoresLanguage"
|
||||
| "ignoresLayout"
|
||||
| "noLetters"
|
||||
| "changesLayout"
|
||||
| "usesLayout"
|
||||
| "nospace"
|
||||
| "changesWordsVisibility"
|
||||
| "changesWordsFrequency"
|
||||
| "changesCapitalisation"
|
||||
| "conflictsWithSymmetricChars"
|
||||
| "symmetricChars"
|
||||
| "speaks"
|
||||
| "unspeakable"
|
||||
| "noInfiniteDuration"
|
||||
| "noLigatures"
|
||||
| `toPush:${number}`
|
||||
| "wordOrder:reverse"
|
||||
| "reverseDirection"
|
||||
| "ignoreReducedMotion";
|
||||
|
||||
type FunboxCSSModification = "typingTest" | "words" | "body" | "main";
|
||||
|
||||
export type FunboxMetadata = {
|
||||
name: FunboxName;
|
||||
alias?: string;
|
||||
description: string;
|
||||
properties?: FunboxProperty[];
|
||||
frontendForcedConfig?: FunboxForcedConfig;
|
||||
frontendFunctions?: string[];
|
||||
difficultyLevel: number;
|
||||
canGetPb: boolean;
|
||||
cssModifications?: FunboxCSSModification[];
|
||||
};
|
||||
250
packages/funbox/src/validation.ts
Normal file
250
packages/funbox/src/validation.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
import { intersect } from "@monkeytype/util/arrays";
|
||||
import { FunboxForcedConfig, FunboxMetadata } from "./types";
|
||||
import { getFunbox } from "./list";
|
||||
import { ConfigValue, FunboxName } from "@monkeytype/schemas/configs";
|
||||
import { safeNumber } from "@monkeytype/util/numbers";
|
||||
|
||||
export function checkForcedConfig(
|
||||
key: string,
|
||||
value: ConfigValue,
|
||||
funboxes: FunboxMetadata[],
|
||||
): {
|
||||
result: boolean;
|
||||
forcedConfigs?: ConfigValue[];
|
||||
} {
|
||||
if (funboxes.length === 0) {
|
||||
return { result: true };
|
||||
}
|
||||
|
||||
if (key === "words" || key === "time") {
|
||||
if (value === 0) {
|
||||
const fb = funboxes.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, ConfigValue[]> = {};
|
||||
// collect all forced configs
|
||||
for (const fb of funboxes) {
|
||||
if (fb.frontendForcedConfig) {
|
||||
//push keys to forcedConfigs, if they don't exist. if they do, intersect the values
|
||||
for (const forcedKey in fb.frontendForcedConfig) {
|
||||
if (forcedConfigs[forcedKey] === undefined) {
|
||||
forcedConfigs[forcedKey] = fb.frontendForcedConfig[
|
||||
forcedKey
|
||||
] as ConfigValue[];
|
||||
} else {
|
||||
forcedConfigs[forcedKey] = intersect(
|
||||
forcedConfigs[forcedKey],
|
||||
fb.frontendForcedConfig[forcedKey] as ConfigValue[],
|
||||
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(value),
|
||||
forcedConfigs: forcedConfigs[key],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function checkCompatibility(
|
||||
funboxNames: FunboxName[],
|
||||
withFunbox?: FunboxName,
|
||||
): boolean {
|
||||
if (funboxNames.length === 0) return true;
|
||||
|
||||
let funboxesToCheck: FunboxMetadata[];
|
||||
|
||||
try {
|
||||
funboxesToCheck = getFunbox(funboxNames);
|
||||
|
||||
if (withFunbox !== undefined) {
|
||||
const toAdd = getFunbox(withFunbox);
|
||||
funboxesToCheck = funboxesToCheck.concat(toAdd);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error when getting funboxes for a compatibility check:",
|
||||
error,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const allFunboxesAreValid = funboxesToCheck.every((f) => f !== undefined);
|
||||
if (!allFunboxesAreValid) return false;
|
||||
|
||||
const oneWordModifierMax =
|
||||
funboxesToCheck.filter(
|
||||
(f) =>
|
||||
f.frontendFunctions?.includes("getWord") === true ||
|
||||
f.frontendFunctions?.includes("pullSection") === true ||
|
||||
f.frontendFunctions?.includes("withWords") === true,
|
||||
).length <= 1;
|
||||
const oneWordOrderMax =
|
||||
funboxesToCheck.filter(
|
||||
(f) =>
|
||||
f.properties?.find((fp) => fp.startsWith("wordOrder")) !== undefined,
|
||||
).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"),
|
||||
) !== undefined,
|
||||
).length <= 1;
|
||||
const oneChangesWordsVisibilityMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp === "changesWordsVisibility"),
|
||||
).length <= 1;
|
||||
const oneFrequencyChangesMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp === "changesWordsFrequency"),
|
||||
).length <= 1;
|
||||
const noFrequencyChangesConflicts =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp === "changesWordsFrequency"),
|
||||
).length === 0 ||
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp === "ignoresLanguage"),
|
||||
).length === 0;
|
||||
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 oneCanSpeakMax =
|
||||
funboxesToCheck.filter((f) => f.properties?.find((fp) => fp === "speaks"))
|
||||
.length <= 1;
|
||||
const hasLanguageToSpeakAndNoUnspeakable =
|
||||
funboxesToCheck.filter((f) => f.properties?.find((fp) => fp === "speaks"))
|
||||
.length === 0 ||
|
||||
(funboxesToCheck.filter((f) => f.properties?.find((fp) => fp === "speaks"))
|
||||
.length === 1 &&
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp === "unspeakable"),
|
||||
).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:")) !== undefined ||
|
||||
f.frontendFunctions?.includes("pullSection"),
|
||||
).length <= 1;
|
||||
const onePunctuateWordMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.frontendFunctions?.includes("punctuateWord"),
|
||||
).length <= 1;
|
||||
const oneGetEmulatedCharMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.frontendFunctions?.includes("getEmulatedChar"),
|
||||
).length <= 1;
|
||||
const oneCharCheckerMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.frontendFunctions?.includes("isCharCorrect"),
|
||||
).length <= 1;
|
||||
const oneCharReplacerMax =
|
||||
funboxesToCheck.filter((f) => f.frontendFunctions?.includes("getWordHtml"))
|
||||
.length <= 1;
|
||||
const oneChangesCapitalisationMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp === "changesCapitalisation"),
|
||||
).length <= 1;
|
||||
|
||||
const oneCssModificationPerElement = Object.values(
|
||||
funboxesToCheck
|
||||
.map((f) => f.cssModifications)
|
||||
.filter((f) => f !== undefined)
|
||||
.flat()
|
||||
.reduce<Record<string, number>>((counts, cssModification) => {
|
||||
counts[cssModification] =
|
||||
(safeNumber(counts[cssModification]) ?? 0) + 1;
|
||||
return counts;
|
||||
}, {}),
|
||||
).every((c) => c <= 1);
|
||||
|
||||
const allowedConfig = {} as FunboxForcedConfig;
|
||||
let noConfigConflicts = true;
|
||||
for (const f of funboxesToCheck) {
|
||||
if (!f.frontendForcedConfig) continue;
|
||||
for (const key in f.frontendForcedConfig) {
|
||||
if (allowedConfig[key]) {
|
||||
if (
|
||||
intersect<string | boolean>(
|
||||
allowedConfig[key],
|
||||
f.frontendForcedConfig[key] as string[] | boolean[],
|
||||
true,
|
||||
).length === 0
|
||||
) {
|
||||
noConfigConflicts = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
allowedConfig[key] = f.frontendForcedConfig[key] as
|
||||
| string[]
|
||||
| boolean[];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
oneWordModifierMax &&
|
||||
layoutUsability &&
|
||||
oneNospaceOrToPushMax &&
|
||||
oneChangesWordsVisibilityMax &&
|
||||
oneFrequencyChangesMax &&
|
||||
noFrequencyChangesConflicts &&
|
||||
capitalisationChangePosibility &&
|
||||
noConflictsWithSymmetricChars &&
|
||||
oneCanSpeakMax &&
|
||||
hasLanguageToSpeakAndNoUnspeakable &&
|
||||
oneToPushOrPullSectionMax &&
|
||||
onePunctuateWordMax &&
|
||||
oneGetEmulatedCharMax &&
|
||||
oneCharCheckerMax &&
|
||||
oneCharReplacerMax &&
|
||||
oneChangesCapitalisationMax &&
|
||||
oneCssModificationPerElement &&
|
||||
noConfigConflicts &&
|
||||
oneWordOrderMax
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user