From d9788a15e7a63060bcb6e9fb5e860da771730b58 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Mon, 23 Sep 2024 14:56:58 +0200 Subject: [PATCH] impr: show xp gain details as list (@fehmer, @miodec) (#5895) ![image](https://github.com/user-attachments/assets/b75f405e-5ce5-4f54-9cc8-f2153964008d) ![image](https://github.com/user-attachments/assets/bfa8f7ca-1335-489f-bc48-eebe9abc29d3) --------- Co-authored-by: Miodec --- frontend/src/html/header.html | 7 +- frontend/src/styles/nav.scss | 71 +++++-- frontend/src/ts/elements/account-button.ts | 216 ++++++++++++++------- frontend/src/ts/test/test-ui.ts | 7 - 4 files changed, 201 insertions(+), 100 deletions(-) diff --git a/frontend/src/html/header.html b/frontend/src/html/header.html index dede891da..cb8ede59c 100644 --- a/frontend/src/html/header.html +++ b/frontend/src/html/header.html @@ -132,8 +132,11 @@
1
-
-
+
+
+ +
+
diff --git a/frontend/src/styles/nav.scss b/frontend/src/styles/nav.scss index fe92573d8..37cda78c0 100644 --- a/frontend/src/styles/nav.scss +++ b/frontend/src/styles/nav.scss @@ -74,15 +74,19 @@ nav { } .xpBar { + z-index: 5; opacity: 0; pointer-events: none; position: absolute; height: 0.25em; bottom: -0.5em; width: 100%; - left: 0; + min-width: 16ch; + right: 0; background: var(--sub-alt-color); border-radius: var(--roundness); + display: grid; + grid-template-columns: auto 2.5em; .bar { left: 0; width: 0%; @@ -90,26 +94,61 @@ nav { background: var(--main-color); border-radius: var(--roundness); } - .xpGain { - position: absolute; - left: 100%; - margin-left: 0.5em; - top: 0.2em; - transform: translateY(-50%); - font-size: 0.75em; - color: var(--main-color); - } .xpBreakdown { + backdrop-filter: blur(1em); + /*background: linear-gradient( + 90deg, + transparent 0%, + var(--bg-color) 10% + );*/ + width: 100%; position: absolute; color: var(--text-color); display: grid; - justify-items: center; - width: 100%; - margin-top: 0.75em; - .text { + gap: 0.5em; + //justify-items: center; + width: 32ch; + justify-self: end; + margin-top: 0.3em; + padding: 0.5em 1em; + margin-right: -0.5em; + border-radius: var(--roundness); + + .total { + text-align: right; + font-size: 1em; + color: var(--main-color); width: max-content; - font-size: 0.75em; - position: absolute; + justify-self: end; + } + .divider { + width: 100%; + height: 0.1rem; + background: var(--sub-alt-color); + border-radius: var(--roundness); + opacity: 0.5; + } + .list { + .line { + font-size: 0.8em; + display: grid; + grid-template-columns: auto 10ch; + gap: 0.5em; + + & div { + text-align: right; + } + + .positive { + color: var(--main-color); + } + .negative { + color: var(--error-color); + } + .total { + font-weight: bold; + } + } } } } diff --git a/frontend/src/ts/elements/account-button.ts b/frontend/src/ts/elements/account-button.ts index 6749055df..be7085f19 100644 --- a/frontend/src/ts/elements/account-button.ts +++ b/frontend/src/ts/elements/account-button.ts @@ -192,7 +192,7 @@ export function update(snapshot: MonkeyTypes.Snapshot | undefined): void { export async function updateXpBar( currentXp: number, addedXp: number, - breakdown?: Record + breakdown?: XpBreakdown ): Promise { skipBreakdown = false; const startingXp = Levels.getXpDetails(currentXp); @@ -207,119 +207,182 @@ export async function updateXpBar( const xpBreakdownPromise = animateXpBreakdown(addedXp, breakdown); await Promise.all([xpBarPromise, xpBreakdownPromise]); - await Misc.sleep(2000); + + if (skipBreakdown) { + void flashTotalXp(addedXp); + $("nav .xpBar .xpBreakdown .list") + .stop(true, true) + .animate( + { + opacity: 0, + }, + 250, + () => { + $("nav .xpBar .xpBreakdown .list").empty(); + } + ); + await Misc.sleep(2000); + } else { + await Misc.sleep(5000); + } } $("nav .level").text(Levels.getLevelFromTotalXp(currentXp + addedXp)); $("nav .xpBar") .stop(true, true) .css("opacity", 1) - .animate({ opacity: 0 }, SlowTimer.get() ? 0 : 250, () => { - $("nav .xpBar .xpGain").text(``); - }); + .animate( + { + opacity: 0, + }, + SlowTimer.get() ? 0 : 250 + ); +} + +async function flashTotalXp(totalXp: number): Promise { + const xpTotalEl = $("nav .xpBar .xpBreakdown .total"); + + xpTotalEl.text(`+${totalXp}`); + + const rand = (Math.random() * 2 - 1) / 4; + const rand2 = (Math.random() + 1) / 2; + + /** + * `borderSpacing` has no visible effect on this element, + * and is used in the animation only to provide numerical + * values for the `step(step)` function. + */ + xpTotalEl + .stop(true, true) + .css({ + transition: "initial", + borderSpacing: 100, + }) + .animate( + { + borderSpacing: 0, + }, + { + step(step) { + xpTotalEl.css( + "transform", + `scale(${1 + (step / 200) * rand2}) rotate(${ + (step / 10) * rand + }deg)` + ); + }, + duration: 2000, + easing: "easeOutCubic", + complete: () => { + xpTotalEl.css({ + backgroundColor: "", + transition: "", + }); + }, + } + ); } async function animateXpBreakdown( addedXp: number, breakdown?: XpBreakdown ): Promise { + const xpBreakdownTotal = $("nav .xpBar .xpBreakdown .total"); + const xpBreakdownList = $("nav .xpBar .xpBreakdown .list"); + xpBreakdownList.css("opacity", 1); if (!breakdown) { - $("nav .xpBar .xpGain").text(`+${addedXp}`); + xpBreakdownTotal.text(`+${addedXp}`); return; } - const delay = 1000; + const delay = 250; let total = 0; - const xpGain = $("nav .xpBar .xpGain"); - const xpBreakdown = $("nav .xpBar .xpBreakdown"); - xpBreakdown.empty(); + xpBreakdownList.empty(); - async function append(string: string): Promise { - if (skipBreakdown) { - total = addedXp; - string = ""; + xpBreakdownTotal.text("+0"); + + async function append( + string: string, + amount: number | string | undefined, + options?: { extraClass?: string } + ): Promise { + if (skipBreakdown) return; + + if (amount === undefined) { + xpBreakdownList.append( + `
${string}
` + ); + } else if (typeof amount === "string") { + xpBreakdownList.append( + ` +
+
${string}
+
${amount}
+
` + ); + } else { + const positive = amount == undefined ? undefined : amount >= 0; + + xpBreakdownList.append(` +
+ +
${string}
+
${positive ? "+" : "-"}${Math.abs(amount)}
+
`); } - xpBreakdown.find(".next").removeClass("next").addClass("previous"); - xpBreakdown.append( - `` - ); - const previous = xpBreakdown.find(".previous"); - previous.animate( - { - marginTop: "-1rem", - opacity: 0, - }, - SlowTimer.get() ? 0 : 250, - () => { - previous.remove(); - } - ); - setTimeout(() => { - xpGain - .stop(true, true) - .text(`+${total}`) - .css({ - borderSpacing: 100, - }) - .animate( - { - borderSpacing: 0, - }, - { - step(step) { - xpGain.css( - "transform", - `scale(${1 + step / 300}) translateY(-50%)` - ); - }, - duration: SlowTimer.get() ? 0 : 250, - easing: "swing", - } - ); - }, 125); + const el = xpBreakdownList.find(`.line[data-string='${string}']`); + el.css("opacity", 0); await Misc.promiseAnimation( - xpBreakdown.find(".next"), + el, { opacity: "1", - marginTop: "0", }, - SlowTimer.get() ? 0 : 250, + 250, "swing" ); } - xpGain.text(`+0`); - xpBreakdown.append( - `` - ); - total += breakdown["base"] ?? 0; + // await Misc.sleep(delay / 2); + + total += breakdown.base ?? 0; + // void flashTotalXp(total); + $("nav .xpBar .xpBreakdown .total").text(`+${total}`); + await append("time typing", breakdown.base); + if (breakdown.fullAccuracy) { await Misc.sleep(delay); - await append(`perfect +${breakdown.fullAccuracy}`); total += breakdown.fullAccuracy; + void flashTotalXp(total); + await append("perfect", breakdown.fullAccuracy); } else if (breakdown.corrected) { await Misc.sleep(delay); - await append(`clean +${breakdown.corrected}`); total += breakdown.corrected; + void flashTotalXp(total); + await append("clean", breakdown.corrected); } if (skipBreakdown) return; if (breakdown.quote) { await Misc.sleep(delay); - await append(`quote +${breakdown.quote}`); total += breakdown.quote; + void flashTotalXp(total); + await append("quote", breakdown.quote); } else { if (breakdown.punctuation) { await Misc.sleep(delay); - await append(`punctuation +${breakdown.punctuation}`); total += breakdown.punctuation; + void flashTotalXp(total); + await append("punctuation", breakdown.punctuation); } if (breakdown.numbers) { await Misc.sleep(delay); - await append(`numbers +${breakdown.numbers}`); total += breakdown.numbers; + void flashTotalXp(total); + await append("numbers", breakdown.numbers); } } @@ -327,56 +390,59 @@ async function animateXpBreakdown( if (breakdown.funbox) { await Misc.sleep(delay); - await append(`funbox +${breakdown.funbox}`); total += breakdown.funbox; + void flashTotalXp(total); + await append("funbox", breakdown.funbox); } if (skipBreakdown) return; if (breakdown.streak) { await Misc.sleep(delay); - await append(`streak +${breakdown.streak}`); total += breakdown.streak; + void flashTotalXp(total); + await append("streak", breakdown.streak); } if (skipBreakdown) return; if (breakdown.accPenalty) { await Misc.sleep(delay); - await append(`accuracy penalty -${breakdown.accPenalty}`); total -= breakdown.accPenalty; + void flashTotalXp(total); + await append("accuracy penalty", breakdown.accPenalty * -1); } if (skipBreakdown) return; if (breakdown.incomplete) { await Misc.sleep(delay); - await append(`incomplete tests +${breakdown.incomplete}`); total += breakdown.incomplete; + void flashTotalXp(total); + await append("incomplete tests", breakdown.incomplete); } if (skipBreakdown) return; if (breakdown.configMultiplier) { await Misc.sleep(delay); - await append(`global multiplier x${breakdown.configMultiplier}`); total *= breakdown.configMultiplier; + void flashTotalXp(total); + await append("global multiplier", `x${breakdown.configMultiplier}`); } if (skipBreakdown) return; if (breakdown.daily) { await Misc.sleep(delay); - await append(`daily bonus +${breakdown.daily}`); total += breakdown.daily; + void flashTotalXp(total); + await append("daily bonus", breakdown.daily); } if (skipBreakdown) return; await Misc.sleep(delay); - await append(""); - return; - //base (100% corrected) (quote punctuation numbers) accPenalty incomplete configMultiplier daily } async function animateXpBar( @@ -387,7 +453,7 @@ async function animateXpBar( $("nav .xpBar").stop(true, true).css("opacity", 0); - await Misc.promiseAnimation( + void Misc.promiseAnimation( $("nav .xpBar"), { opacity: "1", diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts index 09d1701fe..4385ae7df 100644 --- a/frontend/src/ts/test/test-ui.ts +++ b/frontend/src/ts/test/test-ui.ts @@ -19,7 +19,6 @@ import * as ConfigEvent from "../observables/config-event"; import * as Hangul from "hangul-js"; import { format } from "date-fns/format"; import { isAuthenticated } from "../firebase"; -import { skipXpBreakdown } from "../elements/account-button"; import * as FunboxList from "./funbox/funbox-list"; import { debounce } from "throttle-debounce"; import * as ResultWordHighlight from "../elements/result-word-highlight"; @@ -1619,12 +1618,6 @@ $("#wordsWrapper").on("click", () => { focusWords(); }); -$(document).on("keypress", () => { - if (resultVisible) { - skipXpBreakdown(); - } -}); - ConfigEvent.subscribe((key, value) => { if (key === "quickRestart") { if (value === "off") {