mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-12 01:08:29 +03:00
checkpoint: insanity ascends (35)
127 errors to go
This commit is contained in:
@@ -1,59 +1,73 @@
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import React from "react";
|
||||
import { Badge } from "react-bootstrap";
|
||||
import { GetGamePTConfig, IDStrings, integer } from "tachi-common";
|
||||
import { GameClassSets } from "tachi-common/game-classes";
|
||||
import { Classes, GPTString, GetGPTString, GetGamePTConfig } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
|
||||
export default function ClassBadge<I extends IDStrings = IDStrings>({
|
||||
export default function ClassBadge<GPT extends GPTString = GPTString>({
|
||||
game,
|
||||
playtype,
|
||||
classSet,
|
||||
classValue,
|
||||
showSetOnHover = true,
|
||||
}: {
|
||||
classSet: GameClassSets[I];
|
||||
classValue: integer;
|
||||
classSet: Classes[GPT];
|
||||
classValue: string;
|
||||
showSetOnHover?: boolean;
|
||||
} & GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const classStyle =
|
||||
// @ts-expect-error hepl i'm trapped in a type factory
|
||||
GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)].classColours[classSet][classValue];
|
||||
|
||||
const data = gptConfig.classHumanisedFormat[classSet][classValue];
|
||||
const data = GetGamePTConfig(game, playtype).classes[classSet].values.find(
|
||||
(e) => e.id === classValue
|
||||
);
|
||||
|
||||
if (!data) {
|
||||
throw new Error(`Unknown class value ${classSet}:${classValue}`);
|
||||
return (
|
||||
<>
|
||||
{classSet} {classValue} (messed up!)
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let badgeComponent;
|
||||
if (data.variant) {
|
||||
|
||||
if (classStyle === null) {
|
||||
badgeComponent = (
|
||||
<Badge className="mx-2" variant={data.variant}>
|
||||
<Badge
|
||||
className="mx-2"
|
||||
style={{
|
||||
backgroundColor: "#131313",
|
||||
}}
|
||||
>
|
||||
{data.display}
|
||||
</Badge>
|
||||
);
|
||||
} else if (data.css) {
|
||||
} else if (typeof classStyle === "string") {
|
||||
badgeComponent = (
|
||||
<Badge className="mx-2" style={data.css}>
|
||||
<Badge className="mx-2" variant={classStyle}>
|
||||
{data.display}
|
||||
</Badge>
|
||||
);
|
||||
} else {
|
||||
badgeComponent = (
|
||||
<Badge className="mx-2" variant="secondary">
|
||||
<Badge className="mx-2" style={classStyle}>
|
||||
{data.display}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
if (data.mouseover && showSetOnHover) {
|
||||
if (data.hoverText && showSetOnHover) {
|
||||
return (
|
||||
<QuickTooltip tooltipContent={`${UppercaseFirst(classSet)}: ${data.mouseover}`}>
|
||||
<QuickTooltip tooltipContent={`${UppercaseFirst(classSet)}: ${data.hoverText}`}>
|
||||
{badgeComponent}
|
||||
</QuickTooltip>
|
||||
);
|
||||
} else if (data.mouseover) {
|
||||
return <QuickTooltip tooltipContent={data.mouseover}>{badgeComponent}</QuickTooltip>;
|
||||
} else if (data.hoverText) {
|
||||
return <QuickTooltip tooltipContent={data.hoverText}>{badgeComponent}</QuickTooltip>;
|
||||
} else if (showSetOnHover) {
|
||||
<QuickTooltip tooltipContent={UppercaseFirst(classSet)}>{badgeComponent}</QuickTooltip>;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ export default function ScoreLeaderboard({
|
||||
const [alg, setAlg] = useState(defaultAlg);
|
||||
|
||||
const SelectComponent =
|
||||
gptConfig.scoreRatingAlgs.length > 1 ? (
|
||||
Object.keys(gptConfig.scoreRatingAlgs).length > 1 ? (
|
||||
<Form.Control as="select" value={alg} onChange={(e) => setAlg(e.target.value as any)}>
|
||||
{gptConfig.scoreRatingAlgs.map((e) => (
|
||||
{Object.keys(gptConfig.scoreRatingAlgs).map((e) => (
|
||||
<option key={e} value={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
import RatingSystemPart from "components/tables/cells/RatingSystemPart";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import ExternalLink from "components/util/ExternalLink";
|
||||
@@ -6,12 +7,13 @@ 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 { UserContext } from "context/UserContext";
|
||||
import { AllLUGPTStatsContext } from "context/AllLUGPTStatsContext";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import React, { useContext } from "react";
|
||||
import { Col, Row } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ChartDocument, FolderDocument, Game, GetGamePTConfig } from "tachi-common";
|
||||
import { ChartDocument, FolderDocument, Game, GetGPTString } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
|
||||
export default function ChartInfoFormat({
|
||||
@@ -19,9 +21,9 @@ export default function ChartInfoFormat({
|
||||
game,
|
||||
playtype,
|
||||
}: { chart: ChartDocument } & GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, playtype)];
|
||||
|
||||
const withTierlists = gptConfig.tierlists.filter((e) => chart.tierlistInfo[e]);
|
||||
const ratingSystems = gptImpl.ratingSystems;
|
||||
|
||||
const { data, error } = useApiQuery<FolderDocument[]>(
|
||||
`/games/${game}/${playtype}/charts/${chart.chartID}/folders`
|
||||
@@ -71,27 +73,44 @@ export default function ChartInfoFormat({
|
||||
<ChartInfoMiddle chart={chart} game={game} />
|
||||
</Col>
|
||||
<Col xs={12} lg={3}>
|
||||
{withTierlists.length !== 0 ? (
|
||||
<MiniTable headers={["Tierlist Info"]} colSpan={2}>
|
||||
{withTierlists.map((e) => (
|
||||
<tr key={e}>
|
||||
<td>{e}</td>
|
||||
<td>
|
||||
{chart.tierlistInfo[e]!.text}{" "}
|
||||
<Muted>({chart.tierlistInfo[e]!.value.toFixed(2)})</Muted>
|
||||
{chart.tierlistInfo[e]!.individualDifference && (
|
||||
<>
|
||||
<br />
|
||||
<QuickTooltip tooltipContent="Individual Difference - The difficulty of this varies massively between people!">
|
||||
<span>
|
||||
<Icon type="balance-scale-left" />
|
||||
</span>
|
||||
</QuickTooltip>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
<RatingSystemPart chart={chart} game={game} />
|
||||
{ratingSystems.length !== 0 ? (
|
||||
<MiniTable headers={["Ratings"]} colSpan={2}>
|
||||
{ratingSystems.map((e) => {
|
||||
// @ts-expect-error bad types
|
||||
const strV = e.toString(chart);
|
||||
// @ts-expect-error bad types
|
||||
const numV = e.toNumber(chart);
|
||||
|
||||
if (
|
||||
strV === null ||
|
||||
strV === undefined ||
|
||||
numV === null ||
|
||||
numV === undefined
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={e.name}>
|
||||
<td>{e}</td>
|
||||
<td>
|
||||
{strV} <Muted>({numV.toFixed(2)})</Muted>
|
||||
{/* @ts-expect-error utterly silly types */}
|
||||
{e.idvDifference(chart) && (
|
||||
<>
|
||||
<br />
|
||||
<QuickTooltip tooltipContent="Individual Difference - The difficulty of this varies massively between people!">
|
||||
<span>
|
||||
<Icon type="balance-scale-left" />
|
||||
</span>
|
||||
</QuickTooltip>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</MiniTable>
|
||||
) : (
|
||||
<Muted>No tierlist info.</Muted>
|
||||
|
||||
@@ -21,8 +21,8 @@ export default function FolderInfoHeader({
|
||||
folderDataset: FolderDataset;
|
||||
folderTitle: string;
|
||||
} & GamePT) {
|
||||
const scoreBucket = useBucket(game, playtype);
|
||||
const [view, setView] = useState<"grade" | "lamp">(scoreBucket);
|
||||
const preferredDefaultEnum = useBucket(game, playtype);
|
||||
const [view, setView] = useState<"grade" | "lamp">(preferredDefaultEnum);
|
||||
const [breakdownInfo, setBreakdownInfo] = useState<"graph" | "switchboard">("switchboard");
|
||||
|
||||
return (
|
||||
@@ -66,7 +66,7 @@ export default function FolderInfoHeader({
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
folderDataset={folderDataset}
|
||||
view={view}
|
||||
enumMetric={view}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { ONE_WEEK } from "util/constants/time";
|
||||
import { CreateChartLink } from "util/data";
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import { ONE_WEEK } from "util/constants/time";
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
import ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import Divider from "components/util/Divider";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { Form } from "react-bootstrap";
|
||||
import { GPT_CLIENT_IMPLEMENTATIONS } from "lib/game-implementations";
|
||||
import React, { useMemo } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
ChartDocument,
|
||||
FormatChart,
|
||||
Game,
|
||||
GamePTConfig,
|
||||
GetGPTString,
|
||||
GetGamePTConfig,
|
||||
GetScoreMetricConf,
|
||||
IIDX_LAMPS,
|
||||
UserDocument,
|
||||
SessionDocument,
|
||||
SessionScoreInfo,
|
||||
UserDocument,
|
||||
} from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { FolderDataset } from "types/tables";
|
||||
import { SessionReturns } from "types/api-returns";
|
||||
|
||||
type Props = {
|
||||
folderDataset: FolderDataset;
|
||||
reqUser: UserDocument;
|
||||
view: "grade" | "lamp";
|
||||
enumMetric: string;
|
||||
} & GamePT;
|
||||
|
||||
export default function FolderMinimap(props: Props) {
|
||||
@@ -61,7 +62,7 @@ export default function FolderMinimap(props: Props) {
|
||||
<FolderMinimapMain {...props} recentSession={session} />
|
||||
</div>
|
||||
<div className="d-block d-lg-none">
|
||||
Sadly, Switchboard view doesn't work very well on mobile at the moment.
|
||||
Sadly, Switchboard view doesn't work on mobile at the moment.
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -71,40 +72,32 @@ function FolderMinimapMain({
|
||||
game,
|
||||
playtype,
|
||||
folderDataset,
|
||||
view,
|
||||
enumMetric,
|
||||
recentSession,
|
||||
}: Props & {
|
||||
recentSession: { session: SessionDocument; scoreInfo: Array<SessionScoreInfo> } | null;
|
||||
}) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const [sortAlg, setSortAlg] = useState(view === "grade" ? "score" : "lamp");
|
||||
|
||||
useEffect(() => {
|
||||
if (view === "grade") {
|
||||
setSortAlg("score");
|
||||
} else {
|
||||
setSortAlg("lamp");
|
||||
}
|
||||
}, [view]);
|
||||
|
||||
const getterFn = useMemo<(f: FolderDataset[0]) => number | undefined>(() => {
|
||||
if (sortAlg === "score") {
|
||||
return (c) => c.__related.pb?.scoreData.percent;
|
||||
} else if (sortAlg === "lamp") {
|
||||
return (c) => c.__related.pb?.scoreData.lampIndex;
|
||||
} else if (sortAlg.startsWith("tierlist:")) {
|
||||
const v = sortAlg.split("tierlist:")[1];
|
||||
const conf = GetScoreMetricConf(gptConfig, enumMetric);
|
||||
|
||||
return (c) => c.tierlistInfo[v as keyof ChartDocument["tierlistInfo"]]?.value;
|
||||
if (!conf) {
|
||||
return () => 0; // wut
|
||||
}
|
||||
|
||||
return () => 0;
|
||||
}, [sortAlg]);
|
||||
if (conf.type === "ENUM") {
|
||||
// @ts-expect-error insane dynamic access
|
||||
return (c) => c.__related.pb?.scoreData.enumIndexes[enumMetric];
|
||||
}
|
||||
|
||||
// @ts-expect-error insane dynamic access
|
||||
return (c) => c.__related.pb?.scoreData[enumMetric];
|
||||
}, [enumMetric]);
|
||||
|
||||
const sortedDataset = useMemo(
|
||||
() => folderDataset.slice(0).sort(NumericSOV((a) => getterFn(a) ?? -Infinity, true)),
|
||||
[getterFn, folderDataset, sortAlg]
|
||||
[getterFn, folderDataset, enumMetric]
|
||||
);
|
||||
|
||||
// For the switchboard chart, display a raise icon on stuff the user has recently played.
|
||||
@@ -114,7 +107,19 @@ function FolderMinimapMain({
|
||||
}
|
||||
|
||||
return recentSession.scoreInfo
|
||||
.filter((e) => e.isNewScore || e.gradeDelta > 0 || e.lampDelta > 0)
|
||||
.filter((e) => {
|
||||
if (e.isNewScore) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const v of Object.values(e.deltas)) {
|
||||
if (v >= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map((e) => e.scoreID);
|
||||
}, [recentSession]);
|
||||
|
||||
@@ -127,37 +132,20 @@ function FolderMinimapMain({
|
||||
key={d.chartID}
|
||||
data={d}
|
||||
gptConfig={gptConfig}
|
||||
view={view}
|
||||
enumMetric={enumMetric}
|
||||
game={game}
|
||||
wasRecent={
|
||||
(d.__related.pb &&
|
||||
recentlyTouched.some(
|
||||
(scoreID) =>
|
||||
d.__related.pb!.composedFrom.scorePB === scoreID ||
|
||||
d.__related.pb!.composedFrom.lampPB === scoreID
|
||||
recentlyTouched.some((scoreID) =>
|
||||
d.__related.pb!.composedFrom.find(
|
||||
(e) => e.scoreID === scoreID
|
||||
)
|
||||
)) ??
|
||||
false
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Divider />
|
||||
<Form.Group>
|
||||
<Form.Label>Sorting switchboard on:</Form.Label>
|
||||
<Form.Control
|
||||
as="select"
|
||||
value={sortAlg}
|
||||
onChange={(e) => setSortAlg(e.target.value)}
|
||||
>
|
||||
{view === "grade" && <option value="score">Your Score</option>}
|
||||
{view === "lamp" && <option value="lamp">Your Lamp</option>}
|
||||
{gptConfig.tierlists.map((e) => (
|
||||
<option key={`tierlist:${e}`} value={`tierlist:${e}`}>
|
||||
Tierlist: {e}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -166,16 +154,18 @@ function FolderMinimapMain({
|
||||
function MinimapElement({
|
||||
data,
|
||||
gptConfig,
|
||||
view,
|
||||
enumMetric,
|
||||
game,
|
||||
wasRecent,
|
||||
}: {
|
||||
data: FolderDataset[0];
|
||||
view: "grade" | "lamp";
|
||||
enumMetric: string;
|
||||
gptConfig: GamePTConfig;
|
||||
game: Game;
|
||||
wasRecent: boolean;
|
||||
}) {
|
||||
const gptImpl = GPT_CLIENT_IMPLEMENTATIONS[GetGPTString(game, data.playtype)];
|
||||
|
||||
let icon = "level-up-alt";
|
||||
|
||||
// if this user recently cleared mare nectaris
|
||||
@@ -183,7 +173,8 @@ function MinimapElement({
|
||||
if (
|
||||
data.chartID === "924bf011fdd8334b609b02e382123f9f5440d16d" &&
|
||||
data.__related.pb &&
|
||||
data.__related.pb?.scoreData.lampIndex >= IIDX_LAMPS.EASY_CLEAR
|
||||
// @ts-expect-error the above chartID guarantees mare nectaris
|
||||
data.__related.pb?.scoreData.enumIndexes.lamp >= IIDX_LAMPS.EASY_CLEAR
|
||||
) {
|
||||
icon = "hand-middle-finger";
|
||||
}
|
||||
@@ -194,8 +185,8 @@ function MinimapElement({
|
||||
}
|
||||
|
||||
// @ts-expect-error unhinged dynamic access (i dont care)
|
||||
return gptConfig[`${view}Colours`][data.__related.pb.scoreData[view]];
|
||||
}, [data.__related.pb, gptConfig, view]);
|
||||
return gptImpl.enumColours[enumMetric][data.__related.pb.scoreData[enumMetric]];
|
||||
}, [data.__related.pb, gptConfig, enumMetric]);
|
||||
|
||||
return (
|
||||
<QuickTooltip
|
||||
@@ -209,17 +200,10 @@ function MinimapElement({
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<span>{data.__related.pb?.scoreData.lamp ?? "Not Played"}</span>
|
||||
{data.__related.pb && (
|
||||
<>
|
||||
<br />
|
||||
{data.__related.pb.scoreData.grade}
|
||||
<br />
|
||||
<span>
|
||||
{data.__related.pb.scoreData.score} (
|
||||
{data.__related.pb.scoreData.percent.toFixed(2)}%)
|
||||
</span>
|
||||
</>
|
||||
{data.__related.pb ? (
|
||||
<ScoreCoreCells chart={data} game={game} score={data.__related.pb} />
|
||||
) : (
|
||||
<span>Not Played</span>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ export default function SongChartInfoFormat({
|
||||
chart: ChartDocument | null;
|
||||
game: Game;
|
||||
}) {
|
||||
// lazy casting hack to avoid IDStrings
|
||||
// lazy casting hack to avoid GPTString
|
||||
const props = { song: tempSong, chart: tempChart, game } as any;
|
||||
const { song, chart } = props;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user