mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 12:28:13 +03:00
checkpoint: insanity ascends (35)
127 errors to go
This commit is contained in:
@@ -199,8 +199,8 @@ function UserGameStatsInfo({ user }: { user: UserDocument }) {
|
||||
const gameConfig = GetGameConfig(a.game);
|
||||
|
||||
return (
|
||||
gameConfig.validPlaytypes.indexOf(a.playtype) -
|
||||
gameConfig.validPlaytypes.indexOf(b.playtype)
|
||||
gameConfig.playtypes.indexOf(a.playtype) -
|
||||
gameConfig.playtypes.indexOf(b.playtype)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,20 @@ import { SelectRightChart } from "util/misc";
|
||||
import { MillisToSince } from "util/time";
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import Card from "components/layout/page/Card";
|
||||
import LampCell from "components/tables/cells/LampCell";
|
||||
import ScoreCell from "components/tables/cells/ScoreCell";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import TargetInfo from "components/tables/dropdowns/components/TargetInfo";
|
||||
import ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells";
|
||||
import PBTable from "components/tables/pbs/PBTable";
|
||||
import ProfilePicture from "components/user/ProfilePicture";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import Loading from "components/util/Loading";
|
||||
import Muted from "components/util/Muted";
|
||||
import SelectLinkButton from "components/util/SelectLinkButton";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import useLUGPTSettings from "components/util/useLUGPTSettings";
|
||||
import { TargetsContext } from "context/TargetsContext";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import { Col, Row } from "react-bootstrap";
|
||||
@@ -23,10 +28,10 @@ import {
|
||||
FormatDifficulty,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
integer,
|
||||
PBScoreDocument,
|
||||
UserDocument,
|
||||
SongDocument,
|
||||
UserDocument,
|
||||
integer,
|
||||
} from "tachi-common";
|
||||
import {
|
||||
ChartPBLeaderboardReturn,
|
||||
@@ -36,12 +41,6 @@ import {
|
||||
} from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { PBDataset } from "types/tables";
|
||||
import useLUGPTSettings from "components/util/useLUGPTSettings";
|
||||
import TargetInfo from "components/tables/dropdowns/components/TargetInfo";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import Divider from "components/util/Divider";
|
||||
import { TargetsContext } from "context/TargetsContext";
|
||||
import SelectLinkButton from "components/util/SelectLinkButton";
|
||||
|
||||
// This component forms a wrapper around the Real GPT Chart Page
|
||||
// which handles the case where activeChart == null.
|
||||
@@ -206,7 +205,7 @@ function InternalGPTChartPage({
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
<TopShowcase data={data} user={user} userMap={userMap} />
|
||||
<TopShowcase data={data} user={user} userMap={userMap} chart={chart} />
|
||||
<Col xs={12} className="mt-4">
|
||||
<Switch>
|
||||
<Route exact path="/games/:game/:playtype/songs/:songID/:difficulty/targets">
|
||||
@@ -375,10 +374,12 @@ function TopShowcase({
|
||||
data,
|
||||
user,
|
||||
userMap,
|
||||
chart,
|
||||
}: {
|
||||
data: ChartPBData;
|
||||
user: UserDocument | null;
|
||||
userMap: Map<integer, UserDocument>;
|
||||
chart: ChartDocument;
|
||||
}) {
|
||||
// We have a couple of conditions.
|
||||
// User is #1: col-12 #1,
|
||||
@@ -392,7 +393,7 @@ function TopShowcase({
|
||||
if (user?.id === bestPlay.userID) {
|
||||
return (
|
||||
<Col xs={12}>
|
||||
<PlayCard name="Best PB" pb={bestPlay} user={bestUser} />
|
||||
<PlayCard name="Best PB" pb={bestPlay} user={bestUser} chart={chart} />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
@@ -401,7 +402,7 @@ function TopShowcase({
|
||||
return (
|
||||
<>
|
||||
<Col xs={12} lg={6}>
|
||||
<PlayCard name="Best PB" pb={bestPlay} user={bestUser} />
|
||||
<PlayCard name="Best PB" pb={bestPlay} user={bestUser} chart={chart} />
|
||||
</Col>
|
||||
<Col xs={12} lg={6}>
|
||||
<Card header="Your PB" className="text-center">
|
||||
@@ -417,16 +418,26 @@ function TopShowcase({
|
||||
return (
|
||||
<>
|
||||
<Col xs={12} lg={6}>
|
||||
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} />
|
||||
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} chart={chart} />
|
||||
</Col>
|
||||
<Col xs={12} lg={6}>
|
||||
<PlayCard name="Your PB" pb={thisUsersPlay} user={user!} />
|
||||
<PlayCard name="Your PB" pb={thisUsersPlay} user={user!} chart={chart} />
|
||||
</Col>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PlayCard({ pb, user, name }: { pb: PBScoreDocument; user: UserDocument; name: string }) {
|
||||
function PlayCard({
|
||||
pb,
|
||||
user,
|
||||
name,
|
||||
chart,
|
||||
}: {
|
||||
pb: PBScoreDocument;
|
||||
user: UserDocument;
|
||||
name: string;
|
||||
chart: ChartDocument;
|
||||
}) {
|
||||
return (
|
||||
<Card header={name}>
|
||||
<Row className="align-items-center">
|
||||
@@ -447,12 +458,7 @@ function PlayCard({ pb, user, name }: { pb: PBScoreDocument; user: UserDocument;
|
||||
</Col>
|
||||
<Col lg={6}>
|
||||
<MiniTable headers={["PB Info"]}>
|
||||
<tr>
|
||||
<ScoreCell score={pb} />
|
||||
</tr>
|
||||
<tr>
|
||||
<LampCell score={pb} />
|
||||
</tr>
|
||||
<ScoreCoreCells game={pb.game} chart={chart} score={pb} />
|
||||
</MiniTable>
|
||||
<div className="text-center">
|
||||
<Muted>
|
||||
|
||||
@@ -4,8 +4,7 @@ import Card from "components/layout/page/Card";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import React from "react";
|
||||
import { FormatGame, GetGameConfig, GetGamePTConfig, IDStrings, gameClasses } from "tachi-common";
|
||||
import { GameClassSets } from "tachi-common/game-classes";
|
||||
import { Classes, FormatGame, GetGameConfig, GetGamePTConfig, GPTString } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
|
||||
export default function GPTDevInfo({ game, playtype }: GamePT) {
|
||||
@@ -28,16 +27,16 @@ export default function GPTDevInfo({ game, playtype }: GamePT) {
|
||||
</Card>
|
||||
<Card className="mt-4" header="Class Badges">
|
||||
<div className="d-flex w-100 justify-content-center" style={{ gap: "30px" }}>
|
||||
{Object.entries(gptConfig.classHumanisedFormat).map(([classSet, value]) => (
|
||||
{Object.entries(gptConfig.classes).map(([classSet, conf]) => (
|
||||
<div>
|
||||
<MiniTable headers={[classSet]} colSpan={2}>
|
||||
{value.map((e, i) => (
|
||||
{conf.values.map((e) => (
|
||||
<tr key={e.id}>
|
||||
<td>{e.id}</td>
|
||||
<td>
|
||||
<ClassBadge
|
||||
classSet={classSet as GameClassSets[IDStrings]}
|
||||
classValue={i}
|
||||
classSet={classSet as Classes[GPTString]}
|
||||
classValue={e.id}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
/>
|
||||
|
||||
@@ -17,7 +17,7 @@ import SelectButton from "components/util/SelectButton";
|
||||
import { useProfileRatingAlg } from "components/util/useScoreRatingAlg";
|
||||
import React, { useState } from "react";
|
||||
import { Col, Form, Row } from "react-bootstrap";
|
||||
import { FormatGame, GetGameConfig, GetGamePTConfig } from "tachi-common";
|
||||
import { AnyProfileRatingAlg, FormatGame, GetGameConfig, GetGamePTConfig } from "tachi-common";
|
||||
import { UserLeaderboardReturns } from "types/api-returns";
|
||||
import { GamePT } from "types/react";
|
||||
import { UGSDataset } from "types/tables";
|
||||
@@ -69,9 +69,9 @@ function ProfileLeaderboard({ game, playtype }: GamePT) {
|
||||
const [alg, setAlg] = useState(defaultAlg);
|
||||
|
||||
const SelectComponent =
|
||||
gptConfig.profileRatingAlgs.length > 1 ? (
|
||||
Object.keys(gptConfig.profileRatingAlgs).length > 1 ? (
|
||||
<Form.Control as="select" value={alg} onChange={(e) => setAlg(e.target.value as any)}>
|
||||
{gptConfig.profileRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.profileRatingAlgs).map((e) => (
|
||||
<option key={e} value={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
@@ -125,7 +125,7 @@ function ProfileLeaderboard({ game, playtype }: GamePT) {
|
||||
headers={[
|
||||
["Ranking", "Rank", NumericSOV((x) => x.__related.index)],
|
||||
["User", "User", StrSOV((x) => x.__related.user.username)],
|
||||
...gptConfig.profileRatingAlgs.map(
|
||||
...(Object.keys(gptConfig.profileRatingAlgs) as Array<AnyProfileRatingAlg>).map(
|
||||
(e) =>
|
||||
[
|
||||
UppercaseFirst(e),
|
||||
@@ -139,11 +139,13 @@ function ProfileLeaderboard({ game, playtype }: GamePT) {
|
||||
<tr>
|
||||
<IndexCell index={r.__related.index} />
|
||||
<UserCell game={game} playtype={playtype} user={r.__related.user} />
|
||||
{gptConfig.profileRatingAlgs.map((e) => (
|
||||
{(
|
||||
Object.keys(gptConfig.profileRatingAlgs) as Array<AnyProfileRatingAlg>
|
||||
).map((e) => (
|
||||
<td key={e}>
|
||||
{r.ratings[e]
|
||||
? gptConfig.profileRatingAlgFormatters[e]
|
||||
? gptConfig.profileRatingAlgFormatters[e]!(r.ratings[e]!)
|
||||
? gptConfig.profileRatingAlgs[e].formatter
|
||||
? gptConfig.profileRatingAlgs[e].formatter!(r.ratings[e]!)
|
||||
: r.ratings[e]!.toFixed(2)
|
||||
: "No Data."}
|
||||
</td>
|
||||
@@ -152,15 +154,18 @@ function ProfileLeaderboard({ game, playtype }: GamePT) {
|
||||
{Object.keys(r.classes).length === 0 ? (
|
||||
<Muted>None</Muted>
|
||||
) : (
|
||||
Object.entries(r.classes).map(([k, v]) => (
|
||||
<ClassBadge
|
||||
key={k}
|
||||
classSet={k as keyof typeof r.classes}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
classValue={v}
|
||||
/>
|
||||
))
|
||||
Object.entries(r.classes).map(
|
||||
([k, v]) =>
|
||||
v && (
|
||||
<ClassBadge
|
||||
key={k}
|
||||
classSet={k as keyof typeof r.classes}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
classValue={v}
|
||||
/>
|
||||
)
|
||||
)
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
SongDocument,
|
||||
} from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { CascadingRatingValue } from "components/tables/headers/ChartHeader";
|
||||
|
||||
export default function GPTSongsPage({ game, playtype }: GamePT) {
|
||||
useSetSubheader(
|
||||
@@ -90,19 +91,7 @@ function SearchSongsTable({ game, playtype, search }: { search: string } & GameP
|
||||
entryName="Charts"
|
||||
dataset={dataset}
|
||||
headers={[
|
||||
[
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV((chart) => {
|
||||
for (const tierlist of gptConfig.tierlists) {
|
||||
if (chart.tierlistInfo[tierlist]) {
|
||||
return chart.tierlistInfo[tierlist]!.value;
|
||||
}
|
||||
}
|
||||
|
||||
return chart.levelNum;
|
||||
}),
|
||||
],
|
||||
["Chart", "Chart", (a, b) => CascadingRatingValue(game, a, b)],
|
||||
["Song Title", "Song", StrSOV((x) => x.__related.song.title)],
|
||||
["Playcount", "Playcount", NumericSOV((x) => x.__playcount)],
|
||||
]}
|
||||
|
||||
@@ -16,10 +16,10 @@ import {
|
||||
FormatGame,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
IDStrings,
|
||||
GPTString,
|
||||
integer,
|
||||
UserDocument,
|
||||
ProfileRatingLookup,
|
||||
ProfileRatingAlgorithms,
|
||||
UserGameStats,
|
||||
} from "tachi-common";
|
||||
import { GameClassSets } from "tachi-common/game-classes";
|
||||
@@ -84,8 +84,8 @@ function LeaderboardsPageContent({
|
||||
}: {
|
||||
reqUser: UserDocument;
|
||||
data: LeaderboardsData;
|
||||
alg: ProfileRatingLookup[IDStrings];
|
||||
setAlg: SetState<ProfileRatingLookup[IDStrings]>;
|
||||
alg: ProfileRatingAlgorithms[GPTString];
|
||||
setAlg: SetState<ProfileRatingAlgorithms[GPTString]>;
|
||||
} & GamePT) {
|
||||
const { stats, leaderboard } = data;
|
||||
|
||||
@@ -142,7 +142,7 @@ function LeaderboardsPageContent({
|
||||
<ClassBadge
|
||||
key={`${k}:${v}`}
|
||||
classValue={v}
|
||||
classSet={k as GameClassSets[IDStrings]}
|
||||
classSet={k as GameClassSets[GPTString]}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
/>
|
||||
|
||||
@@ -167,7 +167,7 @@ function UserHistory({
|
||||
setRating(e.target.value as keyof UserGameStats["ratings"])
|
||||
}
|
||||
>
|
||||
{gptConfig.profileRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.profileRatingAlgs).map((e) => (
|
||||
<option key={e} value={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
@@ -219,7 +219,7 @@ function UserHistory({
|
||||
setRating(e.target.value as keyof UserGameStats["ratings"])
|
||||
}
|
||||
>
|
||||
{gptConfig.profileRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.profileRatingAlgs).map((e) => (
|
||||
<option key={e} value={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
GamePTConfig,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
IDStrings,
|
||||
GPTString,
|
||||
PBScoreDocument,
|
||||
UserDocument,
|
||||
ScoreCalculatedDataLookup,
|
||||
ScoreRatingAlgorithms,
|
||||
ScoreDocument,
|
||||
SongDocument,
|
||||
UnsuccessfulAPIResponse,
|
||||
@@ -77,7 +77,7 @@ export default function ScoresPage({
|
||||
<Switch>
|
||||
<Route exact path="/u/:userID/games/:game/:playtype/scores">
|
||||
<>
|
||||
{gptConfig.scoreRatingAlgs.length > 1 && (
|
||||
{Object.keys(gptConfig.scoreRatingAlgs).length > 1 && (
|
||||
<AlgSelector {...{ alg, setAlg, gptConfig }} />
|
||||
)}
|
||||
<PBsOverview
|
||||
@@ -120,8 +120,8 @@ function AlgSelector({
|
||||
setAlg,
|
||||
}: {
|
||||
gptConfig: GamePTConfig;
|
||||
alg: ScoreCalculatedDataLookup[IDStrings];
|
||||
setAlg: SetState<ScoreCalculatedDataLookup[IDStrings]>;
|
||||
alg: ScoreRatingAlgorithms[GPTString];
|
||||
setAlg: SetState<ScoreRatingAlgorithms[GPTString]>;
|
||||
}) {
|
||||
return (
|
||||
<div className="row justify-content-center mb-4">
|
||||
@@ -132,7 +132,7 @@ function AlgSelector({
|
||||
value={alg}
|
||||
onChange={(e) => setAlg(e.target.value as any)}
|
||||
>
|
||||
{gptConfig.scoreRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.scoreRatingAlgs).map((e) => (
|
||||
<option key={e}>{e}</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -172,7 +172,7 @@ function PBsOverview({
|
||||
url: string;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
alg?: ScoreCalculatedDataLookup[IDStrings];
|
||||
alg?: ScoreRatingAlgorithms[GPTString];
|
||||
} & GamePT) {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
@@ -212,9 +212,9 @@ function PBsOverview({
|
||||
|
||||
function FormatData<
|
||||
D extends PBScoreDocument | ScoreDocument,
|
||||
I extends IDStrings = IDStrings,
|
||||
GPT extends GPTString = GPTString,
|
||||
G extends Game = Game
|
||||
>(d: D[], songs: SongDocument<G>[], charts: ChartDocument<I>[], reqUser: UserDocument) {
|
||||
>(d: D[], songs: SongDocument<G>[], charts: ChartDocument<GPT>[], reqUser: UserDocument) {
|
||||
const songMap = new Map();
|
||||
const chartMap = new Map();
|
||||
|
||||
@@ -266,7 +266,7 @@ function PBsSearch({
|
||||
}: {
|
||||
reqUser: UserDocument;
|
||||
search: string;
|
||||
alg?: ScoreCalculatedDataLookup[IDStrings];
|
||||
alg?: ScoreRatingAlgorithms[GPTString];
|
||||
} & GamePT) {
|
||||
const { data, error } = useFetchPBs(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs?search=${search}`,
|
||||
|
||||
@@ -135,7 +135,7 @@ function SearchSessionsTable({
|
||||
__related: {
|
||||
index: i,
|
||||
},
|
||||
}));
|
||||
})) as SessionDataset;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
GetGamePTConfig,
|
||||
ShowcaseStatDetails,
|
||||
TableDocument,
|
||||
UGPTSettings,
|
||||
UGPTSettingsDocument,
|
||||
UserDocument,
|
||||
} from "tachi-common";
|
||||
import { SetState, UGPT } from "types/react";
|
||||
@@ -109,7 +109,8 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
gameSpecific: settings!.preferences.gameSpecific as any,
|
||||
defaultTable: settings!.preferences.defaultTable,
|
||||
scoreBucket: settings!.preferences.scoreBucket ?? gptConfig.scoreBucket,
|
||||
preferredDefaultEnum:
|
||||
settings!.preferences.preferredDefaultEnum ?? gptConfig.preferredDefaultEnum,
|
||||
preferredRanking: settings!.preferences.preferredRanking ?? "global",
|
||||
},
|
||||
onSubmit: async (values) => {
|
||||
@@ -129,7 +130,7 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
if (rj.success) {
|
||||
setLoggedInData({
|
||||
...loggedInData,
|
||||
settings: deepmerge(settings as UGPTSettings, { preferences: values }),
|
||||
settings: deepmerge(settings as UGPTSettingsDocument, { preferences: values }),
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -153,7 +154,7 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
|
||||
return (
|
||||
<Form onSubmit={formik.handleSubmit}>
|
||||
{gptConfig.scoreRatingAlgs.length > 1 && (
|
||||
{Object.keys(gptConfig.scoreRatingAlgs).length > 1 && (
|
||||
<Form.Group>
|
||||
<Form.Label>Preferred Score Algorithm</Form.Label>
|
||||
<Form.Control
|
||||
@@ -162,7 +163,7 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
value={formik.values.preferredScoreAlg}
|
||||
onChange={formik.handleChange}
|
||||
>
|
||||
{gptConfig.scoreRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.scoreRatingAlgs).map((e) => (
|
||||
<option key={e}>{e}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
@@ -172,7 +173,7 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
)}
|
||||
{gptConfig.sessionRatingAlgs.length > 1 && (
|
||||
{Object.keys(gptConfig.sessionRatingAlgs).length > 1 && (
|
||||
<Form.Group>
|
||||
<Form.Label>Preferred Session Algorithm</Form.Label>
|
||||
<Form.Control
|
||||
@@ -181,7 +182,7 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
value={formik.values.preferredSessionAlg}
|
||||
onChange={formik.handleChange}
|
||||
>
|
||||
{gptConfig.sessionRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.sessionRatingAlgs).map((e) => (
|
||||
<option key={e}>{e}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
@@ -191,7 +192,7 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
)}
|
||||
{gptConfig.profileRatingAlgs.length > 1 && (
|
||||
{Object.keys(gptConfig.profileRatingAlgs).length > 1 && (
|
||||
<Form.Group>
|
||||
<Form.Label>Preferred Profile Algorithm</Form.Label>
|
||||
<Form.Control
|
||||
@@ -200,7 +201,7 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
value={formik.values.preferredProfileAlg}
|
||||
onChange={formik.handleChange}
|
||||
>
|
||||
{gptConfig.profileRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.profileRatingAlgs).map((e) => (
|
||||
<option key={e}>{e}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
@@ -214,8 +215,8 @@ function PreferencesForm({ reqUser, game, playtype }: UGPT) {
|
||||
<Form.Label>Preferred Folder Info</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
id="scoreBucket"
|
||||
value={formik.values.scoreBucket}
|
||||
id="preferredDefaultEnum"
|
||||
value={formik.values.preferredDefaultEnum}
|
||||
onChange={formik.handleChange}
|
||||
>
|
||||
<option value="grade">Grades</option>
|
||||
@@ -408,7 +409,7 @@ function ShowcaseForm({ reqUser, game, playtype }: UGPT) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const SaveChanges = async () => {
|
||||
const r = await APIFetchV1<UGPTSettings>(
|
||||
const r = await APIFetchV1<UGPTSettingsDocument>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/showcase`,
|
||||
{
|
||||
method: "PUT",
|
||||
|
||||
-15
@@ -125,8 +125,6 @@ function FolderCompare({
|
||||
withUser: UserDocument;
|
||||
folder: FolderDocument;
|
||||
} & GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const { data: baseData, error: baseError } = useApiQuery<UGPTFolderReturns>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/folders/${folder.folderID}`
|
||||
);
|
||||
@@ -136,11 +134,6 @@ function FolderCompare({
|
||||
);
|
||||
|
||||
const [shouldIncludeNotPlayed, setShouldIncludeNotPlayed] = useState(false);
|
||||
const [shouldESD, setShouldESD] = useState(gptConfig.supportsESD);
|
||||
|
||||
useEffect(() => {
|
||||
setShouldESD(gptConfig.supportsESD);
|
||||
}, [gptConfig]);
|
||||
|
||||
const dataset = useMemo(() => {
|
||||
// i *LOVE* the rules of hooks! they're so convenient!
|
||||
@@ -197,13 +190,6 @@ function FolderCompare({
|
||||
onChange={() => setShouldIncludeNotPlayed(!shouldIncludeNotPlayed)}
|
||||
label="Include charts without plays?"
|
||||
/>
|
||||
{gptConfig.supportsESD && (
|
||||
<Form.Check
|
||||
checked={shouldESD}
|
||||
onChange={() => setShouldESD(!shouldESD)}
|
||||
label="Sort score differences intelligently? (i.e. Treat the difference between 70% and 71% as smaller than the difference between 99% and 100%)"
|
||||
/>
|
||||
)}
|
||||
<Divider />
|
||||
</Col>
|
||||
<ComparePBsTable
|
||||
@@ -211,7 +197,6 @@ function FolderCompare({
|
||||
compareUser={withUser.username}
|
||||
dataset={dataset}
|
||||
game={game}
|
||||
shouldESD={shouldESD}
|
||||
playtype={playtype}
|
||||
/>
|
||||
</Row>
|
||||
|
||||
+2
-2
@@ -83,9 +83,9 @@ export function FolderInfoComponent({
|
||||
}: UGPT & { folder: FolderDocument; folderStats: FolderStatsInfo }) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const scoreBucket = useBucket(game, playtype);
|
||||
const preferredDefaultEnum = useBucket(game, playtype);
|
||||
|
||||
const [elements, setElements] = useState<"grade" | "lamp">(scoreBucket);
|
||||
const [elements, setElements] = useState<"grade" | "lamp">(preferredDefaultEnum);
|
||||
|
||||
const base = useUGPTBase({ reqUser, game, playtype });
|
||||
|
||||
|
||||
+34
-489
@@ -1,47 +1,35 @@
|
||||
import { ONE_DAY } from "util/constants/time";
|
||||
import { CreateChartIDMap, CreateChartLink, CreateSongMap } from "util/data";
|
||||
import { DistinctArr, NO_OP } from "util/misc";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { GetScaleAchievedFn } from "util/tierlist";
|
||||
import { FormatDate, FormatTime } from "util/time";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import FolderInfoHeader from "components/game/folder/FolderInfoHeader";
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
import Card from "components/layout/page/Card";
|
||||
import FolderTable from "components/tables/folders/FolderTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import Loading from "components/util/Loading";
|
||||
import Muted from "components/util/Muted";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import ReferToUser from "components/util/ReferToUser";
|
||||
import SelectLinkButton from "components/util/SelectLinkButton";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import useUGPTBase from "components/util/useUGPTBase";
|
||||
import { useFormik } from "formik";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Col, Form, Row } from "react-bootstrap";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { Link, Route, Switch, useParams } from "react-router-dom";
|
||||
import {
|
||||
ChartDocument,
|
||||
ChartTierlistInfo,
|
||||
FormatDifficulty,
|
||||
FormatDifficultyShort,
|
||||
Game,
|
||||
GetGamePTConfig,
|
||||
GPTTierlists,
|
||||
IDStrings,
|
||||
integer,
|
||||
UserDocument,
|
||||
GetScoreEnumConfs,
|
||||
Playtype,
|
||||
ScoreDocument,
|
||||
SongDocument,
|
||||
Playtype,
|
||||
COLOUR_SET,
|
||||
UserDocument,
|
||||
integer,
|
||||
} from "tachi-common";
|
||||
import { UGPTFolderReturns } from "types/api-returns";
|
||||
import { FolderDataset } from "types/tables";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import DifficultyCell from "components/tables/cells/DifficultyCell";
|
||||
import { ConfEnumScoreMetric } from "tachi-common/types/metrics";
|
||||
import FolderComparePage from "./FolderComparePage";
|
||||
import FolderQuestsPage from "./FolderQuestsPage";
|
||||
|
||||
@@ -58,8 +46,6 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/folders/${folderID}`
|
||||
);
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const folderDataset = useMemo(() => {
|
||||
if (!data) {
|
||||
return null;
|
||||
@@ -124,15 +110,6 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
<Icon type="table" />
|
||||
Normal View
|
||||
</SelectLinkButton>
|
||||
{gptConfig.tierlists.length !== 0 &&
|
||||
// temp: tierlist view sucks for BMS and PMS
|
||||
game !== "bms" &&
|
||||
game !== "pms" && (
|
||||
<SelectLinkButton to={`${base}/tierlist`}>
|
||||
<Icon type="sort-alpha-up" />
|
||||
Tierlist View
|
||||
</SelectLinkButton>
|
||||
)}
|
||||
<SelectLinkButton to={`${base}/timeline`}>
|
||||
<Icon type="stream" />
|
||||
Timeline View
|
||||
@@ -155,15 +132,6 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
<Route exact path={base}>
|
||||
<FolderTable dataset={folderDataset} game={game} playtype={playtype} />
|
||||
</Route>
|
||||
<Route exact path={`${base}/tierlist`}>
|
||||
<TierlistBreakdown
|
||||
folderDataset={folderDataset}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
reqUser={reqUser}
|
||||
data={data}
|
||||
/>
|
||||
</Route>
|
||||
<Route exact path={`${base}/timeline`}>
|
||||
<TimelineView
|
||||
game={game}
|
||||
@@ -196,20 +164,17 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
|
||||
function TimelineView({ game, playtype, reqUser, folderID }: Props & { folderID: string }) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const [type, setType] = useState<"lamp" | "grade">(gptConfig.scoreBucket);
|
||||
const [value, setValue] = useState<integer>(
|
||||
type === "grade"
|
||||
? gptConfig.grades.indexOf(gptConfig.clearGrade)
|
||||
: gptConfig.lamps.indexOf(gptConfig.clearLamp)
|
||||
);
|
||||
const enumConfs = GetScoreEnumConfs(gptConfig);
|
||||
|
||||
const [selectedEnum, setSelectedEnum] = useState<string>(gptConfig.preferredDefaultEnum);
|
||||
const [enumConf, setEnumConf] = useState<ConfEnumScoreMetric<string>>(enumConfs[selectedEnum]!);
|
||||
|
||||
const [value, setValue] = useState<string>(enumConf.minimumRelevantValue);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(
|
||||
type === "grade"
|
||||
? gptConfig.grades.indexOf(gptConfig.clearGrade)
|
||||
: gptConfig.lamps.indexOf(gptConfig.clearLamp)
|
||||
);
|
||||
}, [type]);
|
||||
setValue(enumConfs[selectedEnum]!.minimumRelevantValue);
|
||||
setEnumConf(enumConfs[selectedEnum]!);
|
||||
}, [selectedEnum]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -225,36 +190,33 @@ function TimelineView({ game, playtype, reqUser, folderID }: Props & { folderID:
|
||||
<div className="col-12 col-lg-6">
|
||||
<Form.Control
|
||||
as="select"
|
||||
value={type}
|
||||
onChange={(e) => setType(e.target.value as "grade" | "lamp")}
|
||||
value={selectedEnum}
|
||||
onChange={(e) => setSelectedEnum(e.target.value)}
|
||||
>
|
||||
<option value="grade">Grades</option>
|
||||
<option value="lamp">Lamps</option>
|
||||
{Object.keys(enumConfs).map((e) => (
|
||||
<option key={e}>{e}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</div>
|
||||
<div className="col-12 col-lg-6">
|
||||
<Form.Control
|
||||
as="select"
|
||||
value={value}
|
||||
onChange={(e) => setValue(Number(e.target.value))}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
>
|
||||
{type === "grade"
|
||||
? gptConfig.grades.map((e, i) => (
|
||||
<option key={e} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))
|
||||
: gptConfig.lamps.map((e, i) => (
|
||||
<option key={e} value={i}>
|
||||
{e}
|
||||
</option>
|
||||
))}
|
||||
{enumConf.values
|
||||
.slice(enumConf.values.indexOf(enumConf.minimumRelevantValue))
|
||||
.map((e) => (
|
||||
<option key={e}>{e}</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Divider />
|
||||
<TimelineMain {...{ reqUser, game, playtype, folderID, type, value }} />
|
||||
<TimelineMain
|
||||
{...{ reqUser, game, playtype, folderID, enumMetric: selectedEnum, value }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -264,19 +226,19 @@ function TimelineMain({
|
||||
game,
|
||||
playtype,
|
||||
folderID,
|
||||
type,
|
||||
enumMetric: enumMetric,
|
||||
value,
|
||||
}: Props & {
|
||||
folderID: string;
|
||||
type: "grade" | "lamp";
|
||||
value: integer;
|
||||
enumMetric: string;
|
||||
value: string;
|
||||
}) {
|
||||
const { data, error } = useApiQuery<{
|
||||
scores: ScoreDocument[];
|
||||
songs: SongDocument[];
|
||||
charts: ChartDocument[];
|
||||
}>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/folders/${folderID}/timeline?criteriaValue=${value}&criteriaType=${type}`
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/folders/${folderID}/timeline?criteriaValue=${value}&criteriaType=${enumMetric}`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -411,420 +373,3 @@ function TimelineElement({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type InfoProps = Props & {
|
||||
folderDataset: FolderDataset;
|
||||
data: UGPTFolderReturns;
|
||||
};
|
||||
|
||||
function TierlistBreakdown({ game, folderDataset, playtype, reqUser }: InfoProps) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
__hideAchieved: false,
|
||||
...Object.fromEntries(gptConfig.tierlists.map((e) => [e, true])),
|
||||
} as { __hideAchieved: boolean } & Partial<Record<GPTTierlists[IDStrings], true>>,
|
||||
onSubmit: NO_OP,
|
||||
});
|
||||
|
||||
const tierlistInfo = useMemo(
|
||||
() => FolderDatasetToTierlistInfo(folderDataset, game, playtype, formik.values),
|
||||
[formik.values]
|
||||
);
|
||||
|
||||
const dataMap = CreateChartIDMap(folderDataset);
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<Card header="Tierlist View Configuration">
|
||||
<span>Here you can select what tierlist blocks to show!</span>
|
||||
<Divider />
|
||||
{gptConfig.tierlists.length > 1 &&
|
||||
gptConfig.tierlists.map((e) => (
|
||||
<>
|
||||
<Form.Check
|
||||
key={e}
|
||||
type="checkbox"
|
||||
id={e}
|
||||
checked={formik.values[e]}
|
||||
onChange={formik.handleChange}
|
||||
label={e}
|
||||
/>
|
||||
<Form.Text>{gptConfig.tierlistDescriptions[e]}</Form.Text>
|
||||
</>
|
||||
))}
|
||||
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
id="__hideAchieved"
|
||||
checked={formik.values.__hideAchieved}
|
||||
onChange={formik.handleChange}
|
||||
label="Hide Achieved"
|
||||
/>
|
||||
<Form.Text>Hide achieved elements of the tierlist.</Form.Text>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<TierlistInfoLadder
|
||||
tierlistInfo={tierlistInfo}
|
||||
dataMap={dataMap}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
reqUser={reqUser}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
const NO_TIERLIST_DATA_VALUE = {
|
||||
text: "No Tierlist Info",
|
||||
value: 0,
|
||||
};
|
||||
|
||||
function TierlistInfoLadder({
|
||||
tierlistInfo,
|
||||
dataMap,
|
||||
game,
|
||||
playtype,
|
||||
reqUser,
|
||||
}: {
|
||||
tierlistInfo: NullableTierlistInfo[];
|
||||
dataMap: Map<string, FolderDataset[0]>;
|
||||
game: Game;
|
||||
playtype: Playtype;
|
||||
reqUser: UserDocument;
|
||||
}) {
|
||||
const buckets: TierlistInfo[][] = useMemo(() => {
|
||||
const buckets: TierlistInfo[][] = [];
|
||||
let currentBucket: TierlistInfo[] = [];
|
||||
const noDataBucket: TierlistInfo[] = [];
|
||||
|
||||
let lastValue;
|
||||
for (const tl of tierlistInfo) {
|
||||
if (tl.data === null) {
|
||||
noDataBucket.push({
|
||||
achieved: tl.achieved,
|
||||
chartID: tl.chartID,
|
||||
data: NO_TIERLIST_DATA_VALUE,
|
||||
key: tl.key,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// no longer nullable
|
||||
const tlx = tl as TierlistInfo;
|
||||
|
||||
if (lastValue && tlx.data.value !== lastValue) {
|
||||
buckets.push(currentBucket);
|
||||
currentBucket = [tlx];
|
||||
} else {
|
||||
currentBucket.push(tlx);
|
||||
}
|
||||
lastValue = tlx.data.value;
|
||||
}
|
||||
|
||||
if (currentBucket.length) {
|
||||
buckets.push(currentBucket);
|
||||
}
|
||||
|
||||
buckets.push(noDataBucket);
|
||||
|
||||
return buckets;
|
||||
}, [tierlistInfo]);
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
for (const bucket of buckets) {
|
||||
bucket.sort(NumericSOV((x) => gptConfig.tierlists.indexOf(x.key), true));
|
||||
}
|
||||
|
||||
if (tierlistInfo.length === 0) {
|
||||
return <Row className="justify-content-center">Got no tierlist data to show you!</Row>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Row className="text-center">
|
||||
{buckets
|
||||
.filter((e) => e.length > 0)
|
||||
.map((bucket, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<Col className="ladder-header" xs={12}>
|
||||
{bucket[0].data!.value} (
|
||||
{DistinctArr(
|
||||
bucket.map((e) =>
|
||||
gptConfig.tierlists.length === 1
|
||||
? e.data.text
|
||||
: `${e.key}: ${e.data.text}`
|
||||
)
|
||||
).join(", ")}
|
||||
)
|
||||
</Col>
|
||||
|
||||
<TierlistBucket {...{ bucket, dataMap, game, playtype, reqUser }} />
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function TierlistBucket({
|
||||
bucket,
|
||||
dataMap,
|
||||
game,
|
||||
reqUser,
|
||||
}: {
|
||||
dataMap: Map<string, FolderDataset[0]>;
|
||||
game: Game;
|
||||
playtype: Playtype;
|
||||
reqUser: UserDocument;
|
||||
bucket: TierlistInfo[];
|
||||
}) {
|
||||
// xs view is tabular
|
||||
if (window.screen.width <= 576) {
|
||||
return (
|
||||
<MiniTable>
|
||||
{bucket.map((tierlistInfo, i) => (
|
||||
<TierlistInfoBucketValues
|
||||
tierlistInfo={tierlistInfo}
|
||||
key={`${tierlistInfo.chartID}-${tierlistInfo.key}`}
|
||||
game={game}
|
||||
dataMap={dataMap}
|
||||
bucket={bucket}
|
||||
i={i}
|
||||
reqUser={reqUser}
|
||||
/>
|
||||
))}
|
||||
</MiniTable>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{bucket.map((tierlistInfo, i) => (
|
||||
<TierlistInfoBucketValues
|
||||
tierlistInfo={tierlistInfo}
|
||||
key={`${tierlistInfo.chartID}-${tierlistInfo.key}`}
|
||||
game={game}
|
||||
dataMap={dataMap}
|
||||
bucket={bucket}
|
||||
i={i}
|
||||
reqUser={reqUser}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TierlistInfoBucketValues({
|
||||
tierlistInfo,
|
||||
game,
|
||||
dataMap,
|
||||
bucket,
|
||||
i,
|
||||
reqUser,
|
||||
}: {
|
||||
tierlistInfo: TierlistInfo;
|
||||
bucket: TierlistInfo[];
|
||||
dataMap: Map<string, FolderDataset[0]>;
|
||||
game: Game;
|
||||
i: integer;
|
||||
reqUser: UserDocument;
|
||||
}) {
|
||||
const data = dataMap.get(tierlistInfo.chartID)!;
|
||||
|
||||
const lastKey = bucket[i - 1];
|
||||
|
||||
let statusClass;
|
||||
|
||||
switch (tierlistInfo.achieved) {
|
||||
case AchievedStatuses.ACHIEVED:
|
||||
statusClass = "achieved";
|
||||
break;
|
||||
case AchievedStatuses.FAILED:
|
||||
statusClass = "unachieved";
|
||||
break;
|
||||
case AchievedStatuses.NOT_PLAYED:
|
||||
case AchievedStatuses.SCORE_BASED:
|
||||
statusClass = "";
|
||||
}
|
||||
|
||||
// xs view
|
||||
if (window.screen.width <= 576) {
|
||||
return (
|
||||
<tr>
|
||||
<DifficultyCell game={game} chart={data} alwaysShort noTierlist />
|
||||
<td className="text-left">
|
||||
<Link className="gentle-link" to={CreateChartLink(data, game)}>
|
||||
{data.__related.song.title}
|
||||
</Link>{" "}
|
||||
<br />
|
||||
<div>
|
||||
{tierlistInfo.key} ({tierlistInfo.data.text})
|
||||
{tierlistInfo.data.individualDifference && (
|
||||
<span className="ml-1">
|
||||
<Icon type="balance-scale-left" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<TierlistInfoCell tierlistInfo={tierlistInfo} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{lastKey && lastKey.key !== tierlistInfo.key && <Col xl={12} className="my-2" />}
|
||||
<Col
|
||||
className={`ladder-element ${
|
||||
i % 12 < 6 ? "ladder-element-dark" : ""
|
||||
} ladder-element-${statusClass} d-none d-sm-block`}
|
||||
xs={12}
|
||||
sm={6}
|
||||
md={4}
|
||||
lg={3}
|
||||
xl={2}
|
||||
>
|
||||
<Link className="gentle-link" to={CreateChartLink(data, game)}>
|
||||
{data.__related.song.title}
|
||||
</Link>{" "}
|
||||
{FormatDifficultyShort(data, game)}
|
||||
<Divider className="my-2" />
|
||||
{tierlistInfo.key} ({tierlistInfo.data.text})
|
||||
{tierlistInfo.data.individualDifference && (
|
||||
<>
|
||||
<br />
|
||||
|
||||
<div className="mt-1">
|
||||
<QuickTooltip tooltipContent="Individual Difference - The difficulty of this varies massively between people!">
|
||||
<span>
|
||||
<Icon type="balance-scale-left" />
|
||||
</span>
|
||||
</QuickTooltip>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Divider className="my-2" />
|
||||
<Muted>
|
||||
<ReferToUser reqUser={reqUser} />{" "}
|
||||
{tierlistInfo.achieved === AchievedStatuses.NOT_PLAYED
|
||||
? "Not Played"
|
||||
: tierlistInfo.achieved === AchievedStatuses.SCORE_BASED
|
||||
? data.__related.pb!.scoreData.grade
|
||||
: data.__related.pb!.scoreData.lamp}
|
||||
</Muted>
|
||||
</Col>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TierlistInfoCell({ tierlistInfo }: { tierlistInfo: TierlistInfo }) {
|
||||
let colour;
|
||||
let text;
|
||||
|
||||
if (
|
||||
tierlistInfo.achieved === AchievedStatuses.NOT_PLAYED ||
|
||||
tierlistInfo.achieved === AchievedStatuses.FAILED
|
||||
) {
|
||||
colour = COLOUR_SET.red;
|
||||
text = "✗";
|
||||
} else {
|
||||
colour = COLOUR_SET.green;
|
||||
text = "✓";
|
||||
}
|
||||
|
||||
return (
|
||||
<td
|
||||
style={{
|
||||
backgroundColor: ChangeOpacity(colour, 0.2),
|
||||
width: "60px",
|
||||
minWidth: "60px",
|
||||
maxWidth: "60px",
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
interface NullableTierlistInfo {
|
||||
chartID: string;
|
||||
key: GPTTierlists[IDStrings];
|
||||
data: ChartTierlistInfo | null;
|
||||
achieved: AchievedStatuses;
|
||||
}
|
||||
|
||||
interface TierlistInfo {
|
||||
chartID: string;
|
||||
key: GPTTierlists[IDStrings];
|
||||
data: ChartTierlistInfo;
|
||||
achieved: AchievedStatuses;
|
||||
}
|
||||
|
||||
enum AchievedStatuses {
|
||||
NOT_PLAYED,
|
||||
FAILED,
|
||||
ACHIEVED,
|
||||
SCORE_BASED,
|
||||
}
|
||||
|
||||
function FolderDatasetToTierlistInfo(
|
||||
folderDataset: FolderDataset,
|
||||
game: Game,
|
||||
playtype: Playtype,
|
||||
options: Partial<Record<GPTTierlists[IDStrings], boolean>> & { __hideAchieved: boolean }
|
||||
) {
|
||||
const tierlistInfo: NullableTierlistInfo[] = [];
|
||||
|
||||
const tierlistKeys: GPTTierlists[IDStrings][] = [];
|
||||
|
||||
for (const k in options) {
|
||||
if (k === "__hideAchieved") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const key = k as GPTTierlists[IDStrings];
|
||||
if (options[key]) {
|
||||
tierlistKeys.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const data of folderDataset) {
|
||||
for (const key of tierlistKeys) {
|
||||
let achieved: AchievedStatuses;
|
||||
|
||||
if (!data.__related.pb) {
|
||||
achieved = AchievedStatuses.NOT_PLAYED;
|
||||
} else {
|
||||
const fn = GetScaleAchievedFn(game, playtype, key);
|
||||
|
||||
if (fn) {
|
||||
achieved = fn(data.__related.pb)
|
||||
? AchievedStatuses.ACHIEVED
|
||||
: AchievedStatuses.FAILED;
|
||||
} else {
|
||||
achieved = AchievedStatuses.SCORE_BASED;
|
||||
}
|
||||
}
|
||||
|
||||
if (options.__hideAchieved && achieved === AchievedStatuses.ACHIEVED) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tierlistInfo.push({
|
||||
chartID: data.chartID,
|
||||
key,
|
||||
data: data.tierlistInfo[key] ?? null,
|
||||
achieved,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tierlistInfo.sort(NumericSOV((x) => (x.data ? x.data.value : -Infinity), true));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user