Skip to content

Commit

Permalink
impr: allow multiple funboxes with css (@notTamion, @Miodec, @fehmer) (
Browse files Browse the repository at this point in the history
…#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 <[email protected]>
Co-authored-by: Christian Fehmer <[email protected]>
  • Loading branch information
3 people authored Feb 4, 2025
1 parent a4b6c17 commit a4b7c00
Show file tree
Hide file tree
Showing 13 changed files with 164 additions and 39 deletions.
1 change: 0 additions & 1 deletion frontend/src/email-handler.html
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion frontend/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 0 additions & 1 deletion frontend/src/privacy-policy.html
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
1 change: 0 additions & 1 deletion frontend/src/security-policy.html
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
1 change: 0 additions & 1 deletion frontend/src/terms-of-service.html
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
28 changes: 10 additions & 18 deletions frontend/src/ts/test/funbox/funbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export async function clear(): Promise<boolean> {
?.join(" ") ?? ""
);

$("#funBoxTheme").removeAttr("href");
$(".funBoxTheme").remove();

$("#wordsWrapper").removeClass("hidden");
MemoryTimer.reset();
Expand Down Expand Up @@ -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;
}
6 changes: 3 additions & 3 deletions frontend/static/funbox/choo_choo.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@keyframes woah {
@keyframes choochoo {
0% {
transform: rotateZ(0deg);
}
Expand All @@ -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;
}
4 changes: 2 additions & 2 deletions frontend/static/funbox/nausea.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@keyframes woah {
@keyframes nausea {
0% {
transform: rotateY(-15deg) skewY(10deg) rotateX(-15deg) scaleX(1.2)
scaleY(0.9);
Expand All @@ -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 {
Expand Down
118 changes: 118 additions & 0 deletions packages/funbox/__test__/validation.spec.ts
Original file line number Diff line number Diff line change
@@ -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
);
});
});
});
10 changes: 10 additions & 0 deletions packages/funbox/src/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,23 @@ const list: Record<FunboxName, FunboxMetadata> = {
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",
Expand All @@ -46,6 +49,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
canGetPb: true,
difficultyLevel: 3,
properties: ["hasCssFile", "ignoreReducedMotion"],
cssModifications: ["typingTest"],
},
simon_says: {
name: "simon_says",
Expand All @@ -69,6 +73,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
frontendFunctions: ["applyConfig", "rememberSettings", "toggleScript"],
name: "tts",
description: "Listen closely.",
cssModifications: ["words"],
},
choo_choo: {
canGetPb: true,
Expand All @@ -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!",
Expand Down Expand Up @@ -145,13 +151,15 @@ const list: Record<FunboxName, FunboxMetadata> = {
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?",
Expand Down Expand Up @@ -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",
Expand All @@ -394,6 +403,7 @@ const list: Record<FunboxName, FunboxMetadata> = {
canGetPb: true,
frontendFunctions: ["alterText"],
difficultyLevel: 3,
cssModifications: ["words"],
},
ddoouubblleedd: {
description: "TTyyppee eevveerryytthhiinngg ttwwiiccee..",
Expand Down
3 changes: 3 additions & 0 deletions packages/funbox/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export type FunboxProperty =
| "wordOrder:reverse"
| "ignoreReducedMotion";

type FunboxCSSModification = "typingTest" | "words" | "body" | "main";

export type FunboxMetadata = {
name: FunboxName;
alias?: string;
Expand All @@ -74,4 +76,5 @@ export type FunboxMetadata = {
frontendFunctions?: string[];
difficultyLevel: number;
canGetPb: boolean;
cssModifications?: FunboxCSSModification[];
};
27 changes: 17 additions & 10 deletions packages/funbox/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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")
Expand All @@ -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) {
Expand All @@ -131,7 +139,6 @@ export function checkCompatibility(
}

return (
allFunboxesAreValid &&
oneWordModifierMax &&
layoutUsability &&
oneNospaceOrToPushMax &&
Expand All @@ -143,11 +150,11 @@ export function checkCompatibility(
oneCanSpeakMax &&
hasLanguageToSpeakAndNoUnspeakable &&
oneToPushOrPullSectionMax &&
oneCssFileMax &&
onePunctuateWordMax &&
oneCharCheckerMax &&
oneCharReplacerMax &&
oneChangesCapitalisationMax &&
oneCssModificationPerElement &&
noConfigConflicts &&
oneWordOrderMax
);
Expand Down
Loading

0 comments on commit a4b7c00

Please sign in to comment.