From a4b7c00ef97beaf0eeef4e0841d212c27512aaa8 Mon Sep 17 00:00:00 2001 From: Tamion <70228790+notTamion@users.noreply.github.com> Date: Tue, 4 Feb 2025 17:52:50 +0100 Subject: [PATCH] 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 Co-authored-by: Christian Fehmer --- frontend/src/email-handler.html | 1 - frontend/src/index.html | 1 - frontend/src/privacy-policy.html | 1 - frontend/src/security-policy.html | 1 - frontend/src/terms-of-service.html | 1 - frontend/src/ts/test/funbox/funbox.ts | 28 ++--- frontend/static/funbox/choo_choo.css | 6 +- frontend/static/funbox/nausea.css | 4 +- packages/funbox/__test__/validation.spec.ts | 118 ++++++++++++++++++++ packages/funbox/src/list.ts | 10 ++ packages/funbox/src/types.ts | 3 + packages/funbox/src/validation.ts | 27 +++-- packages/funbox/tsconfig.json | 2 +- 13 files changed, 164 insertions(+), 39 deletions(-) create mode 100644 packages/funbox/__test__/validation.spec.ts diff --git a/frontend/src/email-handler.html b/frontend/src/email-handler.html index 7deacb540..78a4529da 100644 --- a/frontend/src/email-handler.html +++ b/frontend/src/email-handler.html @@ -7,7 +7,6 @@ - - diff --git a/frontend/src/privacy-policy.html b/frontend/src/privacy-policy.html index 8c901a573..81b44fcb0 100644 --- a/frontend/src/privacy-policy.html +++ b/frontend/src/privacy-policy.html @@ -7,7 +7,6 @@ - diff --git a/frontend/src/security-policy.html b/frontend/src/security-policy.html index 7d6e5c370..bd23aa742 100644 --- a/frontend/src/security-policy.html +++ b/frontend/src/security-policy.html @@ -7,7 +7,6 @@ - diff --git a/frontend/src/terms-of-service.html b/frontend/src/terms-of-service.html index 6e9a07d65..f473e7711 100644 --- a/frontend/src/terms-of-service.html +++ b/frontend/src/terms-of-service.html @@ -7,7 +7,6 @@ - diff --git a/frontend/src/ts/test/funbox/funbox.ts b/frontend/src/ts/test/funbox/funbox.ts index 414c0ceb2..0f4411f86 100644 --- a/frontend/src/ts/test/funbox/funbox.ts +++ b/frontend/src/ts/test/funbox/funbox.ts @@ -72,7 +72,7 @@ export async function clear(): Promise { ?.join(" ") ?? "" ); - $("#funBoxTheme").removeAttr("href"); + $(".funBoxTheme").remove(); $("#wordsWrapper").removeClass("hidden"); MemoryTimer.reset(); @@ -237,23 +237,15 @@ async function setFunboxBodyClasses(): Promise { } async function applyFunboxCSS(): Promise { - 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; } diff --git a/frontend/static/funbox/choo_choo.css b/frontend/static/funbox/choo_choo.css index 7efd0731e..1b117c041 100644 --- a/frontend/static/funbox/choo_choo.css +++ b/frontend/static/funbox/choo_choo.css @@ -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; } diff --git a/frontend/static/funbox/nausea.css b/frontend/static/funbox/nausea.css index 902a8d9ac..ffdbc42a8 100644 --- a/frontend/static/funbox/nausea.css +++ b/frontend/static/funbox/nausea.css @@ -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 { diff --git a/packages/funbox/__test__/validation.spec.ts b/packages/funbox/__test__/validation.spec.ts new file mode 100644 index 000000000..71978ba59 --- /dev/null +++ b/packages/funbox/__test__/validation.spec.ts @@ -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 + ); + }); + }); +}); diff --git a/packages/funbox/src/list.ts b/packages/funbox/src/list.ts index b1084d704..498b9fd6f 100644 --- a/packages/funbox/src/list.ts +++ b/packages/funbox/src/list.ts @@ -24,6 +24,7 @@ const list: Record = { properties: ["hasCssFile"], canGetPb: true, difficultyLevel: 3, + cssModifications: ["main"], }, upside_down: { name: "upside_down", @@ -31,6 +32,7 @@ const list: Record = { properties: ["hasCssFile"], canGetPb: true, difficultyLevel: 3, + cssModifications: ["main"], }, nausea: { name: "nausea", @@ -38,6 +40,7 @@ const list: Record = { canGetPb: true, difficultyLevel: 2, properties: ["hasCssFile", "ignoreReducedMotion"], + cssModifications: ["typingTest"], }, round_round_baby: { name: "round_round_baby", @@ -46,6 +49,7 @@ const list: Record = { canGetPb: true, difficultyLevel: 3, properties: ["hasCssFile", "ignoreReducedMotion"], + cssModifications: ["typingTest"], }, simon_says: { name: "simon_says", @@ -69,6 +73,7 @@ const list: Record = { frontendFunctions: ["applyConfig", "rememberSettings", "toggleScript"], name: "tts", description: "Listen closely.", + cssModifications: ["words"], }, choo_choo: { canGetPb: true, @@ -81,6 +86,7 @@ const list: Record = { ], 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 = { 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 = { difficultyLevel: 0, properties: ["hasCssFile", "ignoreReducedMotion"], name: "space_balls", + cssModifications: ["body"], }, gibberish: { description: "Anvbuefl dizzs eoos alsb?", @@ -381,6 +389,7 @@ const list: Record = { properties: ["hasCssFile", "noLigatures"], frontendFunctions: ["applyGlobalCSS", "clearGlobal"], name: "crt", + cssModifications: ["body"], }, backwards: { description: "...sdrawkcab epyt ot yrt woN", @@ -394,6 +403,7 @@ const list: Record = { canGetPb: true, frontendFunctions: ["alterText"], difficultyLevel: 3, + cssModifications: ["words"], }, ddoouubblleedd: { description: "TTyyppee eevveerryytthhiinngg ttwwiiccee..", diff --git a/packages/funbox/src/types.ts b/packages/funbox/src/types.ts index 9e0336aac..ff4463b2a 100644 --- a/packages/funbox/src/types.ts +++ b/packages/funbox/src/types.ts @@ -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[]; }; diff --git a/packages/funbox/src/validation.ts b/packages/funbox/src/validation.ts index 70cdaa5df..138b3573d 100644 --- a/packages/funbox/src/validation.ts +++ b/packages/funbox/src/validation.ts @@ -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>((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 ); diff --git a/packages/funbox/tsconfig.json b/packages/funbox/tsconfig.json index de674c340..e263d2471 100644 --- a/packages/funbox/tsconfig.json +++ b/packages/funbox/tsconfig.json @@ -8,7 +8,7 @@ "moduleResolution": "Bundler", "module": "ES6", "target": "ES2015", - "lib": ["es2016"] + "lib": ["es2019"] }, "include": ["src"], "exclude": ["node_modules", "dist"]