rework beatleader data tracking
This commit is contained in:
21
projects/website/src/common/change.tsx
Normal file
21
projects/website/src/common/change.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>-></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>
|
||||
);
|
||||
}
|
||||
|
@ -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`}
|
||||
>
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
);
|
||||
|
@ -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={{
|
||||
|
Reference in New Issue
Block a user