refactor(xp bar): move code to its own file

This commit is contained in:
Miodec 2024-09-24 20:12:26 +02:00
parent 072c191f81
commit db319b1728
4 changed files with 503 additions and 501 deletions

View file

@ -1,65 +1,14 @@
import * as Misc from "../utils/misc";
import * as Levels from "../utils/levels";
import { getAll } from "./theme-colors";
import * as SlowTimer from "../states/slow-timer";
import { XpBreakdown } from "@monkeytype/contracts/schemas/results";
import {
getHtmlByUserFlags,
SupportsFlags,
} from "../controllers/user-flag-controller";
import { isAuthenticated } from "../firebase";
import { mapRange } from "@monkeytype/util/numbers";
import { Snapshot } from "../db";
import * as XpBar from "./xp-bar";
let usingAvatar = false;
let breakdownVisible = false;
let skipBreakdown = false;
let breakdownDone = false;
export async function skipXpBreakdown(): Promise<void> {
skipBreakdown = true;
if (!breakdownVisible) return;
if (!breakdownDone) {
void flashTotalXp(lastUpdateXpBar.addedXp, true);
} else {
$("nav .xpBar .xpBreakdown .total").text(`+${lastUpdateXpBar.addedXp}`);
}
$("nav .xpBar .xpBreakdown .list")
.stop(true, true)
.empty()
.addClass("hidden");
$("nav .level").text(
Levels.getLevelFromTotalXp(
lastUpdateXpBar.currentXp + lastUpdateXpBar.addedXp
)
);
const endingDetails = Levels.getXpDetails(
lastUpdateXpBar.currentXp + lastUpdateXpBar.addedXp
);
const endingLevel =
endingDetails.level +
endingDetails.levelCurrentXp / endingDetails.levelMaxXp;
const barEl = $("nav .xpBar .bar");
barEl.css("width", `${(endingLevel % 1) * 100}%`);
await Misc.sleep(2000);
breakdownVisible = false;
$("nav .xpBar")
.stop(true, true)
.css("opacity", 1)
.animate(
{
opacity: 0,
},
SlowTimer.get() ? 0 : 250
);
}
export function hide(): void {
$("nav .accountButtonAndMenu").addClass("hidden");
$("nav .textButton.view-login").addClass("hidden");
@ -163,15 +112,6 @@ function updateFlags(flags: SupportsFlags): void {
);
}
function updateXp(xp: number): void {
const xpDetails = Levels.getXpDetails(xp);
const levelCompletionRatio = xpDetails.levelCurrentXp / xpDetails.levelMaxXp;
$("header nav .level").text(xpDetails.level);
$("header nav .bar").css({
width: levelCompletionRatio * 100 + "%",
});
}
export function updateAvatar(
discordId: string | undefined,
discordAvatar: string | undefined
@ -204,7 +144,7 @@ export function update(snapshot: Snapshot | undefined): void {
updateName(name);
updateFlags(snapshot ?? {});
updateXp(xp);
XpBar.setXp(xp);
updateAvatar(discordId ?? "", discordAvatar ?? "");
$("nav .accountButtonAndMenu .menu .items .goToProfile").attr(
@ -224,447 +164,13 @@ export function update(snapshot: Snapshot | undefined): void {
async () => {
updateName("");
updateFlags({});
updateXp(0);
XpBar.setXp(0);
updateAvatar(undefined, undefined);
}
);
}
}
let lastUpdateXpBar: {
currentXp: number;
addedXp: number;
breakdown?: XpBreakdown;
} = {
currentXp: 0,
addedXp: 0,
breakdown: undefined,
};
export async function updateXpBar(
currentXp: number,
addedXp: number,
breakdown?: XpBreakdown
): Promise<void> {
skipBreakdown = false;
breakdownVisible = true;
lastUpdateXpBar = {
currentXp,
addedXp,
breakdown,
};
const startingXp = Levels.getXpDetails(currentXp);
const endingXp = Levels.getXpDetails(currentXp + addedXp);
const startingLevel =
startingXp.level + startingXp.levelCurrentXp / startingXp.levelMaxXp;
const endingLevel =
endingXp.level + endingXp.levelCurrentXp / endingXp.levelMaxXp;
const breakdownList = $("nav .xpBar .xpBreakdown .list");
$("nav .xpBar .xpBreakdown .list").stop(true, true).css("opacity", 0).empty();
$("nav .xpBar").stop(true, true).css("opacity", 0);
$("nav .xpBar .xpBreakdown .total").text("");
const showParent = Misc.promiseAnimation(
$("nav .xpBar"),
{
opacity: "1",
},
SlowTimer.get() ? 0 : 125,
"linear"
);
const showList = Misc.promiseAnimation(
$("nav .xpBar .xpBreakdown .list"),
{
opacity: "1",
},
SlowTimer.get() ? 0 : 125,
"linear"
);
if (breakdown !== undefined) {
breakdownList.removeClass("hidden");
void Promise.all([showParent, showList]);
} else {
breakdownList.addClass("hidden");
void showParent;
}
if (skipBreakdown) return;
const xpBarPromise = animateXpBar(startingLevel, endingLevel);
const xpBreakdownPromise = animateXpBreakdown(addedXp, breakdown);
await Promise.all([xpBarPromise, xpBreakdownPromise]);
if (skipBreakdown) return;
await Misc.sleep(5000);
if (skipBreakdown) return;
breakdownVisible = false;
$("nav .level").text(Levels.getLevelFromTotalXp(currentXp + addedXp));
$("nav .xpBar")
.stop(true, true)
.css("opacity", 1)
.animate(
{
opacity: 0,
},
SlowTimer.get() ? 0 : 250
);
}
async function flashTotalXp(totalXp: number, force = false): Promise<void> {
if (!force && skipBreakdown) return;
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> {
if (skipBreakdown) return;
const xpBreakdownTotal = $("nav .xpBar .xpBreakdown .total");
const xpBreakdownList = $("nav .xpBar .xpBreakdown .list");
xpBreakdownList.css("opacity", 1);
if (!breakdown) {
xpBreakdownTotal.text(`+${addedXp}`);
return;
}
const delay = 250;
let total = 0;
xpBreakdownList.empty();
xpBreakdownList.removeClass("hidden");
xpBreakdownTotal.text("+0");
async function append(
string: string,
amount: number | string | undefined,
options?: { extraClass?: string; noAnimation?: boolean }
): 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>`);
}
if (options?.noAnimation) return;
const el = xpBreakdownList.find(`.line[data-string='${string}']`);
el.css("opacity", 0);
await Misc.promiseAnimation(
el,
{
opacity: "1",
},
250,
"swing"
);
}
total += breakdown.base ?? 0;
$("nav .xpBar .xpBreakdown .total").text(`+${total}`);
await append("time typing", breakdown.base, { noAnimation: true });
await Misc.sleep(delay);
if (breakdown.fullAccuracy) {
await Misc.sleep(delay);
total += breakdown.fullAccuracy;
void flashTotalXp(total);
await append("perfect", breakdown.fullAccuracy);
} else if (breakdown.corrected) {
await Misc.sleep(delay);
total += breakdown.corrected;
void flashTotalXp(total);
await append("clean", breakdown.corrected);
}
if (skipBreakdown) return;
if (breakdown.quote) {
await Misc.sleep(delay);
total += breakdown.quote;
void flashTotalXp(total);
await append("quote", breakdown.quote);
} else {
if (breakdown.punctuation) {
await Misc.sleep(delay);
total += breakdown.punctuation;
void flashTotalXp(total);
await append("punctuation", breakdown.punctuation);
}
if (breakdown.numbers) {
await Misc.sleep(delay);
total += breakdown.numbers;
void flashTotalXp(total);
await append("numbers", breakdown.numbers);
}
}
if (skipBreakdown) return;
if (breakdown.funbox) {
await Misc.sleep(delay);
total += breakdown.funbox;
void flashTotalXp(total);
await append("funbox", breakdown.funbox);
}
if (skipBreakdown) return;
if (breakdown.streak) {
await Misc.sleep(delay);
total += breakdown.streak;
void flashTotalXp(total);
await append("streak", breakdown.streak);
}
if (skipBreakdown) return;
if (breakdown.accPenalty) {
await Misc.sleep(delay);
total -= breakdown.accPenalty;
void flashTotalXp(total);
await append("accuracy penalty", breakdown.accPenalty * -1);
}
if (skipBreakdown) return;
if (breakdown.incomplete) {
await Misc.sleep(delay);
total += breakdown.incomplete;
void flashTotalXp(total);
await append("incomplete tests", breakdown.incomplete);
}
if (skipBreakdown) return;
if (breakdown.configMultiplier) {
await Misc.sleep(delay);
total *= breakdown.configMultiplier;
void flashTotalXp(total);
await append("global multiplier", `x${breakdown.configMultiplier}`);
}
if (skipBreakdown) return;
if (breakdown.daily) {
await Misc.sleep(delay);
total += breakdown.daily;
void flashTotalXp(total);
await append("daily bonus", breakdown.daily);
}
breakdownDone = true;
if (skipBreakdown) return;
await Misc.sleep(delay);
}
async function animateXpBar(
startingLevel: number,
endingLevel: number
): Promise<void> {
if (skipBreakdown) return;
const difference = endingLevel - startingLevel;
const barEl = $("nav .xpBar .bar");
barEl.css("width", `${(startingLevel % 1) * 100}%`);
if (endingLevel % 1 === 0) {
await Misc.promiseAnimation(
barEl,
{
width: "100%",
},
SlowTimer.get() ? 0 : 1000,
"easeOutExpo"
);
if (skipBreakdown) return;
void flashLevel();
barEl.css("width", `0%`);
} else if (Math.floor(startingLevel) === Math.floor(endingLevel)) {
await Misc.promiseAnimation(
barEl,
{ width: `${(endingLevel % 1) * 100}%` },
SlowTimer.get() ? 0 : 1000,
"easeOutExpo"
);
} else {
// const quickSpeed = Misc.mapRange(difference, 10, 2000, 200, 1);
const quickSpeed = Math.min(1000 / difference, 200);
let toAnimate = difference;
let firstOneDone = false;
let animationDuration = quickSpeed;
let animationEasing = "linear";
let decrement = 1 - (startingLevel % 1);
do {
if (skipBreakdown) return;
if (toAnimate - 1 < 1) {
animationDuration = mapRange(toAnimate - 1, 0, 0.5, 1000, 200);
animationEasing = "easeOutQuad";
}
if (firstOneDone) {
void flashLevel();
barEl.css("width", "0%");
decrement = 1;
}
await Misc.promiseAnimation(
barEl,
{
width: "100%",
},
SlowTimer.get() ? 0 : animationDuration,
animationEasing
);
toAnimate -= decrement;
firstOneDone = true;
} while (toAnimate > 1);
if (skipBreakdown) return;
void flashLevel();
barEl.css("width", "0%");
if (skipBreakdown) return;
await Misc.promiseAnimation(
barEl,
{
width: `${(toAnimate % 1) * 100}%`,
},
SlowTimer.get() ? 0 : 1000,
"easeOutExpo"
);
}
return;
}
async function flashLevel(): Promise<void> {
const themecolors = await getAll();
const levelEl = $("nav .level");
levelEl.text(parseInt(levelEl.text()) + 1);
const rand = Math.random() * 2 - 1;
const rand2 = Math.random() + 1;
/**
* `borderSpacing` has no visible effect on this element,
* and is used in the animation only to provide numerical
* values for the `step(step)` function.
*/
levelEl
.stop(true, true)
.css({
backgroundColor: themecolors.main,
// transform: "scale(1.5) rotate(10deg)",
borderSpacing: 100,
transition: "initial",
})
.animate(
{
backgroundColor: themecolors.sub,
borderSpacing: 0,
},
{
step(step) {
levelEl.css(
"transform",
`scale(${1 + (step / 200) * rand2}) rotate(${
(step / 10) * rand
}deg)`
);
},
duration: 2000,
easing: "easeOutCubic",
complete: () => {
levelEl.css({
backgroundColor: "",
transition: "",
});
},
}
);
}
const coarse = window.matchMedia("(pointer:coarse)")?.matches;
if (coarse) {
$("nav .accountButtonAndMenu .textButton.view-account").attr("href", "");

View file

@ -1,7 +1,6 @@
import { formatDistanceToNowStrict } from "date-fns/formatDistanceToNowStrict";
import Ape from "../ape";
import { isAuthenticated } from "../firebase";
import * as AccountButton from "../elements/account-button";
import * as DB from "../db";
import * as NotificationEvent from "../observables/notification-event";
import * as BadgeController from "../controllers/badge-controller";
@ -11,6 +10,7 @@ import { escapeHTML } from "../utils/misc";
import AnimatedModal from "../utils/animated-modal";
import { updateXp as accountPageUpdateProfile } from "./profile";
import { MonkeyMail } from "@monkeytype/contracts/schemas/users";
import * as XPBar from "../elements/xp-bar";
let accountAlerts: MonkeyMail[] = [];
let maxMail = 0;
@ -91,7 +91,7 @@ function hide(): void {
if (totalXpClaimed > 0) {
const snapxp = DB.getSnapshot()?.xp ?? 0;
void AccountButton.updateXpBar(snapxp, totalXpClaimed);
void XPBar.update(snapxp, totalXpClaimed);
accountPageUpdateProfile(snapxp + totalXpClaimed);
DB.addXp(totalXpClaimed);
}

View file

@ -0,0 +1,495 @@
import * as Misc from "../utils/misc";
import * as Levels from "../utils/levels";
import { getAll } from "./theme-colors";
import * as SlowTimer from "../states/slow-timer";
import { XpBreakdown } from "@monkeytype/contracts/schemas/results";
import { mapRange } from "@monkeytype/util/numbers";
let breakdownVisible = false;
let skip = false;
let breakdownDone = false;
let lastUpdate: {
currentXp: number;
addedXp: number;
breakdown?: XpBreakdown;
} = {
currentXp: 0,
addedXp: 0,
breakdown: undefined,
};
const xpBreakdownTotalEl = $("nav .xpBar .xpBreakdown .total");
const xpBreakdownListEl = $("nav .xpBar .xpBreakdown .list");
const levelEl = $("nav .level");
const barEl = $("nav .xpBar .bar");
const barWrapperEl = $("nav .xpBar");
export async function skipBreakdown(): Promise<void> {
skip = true;
if (!breakdownVisible) return;
if (!breakdownDone) {
void flashTotalXp(lastUpdate.addedXp, true);
} else {
xpBreakdownTotalEl.text(`+${lastUpdate.addedXp}`);
}
xpBreakdownListEl.stop(true, true).empty().addClass("hidden");
levelEl.text(
Levels.getLevelFromTotalXp(lastUpdate.currentXp + lastUpdate.addedXp)
);
const endingDetails = Levels.getXpDetails(
lastUpdate.currentXp + lastUpdate.addedXp
);
const endingLevel =
endingDetails.level +
endingDetails.levelCurrentXp / endingDetails.levelMaxXp;
barEl.css("width", `${(endingLevel % 1) * 100}%`);
await Misc.sleep(2000);
breakdownVisible = false;
barWrapperEl
.stop(true, true)
.css("opacity", 1)
.animate(
{
opacity: 0,
},
SlowTimer.get() ? 0 : 250
);
}
export function setXp(xp: number): void {
const xpDetails = Levels.getXpDetails(xp);
const levelCompletionRatio = xpDetails.levelCurrentXp / xpDetails.levelMaxXp;
levelEl.text(xpDetails.level);
barEl.css({
width: levelCompletionRatio * 100 + "%",
});
}
export async function update(
currentXp: number,
addedXp: number,
breakdown?: XpBreakdown
): Promise<void> {
skip = false;
breakdownVisible = true;
lastUpdate = {
currentXp,
addedXp,
breakdown,
};
const startingXp = Levels.getXpDetails(currentXp);
const endingXp = Levels.getXpDetails(currentXp + addedXp);
const startingLevel =
startingXp.level + startingXp.levelCurrentXp / startingXp.levelMaxXp;
const endingLevel =
endingXp.level + endingXp.levelCurrentXp / endingXp.levelMaxXp;
const breakdownList = xpBreakdownListEl;
xpBreakdownListEl.stop(true, true).css("opacity", 0).empty();
barWrapperEl.stop(true, true).css("opacity", 0);
xpBreakdownTotalEl.text("");
const showParent = Misc.promiseAnimation(
barWrapperEl,
{
opacity: "1",
},
SlowTimer.get() ? 0 : 125,
"linear"
);
const showList = Misc.promiseAnimation(
xpBreakdownListEl,
{
opacity: "1",
},
SlowTimer.get() ? 0 : 125,
"linear"
);
if (breakdown !== undefined) {
breakdownList.removeClass("hidden");
void Promise.all([showParent, showList]);
} else {
breakdownList.addClass("hidden");
void showParent;
}
if (skip) return;
const xpBarPromise = animateXpBar(startingLevel, endingLevel);
const xpBreakdownPromise = animateXpBreakdown(addedXp, breakdown);
await Promise.all([xpBarPromise, xpBreakdownPromise]);
if (skip) return;
await Misc.sleep(5000);
if (skip) return;
breakdownVisible = false;
levelEl.text(Levels.getLevelFromTotalXp(currentXp + addedXp));
barWrapperEl
.stop(true, true)
.css("opacity", 1)
.animate(
{
opacity: 0,
},
SlowTimer.get() ? 0 : 250
);
}
async function flashTotalXp(totalXp: number, force = false): Promise<void> {
if (!force && skip) return;
xpBreakdownTotalEl.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.
*/
xpBreakdownTotalEl
.stop(true, true)
.css({
transition: "initial",
borderSpacing: 100,
})
.animate(
{
borderSpacing: 0,
},
{
step(step) {
xpBreakdownTotalEl.css(
"transform",
`scale(${1 + (step / 200) * rand2}) rotate(${
(step / 10) * rand
}deg)`
);
},
duration: 2000,
easing: "easeOutCubic",
complete: () => {
xpBreakdownTotalEl.css({
backgroundColor: "",
transition: "",
});
},
}
);
}
async function addBreakdownListItem(
string: string,
amount: number | string | undefined,
options?: { extraClass?: string; noAnimation?: boolean }
): Promise<void> {
if (skip) return;
if (amount === undefined) {
xpBreakdownListEl.append(
`<div class="line" data-string='${string}'><div>${string}</div><div></div></div>`
);
} else if (typeof amount === "string") {
xpBreakdownListEl.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;
xpBreakdownListEl.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>`);
}
if (options?.noAnimation) return;
const el = xpBreakdownListEl.find(`.line[data-string='${string}']`);
el.css("opacity", 0);
await Misc.promiseAnimation(
el,
{
opacity: "1",
},
250,
"swing"
);
}
async function animateXpBreakdown(
addedXp: number,
breakdown?: XpBreakdown
): Promise<void> {
if (skip) return;
xpBreakdownListEl.css("opacity", 1);
if (!breakdown) {
xpBreakdownTotalEl.text(`+${addedXp}`);
return;
}
const delay = 250;
let total = 0;
xpBreakdownListEl.empty();
xpBreakdownListEl.removeClass("hidden");
xpBreakdownTotalEl.text("+0");
total += breakdown.base ?? 0;
xpBreakdownTotalEl.text(`+${total}`);
await addBreakdownListItem("time typing", breakdown.base, {
noAnimation: true,
});
await Misc.sleep(delay);
if (breakdown.fullAccuracy) {
await Misc.sleep(delay);
total += breakdown.fullAccuracy;
void flashTotalXp(total);
await addBreakdownListItem("perfect", breakdown.fullAccuracy);
} else if (breakdown.corrected) {
await Misc.sleep(delay);
total += breakdown.corrected;
void flashTotalXp(total);
await addBreakdownListItem("clean", breakdown.corrected);
}
if (skip) return;
if (breakdown.quote) {
await Misc.sleep(delay);
total += breakdown.quote;
void flashTotalXp(total);
await addBreakdownListItem("quote", breakdown.quote);
} else {
if (breakdown.punctuation) {
await Misc.sleep(delay);
total += breakdown.punctuation;
void flashTotalXp(total);
await addBreakdownListItem("punctuation", breakdown.punctuation);
}
if (breakdown.numbers) {
await Misc.sleep(delay);
total += breakdown.numbers;
void flashTotalXp(total);
await addBreakdownListItem("numbers", breakdown.numbers);
}
}
if (skip) return;
if (breakdown.funbox) {
await Misc.sleep(delay);
total += breakdown.funbox;
void flashTotalXp(total);
await addBreakdownListItem("funbox", breakdown.funbox);
}
if (skip) return;
if (breakdown.streak) {
await Misc.sleep(delay);
total += breakdown.streak;
void flashTotalXp(total);
await addBreakdownListItem("streak", breakdown.streak);
}
if (skip) return;
if (breakdown.accPenalty) {
await Misc.sleep(delay);
total -= breakdown.accPenalty;
void flashTotalXp(total);
await addBreakdownListItem("accuracy penalty", breakdown.accPenalty * -1);
}
if (skip) return;
if (breakdown.incomplete) {
await Misc.sleep(delay);
total += breakdown.incomplete;
void flashTotalXp(total);
await addBreakdownListItem("incomplete tests", breakdown.incomplete);
}
if (skip) return;
if (breakdown.configMultiplier) {
await Misc.sleep(delay);
total *= breakdown.configMultiplier;
void flashTotalXp(total);
await addBreakdownListItem(
"global multiplier",
`x${breakdown.configMultiplier}`
);
}
if (skip) return;
if (breakdown.daily) {
await Misc.sleep(delay);
total += breakdown.daily;
void flashTotalXp(total);
await addBreakdownListItem("daily bonus", breakdown.daily);
}
breakdownDone = true;
if (skip) return;
await Misc.sleep(delay);
}
async function animateXpBar(
startingLevel: number,
endingLevel: number
): Promise<void> {
if (skip) return;
const difference = endingLevel - startingLevel;
barEl.css("width", `${(startingLevel % 1) * 100}%`);
if (endingLevel % 1 === 0) {
await Misc.promiseAnimation(
barEl,
{
width: "100%",
},
SlowTimer.get() ? 0 : 1000,
"easeOutExpo"
);
if (skip) return;
void flashLevel();
barEl.css("width", `0%`);
} else if (Math.floor(startingLevel) === Math.floor(endingLevel)) {
await Misc.promiseAnimation(
barEl,
{ width: `${(endingLevel % 1) * 100}%` },
SlowTimer.get() ? 0 : 1000,
"easeOutExpo"
);
} else {
// const quickSpeed = Misc.mapRange(difference, 10, 2000, 200, 1);
const quickSpeed = Math.min(1000 / difference, 200);
let toAnimate = difference;
let firstOneDone = false;
let animationDuration = quickSpeed;
let animationEasing = "linear";
let decrement = 1 - (startingLevel % 1);
do {
if (skip) return;
if (toAnimate - 1 < 1) {
animationDuration = mapRange(toAnimate - 1, 0, 0.5, 1000, 200);
animationEasing = "easeOutQuad";
}
if (firstOneDone) {
void flashLevel();
barEl.css("width", "0%");
decrement = 1;
}
await Misc.promiseAnimation(
barEl,
{
width: "100%",
},
SlowTimer.get() ? 0 : animationDuration,
animationEasing
);
toAnimate -= decrement;
firstOneDone = true;
} while (toAnimate > 1);
if (skip) return;
void flashLevel();
barEl.css("width", "0%");
if (skip) return;
await Misc.promiseAnimation(
barEl,
{
width: `${(toAnimate % 1) * 100}%`,
},
SlowTimer.get() ? 0 : 1000,
"easeOutExpo"
);
}
return;
}
async function flashLevel(): Promise<void> {
const themecolors = await getAll();
levelEl.text(parseInt(levelEl.text()) + 1);
const rand = Math.random() * 2 - 1;
const rand2 = Math.random() + 1;
/**
* `borderSpacing` has no visible effect on this element,
* and is used in the animation only to provide numerical
* values for the `step(step)` function.
*/
levelEl
.stop(true, true)
.css({
backgroundColor: themecolors.main,
// transform: "scale(1.5) rotate(10deg)",
borderSpacing: 100,
transition: "initial",
})
.animate(
{
backgroundColor: themecolors.sub,
borderSpacing: 0,
},
{
step(step) {
levelEl.css(
"transform",
`scale(${1 + (step / 200) * rand2}) rotate(${
(step / 10) * rand
}deg)`
);
},
duration: 2000,
easing: "easeOutCubic",
complete: () => {
levelEl.css({
backgroundColor: "",
transition: "",
});
},
}
);
}

View file

@ -64,6 +64,7 @@ import {
CompletedEvent,
CustomTextDataWithTextLen,
} from "@monkeytype/contracts/schemas/results";
import * as XPBar from "../elements/xp-bar";
let failReason = "";
const koInputVisual = document.getElementById("koInputVisual") as HTMLElement;
@ -268,7 +269,7 @@ export function restart(options = {} as RestartOptions): void {
if (Config.randomTheme !== "off") {
void ThemeController.randomizeTheme();
}
void AccountButton.skipXpBreakdown();
void XPBar.skipBreakdown();
}
if (!ConnectionState.get()) {
@ -1173,7 +1174,7 @@ async function saveResult(
if (data.xp !== undefined) {
const snapxp = DB.getSnapshot()?.xp ?? 0;
void AccountButton.updateXpBar(
void XPBar.update(
snapxp,
data.xp,
TestUI.resultVisible ? data.xpBreakdown : undefined