make the score improvement text smaller and show a previous pp inaccuracy warning
All checks were successful
Deploy Website / docker (ubuntu-latest) (push) Successful in 2m21s

This commit is contained in:
Lee 2024-10-23 08:13:07 +01:00
parent 8090361615
commit 08295d7b04
8 changed files with 74 additions and 20 deletions

@ -1,5 +1,6 @@
import React from "react"; import React from "react";
import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils"; import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils";
import { clsx } from "clsx";
type ChangeProps = { type ChangeProps = {
/** /**
@ -13,6 +14,11 @@ type ChangeProps = {
*/ */
formatValue?: (value: number) => string; formatValue?: (value: number) => string;
/**
* The additional class names
*/
className?: string;
/** /**
* Whether the number is a pp number * Whether the number is a pp number
*/ */
@ -24,7 +30,7 @@ type ChangeProps = {
showColors?: boolean; showColors?: boolean;
}; };
export function Change({ change, formatValue, isPp, showColors }: ChangeProps) { export function Change({ change, formatValue, className, isPp, showColors }: ChangeProps) {
if (change === 0 || (change && change > 0 && change < 0.01) || change === undefined) { if (change === 0 || (change && change > 0 && change < 0.01) || change === undefined) {
return null; return null;
} }
@ -38,7 +44,7 @@ export function Change({ change, formatValue, isPp, showColors }: ChangeProps) {
} }
return ( return (
<p className={`text-sm ${showColors && (change > 0 ? "text-green-400" : "text-red-400")}`}> <p className={clsx("text-sm", showColors && (change > 0 ? "text-green-400" : "text-red-400"), className)}>
{change > 0 ? "+" : ""} {change > 0 ? "+" : ""}
{`${formatValue(change)}${isPp ? "pp" : ""}`} {`${formatValue(change)}${isPp ? "pp" : ""}`}
</p> </p>

@ -26,7 +26,11 @@ export function HandAccuracyBadge({ score, hand }: HandAccuracyProps) {
</Tooltip> </Tooltip>
{scoreImprovement && previousHandAccuracy && ( {scoreImprovement && previousHandAccuracy && (
<Tooltip display={`Previous ${formattedHand} Hand Accuracy: ${previousHandAccuracy.toFixed(2)}`}> <Tooltip display={`Previous ${formattedHand} Hand Accuracy: ${previousHandAccuracy.toFixed(2)}`}>
<Change change={scoreImprovement.handAccuracy[hand]} formatValue={num => num.toFixed(2)} /> <Change
className="text-xs"
change={scoreImprovement.handAccuracy[hand]}
formatValue={num => num.toFixed(2)}
/>
</Tooltip> </Tooltip>
)} )}
</div> </div>

@ -58,7 +58,7 @@ export function ScoreAccuracyBadge({ score, leaderboard }: ScoreAccuracyProps) {
</Tooltip> </Tooltip>
{scoreImprovement && previousAccuracy && ( {scoreImprovement && previousAccuracy && (
<Tooltip display={`Previous Accuracy: ${previousAccuracy.toFixed(2)}%`}> <Tooltip display={`Previous Accuracy: ${previousAccuracy.toFixed(2)}%`}>
<Change change={scoreImprovement.accuracy} formatValue={num => `${num.toFixed(2)}%`} /> <Change className="text-xs" change={scoreImprovement.accuracy} formatValue={num => `${num.toFixed(2)}%`} />
</Tooltip> </Tooltip>
)} )}
</div> </div>

@ -27,6 +27,8 @@ export default function ScoreMissesBadge({ score, hideXMark }: ScoreMissesBadgeP
wallsHit: (scoreImprovement.misses.wallsHit - misses.wallsHit) * -1, wallsHit: (scoreImprovement.misses.wallsHit - misses.wallsHit) * -1,
}; };
const previousScoreFc = previousScoreMisses?.misses == 0; const previousScoreFc = previousScoreMisses?.misses == 0;
const isMissImprovement =
previousScoreMisses && scoreImprovement && previousScoreMisses.misses > scoreImprovement.misses.misses;
return ( return (
<div className="flex flex-col justify-center items-center"> <div className="flex flex-col justify-center items-center">
@ -42,7 +44,7 @@ export default function ScoreMissesBadge({ score, hideXMark }: ScoreMissesBadgeP
{!hideXMark && <XMarkIcon className={clsx("w-5 h-5", score.fullCombo ? "hidden" : "text-red-400")} />} {!hideXMark && <XMarkIcon className={clsx("w-5 h-5", score.fullCombo ? "hidden" : "text-red-400")} />}
</div> </div>
</ScoreMissesTooltip> </ScoreMissesTooltip>
{additionalData && previousScoreMisses && scoreImprovement && misses && ( {additionalData && previousScoreMisses && scoreImprovement && misses && isMissImprovement && (
<div className="flex gap-2 items-center justify-center"> <div className="flex gap-2 items-center justify-center">
<ScoreMissesTooltip <ScoreMissesTooltip
missedNotes={previousScoreMisses.missedNotes} missedNotes={previousScoreMisses.missedNotes}
@ -51,7 +53,7 @@ export default function ScoreMissesBadge({ score, hideXMark }: ScoreMissesBadgeP
wallsHit={previousScoreMisses.wallsHit} wallsHit={previousScoreMisses.wallsHit}
fullCombo={previousScoreFc} fullCombo={previousScoreFc}
> >
<div className="flex gap-1 items-center"> <div className="flex gap-1 items-center text-xs">
{previousScoreFc ? ( {previousScoreFc ? (
<p className="text-green-400">FC</p> <p className="text-green-400">FC</p>
) : ( ) : (
@ -67,7 +69,7 @@ export default function ScoreMissesBadge({ score, hideXMark }: ScoreMissesBadgeP
wallsHit={misses.wallsHit} wallsHit={misses.wallsHit}
fullCombo={additionalData.fullCombo} fullCombo={additionalData.fullCombo}
> >
<div className="flex gap-1 items-center"> <div className="flex gap-1 items-center text-xs">
{additionalData.fullCombo ? <p className="text-green-400">FC</p> : formatNumberWithCommas(misses.misses)} {additionalData.fullCombo ? <p className="text-green-400">FC</p> : formatNumberWithCommas(misses.misses)}
</div> </div>
</ScoreMissesTooltip> </ScoreMissesTooltip>

@ -1,9 +1,10 @@
import { ScoreBadgeProps } from "@/components/score/badges/badge-props"; import { ScoreBadgeProps } from "@/components/score/badges/badge-props";
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard"; import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
import Tooltip from "@/components/tooltip"; import Tooltip from "@/components/tooltip";
import { formatPp } from "@ssr/common/utils/number-utils"; import { ensurePositiveNumber, formatPp } from "@ssr/common/utils/number-utils";
import { scoresaberService } from "@ssr/common/service/impl/scoresaber"; import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
import { Change } from "@/common/change"; import { Change } from "@/common/change";
import { Warning } from "@/components/warning";
type ScorePpProps = ScoreBadgeProps & { type ScorePpProps = ScoreBadgeProps & {
/** /**
@ -22,26 +23,42 @@ export function ScorePpBadge({ score, leaderboard }: ScorePpProps) {
return undefined; return undefined;
} }
const weightedPp = pp * weight; const weightedPp = pp * weight;
const previousPp = fcAccuracy ? scoresaberService.getPp(leaderboard.stars, fcAccuracy).toFixed(0) : undefined; const fcPp =
const isSamePp = previousPp === pp.toFixed(0); !score.fullCombo && fcAccuracy ? scoresaberService.getPp(leaderboard.stars, fcAccuracy).toFixed(0) : undefined;
return ( return (
<> <>
<Tooltip <Tooltip
display={ display={
<div className="flex flex-col gap-2">
<div> <div>
<p className="font-semibold">Performance Points</p> <p className="font-semibold">Performance Points</p>
<p>Raw: {formatPp(pp)}pp</p> <p>Raw: {formatPp(pp)}pp</p>
<p> <p>
Weighted: {formatPp(weightedPp)}pp ({(100 * weight).toFixed(2)}%) Weighted: {formatPp(weightedPp)}pp ({(100 * weight).toFixed(2)}%)
</p> </p>
{previousPp && !isSamePp && <p>Full Combo: {previousPp}pp</p>} {fcPp && <p>Full Combo: {fcPp}pp</p>}
</div>
{previousAccuracy && (
<Warning>
<p className="text-red-700">
The previous pp may not be 100% accurate due to ScoreSaber API limitations.
</p>
</Warning>
)}
</div> </div>
} }
> >
<div className="flex flex-col items-center justify-center cursor-default"> <div className="flex flex-col items-center justify-center cursor-default">
<p>{formatPp(pp)}pp</p> <p>{formatPp(pp)}pp</p>
{previousAccuracy && <Change change={previousAccuracy} isPp />} {previousAccuracy && (
<Change
className="text-xs"
change={ensurePositiveNumber(pp - scoresaberService.getPp(leaderboard.stars, previousAccuracy))}
isPp
/>
)}
</div> </div>
</Tooltip> </Tooltip>
</> </>

@ -8,7 +8,7 @@ export function ScoreScoreBadge({ score }: ScoreBadgeProps) {
return ( return (
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
<p>{formatNumberWithCommas(Number(score.score.toFixed(0)))}</p> <p>{formatNumberWithCommas(Number(score.score.toFixed(0)))}</p>
{scoreImprovement && <Change change={scoreImprovement.score} />} {scoreImprovement && <Change className="text-xs" change={scoreImprovement.score} />}
</div> </div>
); );
} }

@ -48,7 +48,9 @@ export default function Tooltip({ children, display, asChild = true, side = "top
{children} {children}
</button> </button>
</TooltipTrigger> </TooltipTrigger>
<TooltipContent side={side}>{display}</TooltipContent> <TooltipContent className="max-w-[350px]" side={side}>
{display}
</TooltipContent>
</ShadCnTooltip> </ShadCnTooltip>
); );
} }

@ -0,0 +1,23 @@
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
import { ReactNode } from "react";
type WarningProps = {
/**
* The size of the warning icon.
*/
size?: number;
/**
* The children to display.
*/
children: ReactNode;
};
export function Warning({ size = 32, children }: WarningProps) {
return (
<div className="flex gap-2 items-center justify-center">
<ExclamationTriangleIcon width={size} height={size} className={`w-[${size}px] h-[${size}px]`} />
{children}
</div>
);
}