Files
Liam 832f4eebe5
All checks were successful
deploy / deploy (push) Successful in 56s
fix rank format on player graph
2023-11-05 13:42:54 +00:00

216 lines
7.2 KiB
TypeScript

import { ScoresaberLeaderboardInfo } from "@/schemas/scoresaber/leaderboard";
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
import { ScoresaberScore } from "@/schemas/scoresaber/score";
import { formatNumber } from "@/utils/numberUtils";
import { getPpGainedFromScore } from "@/utils/scoresaber/scores";
import {
scoresaberDifficultyNumberToName,
songDifficultyToColor,
} from "@/utils/songUtils";
import { formatDate, formatTimeAgo } from "@/utils/timeUtils";
import {
CheckIcon,
GlobeAsiaAustraliaIcon,
StarIcon,
XMarkIcon,
} from "@heroicons/react/20/solid";
import clsx from "clsx";
import Image from "next/image";
import Link from "next/link";
import HeadsetIcon from "../icons/HeadsetIcon";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/Tooltip";
import ScoreStatLabel from "./ScoreStatLabel";
type ScoreProps = {
score: ScoresaberScore;
player: ScoresaberPlayer;
leaderboard: ScoresaberLeaderboardInfo;
ownProfile?: ScoresaberPlayer;
};
export default function Score({
score,
player,
leaderboard,
ownProfile,
}: ScoreProps) {
const isFullCombo = score.missedNotes + score.badCuts === 0;
const diffName = scoresaberDifficultyNumberToName(
leaderboard.difficulty.difficulty,
);
const diffColor = songDifficultyToColor(diffName);
const accuracy = ((score.baseScore / leaderboard.maxScore) * 100).toFixed(2);
const totalMissedNotes = score.missedNotes + score.badCuts;
const weightedPp = formatNumber(getPpGainedFromScore(player.id, score), 2);
return (
<div className="grid grid-cols-1 pb-2 pt-2 first:pt-0 last:pb-0 md:grid-cols-[0.85fr_6fr_1.3fr]">
<div className="flex flex-col items-center justify-center">
<div className="hidden w-fit flex-row items-center justify-center gap-1 md:flex">
<GlobeAsiaAustraliaIcon width={20} height={20} />
<p>#{formatNumber(score.rank)}</p>
<HeadsetIcon id={score.hmd} size={20} />
</div>
<Tooltip>
<TooltipTrigger className="hidden md:block">
<p className="text-sm text-gray-200">
{formatTimeAgo(score.timeSet)}
</p>
</TooltipTrigger>
<TooltipContent>
<div>
<p className="font-bold">Time Submitted</p>
<p>{formatDate(score.timeSet)}</p>
</div>
</TooltipContent>
</Tooltip>
</div>
{/* Song Image */}
<div className="flex w-full items-center gap-2">
<div className="flex justify-center">
<Image
src={leaderboard.coverImage}
alt={leaderboard.songName}
className="h-fit min-w-[60px] rounded-md"
width={60}
height={60}
loading="lazy"
/>
<div
className="absolute mt-12 flex w-[55px] cursor-default items-center justify-center divide-x divide-y rounded-sm pl-[3px] pr-[3px] text-[0.8rem] opacity-90"
style={{
backgroundColor: diffColor,
}}
>
{leaderboard.ranked ? (
<Tooltip>
<TooltipTrigger>
<div className="flex items-center justify-center gap-[2px]">
<StarIcon width={13} height={13} />
{leaderboard.stars.toFixed(2)}
</div>
</TooltipTrigger>
<TooltipContent>
<>
<p className="font-bold">Difficulty</p>
<p>{diffName}</p>
</>
</TooltipContent>
</Tooltip>
) : (
<p>{diffName}</p>
)}
</div>
</div>
{/* Song Info */}
<Link
href={`/leaderboard/${leaderboard.id}/1`}
className="transform-gpu transition-all hover:opacity-70"
>
<div className="w-fit text-clip text-blue-500">
<p className="font-bold">{leaderboard.songName}</p>
<p className="text-blue-300">{leaderboard.songAuthorName}</p>
<p className="text-sm text-gray-400">
{leaderboard.levelAuthorName}
</p>
</div>
</Link>
</div>
<div className="flex items-center justify-between p-1 md:items-start md:justify-end">
<div className="flex flex-col md:hidden">
{/* Score rank */}
<div className="flex items-center gap-1">
<GlobeAsiaAustraliaIcon width={20} height={20} />
<p>#{formatNumber(score.rank)}</p>
<HeadsetIcon id={score.hmd} size={20} />
</div>
{/* Time Set (Mobile) */}
<div>
<Tooltip>
<TooltipTrigger>
<p className="text-sm text-gray-200">
{formatTimeAgo(score.timeSet)}
</p>
</TooltipTrigger>
<TooltipContent>
<div>
<p className="font-bold">Time Submitted</p>
<p>{formatDate(score.timeSet)}</p>
</div>
</TooltipContent>
</Tooltip>
</div>
</div>
{/* PP */}
<div className="flex flex-col justify-end gap-2">
<div className="flex justify-end gap-2">
{score.pp > 0 && (
<ScoreStatLabel
className="bg-blue-500 text-center"
value={formatNumber(score.pp.toFixed(2)) + "pp"}
tooltip={
<div>
<p className="font-bold">Performance Points</p>
{weightedPp !== null && <p>Weighted PP: {weightedPp}pp</p>}
<p>Raw PP: {formatNumber(score.pp, 2)}pp</p>
</div>
}
/>
)}
{/* Percentage score */}
<ScoreStatLabel
tooltip={
<div>
<p className="font-bold">Score</p>
<p>Accuracy: {accuracy}%</p>
<p>Raw Score: {formatNumber(score.baseScore)}</p>
</div>
}
value={
!leaderboard.maxScore
? formatNumber(score.baseScore)
: accuracy + "%"
}
/>
</div>
<div className="flex justify-end gap-2">
{/* Missed Notes */}
<ScoreStatLabel
className={clsx(
"min-w-[2rem]",
isFullCombo ? "bg-green-500" : "bg-red-500",
)}
tooltip={
<div>
<p className="font-bold">Mistakes</p>
{isFullCombo ? (
<p>Full Combo</p>
) : (
<>
<p>Misses: {score.missedNotes}</p>
<p>Bad Cuts: {score.badCuts}</p>
</>
)}
</div>
}
icon={
isFullCombo ? (
<CheckIcon width={20} height={20} />
) : (
<XMarkIcon width={20} height={20} />
)
}
value={isFullCombo ? "FC" : formatNumber(totalMissedNotes) + "x"}
/>
</div>
</div>
</div>
</div>
);
}