mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-29 18:37:58 +03:00
GPTCharts Update
This commit is contained in:
@@ -0,0 +1,325 @@
|
||||
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 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 SelectButton from "components/util/SelectButton";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import { Col, Row } from "react-bootstrap";
|
||||
import { useQuery } from "react-query";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import {
|
||||
ChartDocument,
|
||||
FormatDifficulty,
|
||||
GamePTConfig,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
integer,
|
||||
PBScoreDocument,
|
||||
PublicUserDocument,
|
||||
SongDocument,
|
||||
} from "tachi-common";
|
||||
import { ChartPBLeaderboardReturn, UGPTChartLeaderboardAdjacent } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { PBDataset } from "types/tables";
|
||||
import { APIFetchV1, UnsuccessfulAPIFetchResponse } from "util/api";
|
||||
import { CreateUserMap } from "util/data";
|
||||
|
||||
// This component forms a wrapper around the Real GPT Chart Page
|
||||
// which handles the case where activeChart == null.
|
||||
export default function GPTChartPage({
|
||||
chart,
|
||||
setActiveChart,
|
||||
game,
|
||||
song,
|
||||
playtype,
|
||||
allCharts,
|
||||
}: {
|
||||
song: SongDocument;
|
||||
chart: ChartDocument | null;
|
||||
setActiveChart: SetState<ChartDocument | null>;
|
||||
allCharts: ChartDocument[];
|
||||
} & GamePT) {
|
||||
const { difficulty } = useParams<{ difficulty: string }>();
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const formatSongTitle = `${song.artist} - ${song.title}`;
|
||||
const formatDiff = chart ? FormatDifficulty(chart, game) : "Loading...";
|
||||
|
||||
useSetSubheader(
|
||||
["Games", GetGameConfig(game).name, playtype, "Songs", formatSongTitle, formatDiff],
|
||||
[game, playtype, chart],
|
||||
`${formatSongTitle} (${formatDiff})`
|
||||
);
|
||||
|
||||
if (!chart) {
|
||||
setActiveChart(SelectRightChart(gptConfig, difficulty, allCharts));
|
||||
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return <InternalGPTChartPage chart={chart} game={game} song={song} playtype={playtype} />;
|
||||
}
|
||||
|
||||
interface ChartPBData {
|
||||
leaderboard: ChartPBLeaderboardReturn;
|
||||
adjacent?: UGPTChartLeaderboardAdjacent;
|
||||
playcount: integer;
|
||||
}
|
||||
|
||||
function InternalGPTChartPage({
|
||||
chart,
|
||||
game,
|
||||
song,
|
||||
playtype,
|
||||
}: {
|
||||
song: SongDocument;
|
||||
chart: ChartDocument;
|
||||
} & GamePT) {
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
const [mode, setMode] = useState<"leaderboard" | "adjacent">("leaderboard");
|
||||
|
||||
const { data, isLoading, error } = useQuery<ChartPBData, UnsuccessfulAPIFetchResponse>(
|
||||
["PBInfo", `${chart.chartID}`],
|
||||
async () => {
|
||||
const lRes = await APIFetchV1<ChartPBLeaderboardReturn>(
|
||||
`/games/${game}/${playtype}/charts/${chart.chartID}/pbs`
|
||||
);
|
||||
|
||||
if (!lRes.success) {
|
||||
throw lRes;
|
||||
}
|
||||
|
||||
const pRes = await APIFetchV1<{ count: integer }>(
|
||||
`/games/${game}/${playtype}/charts/${chart.chartID}/playcount`
|
||||
);
|
||||
|
||||
if (!pRes.success) {
|
||||
throw pRes;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
const nRes = await APIFetchV1<UGPTChartLeaderboardAdjacent>(
|
||||
`/users/${user.id}/games/${game}/${playtype}/pbs/${chart.chartID}/leaderboard-adjacent`
|
||||
);
|
||||
|
||||
if (!nRes.success) {
|
||||
return { leaderboard: lRes.body, playcount: pRes.body.count };
|
||||
}
|
||||
|
||||
return {
|
||||
leaderboard: lRes.body,
|
||||
adjacent: nRes.body,
|
||||
playcount: pRes.body.count,
|
||||
};
|
||||
}
|
||||
|
||||
return { leaderboard: lRes.body, playcount: pRes.body.count };
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (data.leaderboard.pbs.length === 0) {
|
||||
return <div className="text-center my-8">Nobody has played this chart!</div>;
|
||||
}
|
||||
|
||||
const userMap = CreateUserMap(data.leaderboard.users);
|
||||
|
||||
if (data.adjacent) {
|
||||
for (const user of data.adjacent.users) {
|
||||
userMap.set(user.id, user);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col xs={12} className="text-center mb-2">
|
||||
<h4 className="display-4">Leaderboard</h4>
|
||||
</Col>
|
||||
<Col xs={12} className="d-flex justify-content-center mb-8">
|
||||
<div className="btn-group">
|
||||
<SelectButton value={mode} setValue={setMode} id="leaderboard">
|
||||
<Icon type="trophy" />
|
||||
Best 100
|
||||
</SelectButton>
|
||||
<SelectButton value={mode} setValue={setMode} id="adjacent">
|
||||
<Icon type="user" />
|
||||
Your Position
|
||||
</SelectButton>
|
||||
</div>
|
||||
</Col>
|
||||
<TopShowcase data={data} user={user} userMap={userMap} />
|
||||
<Col xs={12} className="mt-4">
|
||||
<ChartLeaderboardTable
|
||||
{...{ data, game, playtype, user, userMap, chart, song, mode }}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function ChartLeaderboardTable({
|
||||
data,
|
||||
userMap,
|
||||
user,
|
||||
game,
|
||||
playtype,
|
||||
mode,
|
||||
chart,
|
||||
song,
|
||||
}: {
|
||||
data: ChartPBData;
|
||||
user: PublicUserDocument | null;
|
||||
userMap: Map<integer, PublicUserDocument>;
|
||||
mode: "leaderboard" | "adjacent";
|
||||
chart: ChartDocument;
|
||||
song: SongDocument;
|
||||
} & GamePT) {
|
||||
const dataset: PBDataset = useMemo(() => {
|
||||
const ds: PBDataset = [];
|
||||
|
||||
let pbs = [];
|
||||
if (mode === "leaderboard") {
|
||||
pbs = data.leaderboard.pbs;
|
||||
} else {
|
||||
pbs = [
|
||||
...data.adjacent!.adjacentAbove,
|
||||
data.adjacent!.pb,
|
||||
...data.adjacent!.adjacentBelow,
|
||||
];
|
||||
}
|
||||
|
||||
for (const pb of pbs) {
|
||||
ds.push({
|
||||
...pb,
|
||||
__related: {
|
||||
chart,
|
||||
song,
|
||||
index: pb.rankingData.rank - 1,
|
||||
user: userMap.get(pb.userID)!,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return ds;
|
||||
}, [data, mode, user]);
|
||||
|
||||
return <PBTable dataset={dataset} game={game} playtype={playtype} showChart={false} showUser />;
|
||||
}
|
||||
|
||||
function TopShowcase({
|
||||
data,
|
||||
user,
|
||||
userMap,
|
||||
}: {
|
||||
data: ChartPBData;
|
||||
user: PublicUserDocument | null;
|
||||
userMap: Map<integer, PublicUserDocument>;
|
||||
}) {
|
||||
// We have a couple of conditions.
|
||||
// User is #1: col-12 #1,
|
||||
// User has played: col-6 col-6,
|
||||
// User has not played: col-12 #1,
|
||||
|
||||
const bestPlay = data.leaderboard.pbs[0];
|
||||
const bestUser = userMap.get(bestPlay.userID)!;
|
||||
|
||||
// User hasn't played, or isn't logged in or something.
|
||||
if (!data.adjacent || user?.id === bestPlay.userID) {
|
||||
return (
|
||||
<Col xs={12}>
|
||||
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} />
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
const thisUsersPlay = data.adjacent.pb;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Col xs={12} lg={6}>
|
||||
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} />
|
||||
</Col>
|
||||
<Col xs={12} lg={6}>
|
||||
<PlayCard name="Your Score" pb={thisUsersPlay} user={user!} />
|
||||
</Col>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function PlayCard({
|
||||
pb,
|
||||
user,
|
||||
name,
|
||||
}: {
|
||||
pb: PBScoreDocument;
|
||||
user: PublicUserDocument;
|
||||
name: string;
|
||||
}) {
|
||||
return (
|
||||
<Card header={name}>
|
||||
<Row className="align-items-center">
|
||||
<Col lg={3}>
|
||||
<ProfilePicture user={user} />
|
||||
</Col>
|
||||
<Col lg={3}>
|
||||
<h4>
|
||||
<Link
|
||||
className="gentle-link"
|
||||
to={`/dashboard/users/${user.username}/games/${pb.game}/${pb.playtype}`}
|
||||
>
|
||||
{user.username}
|
||||
</Link>
|
||||
</h4>
|
||||
<strong className="display-4">#{pb.rankingData.rank}</strong>
|
||||
<span className="text-muted">/{pb.rankingData.outOf}</span>
|
||||
</Col>
|
||||
<Col lg={6}>
|
||||
<MiniTable headers={["Score Info"]}>
|
||||
<tr>
|
||||
<ScoreCell score={pb} />
|
||||
</tr>
|
||||
<tr>
|
||||
<LampCell score={pb} />
|
||||
</tr>
|
||||
</MiniTable>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectRightChart(gptConfig: GamePTConfig, chartID: string, charts: ChartDocument[]) {
|
||||
if (gptConfig.difficulties.includes(chartID as any)) {
|
||||
for (const chart of charts) {
|
||||
if (chart.difficulty === chartID && chart.isPrimary) {
|
||||
return chart;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// else, its a chart ID.
|
||||
for (const chart of charts) {
|
||||
if (chartID === chart.chartID) {
|
||||
return chart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import LinkButton from "components/util/LinkButton";
|
||||
import React, { useEffect } from "react";
|
||||
import { Button, Row } from "react-bootstrap";
|
||||
import { ChartDocument, FormatDifficulty, GetGameConfig, GetGamePTConfig } from "tachi-common";
|
||||
import { SongsReturn } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { NumericSOV } from "util/sorts";
|
||||
|
||||
export default function GPTSongPage({
|
||||
game,
|
||||
playtype,
|
||||
song,
|
||||
charts,
|
||||
setActiveChart,
|
||||
}: { setActiveChart: SetState<ChartDocument | null> } & SongsReturn & GamePT) {
|
||||
const formatSongTitle = `${song.artist} - ${song.title}`;
|
||||
|
||||
useSetSubheader(
|
||||
["Games", GetGameConfig(game).name, playtype, "Songs", formatSongTitle],
|
||||
[game, playtype],
|
||||
formatSongTitle
|
||||
);
|
||||
|
||||
setActiveChart(null);
|
||||
|
||||
return <div className="w-100 text-center">You gotta select a chart to see leaderboards!</div>;
|
||||
}
|
||||
@@ -114,7 +114,7 @@ function PreferencesForm({ reqUser }: { reqUser: PublicUserDocument }) {
|
||||
id="contentiousContent"
|
||||
checked={formik.values.contentiousContent}
|
||||
onChange={formik.handleChange}
|
||||
label="Contentious Content"
|
||||
label="Family Unfriendly Mode"
|
||||
/>
|
||||
<Form.Text>
|
||||
Show slightly less appropriate splash texts in certain places.
|
||||
|
||||
@@ -193,7 +193,6 @@ function PBsOverview({
|
||||
{...{ isLoading, error, dataset: data }}
|
||||
>
|
||||
<PBTable
|
||||
reqUser={reqUser}
|
||||
dataset={data!}
|
||||
game={game}
|
||||
showPlaycount={showPlaycount}
|
||||
@@ -276,7 +275,6 @@ function PBsSearch({
|
||||
return (
|
||||
<LoadingWrapper style={{ height: 500 }} {...{ isLoading, error, dataset: data }}>
|
||||
<PBTable
|
||||
reqUser={reqUser}
|
||||
indexCol={false}
|
||||
dataset={data!}
|
||||
game={game}
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
import PlaytypeSelect from "app/pages/dashboard/games/_game/PlaytypeSelect";
|
||||
import GPTSongsPage from "app/pages/dashboard/games/_game/_playtype/GPTSongsPage";
|
||||
import GPTChartPage from "app/pages/dashboard/games/_game/_playtype/GPTChartPage";
|
||||
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 GPTSongPage from "app/pages/dashboard/games/_game/_playtype/GPTSongPage";
|
||||
import GPTSongsPage from "app/pages/dashboard/games/_game/_playtype/GPTSongsPage";
|
||||
import { ErrorPage } from "app/pages/ErrorPage";
|
||||
import { GPTBottomNav, GPTHeaderBody } from "components/game/GPTHeader";
|
||||
import LayoutHeaderContainer from "components/layout/LayoutHeaderContainer";
|
||||
import React from "react";
|
||||
import ChartInfoFormat from "components/game/charts/ChartInfoFormat";
|
||||
import { GPTBottomNav } from "components/game/GPTHeader";
|
||||
import SongInfoFormat from "components/game/songs/SongInfoFormat";
|
||||
import Card from "components/layout/page/Card";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import Divider from "components/util/Divider";
|
||||
import LinkButton from "components/util/LinkButton";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { Col, Row } from "react-bootstrap";
|
||||
import { Redirect, Route, Switch, useParams } from "react-router-dom";
|
||||
import { FormatGame, Game, GetGameConfig } from "tachi-common";
|
||||
import {
|
||||
ChartDocument,
|
||||
FormatDifficulty,
|
||||
Game,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
} from "tachi-common";
|
||||
import { SongsReturn } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { NumericSOV } from "util/sorts";
|
||||
|
||||
export default function GameRoutes() {
|
||||
const { game } = useParams<{ game: string }>();
|
||||
@@ -60,12 +81,10 @@ function GamePlaytypeRoutes({ game }: { game: Game }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutHeaderContainer
|
||||
footer={<GPTBottomNav baseUrl={`/dashboard/games/${game}/${playtype}`} />}
|
||||
header={FormatGame(game, playtype)}
|
||||
>
|
||||
<GPTHeaderBody game={game} playtype={playtype} />
|
||||
</LayoutHeaderContainer>
|
||||
<div className="card">
|
||||
<GPTBottomNav baseUrl={`/dashboard/games/${game}/${playtype}`} />
|
||||
</div>
|
||||
<Divider />
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/games/:game/:playtype">
|
||||
<GPTMainPage game={game} playtype={playtype} />
|
||||
@@ -75,10 +94,13 @@ function GamePlaytypeRoutes({ game }: { game: Game }) {
|
||||
<GPTSongsPage game={game} playtype={playtype} />
|
||||
</Route>
|
||||
|
||||
<Route path="/dashboard/games/:game/:playtype/songs/:songID">
|
||||
<SongChartRoutes 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>
|
||||
@@ -90,3 +112,157 @@ function GamePlaytypeRoutes({ game }: { game: Game }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SongChartRoutes({ game, playtype }: GamePT) {
|
||||
const { songID } = useParams<{ songID: string }>();
|
||||
|
||||
const { data, isLoading, error } = useApiQuery<SongsReturn>(
|
||||
`/games/${game}/${playtype}/songs/${songID}`
|
||||
);
|
||||
|
||||
const { settings } = useContext(UserSettingsContext);
|
||||
|
||||
const [activeChart, setActiveChart] = useState<ChartDocument | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveChart(null);
|
||||
}, [game, playtype]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage statusCode={error.statusCode} customMessage={error.description} />;
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SongInfoHeader
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
{...data}
|
||||
activeChart={activeChart}
|
||||
setActiveChart={setActiveChart}
|
||||
/>
|
||||
<Divider />
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/games/:game/:playtype/songs/:songID">
|
||||
<GPTSongPage
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
song={data.song}
|
||||
charts={data.charts}
|
||||
setActiveChart={setActiveChart}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route exact path="/dashboard/games/:game/:playtype/songs/:songID/:difficulty">
|
||||
<GPTChartPage
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
song={data.song}
|
||||
chart={activeChart}
|
||||
setActiveChart={setActiveChart}
|
||||
allCharts={data.charts}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
<Route path="*">
|
||||
<ErrorPage statusCode={404} />
|
||||
</Route>
|
||||
</Switch>
|
||||
{settings?.preferences.developerMode && (
|
||||
<>
|
||||
<Divider />
|
||||
<Card header="Dev Info">
|
||||
<DebugContent data={data} />
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SongInfoHeader({
|
||||
game,
|
||||
playtype,
|
||||
song,
|
||||
charts,
|
||||
activeChart,
|
||||
setActiveChart,
|
||||
}: { activeChart: ChartDocument | null; setActiveChart: SetState<ChartDocument | null> } & GamePT &
|
||||
SongsReturn) {
|
||||
// @TODO: Custom game song formatting functions, since for some games
|
||||
// (like popn) players use the genres to discuss them.
|
||||
const formatSongTitle = `${song.artist} - ${song.title}`;
|
||||
|
||||
const [imgShow, setImgShow] = useState(true);
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
// accidentally O(n^2) but this is a short list so who cares
|
||||
const sortedCharts = charts
|
||||
.slice(0)
|
||||
.sort(NumericSOV(x => gptConfig.difficulties.indexOf(x.difficulty)));
|
||||
|
||||
return (
|
||||
<Card header="Song Info">
|
||||
<Row
|
||||
className="align-items-center"
|
||||
style={{
|
||||
justifyContent: "space-evenly",
|
||||
}}
|
||||
>
|
||||
{imgShow && (
|
||||
<Col xs={12} lg={3} className="text-center">
|
||||
<img
|
||||
src="https://kamaitachi.xyz/static/images/gameicons/iidx/18z.png"
|
||||
onError={() => setImgShow(false)}
|
||||
className="w-100"
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
<Col xs={12} lg={4} className="text-center">
|
||||
<SongInfoFormat {...{ game, song, chart: activeChart }} />
|
||||
</Col>
|
||||
{game !== "bms" && (
|
||||
<Col xs={12} lg={3} className="text-center">
|
||||
<h5>Charts</h5>
|
||||
<hr />
|
||||
<div className="btn-group-vertical d-flex justify-content-center">
|
||||
{sortedCharts.map(e => (
|
||||
<LinkButton
|
||||
onClick={() => setActiveChart(e)}
|
||||
className="btn-secondary"
|
||||
key={e.chartID}
|
||||
to={`/dashboard/games/${game}/${playtype}/songs/${song.id}/${e.difficulty}`}
|
||||
style={{
|
||||
backgroundColor: gptConfig.difficultyColours[e.difficulty]
|
||||
? ChangeOpacity(
|
||||
gptConfig.difficultyColours[e.difficulty]!,
|
||||
activeChart?.chartID === e.chartID ? 0.4 : 0.2
|
||||
)
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
{activeChart?.chartID === e.chartID ? (
|
||||
<strong>{FormatDifficulty(e, game)}</strong>
|
||||
) : (
|
||||
FormatDifficulty(e, game)
|
||||
)}
|
||||
</LinkButton>
|
||||
))}
|
||||
</div>
|
||||
</Col>
|
||||
)}
|
||||
{activeChart && (
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
<ChartInfoFormat playtype={playtype} chart={activeChart} game={game} />
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,45 +1,7 @@
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import QuickTooltip from "components/layout/misc/QuickTooltip";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
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 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 { GamePT } from "types/react";
|
||||
|
||||
export default function ChartInfoFormat({
|
||||
chart,
|
||||
game,
|
||||
playtype,
|
||||
}: { chart: ChartDocument } & GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const withTierlists = gptConfig.tierlists.filter(e => chart.tierlistInfo[e]);
|
||||
|
||||
const { data, isLoading, error } = useApiQuery<FolderDocument[]>(
|
||||
`/games/${game}/${playtype}/charts/${chart.chartID}/folders`
|
||||
);
|
||||
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
if (error) {
|
||||
<ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Row
|
||||
className="text-center align-items-center"
|
||||
style={{
|
||||
paddingTop: "2.2rem",
|
||||
justifyContent: "space-evenly",
|
||||
}}
|
||||
>
|
||||
<Col xs={12} lg={3}>
|
||||
<h4>Appears In</h4>
|
||||
{data.length !== 0 ? (
|
||||
data.map(e => (
|
||||
<li key={e.folderID}>
|
||||
{user ? (
|
||||
<Link
|
||||
className="gentle-link"
|
||||
to={`/dashboard/users/${user.username}/games/${game}/${playtype}/folders/${e.folderID}`}
|
||||
>
|
||||
{e.title}
|
||||
</Link>
|
||||
) : (
|
||||
<span>{e.title}</span>
|
||||
)}
|
||||
</li>
|
||||
))
|
||||
) : (
|
||||
<Muted>No folders...</Muted>
|
||||
)}
|
||||
</Col>
|
||||
<Col xs={12} lg={4}>
|
||||
<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})</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>
|
||||
))}
|
||||
</MiniTable>
|
||||
<Muted>Disagree with these tiers? Let us know in the discord.</Muted>
|
||||
</>
|
||||
) : (
|
||||
<Muted>No tierlist info.</Muted>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function ChartInfoMiddle({ game, chart }: { chart: ChartDocument; game: Game }) {
|
||||
// someday
|
||||
|
||||
return <></>;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { ChartDocument, FormatDifficulty, Game, SongDocument } from "tachi-common";
|
||||
|
||||
export default function DefaultSongChartInfoFormat({
|
||||
song,
|
||||
chart,
|
||||
game,
|
||||
}: {
|
||||
song: SongDocument;
|
||||
chart: ChartDocument | null;
|
||||
game: Game;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<h4>
|
||||
{song.artist} - {song.title}
|
||||
</h4>
|
||||
{chart && <h5>({FormatDifficulty(chart, game)})</h5>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import { ChartDocument, FormatDifficulty, Game, SongDocument } from "tachi-common";
|
||||
import { FormatBMSTables } from "util/misc";
|
||||
import DefaultSongChartInfoFormat from "./DefaultSongInfoFormat";
|
||||
|
||||
export default function SongChartInfoFormat({
|
||||
song: tempSong,
|
||||
chart: tempChart,
|
||||
game,
|
||||
}: {
|
||||
song: SongDocument;
|
||||
chart: ChartDocument | null;
|
||||
game: Game;
|
||||
}) {
|
||||
// lazy casting hack to avoid IDStrings
|
||||
const props = { song: tempSong, chart: tempChart, game } as any;
|
||||
const { song, chart } = props;
|
||||
|
||||
if (game === "bms") {
|
||||
if (!chart) {
|
||||
return (
|
||||
<>
|
||||
<h4>{song.data.genre}</h4>
|
||||
<h4 style={{ fontSize: "2.5rem", fontWeight: "bold" }}>{song.title}</h4>
|
||||
<h4>{song.artist}</h4>
|
||||
</>
|
||||
);
|
||||
}
|
||||
const hasLevel = chart.data.tableFolders.length > 0;
|
||||
|
||||
const levelText = hasLevel ? FormatBMSTables(chart.data.tableFolders) : "No Level";
|
||||
|
||||
return (
|
||||
<>
|
||||
<h4>{song.data.genre}</h4>
|
||||
<h4 style={{ fontSize: "2.5rem", fontWeight: "bold" }}>{song.title}</h4>
|
||||
<h4>{song.artist}</h4>
|
||||
{chart && <h5>({levelText})</h5>}
|
||||
</>
|
||||
);
|
||||
} else if (game === "iidx") {
|
||||
return (
|
||||
<>
|
||||
<h4>{song.data.genre}</h4>
|
||||
<h4 style={{ fontSize: "2.5rem", fontWeight: "bold" }}>{song.title}</h4>
|
||||
<h4>{song.artist}</h4>
|
||||
{chart && <h5>({FormatDifficulty(chart, game)})</h5>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <DefaultSongChartInfoFormat {...props} />;
|
||||
}
|
||||
@@ -1,23 +1,18 @@
|
||||
import ApiError from "components/util/ApiError";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import HasDevModeOn from "components/util/HasDevModeOn";
|
||||
import Icon from "components/util/Icon";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import DropdownStructure from "./components/DropdownStructure";
|
||||
import {
|
||||
PublicUserDocument,
|
||||
ChartDocument,
|
||||
ScoreDocument,
|
||||
PBScoreDocument,
|
||||
IDStrings,
|
||||
} from "tachi-common";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import Loading from "components/util/Loading";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import { ChartDocument, IDStrings, integer, PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import DropdownStructure from "./components/DropdownStructure";
|
||||
import GenericScoreContentDropdown from "./components/GenericScoreContentDropdown";
|
||||
import PlayHistory from "./components/PlayHistory";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import HasDevModeOn from "components/util/HasDevModeOn";
|
||||
|
||||
export interface ScoreState {
|
||||
highlight: boolean;
|
||||
@@ -33,13 +28,13 @@ export interface ScoreDropdownProps<I extends IDStrings = IDStrings> {
|
||||
export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
game,
|
||||
playtype,
|
||||
reqUser,
|
||||
chart,
|
||||
scoreState,
|
||||
defaultView = "pb",
|
||||
userID,
|
||||
DocComponent = GenericScoreContentDropdown,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
userID: integer;
|
||||
chart: ChartDocument;
|
||||
scoreState: ScoreState;
|
||||
defaultView?: "pb" | "scorePB" | "lampPB" | "history" | "debug";
|
||||
@@ -55,12 +50,12 @@ export default function GenericPBDropdown<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/${userID}/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/${userID}/games/${game}/${playtype}/scores/${chart.chartID}`);
|
||||
|
||||
const currentScoreDoc: ScoreDocument<I> | PBScoreDocument<I> | null = useMemo(() => {
|
||||
if (!data) {
|
||||
@@ -73,7 +68,7 @@ export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
// scores have more information than PBs.
|
||||
// In this case, the PB is only composed of one score,
|
||||
// so we should default to this instead.
|
||||
return data.scores[0];
|
||||
return data.scores.filter(e => e.scoreID === data.pb.composedFrom.lampPB)[0];
|
||||
}
|
||||
return data.pb;
|
||||
}
|
||||
@@ -89,7 +84,7 @@ export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
}, [view, data]);
|
||||
|
||||
if (error) {
|
||||
return <>An error has occured. Whoops.</>;
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from "react";
|
||||
import { ChartDocument, PBScoreDocument, PublicUserDocument, ScoreDocument } from "tachi-common";
|
||||
import {
|
||||
ChartDocument,
|
||||
integer,
|
||||
PBScoreDocument,
|
||||
PublicUserDocument,
|
||||
ScoreDocument,
|
||||
} from "tachi-common";
|
||||
import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import DropdownScoreButtons from "./components/DropdownScoreButtons";
|
||||
@@ -10,11 +16,11 @@ import GenericPBDropdown, { ScoreState } from "./GenericPBDropdown";
|
||||
export default function IIDXPBDropdown({
|
||||
game,
|
||||
playtype,
|
||||
reqUser,
|
||||
chart,
|
||||
scoreState,
|
||||
userID,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
userID: integer;
|
||||
chart: ChartDocument<"iidx:SP" | "iidx:DP">;
|
||||
scoreState: {
|
||||
highlight: boolean;
|
||||
@@ -23,7 +29,7 @@ export default function IIDXPBDropdown({
|
||||
} & GamePT) {
|
||||
return (
|
||||
<GenericPBDropdown
|
||||
{...{ game, playtype, chart, reqUser, scoreState }}
|
||||
{...{ game, playtype, chart, userID, scoreState }}
|
||||
DocComponent={DocumentDisplay}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -94,7 +94,7 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={data}
|
||||
reqUser={reqUser}
|
||||
userID={score.userID}
|
||||
game={game}
|
||||
playtype={data.playtype}
|
||||
scoreState={scoreState}
|
||||
|
||||
@@ -106,7 +106,7 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={data}
|
||||
reqUser={reqUser}
|
||||
userID={score.userID}
|
||||
game={game}
|
||||
playtype={data.playtype}
|
||||
scoreState={scoreState}
|
||||
|
||||
@@ -117,7 +117,7 @@ function Row({
|
||||
dropdown={
|
||||
<IIDXPBDropdown
|
||||
chart={data}
|
||||
reqUser={reqUser}
|
||||
userID={score.userID}
|
||||
game={game}
|
||||
playtype={data.playtype}
|
||||
scoreState={scoreState}
|
||||
|
||||
@@ -89,7 +89,7 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={data}
|
||||
reqUser={reqUser}
|
||||
userID={score.userID}
|
||||
game={game}
|
||||
playtype={data.playtype}
|
||||
scoreState={scoreState}
|
||||
|
||||
@@ -88,7 +88,7 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={data}
|
||||
reqUser={reqUser}
|
||||
userID={score.userID}
|
||||
game={game}
|
||||
playtype={data.playtype}
|
||||
scoreState={scoreState}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from "react";
|
||||
import { Game, IDStrings, ScoreCalculatedDataLookup, ScoreDocument } from "tachi-common";
|
||||
import {
|
||||
Game,
|
||||
IDStrings,
|
||||
PBScoreDocument,
|
||||
ScoreCalculatedDataLookup,
|
||||
ScoreDocument,
|
||||
} from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import BMSScoreCoreCells from "./BMSScoreCoreCells";
|
||||
import GenericScoreCoreCells from "./GenericScoreCoreCells";
|
||||
@@ -12,7 +18,7 @@ export default function ScoreCoreCells({
|
||||
score,
|
||||
rating,
|
||||
}: {
|
||||
score: ScoreDocument;
|
||||
score: ScoreDocument | PBScoreDocument;
|
||||
rating: ScoreCalculatedDataLookup[IDStrings];
|
||||
game: Game;
|
||||
}) {
|
||||
|
||||
@@ -36,12 +36,10 @@ export default function BMSPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"bms:7K" | "bms:14K">;
|
||||
indexCol?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
showPlaycount?: boolean;
|
||||
playtype: "7K" | "14K";
|
||||
}) {
|
||||
@@ -110,7 +108,6 @@ export default function BMSPBTable({
|
||||
<Row
|
||||
pb={pb}
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
showPlaycount={showPlaycount}
|
||||
indexCol={indexCol}
|
||||
/>
|
||||
@@ -122,13 +119,11 @@ export default function BMSPBTable({
|
||||
function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
reqUser,
|
||||
showPlaycount,
|
||||
}: {
|
||||
pb: PBDataset<"bms:7K" | "bms:14K">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
@@ -137,7 +132,7 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={pb.__related.chart}
|
||||
reqUser={reqUser}
|
||||
userID={pb.userID}
|
||||
game={pb.game}
|
||||
playtype={pb.playtype}
|
||||
scoreState={scoreState}
|
||||
|
||||
@@ -24,7 +24,6 @@ export default function GenericPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
game,
|
||||
playtype,
|
||||
showScore,
|
||||
@@ -32,7 +31,6 @@ export default function GenericPBTable({
|
||||
dataset: PBDataset;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
game: Game;
|
||||
playtype: Playtype;
|
||||
showScore?: boolean;
|
||||
@@ -73,7 +71,6 @@ export default function GenericPBTable({
|
||||
<Row
|
||||
pb={pb}
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
indexCol={indexCol}
|
||||
showScore={showScore}
|
||||
showPlaycount={showPlaycount}
|
||||
@@ -86,14 +83,12 @@ export default function GenericPBTable({
|
||||
function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
reqUser,
|
||||
showScore,
|
||||
showPlaycount,
|
||||
}: {
|
||||
pb: PBDataset[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
showScore?: boolean;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
@@ -103,13 +98,12 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={pb.__related.chart}
|
||||
reqUser={reqUser}
|
||||
userID={pb.userID}
|
||||
game={pb.game}
|
||||
playtype={pb.playtype}
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game={pb.game} chart={pb.__related.chart} />
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import { nanoid } from "nanoid";
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { PublicUserDocument, ScoreCalculatedDataLookup } from "tachi-common";
|
||||
import { FormatDifficulty } from "tachi-common/js/utils/util";
|
||||
import { PBDataset } from "types/tables";
|
||||
import { IsNullish } from "util/misc";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { HumanFriendlyStrToGradeIndex, HumanFriendlyStrToLampIndex } from "util/str-to-num";
|
||||
import { CreateDefaultPBSearchParams } from "util/tables/create-search";
|
||||
import { GetPBLeadingHeaders } from "util/tables/get-pb-leaders";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IIDXLampCell from "../cells/IIDXLampCell";
|
||||
@@ -17,36 +17,40 @@ import RankingCell from "../cells/RankingCell";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
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";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
import IIDXPBDropdown from "../dropdowns/IIDXPBDropdown";
|
||||
import IndicatorHeader from "../headers/IndicatorHeader";
|
||||
|
||||
export default function IIDXPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
showUser = false,
|
||||
showChart = true,
|
||||
alg,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"iidx:SP" | "iidx:DP">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
showUser?: boolean;
|
||||
showChart?: boolean;
|
||||
alg?: ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"];
|
||||
playtype: "SP" | "DP";
|
||||
}) {
|
||||
const defaultRating = useScoreRatingAlg<"iidx:SP" | "iidx:DP">("iidx", playtype);
|
||||
|
||||
console.log(alg, defaultRating);
|
||||
|
||||
const [rating, setRating] = useState<ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"]>(
|
||||
alg ?? defaultRating
|
||||
);
|
||||
|
||||
const headers: Header<PBDataset<"iidx:SP" | "iidx:DP">[0]>[] = [
|
||||
[
|
||||
...GetPBLeadingHeaders(showUser, showChart, [
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(
|
||||
@@ -55,9 +59,7 @@ export default function IIDXPBTable({
|
||||
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)],
|
||||
@@ -69,7 +71,7 @@ export default function IIDXPBTable({
|
||||
<SelectableRating<"iidx:SP" | "iidx:DP">
|
||||
key={nanoid()}
|
||||
game="iidx"
|
||||
playtype="SP"
|
||||
playtype={playtype}
|
||||
rating={rating}
|
||||
setRating={setRating}
|
||||
{...thProps}
|
||||
@@ -84,10 +86,16 @@ export default function IIDXPBTable({
|
||||
headers.push(["Playcount", "Plays", NumericSOV(x => x.__playcount ?? 0)]);
|
||||
}
|
||||
|
||||
// if (hideRankingCell) {
|
||||
|
||||
// }
|
||||
|
||||
if (indexCol) {
|
||||
headers.unshift(["#", "#", NumericSOV(x => x.__related.index)]);
|
||||
}
|
||||
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
return (
|
||||
<TachiTable
|
||||
dataset={dataset}
|
||||
@@ -99,10 +107,12 @@ export default function IIDXPBTable({
|
||||
<Row
|
||||
pb={pb}
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
showPlaycount={showPlaycount}
|
||||
indexCol={indexCol}
|
||||
rating={rating}
|
||||
showUser={showUser}
|
||||
showChart={showChart}
|
||||
user={user}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -112,24 +122,29 @@ export default function IIDXPBTable({
|
||||
function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
reqUser,
|
||||
rating,
|
||||
showPlaycount,
|
||||
showChart,
|
||||
showUser,
|
||||
user,
|
||||
}: {
|
||||
pb: PBDataset<"iidx:SP" | "iidx:DP">[0];
|
||||
indexCol: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
showPlaycount: boolean;
|
||||
showUser: boolean;
|
||||
showChart: boolean;
|
||||
rating: ScoreCalculatedDataLookup["iidx:SP"];
|
||||
user: PublicUserDocument | null;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={pb.userID === user?.id ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<IIDXPBDropdown
|
||||
chart={pb.__related.chart}
|
||||
reqUser={reqUser}
|
||||
userID={pb.userID}
|
||||
game="iidx"
|
||||
playtype={pb.playtype}
|
||||
scoreState={scoreState}
|
||||
@@ -137,9 +152,27 @@ function Row({
|
||||
}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell chart={pb.__related.chart} game={"iidx"} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="iidx" />
|
||||
{showUser && showChart && (
|
||||
<>
|
||||
<UserCell game="iidx" playtype={pb.playtype} user={pb.__related.user!} />
|
||||
<DifficultyCell chart={pb.__related.chart} game={"iidx"} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="iidx" />
|
||||
</>
|
||||
)}
|
||||
{showUser && !showChart && (
|
||||
<>
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<UserCell game="iidx" playtype={pb.playtype} user={pb.__related.user!} />
|
||||
</>
|
||||
)}
|
||||
{!showUser && showChart && (
|
||||
<>
|
||||
<DifficultyCell chart={pb.__related.chart} game={"iidx"} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="iidx" />
|
||||
</>
|
||||
)}
|
||||
<ScoreCell score={pb} />
|
||||
<DeltaCell
|
||||
game="iidx"
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import { PublicUserDocument } from "tachi-common";
|
||||
import { PBDataset } from "types/tables";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { HumanFriendlyStrToGradeIndex, HumanFriendlyStrToLampIndex } from "util/str-to-num";
|
||||
import { CreateDefaultPBSearchParams } from "util/tables/create-search";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndexCell from "../cells/IndexCell";
|
||||
@@ -24,13 +23,11 @@ export default function MusecaPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"museca:Single">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
playtype: "Single";
|
||||
}) {
|
||||
const headers: Header<PBDataset<"museca:Single">[0]>[] = [
|
||||
@@ -64,7 +61,6 @@ export default function MusecaPBTable({
|
||||
<Row
|
||||
pb={pb}
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
indexCol={indexCol}
|
||||
showPlaycount={showPlaycount}
|
||||
/>
|
||||
@@ -76,13 +72,11 @@ export default function MusecaPBTable({
|
||||
function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
reqUser,
|
||||
showPlaycount,
|
||||
}: {
|
||||
pb: PBDataset<"museca:Single">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
@@ -91,7 +85,7 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={pb.__related.chart}
|
||||
reqUser={reqUser}
|
||||
userID={pb.userID}
|
||||
game={pb.game}
|
||||
playtype={pb.playtype}
|
||||
scoreState={scoreState}
|
||||
|
||||
@@ -11,23 +11,25 @@ import SDVXPBTable from "./SDVXPBTable";
|
||||
export default function PBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
reqUser,
|
||||
playtype,
|
||||
showPlaycount,
|
||||
game,
|
||||
alg,
|
||||
showChart = true,
|
||||
showUser = false,
|
||||
}: {
|
||||
dataset: PBDataset;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
alg?: ScoreCalculatedDataLookup[IDStrings];
|
||||
showChart?: boolean;
|
||||
showUser?: boolean;
|
||||
} & 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, showPlaycount, alg } as any;
|
||||
const props = { dataset, indexCol, playtype, showPlaycount, alg, showChart, showUser } as any;
|
||||
|
||||
if (game === "iidx") {
|
||||
return <IIDXPBTable {...props} />;
|
||||
|
||||
@@ -23,13 +23,11 @@ export default function SDVXPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"sdvx:Single">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
playtype: "7K" | "14K";
|
||||
}) {
|
||||
const headers: Header<PBDataset<"sdvx:Single">[0]>[] = [
|
||||
@@ -63,7 +61,6 @@ export default function SDVXPBTable({
|
||||
<Row
|
||||
pb={pb}
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
indexCol={indexCol}
|
||||
showPlaycount={showPlaycount}
|
||||
/>
|
||||
@@ -76,12 +73,10 @@ function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
showPlaycount,
|
||||
reqUser,
|
||||
}: {
|
||||
pb: PBDataset<"sdvx:Single">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
@@ -90,13 +85,12 @@ function Row({
|
||||
dropdown={
|
||||
<GenericPBDropdown
|
||||
chart={pb.__related.chart}
|
||||
reqUser={reqUser}
|
||||
userID={pb.userID}
|
||||
game={pb.game}
|
||||
playtype={pb.playtype}
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game="sdvx" chart={pb.__related.chart} />
|
||||
|
||||
@@ -87,7 +87,6 @@ function Row({
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import useScoreRatingAlg from "components/util/useScoreRatingAlg";
|
||||
import { nanoid } from "nanoid";
|
||||
import React, { useState } from "react";
|
||||
import { integer, Playtypes, PublicUserDocument, ScoreCalculatedDataLookup } from "tachi-common";
|
||||
import { integer, Playtypes, ScoreCalculatedDataLookup } from "tachi-common";
|
||||
import { ScoreDataset } from "types/tables";
|
||||
import { Playtype } from "types/tachi";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
@@ -108,7 +108,6 @@ function Row({
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
|
||||
dropdown={
|
||||
<IIDXScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
|
||||
@@ -68,7 +68,6 @@ function Row({
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { CSSProperties } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { JustChildren } from "types/react";
|
||||
|
||||
@@ -6,12 +6,16 @@ export default function LinkButton({
|
||||
to,
|
||||
className = "btn-primary",
|
||||
children,
|
||||
style,
|
||||
onClick,
|
||||
}: {
|
||||
to: string;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
onClick?: () => void;
|
||||
} & JustChildren) {
|
||||
return (
|
||||
<Link to={to} className={`btn ${className}`}>
|
||||
<Link onClick={onClick} to={to} className={`btn ${className}`} style={style}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
|
||||
@@ -136,3 +136,20 @@ export interface RecentClassesReturn {
|
||||
classes: ClassAchievementDocument[];
|
||||
users: PublicUserDocument[];
|
||||
}
|
||||
|
||||
export interface SongsReturn<I extends IDStrings = IDStrings> {
|
||||
song: SongDocument<IDStringToGame[I]>;
|
||||
charts: ChartDocument<I>[];
|
||||
}
|
||||
|
||||
export interface ChartPBLeaderboardReturn<I extends IDStrings = IDStrings> {
|
||||
users: PublicUserDocument[];
|
||||
pbs: PBScoreDocument<I>[];
|
||||
}
|
||||
|
||||
export interface UGPTChartLeaderboardAdjacent<I extends IDStrings = IDStrings> {
|
||||
users: PublicUserDocument[];
|
||||
pb: PBScoreDocument<I>;
|
||||
adjacentAbove: PBScoreDocument<I>[];
|
||||
adjacentBelow: PBScoreDocument<I>[];
|
||||
}
|
||||
|
||||
@@ -10,7 +10,12 @@ import {
|
||||
} from "tachi-common";
|
||||
|
||||
export type PBDataset<I extends IDStrings = IDStrings> = (PBScoreDocument<I> & {
|
||||
__related: { chart: ChartDocument<I>; song: SongDocument<IDStringToGame[I]>; index: integer };
|
||||
__related: {
|
||||
chart: ChartDocument<I>;
|
||||
song: SongDocument<IDStringToGame[I]>;
|
||||
index: integer;
|
||||
user?: PublicUserDocument;
|
||||
};
|
||||
__playcount?: integer;
|
||||
})[];
|
||||
|
||||
@@ -30,3 +35,9 @@ export type FolderDataset<I extends IDStrings = IDStrings> = (ChartDocument<I> &
|
||||
user: PublicUserDocument;
|
||||
};
|
||||
})[];
|
||||
|
||||
export type ChartLeaderboardDataset<I extends IDStrings = IDStrings> = (PBScoreDocument<I> & {
|
||||
__related: {
|
||||
user: PublicUserDocument;
|
||||
};
|
||||
})[];
|
||||
|
||||
@@ -48,9 +48,9 @@ export async function APIFetchV1<T = unknown>(
|
||||
const rj = await res.json();
|
||||
|
||||
if (!rj.success) {
|
||||
console.error(rj);
|
||||
console.warn(rj);
|
||||
} else {
|
||||
console.log(rj.description);
|
||||
console.debug(rj.description);
|
||||
}
|
||||
|
||||
if (!rj.success && displayFailure) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { IDStrings } from "tachi-common";
|
||||
|
||||
export const neutralSplashes = ["A Rhythm Game Score Manager", "The Rhythm Game Score Manager"];
|
||||
|
||||
export const loggedInSplashes = [
|
||||
|
||||
@@ -47,6 +47,7 @@ export function CreateDefaultPBSearchParams<I extends IDStrings = IDStrings>(
|
||||
percent: x => x.scoreData.percent,
|
||||
ranking: x => x.rankingData.rank,
|
||||
highlight: x => !!x.highlight,
|
||||
username: x => x.__related.user?.username ?? null,
|
||||
lamp: {
|
||||
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
|
||||
strToNum: HumanFriendlyStrToLampIndex(game, playtype),
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Header } from "components/tables/components/TachiTable";
|
||||
import IndicatorHeader from "components/tables/headers/IndicatorHeader";
|
||||
import { PBDataset } from "types/tables";
|
||||
import { StrSOV } from "util/sorts";
|
||||
|
||||
type PBHeader = Header<PBDataset[0]>;
|
||||
|
||||
export function GetPBLeadingHeaders(
|
||||
showUser: boolean,
|
||||
showChart: boolean,
|
||||
chartHeader: PBHeader
|
||||
): PBHeader[] {
|
||||
const userHeader: PBHeader = ["User", "User", StrSOV(x => x.__related.user!.username)];
|
||||
const songHeader: PBHeader = ["Song", "Song", StrSOV(x => x.__related.song.title)];
|
||||
|
||||
if (showUser && showChart) {
|
||||
return [userHeader, chartHeader, IndicatorHeader, songHeader];
|
||||
}
|
||||
if (showUser && !showChart) {
|
||||
return [IndicatorHeader, userHeader];
|
||||
}
|
||||
|
||||
return [chartHeader, IndicatorHeader, songHeader];
|
||||
}
|
||||
Reference in New Issue
Block a user