mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-24 06:48:02 +08:00
impr: show xp gain details as list (@fehmer, @miodec) (#5895)
  --------- Co-authored-by: Miodec <jack@monkeytype.com>
This commit is contained in:
parent
8d6f2b4edc
commit
d9788a15e7
4 changed files with 201 additions and 100 deletions
|
|
@ -132,8 +132,11 @@
|
|||
<div class="level" data-balloon-pos="up">1</div>
|
||||
<div class="xpBar">
|
||||
<div class="bar"></div>
|
||||
<div class="xpGain"></div>
|
||||
<div class="xpBreakdown"></div>
|
||||
<div class="xpBreakdown">
|
||||
<div class="total"></div>
|
||||
<!-- <div class="divider"></div> -->
|
||||
<div class="list"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ export function update(snapshot: MonkeyTypes.Snapshot | undefined): void {
|
|||
export async function updateXpBar(
|
||||
currentXp: number,
|
||||
addedXp: number,
|
||||
breakdown?: Record<string, number>
|
||||
breakdown?: XpBreakdown
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
if (skipBreakdown) {
|
||||
total = addedXp;
|
||||
string = "";
|
||||
xpBreakdownTotal.text("+0");
|
||||
|
||||
async function append(
|
||||
string: string,
|
||||
amount: number | string | undefined,
|
||||
options?: { extraClass?: string }
|
||||
): Promise<void> {
|
||||
if (skipBreakdown) return;
|
||||
|
||||
if (amount === undefined) {
|
||||
xpBreakdownList.append(
|
||||
`<div class="line" data-string='${string}'><div>${string}</div><div></div></div>`
|
||||
);
|
||||
} else if (typeof amount === "string") {
|
||||
xpBreakdownList.append(
|
||||
`
|
||||
<div class="line" data-string='${string}'>
|
||||
<div class="${options?.extraClass}">${string}</div>
|
||||
<div class="${options?.extraClass}">${amount}</div>
|
||||
</div>`
|
||||
);
|
||||
} else {
|
||||
const positive = amount == undefined ? undefined : amount >= 0;
|
||||
|
||||
xpBreakdownList.append(`
|
||||
<div class="line" data-string='${string}'>
|
||||
|
||||
<div class="${options?.extraClass}">${string}</div>
|
||||
<div class="${positive ? "positive" : "negative"} ${
|
||||
options?.extraClass
|
||||
}">${positive ? "+" : "-"}${Math.abs(amount)}</div>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
xpBreakdown.find(".next").removeClass("next").addClass("previous");
|
||||
xpBreakdown.append(
|
||||
`<div class='text next' style="opacity: 0; margin-top: 1rem;">${string}</div>`
|
||||
);
|
||||
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(
|
||||
`<div class='text next'>time typing +${breakdown.base}</div>`
|
||||
);
|
||||
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",
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue