mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-11-09 13:44:29 +08:00
impr: allow multiple funboxes with css (@notTamion, @miodec, @fehmer) (#6017)
### Description Allows enabling multiple funboxes that have a stylesheet. Which funboxes should be deemed compatible with each other will need some discussion --------- Co-authored-by: Jack <jack@monkeytype.com> Co-authored-by: Christian Fehmer <cfe@sexy-developer.com>
This commit is contained in:
parent
a4b6c17cd0
commit
a4b7c00ef9
13 changed files with 164 additions and 39 deletions
|
|
@ -7,7 +7,6 @@
|
|||
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
|
||||
<link rel="stylesheet" href="css/balloon.min.css" />
|
||||
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
|
||||
<link rel="stylesheet" href="" id="funBoxTheme" />
|
||||
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
|
||||
<link rel="shortcut icon" href="images/fav.png" />
|
||||
<link
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@
|
|||
defer
|
||||
></script>
|
||||
<link rel="stylesheet" href="/themes/serika_dark.css" id="currentTheme" />
|
||||
<link rel="stylesheet" href="" id="funBoxTheme" />
|
||||
<link rel="stylesheet" href="" id="globalFunBoxTheme" type="text/css" />
|
||||
<script type="module" src="ts/index.ts"></script>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
|
||||
<link rel="stylesheet" href="css/balloon.min.css" />
|
||||
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
|
||||
<link rel="stylesheet" href="" id="funBoxTheme" />
|
||||
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
|
||||
<link rel="shortcut icon" href="images/fav.png" />
|
||||
<link rel="stylesheet" href="styles/index.scss" />
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
|
||||
<link rel="stylesheet" href="css/balloon.min.css" />
|
||||
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
|
||||
<link rel="stylesheet" href="" id="funBoxTheme" />
|
||||
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
|
||||
<link rel="shortcut icon" href="images/fav.png" />
|
||||
<link rel="stylesheet" href="styles/index.scss" />
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
<!-- <link rel="stylesheet" href="css/fa.css" /> -->
|
||||
<link rel="stylesheet" href="css/balloon.min.css" />
|
||||
<link rel="stylesheet" href="themes/serika_dark.css" id="currentTheme" />
|
||||
<link rel="stylesheet" href="" id="funBoxTheme" />
|
||||
<link id="favicon" rel="shortcut icon" href="images/fav.png" />
|
||||
<link rel="shortcut icon" href="images/fav.png" />
|
||||
<link rel="stylesheet" href="styles/index.scss" />
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export async function clear(): Promise<boolean> {
|
|||
?.join(" ") ?? ""
|
||||
);
|
||||
|
||||
$("#funBoxTheme").removeAttr("href");
|
||||
$(".funBoxTheme").remove();
|
||||
|
||||
$("#wordsWrapper").removeClass("hidden");
|
||||
MemoryTimer.reset();
|
||||
|
|
@ -237,23 +237,15 @@ async function setFunboxBodyClasses(): Promise<boolean> {
|
|||
}
|
||||
|
||||
async function applyFunboxCSS(): Promise<boolean> {
|
||||
const $theme = $("#funBoxTheme");
|
||||
|
||||
//currently we only support one active funbox with hasCSS
|
||||
const activeFunboxWithTheme = getActiveFunboxes().find((fb) =>
|
||||
fb?.properties?.includes("hasCssFile")
|
||||
);
|
||||
|
||||
const activeTheme =
|
||||
activeFunboxWithTheme != null
|
||||
? "funbox/" + activeFunboxWithTheme.name + ".css"
|
||||
: "";
|
||||
|
||||
const currentTheme = ($theme.attr("href") ?? "") || null;
|
||||
|
||||
if (activeTheme != currentTheme) {
|
||||
$theme.attr("href", activeTheme);
|
||||
$(".funBoxTheme").remove();
|
||||
for (const funbox of getActiveFunboxes()) {
|
||||
if (funbox.properties?.includes("hasCssFile")) {
|
||||
const css = document.createElement("link");
|
||||
css.classList.add("funBoxTheme");
|
||||
css.rel = "stylesheet";
|
||||
css.href = "funbox/" + funbox.name + ".css";
|
||||
document.head.appendChild(css);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@keyframes woah {
|
||||
@keyframes choochoo {
|
||||
0% {
|
||||
transform: rotateZ(0deg);
|
||||
}
|
||||
|
|
@ -13,6 +13,6 @@
|
|||
}
|
||||
|
||||
#words {
|
||||
--correct-letter-animation: woah 2s infinite linear;
|
||||
--untyped-letter-animation: woah 2s infinite linear;
|
||||
--correct-letter-animation: choochoo 2s infinite linear;
|
||||
--untyped-letter-animation: choochoo 2s infinite linear;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@keyframes woah {
|
||||
@keyframes nausea {
|
||||
0% {
|
||||
transform: rotateY(-15deg) skewY(10deg) rotateX(-15deg) scaleX(1.2)
|
||||
scaleY(0.9);
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
}
|
||||
|
||||
#typingTest {
|
||||
animation: woah 7s infinite cubic-bezier(0.5, 0, 0.5, 1);
|
||||
animation: nausea 7s infinite cubic-bezier(0.5, 0, 0.5, 1);
|
||||
}
|
||||
|
||||
header {
|
||||
|
|
|
|||
118
packages/funbox/__test__/validation.spec.ts
Normal file
118
packages/funbox/__test__/validation.spec.ts
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import * as List from "../src/list";
|
||||
import * as Validation from "../src/validation";
|
||||
import { FunboxMetadata } from "../src/types";
|
||||
|
||||
describe("validation", () => {
|
||||
describe("checkCompatibility", () => {
|
||||
const getFunboxMock = vi.spyOn(List, "getFunbox");
|
||||
|
||||
beforeEach(() => {
|
||||
getFunboxMock.mockReset();
|
||||
});
|
||||
|
||||
it("should pass without funboxNames", () => {
|
||||
//WHEN / THEN
|
||||
expect(Validation.checkCompatibility([])).toBe(true);
|
||||
});
|
||||
|
||||
it("should fail for undefined funboxNames", () => {
|
||||
//GIVEN
|
||||
getFunboxMock.mockReturnValueOnce([
|
||||
{
|
||||
name: "plus_one",
|
||||
} as FunboxMetadata,
|
||||
undefined as unknown as FunboxMetadata,
|
||||
]);
|
||||
|
||||
//WHEN / THEN
|
||||
expect(Validation.checkCompatibility(["plus_one", "plus_two"])).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail for undefined withFunbox param", () => {
|
||||
//GIVEN
|
||||
getFunboxMock
|
||||
.mockReturnValueOnce([])
|
||||
.mockReturnValue([undefined as unknown as FunboxMetadata]);
|
||||
|
||||
//WHEN / THEN
|
||||
expect(
|
||||
Validation.checkCompatibility(["plus_one", "plus_two"], "plus_three")
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should check for optional withFunbox param ", () => {
|
||||
//GIVEN
|
||||
getFunboxMock
|
||||
.mockReturnValueOnce([
|
||||
{
|
||||
name: "plus_one",
|
||||
cssModifications: ["body", "main"],
|
||||
} as FunboxMetadata,
|
||||
{
|
||||
name: "plus_two",
|
||||
} as FunboxMetadata,
|
||||
])
|
||||
.mockReturnValueOnce([
|
||||
{
|
||||
name: "plus_three",
|
||||
cssModifications: ["main", "typingTest"],
|
||||
} as FunboxMetadata,
|
||||
]);
|
||||
|
||||
//WHEN
|
||||
const result = Validation.checkCompatibility(
|
||||
["plus_one", "plus_two"],
|
||||
"plus_three"
|
||||
);
|
||||
|
||||
//THEN
|
||||
expect(result).toBe(false);
|
||||
|
||||
expect(getFunboxMock).toHaveBeenNthCalledWith(1, [
|
||||
"plus_one",
|
||||
"plus_two",
|
||||
]);
|
||||
expect(getFunboxMock).toHaveBeenNthCalledWith(2, "plus_three");
|
||||
});
|
||||
|
||||
it("should reject two funboxes modifying the same css element", () => {
|
||||
//GIVEN
|
||||
getFunboxMock.mockReturnValueOnce([
|
||||
{
|
||||
name: "plus_one",
|
||||
cssModifications: ["body", "main"],
|
||||
} as FunboxMetadata,
|
||||
{
|
||||
name: "plus_two",
|
||||
cssModifications: ["main", "typingTest"],
|
||||
} as FunboxMetadata,
|
||||
]);
|
||||
|
||||
//WHEN / THEN
|
||||
expect(Validation.checkCompatibility(["plus_one", "plus_two"])).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("should allow two funboxes modifying different css elements", () => {
|
||||
//GIVEN
|
||||
getFunboxMock.mockReturnValueOnce([
|
||||
{
|
||||
name: "plus_one",
|
||||
cssModifications: ["body", "main"],
|
||||
} as FunboxMetadata,
|
||||
{
|
||||
name: "plus_two",
|
||||
cssModifications: ["words"],
|
||||
} as FunboxMetadata,
|
||||
]);
|
||||
|
||||
//WHEN / THEN
|
||||
expect(Validation.checkCompatibility(["plus_one", "plus_two"])).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -24,6 +24,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
properties: ["hasCssFile"],
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
cssModifications: ["main"],
|
||||
},
|
||||
upside_down: {
|
||||
name: "upside_down",
|
||||
|
|
@ -31,6 +32,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
properties: ["hasCssFile"],
|
||||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
cssModifications: ["main"],
|
||||
},
|
||||
nausea: {
|
||||
name: "nausea",
|
||||
|
|
@ -38,6 +40,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
canGetPb: true,
|
||||
difficultyLevel: 2,
|
||||
properties: ["hasCssFile", "ignoreReducedMotion"],
|
||||
cssModifications: ["typingTest"],
|
||||
},
|
||||
round_round_baby: {
|
||||
name: "round_round_baby",
|
||||
|
|
@ -46,6 +49,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
canGetPb: true,
|
||||
difficultyLevel: 3,
|
||||
properties: ["hasCssFile", "ignoreReducedMotion"],
|
||||
cssModifications: ["typingTest"],
|
||||
},
|
||||
simon_says: {
|
||||
name: "simon_says",
|
||||
|
|
@ -69,6 +73,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
frontendFunctions: ["applyConfig", "rememberSettings", "toggleScript"],
|
||||
name: "tts",
|
||||
description: "Listen closely.",
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
choo_choo: {
|
||||
canGetPb: true,
|
||||
|
|
@ -81,6 +86,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
],
|
||||
name: "choo_choo",
|
||||
description: "All the letters are spinning!",
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
arrows: {
|
||||
description: "Play it on a pad!",
|
||||
|
|
@ -145,6 +151,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
difficultyLevel: 1,
|
||||
properties: ["hasCssFile", "noLigatures", "ignoreReducedMotion"],
|
||||
name: "earthquake",
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
space_balls: {
|
||||
description: "In a galaxy far far away.",
|
||||
|
|
@ -152,6 +159,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
difficultyLevel: 0,
|
||||
properties: ["hasCssFile", "ignoreReducedMotion"],
|
||||
name: "space_balls",
|
||||
cssModifications: ["body"],
|
||||
},
|
||||
gibberish: {
|
||||
description: "Anvbuefl dizzs eoos alsb?",
|
||||
|
|
@ -381,6 +389,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
properties: ["hasCssFile", "noLigatures"],
|
||||
frontendFunctions: ["applyGlobalCSS", "clearGlobal"],
|
||||
name: "crt",
|
||||
cssModifications: ["body"],
|
||||
},
|
||||
backwards: {
|
||||
description: "...sdrawkcab epyt ot yrt woN",
|
||||
|
|
@ -394,6 +403,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
|
|||
canGetPb: true,
|
||||
frontendFunctions: ["alterText"],
|
||||
difficultyLevel: 3,
|
||||
cssModifications: ["words"],
|
||||
},
|
||||
ddoouubblleedd: {
|
||||
description: "TTyyppee eevveerryytthhiinngg ttwwiiccee..",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ export type FunboxProperty =
|
|||
| "wordOrder:reverse"
|
||||
| "ignoreReducedMotion";
|
||||
|
||||
type FunboxCSSModification = "typingTest" | "words" | "body" | "main";
|
||||
|
||||
export type FunboxMetadata = {
|
||||
name: FunboxName;
|
||||
alias?: string;
|
||||
|
|
@ -74,4 +76,5 @@ export type FunboxMetadata = {
|
|||
frontendFunctions?: string[];
|
||||
difficultyLevel: number;
|
||||
canGetPb: boolean;
|
||||
cssModifications?: FunboxCSSModification[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ export function checkCompatibility(
|
|||
funboxNames: FunboxName[],
|
||||
withFunbox?: FunboxName
|
||||
): boolean {
|
||||
if (withFunbox === undefined || funboxNames.length === 0) return true;
|
||||
if (funboxNames.length === 0) return true;
|
||||
let funboxesToCheck = getFunbox(funboxNames);
|
||||
|
||||
if (withFunbox !== undefined) {
|
||||
funboxesToCheck = funboxesToCheck.concat(getFunbox(withFunbox));
|
||||
}
|
||||
|
||||
const allFunboxesAreValid = getFunbox(funboxNames).every(
|
||||
(f) => f !== undefined
|
||||
);
|
||||
const allFunboxesAreValid = funboxesToCheck.every((f) => f !== undefined);
|
||||
if (!allFunboxesAreValid) return false;
|
||||
|
||||
const oneWordModifierMax =
|
||||
funboxesToCheck.filter(
|
||||
|
|
@ -87,10 +87,6 @@ export function checkCompatibility(
|
|||
(f.properties?.find((fp) => fp.startsWith("toPush:")) ?? "") ||
|
||||
f.frontendFunctions?.includes("pullSection")
|
||||
).length <= 1;
|
||||
const oneCssFileMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.properties?.find((fp) => fp === "hasCssFile")
|
||||
).length <= 1;
|
||||
const onePunctuateWordMax =
|
||||
funboxesToCheck.filter((f) =>
|
||||
f.frontendFunctions?.includes("punctuateWord")
|
||||
|
|
@ -106,6 +102,18 @@ export function checkCompatibility(
|
|||
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] = (counts[cssModification] || 0) + 1;
|
||||
return counts;
|
||||
}, {})
|
||||
).every((c) => c <= 1);
|
||||
|
||||
const allowedConfig = {} as FunboxForcedConfig;
|
||||
let noConfigConflicts = true;
|
||||
for (const f of funboxesToCheck) {
|
||||
|
|
@ -131,7 +139,6 @@ export function checkCompatibility(
|
|||
}
|
||||
|
||||
return (
|
||||
allFunboxesAreValid &&
|
||||
oneWordModifierMax &&
|
||||
layoutUsability &&
|
||||
oneNospaceOrToPushMax &&
|
||||
|
|
@ -143,11 +150,11 @@ export function checkCompatibility(
|
|||
oneCanSpeakMax &&
|
||||
hasLanguageToSpeakAndNoUnspeakable &&
|
||||
oneToPushOrPullSectionMax &&
|
||||
oneCssFileMax &&
|
||||
onePunctuateWordMax &&
|
||||
oneCharCheckerMax &&
|
||||
oneCharReplacerMax &&
|
||||
oneChangesCapitalisationMax &&
|
||||
oneCssModificationPerElement &&
|
||||
noConfigConflicts &&
|
||||
oneWordOrderMax
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"moduleResolution": "Bundler",
|
||||
"module": "ES6",
|
||||
"target": "ES2015",
|
||||
"lib": ["es2016"]
|
||||
"lib": ["es2019"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue