mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-05 21:38:06 +03:00
feat: rough folder avgs
This commit is contained in:
@@ -4,12 +4,14 @@ import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import { useBucket } from "components/util/useBucket";
|
||||
import React, { useState } from "react";
|
||||
import { GetGamePTConfig, GetScoreEnumConfs, UserDocument } from "tachi-common";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { GPTString, GetGamePTConfig, GetScoreEnumConfs, UserDocument } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { FolderDataset } from "types/tables";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import FolderMinimap from "./FolderMinimap";
|
||||
import FolderScoreDistributionChart from "./FolderScoreDistributionChart";
|
||||
import FolderScoreAverages from "./FolderScoreAverages";
|
||||
|
||||
export default function FolderInfoHeader({
|
||||
game,
|
||||
@@ -23,53 +25,67 @@ export default function FolderInfoHeader({
|
||||
folderTitle: string;
|
||||
} & GamePT) {
|
||||
const preferredDefaultEnum = useBucket(game, playtype);
|
||||
const [enumMetric, setEnumMetric] = useState<string>(preferredDefaultEnum);
|
||||
const [breakdownInfo, setBreakdownInfo] = useState<"graph" | "switchboard">("switchboard");
|
||||
|
||||
const [currentGraph, setCurrentGraph] = useState<string>(`${preferredDefaultEnum}-stats`);
|
||||
|
||||
const enumGraphs = ["minimap", "stats"];
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[`${game}:${playtype}` as GPTString];
|
||||
|
||||
const enumConf = GetScoreEnumConfs(gptConfig);
|
||||
|
||||
const [metric, type] = useMemo(() => currentGraph.split("-"), [currentGraph]);
|
||||
|
||||
return (
|
||||
<Card header={`${reqUser.username}'s ${folderTitle} Breakdown`}>
|
||||
<div className="col-12 d-flex justify-content-center">
|
||||
<div className="btn-group">
|
||||
{Object.keys(enumConf).map((k) => (
|
||||
<SelectButton value={enumMetric} setValue={setEnumMetric} id={k}>
|
||||
<Icon type="chart-area" />
|
||||
{UppercaseFirst(k)} Info
|
||||
</SelectButton>
|
||||
))}
|
||||
{enumGraphs.flatMap((g) =>
|
||||
Object.keys(enumConf).flatMap((en) => (
|
||||
<SelectButton
|
||||
className={g === "minimap" ? "d-none d-lg-block" : ""}
|
||||
value={currentGraph}
|
||||
setValue={setCurrentGraph}
|
||||
id={`${en}-${g}`}
|
||||
>
|
||||
{/* @ts-expect-error zzz */}
|
||||
<Icon type={gptImpl.enumIcons[en]} />
|
||||
{UppercaseFirst(en)} {UppercaseFirst(g)}
|
||||
</SelectButton>
|
||||
))
|
||||
)}
|
||||
<SelectButton value={currentGraph} setValue={setCurrentGraph} id="score">
|
||||
<Icon type="sort" />
|
||||
Score Averages
|
||||
</SelectButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 mt-4 d-flex justify-content-center">
|
||||
<select
|
||||
className="form-control"
|
||||
value={breakdownInfo}
|
||||
onChange={(e) => setBreakdownInfo(e.target.value as any)}
|
||||
>
|
||||
<option value="graph">Thermometer</option>
|
||||
<option value="switchboard">Switchboard</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
{breakdownInfo === "graph" ? (
|
||||
{type === "stats" ? (
|
||||
<FolderScoreDistributionChart
|
||||
game={game}
|
||||
folderDataset={folderDataset}
|
||||
playtype={playtype}
|
||||
view={enumMetric}
|
||||
view={metric}
|
||||
/>
|
||||
) : (
|
||||
) : type === "minimap" ? (
|
||||
<FolderMinimap
|
||||
reqUser={reqUser}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
folderDataset={folderDataset}
|
||||
enumMetric={enumMetric}
|
||||
enumMetric={metric}
|
||||
/>
|
||||
) : (
|
||||
<FolderScoreAverages
|
||||
reqUser={reqUser}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
folderDataset={folderDataset}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
import React, { useMemo } from "react";
|
||||
import { GetGamePTConfig, integer } from "tachi-common";
|
||||
import { ConfDecimalScoreMetric, ConfIntegerScoreMetric } from "tachi-common/types/metrics";
|
||||
import { UGPT } from "types/react";
|
||||
import { FolderDataset } from "types/tables";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import Muted from "components/util/Muted";
|
||||
import DifficultyCell from "components/tables/cells/DifficultyCell";
|
||||
import TitleCell from "components/tables/cells/TitleCell";
|
||||
import ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells";
|
||||
import SongChartInfoFormat from "../songs/SongInfoFormat";
|
||||
|
||||
export default function FolderScoreAverages({
|
||||
folderDataset,
|
||||
game,
|
||||
playtype,
|
||||
reqUser,
|
||||
}: UGPT & { folderDataset: FolderDataset }) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const metrics = {
|
||||
...gptConfig.providedMetrics,
|
||||
...gptConfig.derivedMetrics,
|
||||
};
|
||||
|
||||
const entries = Object.entries(metrics);
|
||||
|
||||
const relevantScoreMetrics = entries
|
||||
.filter(([k, v]) => {
|
||||
if (v.type !== "DECIMAL" && v.type !== "INTEGER") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const v2 = v as ConfIntegerScoreMetric | ConfDecimalScoreMetric;
|
||||
|
||||
if (v2.chartDependentMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((e) => e[0]);
|
||||
|
||||
const { unplayed, played, data } = useMemo(() => {
|
||||
const data: Record<string, any> = {};
|
||||
|
||||
const played = folderDataset.filter((e) => e.__related.pb);
|
||||
|
||||
const unplayed = folderDataset.length - played.length;
|
||||
|
||||
for (const metric of relevantScoreMetrics) {
|
||||
const total = played.reduce(
|
||||
// @ts-expect-error accessing property unchecked oh nooo
|
||||
(acc, e) => acc + e.__related.pb?.scoreData[metric] ?? 0,
|
||||
0
|
||||
);
|
||||
|
||||
const sorted = played.sort(
|
||||
// @ts-expect-error accessing property unchecked oh nooo
|
||||
NumericSOV((k) => k.__related.pb?.scoreData[metric] ?? -Infinity)
|
||||
);
|
||||
|
||||
const worst = sorted[0];
|
||||
const best = sorted[sorted.length - 1];
|
||||
|
||||
let avgAll = total / folderDataset.length;
|
||||
let avgPlayed = total / played.length;
|
||||
|
||||
if (metrics[metric].type === "INTEGER") {
|
||||
avgAll = Math.floor(avgAll);
|
||||
avgPlayed = Math.floor(avgPlayed);
|
||||
}
|
||||
|
||||
data[metric] = {
|
||||
avgAll,
|
||||
avgPlayed,
|
||||
worst,
|
||||
best,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
played,
|
||||
unplayed,
|
||||
data,
|
||||
};
|
||||
}, [folderDataset, relevantScoreMetrics]);
|
||||
|
||||
if (relevantScoreMetrics.length === 0) {
|
||||
return <div>This game has nothing to show you!</div>;
|
||||
}
|
||||
|
||||
if (played.length === 0) {
|
||||
return <div>You haven't played anything in this folder!</div>;
|
||||
}
|
||||
|
||||
const playRate = (100 * played.length) / folderDataset.length;
|
||||
|
||||
return (
|
||||
<MiniTable className="table-responsive-md" headers={["Score Stats"]} colSpan={100}>
|
||||
<tr>
|
||||
<td>Played</td>
|
||||
<td
|
||||
colSpan={6}
|
||||
className={`text-${
|
||||
playRate === 100 ? "success" : playRate > 50 ? "warning" : "danger"
|
||||
}`}
|
||||
>
|
||||
{playRate.toFixed(2)}%
|
||||
{unplayed > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<Muted>
|
||||
({played.length} played, {unplayed} unplayed)
|
||||
</Muted>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
{Object.entries(data).map(([k, v]) => (
|
||||
<>
|
||||
<tr>
|
||||
<td>{UppercaseFirst(k)} Average (Played Charts)</td>
|
||||
{/* @ts-expect-error this won't fail */}
|
||||
<td colSpan={6}>{metrics[k].formatter(v.avgPlayed)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{UppercaseFirst(k)} Average (All Charts)</td>
|
||||
{/* @ts-expect-error this won't fail */}
|
||||
<td colSpan={6}>{metrics[k].formatter(v.avgAll)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Best {UppercaseFirst(k)}</td>
|
||||
<DifficultyCell alwaysShort chart={v.best} game={game} />
|
||||
<TitleCell
|
||||
game={game}
|
||||
song={v.best.__related.song}
|
||||
chart={v.best}
|
||||
comment={v.best.__related.pb.comment}
|
||||
/>
|
||||
<ScoreCoreCells
|
||||
game={game}
|
||||
score={v.best.__related.pb}
|
||||
short
|
||||
chart={v.best}
|
||||
/>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Worst {UppercaseFirst(k)}</td>
|
||||
<DifficultyCell alwaysShort chart={v.worst} game={game} />
|
||||
<TitleCell
|
||||
game={game}
|
||||
song={v.worst.__related.song}
|
||||
chart={v.worst}
|
||||
comment={v.worst.__related.pb.comment}
|
||||
/>
|
||||
<ScoreCoreCells
|
||||
game={game}
|
||||
score={v.worst.__related.pb}
|
||||
short
|
||||
chart={v.worst}
|
||||
/>
|
||||
</tr>
|
||||
</>
|
||||
))}
|
||||
</MiniTable>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user