From bc3ecb25eb1e3cef9f8453138b4ba3b7cfdc9746 Mon Sep 17 00:00:00 2001 From: Miodec Date: Tue, 28 May 2024 17:11:10 +0200 Subject: [PATCH] impr(result): add new crown states (error, warning, ineligible, normal) --- frontend/src/html/pages/test.html | 10 +- frontend/src/styles/test.scss | 80 ++++++++++---- frontend/src/ts/test/pb-crown.ts | 42 ++++++-- frontend/src/ts/test/result.ts | 163 +++++++++++++++++++++++------ frontend/src/ts/test/test-logic.ts | 5 +- shared-types/types.d.ts | 1 + 6 files changed, 235 insertions(+), 66 deletions(-) diff --git a/frontend/src/html/pages/test.html b/frontend/src/html/pages/test.html index 177e25dd3..288f0d062 100644 --- a/frontend/src/html/pages/test.html +++ b/frontend/src/html/pages/test.html @@ -181,8 +181,16 @@
wpm
-
-
diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss index d1fb2a095..5a860d348 100644 --- a/frontend/src/styles/test.scss +++ b/frontend/src/styles/test.scss @@ -822,32 +822,76 @@ font-size: 2rem; line-height: 1.5rem; display: flex; - // margin-top: -0.5rem; - - // .crownWrapper { - // width: 1.7rem; - // overflow: hidden; - // height: 1.7rem; - // margin-left: 0.5rem; - // // margin-top: 0.98rem; - // margin-top: -0.5rem; .crown { - height: 1.7rem; - width: 1.7rem; + --main: var(--main-color); + --alt: var(--bg-color); + margin-left: 0.5rem; margin-top: -0.2rem; font-size: 0.7rem; - line-height: 1.7rem; - background: var(--main-color); - color: var(--bg-color); - border-radius: 0.6rem; - text-align: center; - align-self: center; + background: var(--main); + color: var(--alt); width: 1.7rem; height: 1.7rem; + border-radius: var(--roundness); + display: grid; + grid-template-areas: "icon"; + align-items: center; + justify-items: center; + + transition: opacity 0.125s, background 0.125s, color 0.125s, + outline 0.125s; + + i { + grid-area: icon; + } + + .fa-slash { + color: var(--main); + font-size: 1.2rem; + opacity: 0; + } + + .fa-question, + .fa-exclamation-triangle { + color: var(--alt); + opacity: 0; + } + + &.half { + --main: var(--bg-color); + --alt: var(--main-color); + outline: 0.2em solid var(--main-color); + } + &.broken { + --main: var(--sub-color); + --alt: var(--bg-color); + .fa-slash { + opacity: 1; + } + } + &.error { + --main: var(--error-color); + --alt: var(--bg-color); + .fa-crown { + opacity: 0; + } + .fa-question { + opacity: 1; + } + } + &.warning { + --main: var(--sub-color); + --alt: var(--bg-color); + .fa-crown { + opacity: 0; + } + .fa-exclamation-triangle { + opacity: 1; + } + } } - // } } .bottom { diff --git a/frontend/src/ts/test/pb-crown.ts b/frontend/src/ts/test/pb-crown.ts index dc7433e4c..9bb424b7b 100644 --- a/frontend/src/ts/test/pb-crown.ts +++ b/frontend/src/ts/test/pb-crown.ts @@ -1,16 +1,36 @@ export function hide(): void { + visible = false; $("#result .stats .wpm .crown").css("opacity", 0).addClass("hidden"); } -export function show(): void { - $("#result .stats .wpm .crown") - .removeClass("hidden") - .css("opacity", "0") - .animate( - { - opacity: 1, - }, - 250, - "easeOutCubic" - ); +export type CrownType = "normal" | "broken" | "half" | "error" | "warning"; + +let visible = false; +let currentType: CrownType = "normal"; + +export function getCurrentType(): CrownType { + return currentType; +} + +export function show(): void { + if (visible) return; + visible = true; + const el = $("#result .stats .wpm .crown"); + el.removeClass("hidden").css("opacity", "0").animate( + { + opacity: 1, + }, + 250, + "easeOutCubic" + ); +} + +export function update(type: CrownType): void { + currentType = type; + const el = $("#result .stats .wpm .crown"); + el.removeClass("broken"); + el.removeClass("half"); + el.removeClass("error"); + el.removeClass("warning"); + el.addClass(type); } diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts index 6d0b6c2b4..2505905b8 100644 --- a/frontend/src/ts/test/result.ts +++ b/frontend/src/ts/test/result.ts @@ -363,8 +363,134 @@ function updateKey(): void { ); } -export function showCrown(): void { +export function showCrown(type: PbCrown.CrownType): void { PbCrown.show(); + PbCrown.update(type); +} + +export function updateCrownType(type: PbCrown.CrownType): void { + PbCrown.update(type); +} + +export async function updateCrown(): Promise { + if (Config.mode === "quote") { + hideCrown(); + return; + } + + let pbDiff = 0; + const canGetPb = await resultCanGetPb(); + + if (canGetPb.value) { + const lpb = await DB.getLocalPB( + Config.mode, + result.mode2, + Config.punctuation, + Config.numbers, + Config.language, + Config.difficulty, + Config.lazyMode, + Config.funbox + ); + pbDiff = result.wpm - lpb; + if (pbDiff <= 0) { + hideCrown(); + } else { + //show half crown as the pb is not confirmed by the server + showCrown("half"); + $("#result .stats .wpm .crown").attr( + "aria-label", + "+" + Format.typingSpeed(pbDiff, { showDecimalPlaces: true }) + ); + } + } else { + const lpb = await DB.getLocalPB( + Config.mode, + result.mode2, + Config.punctuation, + Config.numbers, + Config.language, + Config.difficulty, + Config.lazyMode, + "none" + ); + pbDiff = result.wpm - lpb; + if (pbDiff <= 0) { + // hideCrown(); + showCrown("warning"); + $("#result .stats .wpm .crown").attr( + "aria-label", + `This result is not eligible for a new PB (${canGetPb.reason})` + ); + } else { + showCrown("broken"); + $("#result .stats .wpm .crown").attr( + "aria-label", + `You could've gotten a new PB (+${Format.typingSpeed(pbDiff, { + showDecimalPlaces: true, + })}), but your config does not allow it (${canGetPb.reason})` + ); + } + } +} + +export function hideCrown(): void { + PbCrown.hide(); + $("#result .stats .wpm .crown").attr("aria-label", ""); +} + +export function showErrorCrownIfNeeded(): void { + if (PbCrown.getCurrentType() !== "half") return; + PbCrown.show(); + PbCrown.update("error"); + $("#result .stats .wpm .crown").attr( + "aria-label", + `Local PB data is out of sync with the server - please refresh (pb mismatch)` + ); +} + +type CanGetPbObject = + | { + value: true; + } + | { + value: false; + reason: string; + }; + +async function resultCanGetPb(): Promise { + const funboxes = result.funbox?.split("#") ?? []; + const funboxObjects = await Promise.all( + funboxes.map(async (f) => JSONData.getFunbox(f)) + ); + const allFunboxesCanGetPb = funboxObjects.every((f) => f?.canGetPb); + + const funboxesOk = + result.funbox === "none" || funboxes.length === 0 || allFunboxesCanGetPb; + const notUsingStopOnLetter = Config.stopOnError !== "letter"; + + if (funboxesOk && notUsingStopOnLetter) { + return { + value: true, + }; + } else { + if (!funboxesOk) { + return { + value: false, + reason: "funbox", + }; + } + if (!notUsingStopOnLetter) { + return { + value: false, + reason: "stop on letter", + }; + } + return { + value: false, + reason: "unknown", + }; + } } export function showConfetti(): void { @@ -399,30 +525,6 @@ export function showConfetti(): void { })(); } -export function hideCrown(): void { - PbCrown.hide(); - $("#result .stats .wpm .crown").attr("aria-label", ""); -} - -export async function updateCrown(): Promise { - let pbDiff = 0; - const lpb = await DB.getLocalPB( - Config.mode, - result.mode2, - Config.punctuation, - Config.numbers, - Config.language, - Config.difficulty, - Config.lazyMode, - Config.funbox - ); - pbDiff = Math.abs(result.wpm - lpb); - $("#result .stats .wpm .crown").attr( - "aria-label", - "+" + Format.typingSpeed(pbDiff, { showDecimalPlaces: true }) - ); -} - async function updateTags(dontSave: boolean): Promise { const activeTags: MonkeyTypes.UserTag[] = []; const userTagsCount = DB.getSnapshot()?.tags?.length ?? 0; @@ -451,14 +553,6 @@ async function updateTags(dontSave: boolean): Promise { ); $("#result .stats .tags .editTagsButton").addClass("invisible"); - const funboxes = result.funbox?.split("#") ?? []; - - const funboxObjects = await Promise.all( - funboxes.map(async (f) => JSONData.getFunbox(f)) - ); - - const allFunboxesCanGetPb = funboxObjects.every((f) => f?.canGetPb); - let annotationSide = "start"; let labelAdjust = 15; activeTags.forEach(async (tag) => { @@ -479,7 +573,7 @@ async function updateTags(dontSave: boolean): Promise { if ( Config.mode !== "quote" && !dontSave && - (result.funbox === "none" || funboxes.length === 0 || allFunboxesCanGetPb) + (await resultCanGetPb()).value ) { if (tpb < result.wpm) { //new pb for that tag @@ -763,6 +857,7 @@ export async function update( updateTestType(randomQuote); updateQuoteSource(randomQuote); updateQuoteFavorite(randomQuote); + await updateCrown(); await updateGraph(); await updateGraphPBLine(); await updateTags(dontSave); diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 3bddf45f7..5f5afc1fb 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -1195,8 +1195,7 @@ async function saveResult( ) { Result.showConfetti(); } - Result.showCrown(); - await Result.updateCrown(); + Result.showCrown("normal"); await DB.saveLocalPB( Config.mode, completedEvent.mode2, @@ -1210,6 +1209,8 @@ async function saveResult( completedEvent.rawWpm, completedEvent.consistency ); + } else { + Result.showErrorCrownIfNeeded(); } // if (response.data.dailyLeaderboardRank) { diff --git a/shared-types/types.d.ts b/shared-types/types.d.ts index 6e13a73ab..5e89f59f1 100644 --- a/shared-types/types.d.ts +++ b/shared-types/types.d.ts @@ -237,6 +237,7 @@ declare namespace SharedTypes { charTotal: number; stringified?: string; hash?: string; + stopOnLetter: boolean; } type CustomTextMode = "repeat" | "random" | "shuffle";