mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-10 00:08:22 +03:00
Big Update 4
This commit is contained in:
+1
-1
@@ -75,7 +75,7 @@
|
||||
"resolve": "1.12.0",
|
||||
"resolve-url-loader": "3.1.0",
|
||||
"socicon": "3.0.5",
|
||||
"tachi-common": "0.2.18"
|
||||
"tachi-common": "0.2.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/humanize-duration": "3.25.1",
|
||||
|
||||
Generated
+4
-4
@@ -77,7 +77,7 @@ specifiers:
|
||||
serve: 11.2.0
|
||||
socicon: 3.0.5
|
||||
style-loader: 1.0.0
|
||||
tachi-common: 0.2.18
|
||||
tachi-common: 0.2.25
|
||||
terser-webpack-plugin: 1.4.1
|
||||
ts-pnp: 1.1.4
|
||||
typescript: 4.3.5
|
||||
@@ -142,7 +142,7 @@ dependencies:
|
||||
resolve: 1.12.0
|
||||
resolve-url-loader: 3.1.0
|
||||
socicon: 3.0.5
|
||||
tachi-common: 0.2.18
|
||||
tachi-common: 0.2.25
|
||||
|
||||
devDependencies:
|
||||
'@types/humanize-duration': 3.25.1
|
||||
@@ -11178,8 +11178,8 @@ packages:
|
||||
strip-ansi: 6.0.0
|
||||
dev: true
|
||||
|
||||
/tachi-common/0.2.18:
|
||||
resolution: {integrity: sha512-moa+Bmo6ilwJIyxQfHNkxNOKxQCgXFWezdLGwwvhCAzAfpLtiT0B6/QXKJUrkEVOqAy1ujRsLHZdK+NR87rFvg==}
|
||||
/tachi-common/0.2.25:
|
||||
resolution: {integrity: sha512-FSMwEWWcvP5Vw2heSD6vD7Iz1/lI+bO1HF0+0iURw5Ybj16yJZQ9wBiaIz2hotNweMhlQ73v57tj2TMUmFyG9w==}
|
||||
dependencies:
|
||||
monk: 7.3.4
|
||||
transitivePeerDependencies:
|
||||
|
||||
@@ -10,7 +10,9 @@ import { LoadingScreen } from "components/layout/screens/LoadingScreen";
|
||||
import { QueryClientProvider, QueryClient } from "react-query";
|
||||
import { UserSettingsContextProvider } from "context/UserSettingsContext";
|
||||
|
||||
const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5 } } });
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { staleTime: 1000 * 60 * 5, retry: 1 } },
|
||||
});
|
||||
|
||||
export default function App({ basename }: { basename: string }) {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import Card from "components/layout/page/Card";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import React from "react";
|
||||
import { GetGameConfig, FormatGame, GetGamePTConfig } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
|
||||
export default function GPTDevInfo({ game, playtype }: GamePT) {
|
||||
useSetSubheader(
|
||||
["Games", GetGameConfig(game).name, playtype, "Dev Info"],
|
||||
[game, playtype],
|
||||
`${FormatGame(game, playtype)} Dev Info`
|
||||
);
|
||||
|
||||
const gameConfig = GetGameConfig(game);
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card header="Game Configuration">
|
||||
<DebugContent data={gameConfig} />
|
||||
</Card>
|
||||
<Card className="mt-4" header="GPT Configuration">
|
||||
<DebugContent data={gptConfig} />
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import React from "react";
|
||||
import { FormatGame, GetGameConfig } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
|
||||
export default function GPTLeaderboardsPage({ game, playtype }: GamePT) {
|
||||
useSetSubheader(
|
||||
["Games", GetGameConfig(game).name, playtype, "Leaderboards"],
|
||||
[game, playtype],
|
||||
`${FormatGame(game, playtype)} Leaderboards`
|
||||
);
|
||||
|
||||
return <div>leaderboard</div>;
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
import { ResponsiveBar } from "@nivo/bar";
|
||||
import { BarChartTooltip } from "components/charts/ChartTooltip";
|
||||
import ClassBadge from "components/game/ClassBadge";
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import Card from "components/layout/page/Card";
|
||||
import ScoreTable from "components/tables/scores/ScoreTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import AsyncLoader from "components/util/AsyncLoader";
|
||||
import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import { TachiConfig } from "lib/config";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Col, Form, Row } from "react-bootstrap";
|
||||
import { Link } from "react-router-dom";
|
||||
import {
|
||||
ChartDocument,
|
||||
ClassAchievementDocument,
|
||||
FormatGame,
|
||||
gameClasses,
|
||||
GamePTConfig,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
integer,
|
||||
PublicUserDocument,
|
||||
ScoreDocument,
|
||||
SongDocument,
|
||||
} from "tachi-common";
|
||||
import { RecentClassesReturn } from "types/api-returns";
|
||||
import { GamePT } from "types/react";
|
||||
import { ScoreDataset } from "types/tables";
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import { DEFAULT_BAR_PROPS } from "util/charts";
|
||||
import { CreateChartMap, CreateSongMap, CreateUserMap } from "util/data";
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
import { MillisToSince } from "util/time";
|
||||
|
||||
export default function GPTMainPage({ game, playtype }: GamePT) {
|
||||
useSetSubheader(
|
||||
["Games", GetGameConfig(game).name, playtype],
|
||||
[game, playtype],
|
||||
FormatGame(game, playtype)
|
||||
);
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card header="Recent Highlighted Scores">
|
||||
<RecentHighlightedScoresComponent game={game} playtype={playtype} />
|
||||
</Card>
|
||||
{Object.keys(gptConfig.classHumanisedFormat).length !== 0 && (
|
||||
<>
|
||||
<Divider />
|
||||
<Card header="Recently Achieved Classes">
|
||||
<RecentClassesComponent game={game} playtype={playtype} />
|
||||
</Card>
|
||||
<Divider />
|
||||
<Card header="Class Distributions">
|
||||
<ClassDistributionComponent game={game} playtype={playtype} />
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function RecentHighlightedScoresComponent({ game, playtype }: GamePT) {
|
||||
const { data, isLoading, error } = useApiQuery<{
|
||||
scores: ScoreDocument[];
|
||||
users: PublicUserDocument[];
|
||||
charts: ChartDocument[];
|
||||
songs: SongDocument[];
|
||||
}>(`/games/${game}/${playtype}/scores/highlighted`);
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const dataset: ScoreDataset = [];
|
||||
|
||||
const songMap = CreateSongMap(data.songs);
|
||||
const chartMap = CreateChartMap(data.charts);
|
||||
const userMap = CreateUserMap(data.users);
|
||||
|
||||
for (const [index, score] of data.scores.entries()) {
|
||||
dataset.push({
|
||||
...score,
|
||||
__related: {
|
||||
chart: chartMap.get(score.chartID)!,
|
||||
song: songMap.get(score.songID)!,
|
||||
index,
|
||||
user: userMap.get(score.userID)!,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col className="text-center" xs={12}>
|
||||
Here's what players have recently marked as highlighted scores!
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<ScoreTable userCol dataset={dataset} game={game} playtype={playtype} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function RecentClassesComponent({ game, playtype }: GamePT) {
|
||||
const { data, isLoading, error } = useApiQuery<RecentClassesReturn>(
|
||||
`/games/${game}/${playtype}/recent-classes`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
// if (data.classes.length === 0) {
|
||||
// return <div className="text-center">Looks like nobodies got anything here... yet!</div>;
|
||||
// }
|
||||
|
||||
const userMap = new Map<integer, PublicUserDocument>();
|
||||
|
||||
for (const user of data.users) {
|
||||
userMap.set(user.id, user);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{data.classes.length === 0 ? (
|
||||
<span>Looks like nobodies ever got a class for this game. Ah well.</span>
|
||||
) : (
|
||||
data.classes.map((c, i) => {
|
||||
const user = userMap.get(c.userID)!;
|
||||
|
||||
return <AchievedClassRow key={i} classInfo={c} user={user} />;
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AchievedClassRow({
|
||||
classInfo,
|
||||
user,
|
||||
}: {
|
||||
classInfo: ClassAchievementDocument;
|
||||
user: PublicUserDocument;
|
||||
}) {
|
||||
const game = classInfo.game;
|
||||
const playtype = classInfo.playtype;
|
||||
|
||||
return (
|
||||
<div className="py-2 timeline-element">
|
||||
<Link to={`/dashboard/users/${user.username}/games/${game}/${playtype}`}>
|
||||
{user.username}
|
||||
</Link>{" "}
|
||||
achieved{" "}
|
||||
<ClassBadge
|
||||
classSet={classInfo.classSet}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
classValue={classInfo.classValue}
|
||||
/>
|
||||
{classInfo.classOldValue && (
|
||||
<>
|
||||
{" "}
|
||||
(Raised from{" "}
|
||||
<ClassBadge
|
||||
classSet={classInfo.classSet}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
classValue={classInfo.classOldValue}
|
||||
/>
|
||||
)
|
||||
</>
|
||||
)}
|
||||
<span className="float-right d-none d-lg-inline">
|
||||
<Icon type="clock" />
|
||||
|
||||
<span className="pl-1">{MillisToSince(classInfo.timeAchieved)}</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ClassDistributionComponent({ game, playtype }: GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const [className, setClassName] = useState<gameClasses.AllClassSets>(
|
||||
Object.keys(gptConfig.classHumanisedFormat)[0] as gameClasses.AllClassSets
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
setClassName(Object.keys(gptConfig.classHumanisedFormat)[0] as gameClasses.AllClassSets);
|
||||
}, [game, playtype]);
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
This will show the distribution of classes among all the players on{" "}
|
||||
{TachiConfig.name}!
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
</Col>
|
||||
<Col xs={12} lg={4} className="offset-lg-4">
|
||||
<Form.Control
|
||||
as="select"
|
||||
value={className}
|
||||
onChange={e => setClassName(e.target.value as gameClasses.AllClassSets)}
|
||||
>
|
||||
{Object.keys(gptConfig.classHumanisedFormat).map(e => (
|
||||
<option value={e} key={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<AsyncLoader
|
||||
promiseFn={async () => {
|
||||
const res = await APIFetchV1<Record<string, integer>>(
|
||||
`/games/${game}/${playtype}/class-distribution?class=${className}`
|
||||
);
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error(res.description);
|
||||
}
|
||||
|
||||
return res.body;
|
||||
}}
|
||||
>
|
||||
{d => <ClassDistChart data={d} gptConfig={gptConfig} className={className} />}
|
||||
</AsyncLoader>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function ClassDistChart({
|
||||
data,
|
||||
gptConfig,
|
||||
className,
|
||||
}: {
|
||||
data: Record<string, integer>;
|
||||
gptConfig: GamePTConfig;
|
||||
className: gameClasses.AllClassSets;
|
||||
}) {
|
||||
const dataset = [];
|
||||
|
||||
for (const key in gptConfig.classHumanisedFormat[className]) {
|
||||
dataset.push({
|
||||
id: key,
|
||||
count: data[key] ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
if (dataset.length === 0) {
|
||||
return (
|
||||
<div style={{ height: 400 }}>
|
||||
<div className="d-flex justify-content-center align-items-center h-100">
|
||||
<span>Looks like nobody has any of these classes yet.</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: 200 + dataset.length * 10 }}>
|
||||
<ResponsiveBar
|
||||
data={dataset}
|
||||
keys={["count"]}
|
||||
indexBy="id"
|
||||
layout="horizontal"
|
||||
margin={{ left: 80, bottom: 40, top: 20, right: 20 }}
|
||||
colors={v => "#cc527a"}
|
||||
axisLeft={{
|
||||
format: v => gptConfig.classHumanisedFormat[className][v]?.display,
|
||||
}}
|
||||
tooltip={d => (
|
||||
<BarChartTooltip
|
||||
point={d}
|
||||
renderFn={d => (
|
||||
<div className="w-100 text-center">
|
||||
{
|
||||
gptConfig.classHumanisedFormat[className][
|
||||
d.indexValue as number
|
||||
].display
|
||||
}
|
||||
: {(d.data as any).count}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{...DEFAULT_BAR_PROPS}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import BMSDifficultyCell from "components/tables/cells/BMSDifficultyCell";
|
||||
import DifficultyCell from "components/tables/cells/DifficultyCell";
|
||||
import TitleCell from "components/tables/cells/TitleCell";
|
||||
import TachiTable from "components/tables/components/TachiTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import DebounceSearch from "components/util/DebounceSearch";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import Divider from "components/util/Divider";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import React, { useState } from "react";
|
||||
import { Col, Row } from "react-bootstrap";
|
||||
import {
|
||||
GetGameConfig,
|
||||
FormatGame,
|
||||
ChartDocument,
|
||||
SongDocument,
|
||||
integer,
|
||||
GetGamePTConfig,
|
||||
} from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { CreateSongMap } from "util/data";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
|
||||
export default function GPTSongsPage({ game, playtype }: GamePT) {
|
||||
useSetSubheader(
|
||||
["Games", GetGameConfig(game).name, playtype, "Songs"],
|
||||
[game, playtype],
|
||||
`${FormatGame(game, playtype)} Songs`
|
||||
);
|
||||
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<DebounceSearch
|
||||
className="form-control-lg"
|
||||
setSearch={setSearch}
|
||||
placeholder="Search songs and charts..."
|
||||
/>
|
||||
<Divider />
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<SearchSongsTable game={game} search={search} playtype={playtype} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchSongsTable({ game, playtype, search }: { search: string } & GamePT) {
|
||||
const params = new URLSearchParams({ search });
|
||||
|
||||
const { data, isLoading, error } = useApiQuery<{
|
||||
charts: (ChartDocument & { __playcount: integer })[];
|
||||
songs: SongDocument[];
|
||||
}>(`/games/${game}/${playtype}/charts${search !== "" ? `?${params.toString()}` : ""}`);
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const songMap = CreateSongMap(data.songs);
|
||||
|
||||
const dataset = [];
|
||||
|
||||
for (const chart of data.charts) {
|
||||
dataset.push({
|
||||
...chart,
|
||||
__related: {
|
||||
song: songMap.get(chart.songID)!,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
return (
|
||||
<>
|
||||
{search === "" && (
|
||||
<div className="w-100 text-center">
|
||||
Displaying the most played charts for {FormatGame(game, playtype)}
|
||||
</div>
|
||||
)}
|
||||
<TachiTable
|
||||
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;
|
||||
}),
|
||||
],
|
||||
["Song Title", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Playcount", "Playcount", NumericSOV(x => x.__playcount)],
|
||||
]}
|
||||
searchFunctions={{
|
||||
title: x => x.__related.song.title,
|
||||
artist: x => x.__related.song.artist,
|
||||
playcount: x => x.__playcount,
|
||||
difficulty: x => x.difficulty,
|
||||
level: x => x.levelNum,
|
||||
}}
|
||||
rowFunction={d => (
|
||||
<tr>
|
||||
{game === "bms" ? (
|
||||
<BMSDifficultyCell
|
||||
chart={(d as unknown) as ChartDocument<"bms:7K" | "bms:14K">}
|
||||
/>
|
||||
) : (
|
||||
<DifficultyCell game={game} chart={d} />
|
||||
)}
|
||||
<TitleCell chart={d} game={game} song={d.__related.song} />
|
||||
<td>{d.__playcount}</td>
|
||||
</tr>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -215,6 +215,7 @@ function RecentSessionScoreInfo({
|
||||
chart,
|
||||
song: songMap.get(chart.songID)!,
|
||||
index: 0,
|
||||
user: reqUser,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -245,7 +246,6 @@ function RecentSessionScoreInfo({
|
||||
dataset={dataset as any}
|
||||
pageLen={5}
|
||||
game={game}
|
||||
reqUser={reqUser}
|
||||
playtype={playtype as any}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +140,7 @@ function AlgSelector({
|
||||
);
|
||||
}
|
||||
|
||||
function useFetchPBs(url: string) {
|
||||
function useFetchPBs(url: string, reqUser: PublicUserDocument) {
|
||||
const { isLoading, error, data } = useQuery(url, async () => {
|
||||
const res = await APIFetchV1<{
|
||||
pbs: PBScoreDocument<"iidx:SP">[];
|
||||
@@ -152,7 +152,7 @@ function useFetchPBs(url: string) {
|
||||
throw res;
|
||||
}
|
||||
|
||||
return FormatData(res.body.pbs, res.body.songs, res.body.charts);
|
||||
return FormatData(res.body.pbs, res.body.songs, res.body.charts, reqUser);
|
||||
});
|
||||
|
||||
return { isLoading, error: error as UnsuccessfulAPIResponse, data };
|
||||
@@ -175,7 +175,7 @@ function PBsOverview({
|
||||
} & GamePT) {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { isLoading, error, data } = useFetchPBs(url);
|
||||
const { isLoading, error, data } = useFetchPBs(url, reqUser);
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
@@ -214,7 +214,7 @@ function FormatData<
|
||||
D extends PBScoreDocument | ScoreDocument,
|
||||
I extends IDStrings = IDStrings,
|
||||
G extends Game = Game
|
||||
>(d: D[], songs: SongDocument<G>[], charts: ChartDocument<I>[]) {
|
||||
>(d: D[], songs: SongDocument<G>[], charts: ChartDocument<I>[], reqUser: PublicUserDocument) {
|
||||
const songMap = new Map();
|
||||
const chartMap = new Map();
|
||||
|
||||
@@ -232,13 +232,14 @@ function FormatData<
|
||||
song: songMap.get(e.songID),
|
||||
chart: chartMap.get(e.chartID),
|
||||
index: i,
|
||||
user: reqUser,
|
||||
},
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function useFetchScores(url: string) {
|
||||
function useFetchScores(url: string, reqUser: PublicUserDocument) {
|
||||
const { isLoading, error, data } = useQuery(url, async () => {
|
||||
const res = await APIFetchV1<{
|
||||
scores: ScoreDocument[];
|
||||
@@ -250,7 +251,7 @@ function useFetchScores(url: string) {
|
||||
throw res;
|
||||
}
|
||||
|
||||
return FormatData(res.body.scores, res.body.songs, res.body.charts);
|
||||
return FormatData(res.body.scores, res.body.songs, res.body.charts, reqUser);
|
||||
});
|
||||
|
||||
return { isLoading, error: error as UnsuccessfulAPIResponse, data };
|
||||
@@ -268,7 +269,8 @@ function PBsSearch({
|
||||
alg?: ScoreCalculatedDataLookup[IDStrings];
|
||||
} & GamePT) {
|
||||
const { isLoading, error, data } = useFetchPBs(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs?search=${search}`
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs?search=${search}`,
|
||||
reqUser
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -289,7 +291,8 @@ function ScoresOverview({ reqUser, game, playtype }: { reqUser: PublicUserDocume
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { isLoading, error, data } = useFetchScores(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/scores/recent`
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/scores/recent`,
|
||||
reqUser
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -307,12 +310,7 @@ function ScoresOverview({ reqUser, game, playtype }: { reqUser: PublicUserDocume
|
||||
style={{ height: 500 }}
|
||||
{...{ isLoading, dataset: data, error }}
|
||||
>
|
||||
<ScoreTable
|
||||
dataset={data!}
|
||||
game={game}
|
||||
reqUser={reqUser}
|
||||
playtype={playtype as any}
|
||||
/>
|
||||
<ScoreTable dataset={data!} game={game} playtype={playtype as any} />
|
||||
</LoadingWrapper>
|
||||
) : (
|
||||
<ScoresSearch {...{ reqUser, game, playtype, search }} />
|
||||
@@ -329,12 +327,13 @@ function ScoresSearch({
|
||||
search,
|
||||
}: { reqUser: PublicUserDocument; search: string } & GamePT) {
|
||||
const { isLoading, error, data } = useFetchScores(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/scores?search=${search}`
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/scores?search=${search}`,
|
||||
reqUser
|
||||
);
|
||||
|
||||
return (
|
||||
<LoadingWrapper style={{ height: 500 }} {...{ isLoading, error, dataset: data }}>
|
||||
<ScoreTable dataset={data!} game={game} reqUser={reqUser} playtype={playtype as any} />
|
||||
<ScoreTable dataset={data!} game={game} playtype={playtype as any} />
|
||||
</LoadingWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@ function FolderInfoComponent({
|
||||
Lamps
|
||||
</SelectButton>
|
||||
</div>
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
{dataset}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+11
-11
@@ -55,14 +55,14 @@ export default function FolderTablePage({ reqUser, game, playtype }: Props) {
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<InputGroup>
|
||||
@@ -82,7 +82,7 @@ export default function FolderTablePage({ reqUser, game, playtype }: Props) {
|
||||
))}
|
||||
</Form.Control>
|
||||
</InputGroup>
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
{table && <TableFolderViewer {...{ reqUser, game, playtype, table }} />}
|
||||
</>
|
||||
);
|
||||
@@ -118,18 +118,18 @@ function TableFolderViewer({ reqUser, game, playtype, table }: Props & { table:
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
if (isLoading || !data || !hasLoadedFolderMap) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !data || !hasLoadedFolderMap) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableBarChart table={table} dataMap={dataMap} />
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
<TableFolderTable
|
||||
reqUser={reqUser}
|
||||
game={game}
|
||||
@@ -267,7 +267,7 @@ function TableBarChart({
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
|
||||
+9
-8
@@ -67,6 +67,7 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
__related: {
|
||||
pb: pbMap.get(chart.chartID) ?? null,
|
||||
song: songMap.get(chart.songID)!,
|
||||
user: reqUser,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -92,14 +93,14 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
);
|
||||
}, [folderDataset]);
|
||||
|
||||
if (isLoading || !data || !folderDataset) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !data || !folderDataset) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
@@ -107,7 +108,7 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
</div>
|
||||
<div className="col-12">{folderInfoHeader}</div>
|
||||
<div className="col-12">
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
</div>
|
||||
<div className="col-12 d-flex">
|
||||
<div className="btn-group mx-auto">
|
||||
@@ -124,7 +125,7 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
</div>
|
||||
<div className="col-12">
|
||||
{mode === "normal" ? (
|
||||
@@ -174,7 +175,7 @@ function TierlistBreakdown({ game, folderDataset, playtype, reqUser }: InfoProps
|
||||
<Col xs={12}>
|
||||
<Card header="Tierlist View Configuration">
|
||||
<span>Here you can select what tierlist blocks to show!</span>
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
{gptConfig.tierlists.length > 1 &&
|
||||
gptConfig.tierlists.map(e => (
|
||||
<>
|
||||
@@ -201,7 +202,7 @@ function TierlistBreakdown({ game, folderDataset, playtype, reqUser }: InfoProps
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<Divider className="my-4" />
|
||||
<Divider />
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<TierlistInfoLadder
|
||||
|
||||
@@ -26,6 +26,10 @@ export function Routes() {
|
||||
{!user && ClientConfig.MANDATE_LOGIN ? <LoginPage /> : <Redirect to="/dashboard" />}
|
||||
</Route>
|
||||
|
||||
<Route path="/michael">
|
||||
<ErrorPage statusCode={999} customMessage="He has been summoned." />
|
||||
</Route>
|
||||
|
||||
<Route path="/dashboard">
|
||||
{!user && ClientConfig.MANDATE_LOGIN ? <LoginPage /> : <DashboardRoutes />}
|
||||
</Route>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import PlaytypeSelect from "app/pages/dashboard/games/_game/PlaytypeSelect";
|
||||
import GPTSongsPage from "app/pages/dashboard/games/_game/_playtype/GPTSongsPage";
|
||||
import GPTDevInfo from "app/pages/dashboard/games/_game/_playtype/GPTDevInfo";
|
||||
import GPTLeaderboardsPage from "app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage";
|
||||
import GPTMainPage from "app/pages/dashboard/games/_game/_playtype/GPTMainPage";
|
||||
import { ErrorPage } from "app/pages/ErrorPage";
|
||||
import { GPTBottomNav, GPTHeaderBody } from "components/game/GPTHeader";
|
||||
import LayoutHeaderContainer from "components/layout/LayoutHeaderContainer";
|
||||
import React from "react";
|
||||
import { Redirect, Route, Switch, useParams } from "react-router-dom";
|
||||
import { Game, GetGameConfig } from "tachi-common";
|
||||
import { FormatGame, Game, GetGameConfig } from "tachi-common";
|
||||
import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts";
|
||||
|
||||
export default function GameRoutes() {
|
||||
@@ -29,7 +35,7 @@ export default function GameRoutes() {
|
||||
)}
|
||||
</Route>
|
||||
|
||||
<Route exact path="/dashboard/games/:game/:playtype">
|
||||
<Route path="/dashboard/games/:game/:playtype">
|
||||
<GamePlaytypeRoutes game={game} />
|
||||
</Route>
|
||||
|
||||
@@ -53,10 +59,34 @@ function GamePlaytypeRoutes({ game }: { game: Game }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/games/:game/:playtype">
|
||||
{game} {playtype}
|
||||
</Route>
|
||||
</Switch>
|
||||
<>
|
||||
<LayoutHeaderContainer
|
||||
footer={<GPTBottomNav baseUrl={`/dashboard/games/${game}/${playtype}`} />}
|
||||
header={FormatGame(game, playtype)}
|
||||
>
|
||||
<GPTHeaderBody game={game} playtype={playtype} />
|
||||
</LayoutHeaderContainer>
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/games/:game/:playtype">
|
||||
<GPTMainPage game={game} playtype={playtype} />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/dashboard/games/:game/:playtype/songs">
|
||||
<GPTSongsPage game={game} playtype={playtype} />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/dashboard/games/:game/:playtype/leaderboards">
|
||||
<GPTLeaderboardsPage game={game} playtype={playtype} />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/dashboard/games/:game/:playtype/dev-info">
|
||||
<GPTDevInfo game={game} playtype={playtype} />
|
||||
</Route>
|
||||
|
||||
<Route path="*">
|
||||
<ErrorPage statusCode={404} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ import UserGamesPage from "app/pages/dashboard/users/UserGamesPage";
|
||||
import UserSettingsPage from "app/pages/dashboard/users/UserSettingsPage";
|
||||
import { ErrorPage } from "app/pages/ErrorPage";
|
||||
import RequireAuthAsUserParam from "components/auth/RequireAuthAsUserParam";
|
||||
import LayoutHeaderContainer from "components/layout/LayoutHeaderContainer";
|
||||
import { UGPTBottomNav, UGPTHeaderBody } from "components/user/UGPTHeader";
|
||||
import { UserBottomNav, UserHeaderBody } from "components/user/UserHeader";
|
||||
import UserHeaderContainer from "components/user/UserHeaderContainer";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import { BackgroundContext } from "context/BackgroundContext";
|
||||
@@ -52,10 +52,6 @@ export default function UserRoutes() {
|
||||
};
|
||||
}, [reqUser]);
|
||||
|
||||
if (isLoading || !reqUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (error && error.statusCode === 404) {
|
||||
return <ErrorPage statusCode={404} customMessage="This user does not exist!" />;
|
||||
}
|
||||
@@ -64,6 +60,10 @@ export default function UserRoutes() {
|
||||
return <ErrorPage statusCode={error.statusCode} customMessage={error.description} />;
|
||||
}
|
||||
|
||||
if (isLoading || !reqUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/dashboard/users/:userID">
|
||||
@@ -81,7 +81,7 @@ export default function UserRoutes() {
|
||||
function UserProfileRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
|
||||
return (
|
||||
<>
|
||||
<UserHeaderContainer
|
||||
<LayoutHeaderContainer
|
||||
header={`${reqUser.username}'s Profile`}
|
||||
footer={
|
||||
<UserBottomNav
|
||||
@@ -91,7 +91,7 @@ function UserProfileRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
|
||||
}
|
||||
>
|
||||
<UserHeaderBody reqUser={reqUser} />
|
||||
</UserHeaderContainer>
|
||||
</LayoutHeaderContainer>
|
||||
<Route exact path="/dashboard/users/:userID">
|
||||
<UserPage reqUser={reqUser} />
|
||||
</Route>
|
||||
@@ -211,7 +211,7 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: PublicUserDocument
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserHeaderContainer
|
||||
<LayoutHeaderContainer
|
||||
header={`${reqUser.username}'s ${FormatGame(game, playtype)} Profile`}
|
||||
footer={
|
||||
<UGPTBottomNav
|
||||
@@ -221,7 +221,7 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: PublicUserDocument
|
||||
}
|
||||
>
|
||||
<UGPTHeaderBody reqUser={reqUser} game={game} playtype={playtype} stats={stats} />
|
||||
</UserHeaderContainer>
|
||||
</LayoutHeaderContainer>
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/users/:userID/games/:game/:playtype">
|
||||
<OverviewPage reqUser={reqUser} game={game} playtype={playtype} />
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import Navbar from "components/nav/Navbar";
|
||||
import NavItem from "components/nav/NavItem";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import React, { useContext } from "react";
|
||||
import { Game } from "tachi-common";
|
||||
import { GPTStatsReturn } from "types/api-returns";
|
||||
import { Playtype } from "types/tachi";
|
||||
|
||||
export function GPTHeaderBody({ game, playtype }: { game: Game; playtype: Playtype }) {
|
||||
const { data, isLoading, error } = useApiQuery<GPTStatsReturn>(`/games/${game}/${playtype}`);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="col-12 col-lg-4 offset-lg-4">
|
||||
{error ? (
|
||||
<ApiError error={error} />
|
||||
) : isLoading || !data ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<MiniTable headers={["Stats"]} colSpan={2}>
|
||||
<tr>
|
||||
<td>Player Count</td>
|
||||
<td>{data.playerCount}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Available Charts</td>
|
||||
<td>{data.chartCount}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Scores</td>
|
||||
<td>{data.scoreCount}</td>
|
||||
</tr>
|
||||
</MiniTable>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function GPTBottomNav({ baseUrl }: { baseUrl: string }) {
|
||||
const { settings } = useContext(UserSettingsContext);
|
||||
|
||||
const navItems = [
|
||||
<NavItem key="overview" to={`${baseUrl}/`}>
|
||||
Overview
|
||||
</NavItem>,
|
||||
<NavItem key="songs" to={`${baseUrl}/songs`}>
|
||||
Songs
|
||||
</NavItem>,
|
||||
<NavItem key="leaderboards" to={`${baseUrl}/leaderboards`}>
|
||||
Leaderboards
|
||||
</NavItem>,
|
||||
];
|
||||
|
||||
if (settings?.preferences.developerMode) {
|
||||
navItems.push(
|
||||
<NavItem key="dev-info" to={`${baseUrl}/dev-info`}>
|
||||
Developer Info
|
||||
</NavItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row align-items-center mb-0">
|
||||
<Navbar>{navItems}</Navbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PublicUserDocument } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
|
||||
export default function UserCell({ user, game, playtype }: { user: PublicUserDocument } & GamePT) {
|
||||
return (
|
||||
<td>
|
||||
<Link
|
||||
className="gentle-link"
|
||||
to={`/dashboard/users/${user.username}/games/${game}/${playtype}`}
|
||||
>
|
||||
{user.username}
|
||||
</Link>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
@@ -36,14 +36,14 @@ export interface ScoreDropdownProps<I extends IDStrings = IDStrings> {
|
||||
export default function GenericScoreDropdown<I extends IDStrings = IDStrings>({
|
||||
game,
|
||||
playtype,
|
||||
reqUser,
|
||||
user,
|
||||
chart,
|
||||
scoreState,
|
||||
thisScore,
|
||||
defaultView = "moreInfo",
|
||||
DocComponent = props => GenericScoreContentDropdown({ renderScoreInfo: false, ...props }),
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
user: PublicUserDocument;
|
||||
chart: ChartDocument;
|
||||
scoreState: ScoreState;
|
||||
defaultView?: "vsPB" | "moreInfo" | "history" | "debug";
|
||||
@@ -57,12 +57,12 @@ export default function GenericScoreDropdown<I extends IDStrings = IDStrings>({
|
||||
const [view, setView] = useState(defaultView);
|
||||
|
||||
const { isLoading, error, data } = useApiQuery<UGPTChartPBComposition<I>>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
`/users/${user.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
const { isLoading: histIsLoading, error: histError, data: histData } = useApiQuery<
|
||||
ScoreDocument<I>[]
|
||||
>(`/users/${reqUser.id}/games/${game}/${playtype}/scores/${chart.chartID}`);
|
||||
>(`/users/${user.id}/games/${game}/${playtype}/scores/${chart.chartID}`);
|
||||
|
||||
if (error) {
|
||||
return <>An error has occured. Whoops.</>;
|
||||
|
||||
@@ -12,12 +12,12 @@ export default function IIDXScoreDropdown({
|
||||
thisScore,
|
||||
game,
|
||||
playtype,
|
||||
reqUser,
|
||||
user,
|
||||
chart,
|
||||
scoreState,
|
||||
}: {
|
||||
thisScore: ScoreDocument<"iidx:SP" | "iidx:DP">;
|
||||
reqUser: PublicUserDocument;
|
||||
user: PublicUserDocument;
|
||||
chart: ChartDocument<"iidx:SP" | "iidx:DP">;
|
||||
scoreState: {
|
||||
highlight: boolean;
|
||||
@@ -27,7 +27,7 @@ export default function IIDXScoreDropdown({
|
||||
};
|
||||
} & GamePT) {
|
||||
const { isLoading, error, data } = useApiQuery<UGPTChartPBComposition<"iidx:SP" | "iidx:DP">>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
`/users/${user.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -44,7 +44,7 @@ export default function IIDXScoreDropdown({
|
||||
|
||||
return (
|
||||
<GenericScoreDropdown<"iidx:SP" | "iidx:DP">
|
||||
{...{ game, playtype, chart, reqUser, scoreState, thisScore }}
|
||||
{...{ game, playtype, chart, reqUser: user, scoreState, thisScore, user }}
|
||||
DocComponent={props =>
|
||||
GenericScoreContentDropdown({
|
||||
...props,
|
||||
|
||||
@@ -16,14 +16,14 @@ export default function PlayHistory({
|
||||
isLoading: boolean;
|
||||
error: UnsuccessfulAPIFetchResponse | null;
|
||||
} & GamePT) {
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <>{error.description}</>;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-12 col-lg-10">
|
||||
<HistoryScoreTable dataset={data} game={game} playtype={playtype} />
|
||||
|
||||
@@ -77,7 +77,7 @@ function Row({
|
||||
game: Game;
|
||||
}) {
|
||||
return (
|
||||
<tr className={sc.highlight ? "highlighted-row" : ""}>
|
||||
<tr>
|
||||
<ScoreCoreCells score={sc} game={game} rating={rating as any} />
|
||||
<TimestampCell time={sc.timeAchieved} service={sc.service} />
|
||||
</tr>
|
||||
|
||||
@@ -143,7 +143,6 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<BMSDifficultyCell chart={pb.__related.chart} />
|
||||
|
||||
@@ -109,7 +109,7 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game={pb.game} chart={pb.__related.chart} />
|
||||
|
||||
@@ -97,7 +97,6 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game="museca" chart={pb.__related.chart} />
|
||||
|
||||
@@ -96,7 +96,7 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game="sdvx" chart={pb.__related.chart} />
|
||||
|
||||
@@ -8,6 +8,7 @@ import BMSDifficultyCell from "../cells/BMSDifficultyCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import UserCell from "../cells/UserCell";
|
||||
import DropdownRow from "../components/DropdownRow";
|
||||
import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
@@ -16,46 +17,50 @@ import BMSScoreCoreCells from "../game-core-cells/BMSScoreCoreCells";
|
||||
import IndicatorHeader from "../headers/IndicatorHeader";
|
||||
|
||||
export default function BMSScoreTable({
|
||||
reqUser,
|
||||
dataset,
|
||||
pageLen,
|
||||
playtype,
|
||||
userCol = false,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
dataset: ScoreDataset<"bms:7K" | "bms:14K">;
|
||||
pageLen?: integer;
|
||||
playtype: Playtypes["bms"];
|
||||
userCol?: boolean;
|
||||
}) {
|
||||
const headers: Header<ScoreDataset<"bms:7K" | "bms:14K">[0]>[] = [
|
||||
[
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(
|
||||
x =>
|
||||
x.__related.chart.tierlistInfo["sgl-EC"]?.value ??
|
||||
x.__related.chart.tierlistInfo["sgl-HC"]?.value ??
|
||||
x.__related.chart.levelNum
|
||||
),
|
||||
],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Deltas", "Deltas", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["Sieglinde", "sgl.", NumericSOV(x => x.calculatedData.sieglinde ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (userCol) {
|
||||
headers.unshift(["User", "User", StrSOV(x => x.__related.user.username)]);
|
||||
}
|
||||
|
||||
return (
|
||||
<TachiTable
|
||||
dataset={dataset}
|
||||
pageLen={pageLen}
|
||||
headers={
|
||||
[
|
||||
[
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(
|
||||
x =>
|
||||
x.__related.chart.tierlistInfo["sgl-EC"]?.value ??
|
||||
x.__related.chart.tierlistInfo["sgl-HC"]?.value ??
|
||||
x.__related.chart.levelNum
|
||||
),
|
||||
],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Deltas", "Deltas", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["Sieglinde", "sgl.", NumericSOV(x => x.calculatedData.sieglinde ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
] as Header<ScoreDataset<"bms:7K" | "bms:14K">[0]>[]
|
||||
}
|
||||
headers={headers}
|
||||
entryName="Scores"
|
||||
searchFunctions={CreateDefaultScoreSearchParams("bms", playtype)}
|
||||
rowFunction={sc => (
|
||||
<Row playtype={playtype} key={sc.scoreID} sc={sc} reqUser={reqUser} />
|
||||
<Row playtype={playtype} key={sc.scoreID} sc={sc} userCol={userCol} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
@@ -63,22 +68,21 @@ export default function BMSScoreTable({
|
||||
|
||||
function Row({
|
||||
sc,
|
||||
reqUser,
|
||||
playtype,
|
||||
userCol,
|
||||
}: {
|
||||
sc: ScoreDataset<"bms:7K" | "bms:14K">[0];
|
||||
reqUser: PublicUserDocument;
|
||||
playtype: Playtype;
|
||||
userCol: boolean;
|
||||
}) {
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
reqUser={reqUser}
|
||||
user={sc.__related.user}
|
||||
game={sc.game}
|
||||
thisScore={sc}
|
||||
playtype={sc.playtype}
|
||||
@@ -86,6 +90,7 @@ function Row({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{userCol && <UserCell game={sc.game} playtype={sc.playtype} user={sc.__related.user} />}
|
||||
<BMSDifficultyCell chart={sc.__related.chart} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell
|
||||
|
||||
@@ -7,8 +7,9 @@ import { CreateDefaultScoreSearchParams } from "util/tables/create-search";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import UserCell from "../cells/UserCell";
|
||||
import DropdownRow from "../components/DropdownRow";
|
||||
import TachiTable from "../components/TachiTable";
|
||||
import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
import GenericScoreDropdown from "../dropdowns/GenericScoreDropdown";
|
||||
import GenericScoreCoreCells from "../game-core-cells/GenericScoreCoreCells";
|
||||
@@ -21,37 +22,51 @@ export default function GenericScoreTable({
|
||||
playtype,
|
||||
showScore,
|
||||
game,
|
||||
userCol = false,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
dataset: ScoreDataset;
|
||||
pageLen?: integer;
|
||||
playtype: Playtype;
|
||||
game: Game;
|
||||
userCol?: boolean;
|
||||
showScore?: boolean;
|
||||
}) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const headers: Header<ScoreDataset[0]>[] = [
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
[
|
||||
gptConfig.defaultScoreRatingAlg,
|
||||
gptConfig.defaultScoreRatingAlg,
|
||||
NumericSOV(x => x.calculatedData[gptConfig.defaultScoreRatingAlg] ?? 0),
|
||||
],
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (userCol) {
|
||||
headers.unshift(["User", "User", StrSOV(x => x.__related.user.username)]);
|
||||
}
|
||||
|
||||
return (
|
||||
<TachiTable
|
||||
dataset={dataset}
|
||||
pageLen={pageLen}
|
||||
headers={[
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
[
|
||||
gptConfig.defaultScoreRatingAlg,
|
||||
gptConfig.defaultScoreRatingAlg,
|
||||
NumericSOV(x => x.calculatedData[gptConfig.defaultScoreRatingAlg] ?? 0),
|
||||
],
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
]}
|
||||
headers={headers}
|
||||
entryName="Scores"
|
||||
searchFunctions={CreateDefaultScoreSearchParams(game, playtype)}
|
||||
rowFunction={sc => (
|
||||
<Row key={sc.scoreID} sc={sc} reqUser={reqUser} showScore={showScore} />
|
||||
<Row
|
||||
key={sc.scoreID}
|
||||
sc={sc}
|
||||
reqUser={reqUser}
|
||||
showScore={showScore}
|
||||
userCol={userCol}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
@@ -61,20 +76,22 @@ function Row({
|
||||
sc,
|
||||
reqUser,
|
||||
showScore,
|
||||
userCol,
|
||||
}: {
|
||||
sc: ScoreDataset[0];
|
||||
reqUser: PublicUserDocument;
|
||||
showScore?: boolean;
|
||||
userCol: boolean;
|
||||
}) {
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
reqUser={reqUser}
|
||||
user={sc.__related.user}
|
||||
game={sc.game}
|
||||
thisScore={sc}
|
||||
playtype={sc.playtype}
|
||||
@@ -82,6 +99,7 @@ function Row({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{userCol && <UserCell game={sc.game} playtype={sc.playtype} user={sc.__related.user} />}
|
||||
<DifficultyCell chart={sc.__related.chart} game={sc.game} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<GenericScoreCoreCells sc={sc} showScore={showScore} />
|
||||
|
||||
@@ -10,6 +10,7 @@ import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import UserCell from "../cells/UserCell";
|
||||
import DropdownRow from "../components/DropdownRow";
|
||||
import SelectableRating from "../components/SelectableRating";
|
||||
import TachiTable, { Header, ZTableTHProps } from "../components/TachiTable";
|
||||
@@ -19,60 +20,64 @@ import IIDXScoreCoreCells from "../game-core-cells/IIDXScoreCoreCells";
|
||||
import IndicatorHeader from "../headers/IndicatorHeader";
|
||||
|
||||
export default function IIDXScoreTable({
|
||||
reqUser,
|
||||
dataset,
|
||||
pageLen,
|
||||
playtype,
|
||||
userCol = false,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
dataset: ScoreDataset<"iidx:SP" | "iidx:DP">;
|
||||
pageLen?: integer;
|
||||
playtype: Playtypes["iidx"];
|
||||
userCol?: boolean;
|
||||
}) {
|
||||
const defaultRating = useScoreRatingAlg<"iidx:SP" | "iidx:DP">("iidx", playtype);
|
||||
const [rating, setRating] = useState<ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"]>(
|
||||
defaultRating
|
||||
);
|
||||
|
||||
const headers: Header<ScoreDataset<"iidx:SP" | "iidx:DP">[0]>[] = [
|
||||
[
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(
|
||||
x =>
|
||||
x.__related.chart.tierlistInfo["kt-NC"]?.value ??
|
||||
x.__related.chart.tierlistInfo["kt-HC"]?.value ??
|
||||
x.__related.chart.levelNum
|
||||
),
|
||||
],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Deltas", "Deltas", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
[
|
||||
"Rating",
|
||||
"Rating",
|
||||
NumericSOV(x => x.calculatedData[rating] ?? 0),
|
||||
(thProps: ZTableTHProps) => (
|
||||
<SelectableRating<"iidx:SP" | "iidx:DP">
|
||||
key={nanoid()}
|
||||
game="iidx"
|
||||
playtype={playtype}
|
||||
rating={rating}
|
||||
setRating={setRating}
|
||||
{...thProps}
|
||||
/>
|
||||
),
|
||||
],
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (userCol) {
|
||||
headers.unshift(["User", "User", StrSOV(x => x.__related.user.username)]);
|
||||
}
|
||||
|
||||
return (
|
||||
<TachiTable
|
||||
dataset={dataset}
|
||||
pageLen={pageLen}
|
||||
headers={
|
||||
[
|
||||
[
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(
|
||||
x =>
|
||||
x.__related.chart.tierlistInfo["kt-NC"]?.value ??
|
||||
x.__related.chart.tierlistInfo["kt-HC"]?.value ??
|
||||
x.__related.chart.levelNum
|
||||
),
|
||||
],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Deltas", "Deltas", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
[
|
||||
"Rating",
|
||||
"Rating",
|
||||
NumericSOV(x => x.calculatedData[rating] ?? 0),
|
||||
(thProps: ZTableTHProps) => (
|
||||
<SelectableRating<"iidx:SP" | "iidx:DP">
|
||||
key={nanoid()}
|
||||
game="iidx"
|
||||
playtype={playtype}
|
||||
rating={rating}
|
||||
setRating={setRating}
|
||||
{...thProps}
|
||||
/>
|
||||
),
|
||||
],
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
] as Header<ScoreDataset<"iidx:SP" | "iidx:DP">[0]>[]
|
||||
}
|
||||
headers={headers}
|
||||
entryName="Scores"
|
||||
searchFunctions={CreateDefaultScoreSearchParams("iidx", playtype)}
|
||||
rowFunction={sc => (
|
||||
@@ -80,8 +85,8 @@ export default function IIDXScoreTable({
|
||||
key={sc.scoreID}
|
||||
playtype={playtype}
|
||||
sc={sc}
|
||||
reqUser={reqUser}
|
||||
rating={rating}
|
||||
userCol={userCol}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -90,31 +95,32 @@ export default function IIDXScoreTable({
|
||||
|
||||
function Row({
|
||||
sc,
|
||||
reqUser,
|
||||
rating,
|
||||
playtype,
|
||||
userCol,
|
||||
}: {
|
||||
sc: ScoreDataset<"iidx:SP" | "iidx:DP">[0];
|
||||
reqUser: PublicUserDocument;
|
||||
rating: ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"];
|
||||
playtype: Playtype;
|
||||
userCol: boolean;
|
||||
}) {
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
|
||||
dropdown={
|
||||
<IIDXScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
game="iidx"
|
||||
playtype={sc.playtype}
|
||||
reqUser={reqUser}
|
||||
user={sc.__related.user}
|
||||
thisScore={sc}
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{userCol && <UserCell game={sc.game} playtype={sc.playtype} user={sc.__related.user} />}
|
||||
<DifficultyCell chart={sc.__related.chart} game="iidx" />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell
|
||||
|
||||
@@ -7,6 +7,7 @@ import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import UserCell from "../cells/UserCell";
|
||||
import DropdownRow from "../components/DropdownRow";
|
||||
import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
@@ -18,32 +19,38 @@ export default function MusecaScoreTable({
|
||||
reqUser,
|
||||
dataset,
|
||||
pageLen,
|
||||
userCol = false,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
dataset: ScoreDataset<"museca:Single">;
|
||||
pageLen?: integer;
|
||||
playtype: Playtypes["museca"];
|
||||
userCol: boolean;
|
||||
}) {
|
||||
const headers: Header<ScoreDataset<"museca:Single">[0]>[] = [
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near-Miss", "Nr. Ms.", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["KtRating", "KtRating", NumericSOV(x => x.calculatedData.ktRating ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (userCol) {
|
||||
headers.unshift(["User", "User", StrSOV(x => x.__related.user.username)]);
|
||||
}
|
||||
|
||||
return (
|
||||
<TachiTable
|
||||
dataset={dataset}
|
||||
pageLen={pageLen}
|
||||
headers={
|
||||
[
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near-Miss", "Nr. Ms.", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["KtRating", "KtRating", NumericSOV(x => x.calculatedData.ktRating ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
] as Header<ScoreDataset<"museca:Single">[0]>[]
|
||||
}
|
||||
headers={headers}
|
||||
entryName="Scores"
|
||||
searchFunctions={CreateDefaultScoreSearchParams("museca", "Single")}
|
||||
rowFunction={sc => <Row key={sc.scoreID} sc={sc} reqUser={reqUser} />}
|
||||
rowFunction={sc => <Row key={sc.scoreID} sc={sc} reqUser={reqUser} userCol={userCol} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -51,19 +58,21 @@ export default function MusecaScoreTable({
|
||||
function Row({
|
||||
sc,
|
||||
reqUser,
|
||||
userCol,
|
||||
}: {
|
||||
sc: ScoreDataset<"museca:Single">[0];
|
||||
reqUser: PublicUserDocument;
|
||||
userCol: boolean;
|
||||
}) {
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
reqUser={reqUser}
|
||||
user={sc.__related.user}
|
||||
game={sc.game}
|
||||
thisScore={sc}
|
||||
playtype={sc.playtype}
|
||||
@@ -71,6 +80,7 @@ function Row({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{userCol && <UserCell game={sc.game} playtype={sc.playtype} user={sc.__related.user} />}
|
||||
<DifficultyCell chart={sc.__related.chart} game="museca" />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell
|
||||
|
||||
@@ -7,6 +7,7 @@ import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import UserCell from "../cells/UserCell";
|
||||
import DropdownRow from "../components/DropdownRow";
|
||||
import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
@@ -18,46 +19,59 @@ export default function SDVXScoreTable({
|
||||
reqUser,
|
||||
dataset,
|
||||
pageLen,
|
||||
userCol = false,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
dataset: ScoreDataset<"sdvx:Single">;
|
||||
pageLen?: integer;
|
||||
playtype: Playtypes["sdvx"];
|
||||
userCol?: boolean;
|
||||
}) {
|
||||
const headers: Header<ScoreDataset<"sdvx:Single">[0]>[] = [
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near - Miss", "Nr. Ms.", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["VF6", "VF6", NumericSOV(x => x.calculatedData.VF6 ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (userCol) {
|
||||
headers.unshift(["User", "User", StrSOV(x => x.__related.user.username)]);
|
||||
}
|
||||
|
||||
return (
|
||||
<TachiTable
|
||||
dataset={dataset}
|
||||
pageLen={pageLen}
|
||||
headers={
|
||||
[
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near - Miss", "Nr. Ms.", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["VF6", "VF6", NumericSOV(x => x.calculatedData.VF6 ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
] as Header<ScoreDataset<"sdvx:Single">[0]>[]
|
||||
}
|
||||
headers={headers}
|
||||
entryName="Scores"
|
||||
searchFunctions={CreateDefaultScoreSearchParams("museca", "Single")}
|
||||
rowFunction={sc => <Row key={sc.scoreID} sc={sc} reqUser={reqUser} />}
|
||||
rowFunction={sc => <Row key={sc.scoreID} sc={sc} reqUser={reqUser} userCol={userCol} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Row({ sc, reqUser }: { sc: ScoreDataset<"sdvx:Single">[0]; reqUser: PublicUserDocument }) {
|
||||
function Row({
|
||||
sc,
|
||||
reqUser,
|
||||
userCol,
|
||||
}: {
|
||||
sc: ScoreDataset<"sdvx:Single">[0];
|
||||
reqUser: PublicUserDocument;
|
||||
userCol: boolean;
|
||||
}) {
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
reqUser={reqUser}
|
||||
user={sc.__related.user}
|
||||
game={sc.game}
|
||||
thisScore={sc}
|
||||
playtype={sc.playtype}
|
||||
@@ -65,6 +79,7 @@ function Row({ sc, reqUser }: { sc: ScoreDataset<"sdvx:Single">[0]; reqUser: Pub
|
||||
/>
|
||||
}
|
||||
>
|
||||
{userCol && <UserCell game={sc.game} playtype={sc.playtype} user={sc.__related.user} />}
|
||||
<DifficultyCell chart={sc.__related.chart} game="sdvx" />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell
|
||||
|
||||
@@ -11,21 +11,21 @@ import MusecaScoreTable from "./MusecaScoreTable";
|
||||
export default function ScoreTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
reqUser,
|
||||
userCol = false,
|
||||
playtype,
|
||||
pageLen = 10,
|
||||
game,
|
||||
}: {
|
||||
dataset: ScoreDataset;
|
||||
indexCol?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
userCol?: boolean;
|
||||
pageLen?: integer;
|
||||
} & GamePT) {
|
||||
// We're just going to ignore all these errors
|
||||
// and assume we just won't make a mistake.
|
||||
// ever.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const props = { dataset, indexCol, reqUser, playtype, pageLen } as any;
|
||||
const props = { dataset, indexCol, playtype, pageLen, userCol } as any;
|
||||
if (game === "iidx") {
|
||||
return <IIDXScoreTable {...props} />;
|
||||
} else if (game === "bms") {
|
||||
|
||||
@@ -62,14 +62,14 @@ export default function UGPTStatContainer({
|
||||
}
|
||||
);
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <>{(error as any).description}</>;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<StatDisplay
|
||||
game={game}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
|
||||
export default function Divider({ className }: { className?: string }) {
|
||||
export default function Divider({ className = "my-4" }: { className?: string }) {
|
||||
return <div className={`separator separator-solid opacity-7 ${className ?? ""}`}></div>;
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ export default function LoadingWrapper({
|
||||
children: JSX.Element | JSX.Element[];
|
||||
style?: CSSProperties;
|
||||
}) {
|
||||
if (isLoading || !dataset) {
|
||||
return <Loading style={style} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <h3>An error has occured. {error.description}</h3>;
|
||||
}
|
||||
|
||||
if (isLoading || !dataset) {
|
||||
return <Loading style={style} />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ export function useZTable<D>(originalDataset: D[], providedOptions?: Partial<ZTa
|
||||
|
||||
if (sortMode !== null) {
|
||||
mutatedSet = mutatedSet.slice().sort(sortFunctions[sortMode]);
|
||||
|
||||
if (reverseSort) {
|
||||
mutatedSet.reverse();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import {
|
||||
ChartDocument,
|
||||
ClassAchievementDocument,
|
||||
ClassDelta,
|
||||
FolderDocument,
|
||||
GamePTConfig,
|
||||
Grades,
|
||||
IDStrings,
|
||||
IDStringToGame,
|
||||
@@ -121,3 +124,15 @@ export interface UGPTFolderReturns<I extends IDStrings = IDStrings> {
|
||||
charts: ChartDocument<I>[];
|
||||
pbs: PBScoreDocument<I>[];
|
||||
}
|
||||
|
||||
export interface GPTStatsReturn {
|
||||
config: GamePTConfig;
|
||||
playerCount: integer;
|
||||
chartCount: integer;
|
||||
scoreCount: integer;
|
||||
}
|
||||
|
||||
export interface RecentClassesReturn {
|
||||
classes: ClassAchievementDocument[];
|
||||
users: PublicUserDocument[];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
IDStringToGame,
|
||||
integer,
|
||||
ScoreDocument,
|
||||
PublicUserDocument,
|
||||
} from "tachi-common";
|
||||
|
||||
export type PBDataset<I extends IDStrings = IDStrings> = (PBScoreDocument<I> & {
|
||||
@@ -14,12 +15,18 @@ export type PBDataset<I extends IDStrings = IDStrings> = (PBScoreDocument<I> & {
|
||||
})[];
|
||||
|
||||
export type ScoreDataset<I extends IDStrings = IDStrings> = (ScoreDocument<I> & {
|
||||
__related: { chart: ChartDocument<I>; song: SongDocument<IDStringToGame[I]>; index: integer };
|
||||
__related: {
|
||||
chart: ChartDocument<I>;
|
||||
song: SongDocument<IDStringToGame[I]>;
|
||||
index: integer;
|
||||
user: PublicUserDocument;
|
||||
};
|
||||
})[];
|
||||
|
||||
export type FolderDataset<I extends IDStrings = IDStrings> = (ChartDocument<I> & {
|
||||
__related: {
|
||||
pb: PBScoreDocument<I> | null;
|
||||
song: SongDocument<IDStringToGame[I]>;
|
||||
user: PublicUserDocument;
|
||||
};
|
||||
})[];
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
SessionScoreInfo,
|
||||
SongDocument,
|
||||
ScoreDocument,
|
||||
PublicUserDocument,
|
||||
} from "tachi-common";
|
||||
|
||||
export function GetPBs(scoreInfo: SessionScoreInfo[]) {
|
||||
@@ -22,6 +23,16 @@ export function CreateSongMap<G extends Game = Game>(songs: SongDocument<G>[]) {
|
||||
return songMap;
|
||||
}
|
||||
|
||||
export function CreateUserMap(users: PublicUserDocument[]) {
|
||||
const userMap = new Map<integer, PublicUserDocument>();
|
||||
|
||||
for (const user of users) {
|
||||
userMap.set(user.id, user);
|
||||
}
|
||||
|
||||
return userMap;
|
||||
}
|
||||
|
||||
export function CreateChartIDMap<T extends { chartID: string }>(arr: T[]): Map<string, T> {
|
||||
const map = new Map();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IDStrings } from "tachi-common";
|
||||
|
||||
export const neutralSplashes = ["A Rhythm Game Score Manager", "The Rhythm Game Score Manager"];
|
||||
|
||||
export const loggedInSplashes = [
|
||||
@@ -67,6 +69,8 @@ export const loggedInSplashes = [
|
||||
"~𝒕𝒉𝒆 𝒈𝒆𝒂𝒓𝒔 𝒕𝒖𝒓𝒏 𝒐𝒏𝒄𝒆 𝒎𝒐𝒓𝒆~",
|
||||
"<script>alert(1)</script>",
|
||||
"come on down",
|
||||
"The preparation for a dive is always a tense time.",
|
||||
"Music Is Math",
|
||||
];
|
||||
|
||||
// Stuff that's bad for the kids, you know?
|
||||
|
||||
Reference in New Issue
Block a user