add score acc chart
Some checks failed
Deploy Backend / docker (ubuntu-latest) (push) Failing after 32s
Deploy Website / docker (ubuntu-latest) (push) Failing after 31s

This commit is contained in:
Lee
2024-10-23 17:44:55 +01:00
parent 0731d20edc
commit 90c57ad086
23 changed files with 387 additions and 19 deletions

View File

@ -2,11 +2,11 @@
import React, { useState } from "react";
import GenericChart, { DatasetConfig } from "@/components/chart/generic-chart";
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
import Card from "@/components/card";
import { DualRangeSlider } from "@/components/ui/dual-range-slider";
import { useDebounce } from "@uidotdev/usehooks";
import { scoresaberService } from "@ssr/common/service/impl/scoresaber";
import ScoreSaberLeaderboard from "@ssr/common/leaderboard/impl/scoresaber-leaderboard";
type Props = {
/**

View File

@ -0,0 +1,48 @@
"use client";
import ScoreSaberScore from "@ssr/common/score/impl/scoresaber-score";
import { ScoreStatsToken } from "@ssr/common/types/token/beatleader/score-stats/score-stats";
import { formatTime } from "@ssr/common/utils/time-utils";
import GenericChart, { DatasetConfig } from "@/components/chart/generic-chart";
type Props = {
/**
* The score stats to use in the chart
*/
scoreStats: ScoreStatsToken;
};
export default function PlayerScoreAccuracyChart({ scoreStats }: Props) {
const graph = scoreStats.scoreGraphTracker.graph;
const histories: Record<string, (number | null)[]> = {};
const labels: string[] = [];
for (let seconds = 0; seconds < graph.length; seconds++) {
labels.push(formatTime(seconds));
const history = histories["accuracy"];
if (!history) {
histories["accuracy"] = [];
}
histories["accuracy"].push(graph[seconds] * 100);
}
const datasetConfig: DatasetConfig[] = [
{
title: "Accuracy",
field: "accuracy",
color: "#3EC1D3",
axisId: "y",
axisConfig: {
reverse: false,
display: true,
displayName: "Accuracy",
position: "left",
},
labelFormatter: (value: number) => `${value.toFixed(2)}%`,
},
];
return <GenericChart labels={labels} datasetConfig={datasetConfig} histories={histories} />;
}

View File

@ -9,7 +9,7 @@ import { useQuery } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import { fetchLeaderboard } from "@ssr/common/utils/leaderboard.util";
import LeaderboardScoresResponse from "@ssr/common/response/leaderboard-scores-response";
import LeaderboardPpChart from "@/components/leaderboard/leaderboard-pp-chart";
import LeaderboardPpChart from "@/components/leaderboard/chart/leaderboard-pp-chart";
import Card from "@/components/card";
type LeaderboardDataProps = {

View File

@ -62,8 +62,7 @@ export default function LeaderboardScores({
selectedLeaderboardId + "",
currentPage
),
staleTime: 30 * 1000,
enabled: (shouldFetch && isLeaderboardPage) || !isLeaderboardPage,
enabled: shouldFetch,
});
/**

View File

@ -15,6 +15,12 @@ import { BeatSaverMap } from "@ssr/common/model/beatsaver/map";
import { useIsMobile } from "@/hooks/use-is-mobile";
import Card from "@/components/card";
import { MapStats } from "@/components/score/map-stats";
import PlayerScoreAccuracyChart from "@/components/leaderboard/chart/player-score-accuracy-chart";
import { useQuery } from "@tanstack/react-query";
import { fetchLeaderboardScores } from "@ssr/common/utils/score-utils";
import LeaderboardScoresResponse from "@ssr/common/response/leaderboard-scores-response";
import { ScoreStatsToken } from "@ssr/common/types/token/beatleader/score-stats/score-stats";
import { beatLeaderService } from "@ssr/common/service/impl/beatleader";
type Props = {
/**
@ -40,10 +46,67 @@ type Props = {
};
};
type LeaderboardDropdownData = {
/**
* The initial scores.
*/
scores?: LeaderboardScoresResponse<ScoreSaberScore, ScoreSaberLeaderboard>;
/**
* The score stats for this score,
*/
scoreStats?: ScoreStatsToken;
};
export default function Score({ leaderboard, beatSaverMap, score, settings }: Props) {
const scoresPage = getPageFromRank(score.rank, 12);
const isMobile = useIsMobile();
const [baseScore, setBaseScore] = useState<number>(score.score);
const [isLeaderboardExpanded, setIsLeaderboardExpanded] = useState(false);
const [loading, setLoading] = useState(false);
const [leaderboardDropdownData, setLeaderboardDropdownData] = useState<LeaderboardDropdownData | undefined>();
const { data, isError, isLoading } = useQuery<LeaderboardDropdownData>({
queryKey: ["leaderboardDropdownData", leaderboard.id, score.id, isLeaderboardExpanded],
queryFn: async () => {
const scores = await fetchLeaderboardScores<ScoreSaberScore, ScoreSaberLeaderboard>(
"scoresaber",
leaderboard.id + "",
scoresPage
);
const scoreStats = score.additionalData
? await beatLeaderService.lookupScoreStats(score.additionalData.scoreId)
: undefined;
return {
scores: scores,
scoreStats: scoreStats,
};
},
staleTime: 30 * 1000,
enabled: loading,
});
useEffect(() => {
if (data) {
setLeaderboardDropdownData({
...data,
scores: data.scores,
scoreStats: data.scoreStats,
});
setLoading(false);
}
}, [data]);
const handleLeaderboardOpen = (isExpanded: boolean) => {
if (!isExpanded) {
setLeaderboardDropdownData(undefined);
}
setLoading(true);
setIsLeaderboardExpanded(isExpanded);
};
/**
* Set the base score
@ -59,6 +122,7 @@ export default function Score({ leaderboard, beatSaverMap, score, settings }: Pr
*/
useEffect(() => {
setIsLeaderboardExpanded(false);
setLeaderboardDropdownData(undefined);
}, [score]);
const accuracy = (baseScore / leaderboard.maxScore) * 100;
@ -81,7 +145,9 @@ export default function Score({ leaderboard, beatSaverMap, score, settings }: Pr
beatSaverMap={beatSaverMap}
score={score}
alwaysSingleLine={isMobile}
setIsLeaderboardExpanded={setIsLeaderboardExpanded}
setIsLeaderboardExpanded={(isExpanded: boolean) => {
handleLeaderboardOpen(isExpanded);
}}
updateScore={score => {
setBaseScore(score.score);
}}
@ -98,7 +164,7 @@ export default function Score({ leaderboard, beatSaverMap, score, settings }: Pr
</div>
{/* Leaderboard */}
{isLeaderboardExpanded && (
{isLeaderboardExpanded && leaderboardDropdownData && !loading && (
<motion.div
initial={{ opacity: 0, y: -50 }}
exit={{ opacity: 0, y: -50 }}
@ -108,8 +174,15 @@ export default function Score({ leaderboard, beatSaverMap, score, settings }: Pr
<Card className="flex gap-4 w-full relative border border-input">
<MapStats leaderboard={leaderboard} beatSaver={beatSaverMap} />
{leaderboardDropdownData.scoreStats && (
<div className="flex gap-2">
<PlayerScoreAccuracyChart scoreStats={leaderboardDropdownData.scoreStats} />
</div>
)}
<LeaderboardScores
initialPage={getPageFromRank(score.rank, 12)}
initialPage={scoresPage}
initialScores={leaderboardDropdownData.scores}
leaderboard={leaderboard}
disableUrlChanging
/>