rework beatleader data tracking
Some checks failed
Deploy Backend / docker (ubuntu-latest) (push) Failing after 34s
Deploy Website / docker (ubuntu-latest) (push) Failing after 32s

This commit is contained in:
Lee
2024-10-22 17:30:14 +01:00
parent fa2ba83c7a
commit f3dee6a7d2
12 changed files with 317 additions and 71 deletions

View File

@ -0,0 +1,21 @@
import React from "react";
/**
* Renders the change for a stat.
*
* @param change the change
* @param isPp whether the stat is pp
* @param formatValue the function to format the value
*/
export function renderChange(change: number | undefined, isPp: boolean, formatValue: (value: number) => string) {
if (change === 0 || (change && change < 0.01) || change === undefined) {
return null;
}
return (
<p className={`text-sm ${change > 0 ? "text-green-400" : "text-red-400"}`}>
{change > 0 ? "+" : ""}
{`${formatValue(change)}${isPp ? "pp" : ""}`}
</p>
);
}

View File

@ -1,8 +1,9 @@
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
import { XMarkIcon } from "@heroicons/react/24/solid";
import clsx from "clsx";
import Tooltip from "@/components/tooltip";
import { ScoreBadgeProps } from "@/components/score/badges/badge-props";
import { ScoreMissesTooltip } from "@/components/score/score-misses-tooltip";
import { Misses } from "@ssr/common/model/additional-score-data/misses";
type ScoreMissesBadgeProps = ScoreBadgeProps & {
/**
@ -12,32 +13,65 @@ type ScoreMissesBadgeProps = ScoreBadgeProps & {
};
export default function ScoreMissesBadge({ score, hideXMark }: ScoreMissesBadgeProps) {
const additionalData = score.additionalData;
const scoreImprovement = additionalData?.scoreImprovement;
const misses = additionalData?.misses;
const previousScoreMisses: Misses | undefined = misses &&
additionalData &&
scoreImprovement && {
misses: misses.misses - scoreImprovement.misses.misses,
missedNotes: misses.missedNotes - scoreImprovement.misses.missedNotes,
badCuts: misses.badCuts - scoreImprovement.misses.badCuts,
bombCuts: misses.bombCuts - scoreImprovement.misses.bombCuts,
wallsHit: misses.wallsHit - scoreImprovement.misses.wallsHit,
};
return (
<Tooltip
display={
<div className="flex flex-col">
{!score.fullCombo ? (
<>
<p className="font-semibold">Misses</p>
<p>Missed Notes: {formatNumberWithCommas(score.missedNotes)}</p>
<p>Bad Cuts: {formatNumberWithCommas(score.badCuts)}</p>
{score.additionalData && (
<>
<p>Bomb Cuts: {formatNumberWithCommas(score.additionalData.bombCuts)}</p>
<p>Wall Hits: {formatNumberWithCommas(score.additionalData.wallsHit)}</p>
</>
)}
</>
) : (
<p>Full Combo</p>
)}
<div className="flex flex-col justify-center items-center">
<ScoreMissesTooltip
missedNotes={score.missedNotes}
badCuts={score.badCuts}
bombCuts={misses?.bombCuts}
wallsHit={misses?.wallsHit}
fullCombo={score.fullCombo}
>
<div className="flex gap-1 items-center">
<p>{score.fullCombo ? <span className="text-green-400">FC</span> : formatNumberWithCommas(score.misses)}</p>
{!hideXMark && <XMarkIcon className={clsx("w-5 h-5", score.fullCombo ? "hidden" : "text-red-400")} />}
</div>
}
>
<div className="flex gap-1 items-center justify-center">
<p>{score.fullCombo ? <span className="text-green-400">FC</span> : formatNumberWithCommas(score.misses)}</p>
{!hideXMark && <XMarkIcon className={clsx("w-5 h-5", score.fullCombo ? "hidden" : "text-red-400")} />}
</div>
</Tooltip>
</ScoreMissesTooltip>
{additionalData && previousScoreMisses && scoreImprovement && misses && (
<div className="flex gap-2 items-center justify-center">
<ScoreMissesTooltip
missedNotes={previousScoreMisses.missedNotes}
badCuts={previousScoreMisses.badCuts}
bombCuts={previousScoreMisses.bombCuts}
wallsHit={previousScoreMisses.wallsHit}
fullCombo={scoreImprovement.fullCombo}
>
<div className="flex gap-1 items-center">
{previousScoreMisses.missedNotes == 0 ? (
<p className="text-green-400">FC</p>
) : (
formatNumberWithCommas(previousScoreMisses.misses)
)}
</div>
</ScoreMissesTooltip>
<p>-&gt;</p>
<ScoreMissesTooltip
missedNotes={misses.missedNotes}
badCuts={misses.badCuts}
bombCuts={misses.bombCuts}
wallsHit={misses.wallsHit}
fullCombo={additionalData.fullCombo}
>
<div className="flex gap-1 items-center">
{additionalData.fullCombo ? <p className="text-green-400">FC</p> : formatNumberWithCommas(misses.misses)}
</div>
</ScoreMissesTooltip>
</div>
)}
</div>
);
}

View File

@ -35,7 +35,7 @@ export default function ScoreButtons({
const { toast } = useToast();
return (
<div className={`flex justify-end gap-2 h-[${alwaysSingleLine ? "32" : "64"}px]`}>
<div className={`flex justify-end gap-2 items-center`}>
<div
className={`flex ${alwaysSingleLine ? "flex-nowrap" : "flex-wrap"} items-center lg:items-start justify-center lg:justify-end gap-1`}
>

View File

@ -0,0 +1,50 @@
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
import Tooltip from "@/components/tooltip";
type ScoreMissesTooltipProps = {
missedNotes: number;
badCuts: number;
bombCuts?: number;
wallsHit?: number;
fullCombo?: boolean;
/**
* The tooltip children
*/
children: React.ReactNode;
};
export function ScoreMissesTooltip({
missedNotes,
badCuts,
bombCuts,
wallsHit,
fullCombo,
children,
}: ScoreMissesTooltipProps) {
return (
<Tooltip
display={
<div className="flex flex-col">
{!fullCombo ? (
<>
<p className="font-semibold">Misses</p>
<p>Missed Notes: {formatNumberWithCommas(missedNotes)}</p>
<p>Bad Cuts: {formatNumberWithCommas(badCuts)}</p>
{bombCuts !== undefined && wallsHit !== undefined && (
<>
<p>Bomb Cuts: {formatNumberWithCommas(bombCuts)}</p>
<p>Wall Hits: {formatNumberWithCommas(wallsHit)}</p>
</>
)}
</>
) : (
<p>Full Combo</p>
)}
</div>
}
>
{children}
</Tooltip>
);
}

View File

@ -7,6 +7,7 @@ import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leade
import ScoreMissesBadge from "@/components/score/badges/score-misses";
import { Modifier } from "@ssr/common/score/modifier";
import { ScoreModifiers } from "@/components/score/score-modifiers";
import { renderChange } from "@/common/change";
const badges: ScoreBadge[] = [
{
@ -48,6 +49,8 @@ const badges: ScoreBadge[] = [
return getScoreBadgeFromAccuracy(acc).color;
},
create: (score: ScoreSaberScore, leaderboard: ScoreSaberLeaderboard) => {
const scoreImprovement = score.additionalData?.scoreImprovement;
const acc = (score.score / leaderboard.maxScore) * 100;
const fcAccuracy = score.additionalData?.fcAccuracy;
const scoreBadge = getScoreBadgeFromAccuracy(acc);
@ -83,9 +86,15 @@ const badges: ScoreBadge[] = [
</div>
}
>
<p className="cursor-default">
{acc.toFixed(2)}% {modCount > 0 && <ScoreModifiers type="simple" limit={1} score={score} />}
</p>
<div className="flex flex-col items-center justify-center cursor-default">
<p>
{acc.toFixed(2)}% {modCount > 0 && <ScoreModifiers type="simple" limit={1} score={score} />}
</p>
{scoreImprovement &&
renderChange(scoreImprovement.accuracy, false, num => {
return `${num.toFixed(2)}%`;
})}
</div>
</Tooltip>
</>
);
@ -94,7 +103,14 @@ const badges: ScoreBadge[] = [
{
name: "Score",
create: (score: ScoreSaberScore) => {
return `${formatNumberWithCommas(Number(score.score.toFixed(0)))}`;
const scoreImprovement = score.additionalData?.scoreImprovement;
return (
<div className="flex flex-col items-center justify-center">
<p>{formatNumberWithCommas(Number(score.score.toFixed(0)))}</p>
{scoreImprovement && renderChange(scoreImprovement.score, false, formatNumberWithCommas)}
</div>
);
},
},
{
@ -104,11 +120,15 @@ const badges: ScoreBadge[] = [
if (!score.additionalData) {
return undefined;
}
const { handAccuracy } = score.additionalData;
const scoreImprovement = score.additionalData.scoreImprovement;
return (
<Tooltip display={"Left Hand Accuracy"}>
<p>{handAccuracy.left.toFixed(2)}</p>
<div className="flex flex-col items-center justify-center">
<p>{handAccuracy.left.toFixed(2)}</p>
{scoreImprovement && renderChange(scoreImprovement.handAccuracy.left, false, num => num.toFixed(2))}
</div>
</Tooltip>
);
},
@ -122,9 +142,13 @@ const badges: ScoreBadge[] = [
}
const { handAccuracy } = score.additionalData;
const scoreImprovement = score.additionalData.scoreImprovement;
return (
<Tooltip display={"Right Hand Accuracy"}>
<p>{handAccuracy.right.toFixed(2)}</p>
<div className="flex flex-col items-center justify-center">
<p>{handAccuracy.right.toFixed(2)}</p>
{scoreImprovement && renderChange(scoreImprovement.handAccuracy.right, false, num => num.toFixed(2))}
</div>
</Tooltip>
);
},
@ -144,7 +168,7 @@ type Props = {
export default function ScoreStats({ score, leaderboard }: Props) {
return (
<div className={`grid grid-cols-3 grid-rows-2 gap-1 ml-0 lg:ml-2 h-[64px]`}>
<div className={`grid grid-cols-3 grid-rows-2 gap-1 ml-0 lg:ml-2 `}>
<ScoreBadges badges={badges} score={score} leaderboard={leaderboard} />
</div>
);

View File

@ -21,7 +21,7 @@ export default function StatValue({ name, color, value }: Props) {
return (
<div
className={clsx(
"flex min-w-16 gap-2 h-[28px] p-1 items-center justify-center rounded-md text-sm cursor-default",
"flex min-w-16 gap-2 h-full p-1 items-center justify-center rounded-md text-sm cursor-default",
color ? color : "bg-accent"
)}
style={{