mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-13 01:38:29 +03:00
More?
This commit is contained in:
@@ -9,6 +9,7 @@ import ApiError from "components/util/ApiError";
|
||||
import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import Loading from "components/util/Loading";
|
||||
import Muted from "components/util/Muted";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
@@ -31,6 +32,7 @@ import { GamePT, SetState } from "types/react";
|
||||
import { PBDataset } from "types/tables";
|
||||
import { APIFetchV1, UnsuccessfulAPIFetchResponse } from "util/api";
|
||||
import { CreateUserMap } from "util/data";
|
||||
import { SelectRightChart } from "util/misc";
|
||||
|
||||
// This component forms a wrapper around the Real GPT Chart Page
|
||||
// which handles the case where activeChart == null.
|
||||
@@ -60,9 +62,9 @@ export default function GPTChartPage({
|
||||
`${formatSongTitle} (${formatDiff})`
|
||||
);
|
||||
|
||||
if (!chart) {
|
||||
setActiveChart(SelectRightChart(gptConfig, difficulty, allCharts));
|
||||
setActiveChart(SelectRightChart(gptConfig, difficulty, allCharts));
|
||||
|
||||
if (!chart) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
@@ -158,7 +160,12 @@ function InternalGPTChartPage({
|
||||
<Icon type="trophy" />
|
||||
Best 100
|
||||
</SelectButton>
|
||||
<SelectButton value={mode} setValue={setMode} id="adjacent">
|
||||
<SelectButton
|
||||
value={mode}
|
||||
setValue={setMode}
|
||||
id="adjacent"
|
||||
disabled={!data.adjacent}
|
||||
>
|
||||
<Icon type="user" />
|
||||
Your Position
|
||||
</SelectButton>
|
||||
@@ -241,7 +248,7 @@ function TopShowcase({
|
||||
const bestUser = userMap.get(bestPlay.userID)!;
|
||||
|
||||
// User hasn't played, or isn't logged in or something.
|
||||
if (!data.adjacent || user?.id === bestPlay.userID) {
|
||||
if (user?.id === bestPlay.userID) {
|
||||
return (
|
||||
<Col xs={12}>
|
||||
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} />
|
||||
@@ -249,6 +256,21 @@ function TopShowcase({
|
||||
);
|
||||
}
|
||||
|
||||
if (!data.adjacent) {
|
||||
return (
|
||||
<>
|
||||
<Col xs={12} lg={6}>
|
||||
<PlayCard name="Best Play" pb={bestPlay} user={bestUser} />
|
||||
</Col>
|
||||
<Col xs={12} lg={6}>
|
||||
<Card header="Your Score" className="text-center">
|
||||
<Muted>You've not played this chart.</Muted>
|
||||
</Card>
|
||||
</Col>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const thisUsersPlay = data.adjacent.pb;
|
||||
|
||||
return (
|
||||
@@ -304,22 +326,3 @@ function PlayCard({
|
||||
</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;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
import ClassBadge from "components/game/ClassBadge";
|
||||
import useSetSubheader from "components/layout/header/useSetSubheader";
|
||||
import React from "react";
|
||||
import { FormatGame, GetGameConfig } from "tachi-common";
|
||||
import IndexCell from "components/tables/cells/IndexCell";
|
||||
import UserCell from "components/tables/cells/UserCell";
|
||||
import TachiTable, { Header } from "components/tables/components/TachiTable";
|
||||
import PBTable from "components/tables/pbs/PBTable";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import Divider from "components/util/Divider";
|
||||
import Icon from "components/util/Icon";
|
||||
import Loading from "components/util/Loading";
|
||||
import Muted from "components/util/Muted";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import useScoreRatingAlg, { useProfileRatingAlg } from "components/util/useScoreRatingAlg";
|
||||
import { UGPTSettingsContext } from "context/UGPTSettingsContext";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Col, Row, Form } from "react-bootstrap";
|
||||
import { FormatGame, GetGameConfig, GetGamePTConfig } from "tachi-common";
|
||||
import { ScoreLeaderboardReturns, UserLeaderboardReturns } from "types/api-returns";
|
||||
import { GamePT } from "types/react";
|
||||
import { PBDataset, UGSDataset } from "types/tables";
|
||||
import { CreateChartMap, CreateSongMap, CreateUserMap } from "util/data";
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
|
||||
export default function GPTLeaderboardsPage({ game, playtype }: GamePT) {
|
||||
useSetSubheader(
|
||||
@@ -10,5 +31,216 @@ export default function GPTLeaderboardsPage({ game, playtype }: GamePT) {
|
||||
`${FormatGame(game, playtype)} Leaderboards`
|
||||
);
|
||||
|
||||
return <div>leaderboard</div>;
|
||||
const { settings } = useContext(UGPTSettingsContext);
|
||||
|
||||
const [mode, setMode] = useState<"profile" | "score">("profile");
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col xs={12} className="d-flex justify-content-center">
|
||||
<div className="btn-group">
|
||||
<SelectButton id="profile" value={mode} setValue={setMode}>
|
||||
<Icon type="user" />
|
||||
User Leaderboards
|
||||
</SelectButton>
|
||||
<SelectButton id="score" value={mode} setValue={setMode}>
|
||||
<Icon type="sort-numeric-up-alt" />
|
||||
Score Leaderboards
|
||||
</SelectButton>
|
||||
</div>
|
||||
</Col>
|
||||
<Col xs={12}>
|
||||
<Divider />
|
||||
{mode === "profile" ? (
|
||||
<ProfileLeaderboard game={game} playtype={playtype} />
|
||||
) : (
|
||||
<ScoreLeaderboard game={game} playtype={playtype} />
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfileLeaderboard({ game, playtype }: GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const defaultAlg = useProfileRatingAlg(game, playtype);
|
||||
|
||||
const [alg, setAlg] = useState(defaultAlg);
|
||||
|
||||
const SelectComponent =
|
||||
gptConfig.profileRatingAlgs.length > 1 ? (
|
||||
<Form.Control as="select" value={alg} onChange={e => setAlg(e.target.value as any)}>
|
||||
{gptConfig.profileRatingAlgs.map(e => (
|
||||
<option key={e} value={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
) : null;
|
||||
|
||||
const { data, isLoading, error } = useApiQuery<UserLeaderboardReturns>(
|
||||
`/games/${game}/${playtype}/leaderboard?alg=${alg}`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<>
|
||||
{SelectComponent}
|
||||
<ApiError error={error} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return (
|
||||
<>
|
||||
{SelectComponent}
|
||||
<Loading />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const userMap = CreateUserMap(data.users);
|
||||
|
||||
const userDataset: UGSDataset = [];
|
||||
|
||||
for (const [index, gs] of data.gameStats.entries()) {
|
||||
userDataset.push({
|
||||
...gs,
|
||||
__related: {
|
||||
user: userMap.get(gs.userID)!,
|
||||
index,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{SelectComponent}
|
||||
<Divider />
|
||||
<TachiTable
|
||||
dataset={userDataset}
|
||||
entryName="Rankers"
|
||||
headers={[
|
||||
["Ranking", "Rank", NumericSOV(x => x.__related.index)],
|
||||
["User", "User", StrSOV(x => x.__related.user.username)],
|
||||
...gptConfig.profileRatingAlgs.map(
|
||||
e =>
|
||||
[
|
||||
UppercaseFirst(e),
|
||||
UppercaseFirst(e),
|
||||
NumericSOV(x => x.ratings[e] ?? -Infinity),
|
||||
] as Header<UGSDataset[0]>
|
||||
),
|
||||
["Classes", "Classes"],
|
||||
]}
|
||||
rowFunction={r => (
|
||||
<tr>
|
||||
<IndexCell index={r.__related.index} />
|
||||
<UserCell game={game} playtype={playtype} user={r.__related.user} />
|
||||
{gptConfig.profileRatingAlgs.map(e => (
|
||||
<td key={e}>
|
||||
{r.ratings[e]
|
||||
? gptConfig.profileRatingAlgFormatters[e]
|
||||
? gptConfig.profileRatingAlgFormatters[e]!(r.ratings[e]!)
|
||||
: r.ratings[e]!.toFixed(2)
|
||||
: "No Data."}
|
||||
</td>
|
||||
))}
|
||||
<td>
|
||||
{Object.keys(r.classes).length === 0 ? (
|
||||
<Muted>None</Muted>
|
||||
) : (
|
||||
Object.entries(r.classes).map(([k, v]) => (
|
||||
<ClassBadge
|
||||
key={k}
|
||||
classSet={k as keyof typeof r.classes}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
classValue={v}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ScoreLeaderboard({ game, playtype }: GamePT) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const defaultAlg = useScoreRatingAlg(game, playtype);
|
||||
|
||||
const [alg, setAlg] = useState(defaultAlg);
|
||||
|
||||
const SelectComponent =
|
||||
gptConfig.scoreRatingAlgs.length > 1 ? (
|
||||
<Form.Control as="select" value={alg} onChange={e => setAlg(e.target.value as any)}>
|
||||
{gptConfig.scoreRatingAlgs.map(e => (
|
||||
<option key={e} value={e}>
|
||||
{UppercaseFirst(e)}
|
||||
</option>
|
||||
))}
|
||||
</Form.Control>
|
||||
) : null;
|
||||
|
||||
const { data, isLoading, error } = useApiQuery<ScoreLeaderboardReturns>(
|
||||
`/games/${game}/${playtype}/score-leaderboard?alg=${alg}`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<>
|
||||
{SelectComponent}
|
||||
<ApiError error={error} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data || isLoading) {
|
||||
return (
|
||||
<>
|
||||
{SelectComponent}
|
||||
<Loading />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const songMap = CreateSongMap(data.songs);
|
||||
const chartMap = CreateChartMap(data.charts);
|
||||
const userMap = CreateUserMap(data.users);
|
||||
|
||||
const pbDataset: PBDataset = [];
|
||||
|
||||
for (const [index, pb] of data.pbs.entries()) {
|
||||
pbDataset.push({
|
||||
...pb,
|
||||
__related: {
|
||||
chart: chartMap.get(pb.chartID)!,
|
||||
song: songMap.get(pb.songID)!,
|
||||
index,
|
||||
user: userMap.get(pb.userID)!,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{SelectComponent}
|
||||
<Divider />
|
||||
<PBTable
|
||||
dataset={pbDataset}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
showUser
|
||||
showChart
|
||||
indexCol
|
||||
alg={alg}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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>;
|
||||
}
|
||||
@@ -155,7 +155,7 @@ function LeaderboardsPageContent({
|
||||
header={"Leaderboard"}
|
||||
footer={
|
||||
<Link
|
||||
to={`/dashboard/games/${game}/${playtype}/leaderboard`}
|
||||
to={`/dashboard/games/${game}/${playtype}/leaderboards`}
|
||||
className="float-right"
|
||||
>
|
||||
View Global Leaderboards
|
||||
|
||||
@@ -3,7 +3,6 @@ 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 ChartInfoFormat from "components/game/charts/ChartInfoFormat";
|
||||
@@ -15,6 +14,8 @@ 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 { UGPTSettingsContext, UGPTSettingsContextProvider } from "context/UGPTSettingsContext";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { Col, Row } from "react-bootstrap";
|
||||
@@ -25,9 +26,11 @@ import {
|
||||
Game,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
UGPTSettings,
|
||||
} from "tachi-common";
|
||||
import { SongsReturn } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { NumericSOV } from "util/sorts";
|
||||
@@ -57,7 +60,9 @@ export default function GameRoutes() {
|
||||
</Route>
|
||||
|
||||
<Route path="/dashboard/games/:game/:playtype">
|
||||
<GamePlaytypeRoutes game={game} />
|
||||
<UGPTSettingsContextProvider>
|
||||
<GamePlaytypeRoutes game={game} />
|
||||
</UGPTSettingsContextProvider>
|
||||
</Route>
|
||||
|
||||
<Route path="*">
|
||||
@@ -79,6 +84,30 @@ function GamePlaytypeRoutes({ game }: { game: Game }) {
|
||||
);
|
||||
}
|
||||
|
||||
const { user } = useContext(UserContext);
|
||||
const { setSettings } = useContext(UGPTSettingsContext);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (user) {
|
||||
const settingsRes = await APIFetchV1<UGPTSettings>(
|
||||
`/users/${user.id}/games/${game}/${playtype}/settings`
|
||||
);
|
||||
|
||||
if (!settingsRes.success) {
|
||||
setSettings(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setSettings(settingsRes.body);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
setSettings(null);
|
||||
};
|
||||
}, [user, game, playtype]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
@@ -122,12 +151,13 @@ function SongChartRoutes({ game, playtype }: GamePT) {
|
||||
|
||||
const { settings } = useContext(UserSettingsContext);
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const [activeChart, setActiveChart] = useState<ChartDocument | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setActiveChart(null);
|
||||
}, [game, playtype]);
|
||||
|
||||
if (error) {
|
||||
return <ErrorPage statusCode={error.statusCode} customMessage={error.description} />;
|
||||
}
|
||||
@@ -148,12 +178,12 @@ function SongChartRoutes({ game, playtype }: GamePT) {
|
||||
<Divider />
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/games/:game/:playtype/songs/:songID">
|
||||
<GPTSongPage
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
song={data.song}
|
||||
charts={data.charts}
|
||||
setActiveChart={setActiveChart}
|
||||
{/* Select the hardest chart for this. */}
|
||||
<Redirect
|
||||
to={`/dashboard/games/${game}/${playtype}/songs/${data.song.id}/${
|
||||
data.charts.slice(0).sort(NumericSOV(x => x.levelNum, true))[0]
|
||||
.difficulty
|
||||
}`}
|
||||
/>
|
||||
</Route>
|
||||
|
||||
@@ -193,10 +223,6 @@ function SongInfoHeader({
|
||||
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);
|
||||
|
||||
@@ -165,8 +165,8 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: PublicUserDocument
|
||||
);
|
||||
|
||||
if (!settingsRes.success) {
|
||||
console.error(settingsRes);
|
||||
throw settingsRes;
|
||||
setSettings(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setSettings(settingsRes.body);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import Dropdown from "react-bootstrap/Dropdown";
|
||||
import Divider from "components/util/Divider";
|
||||
import { PublicUserDocument } from "tachi-common";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { APIFetchV1, ToAPIURL } from "util/api";
|
||||
import toast from "react-hot-toast";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import DropdownToggleOverride from "components/util/DropdownToggleOverride";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import React, { useContext } from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
import Dropdown from "react-bootstrap/Dropdown";
|
||||
import toast from "react-hot-toast";
|
||||
import { Link } from "react-router-dom";
|
||||
import { PublicUserDocument } from "tachi-common";
|
||||
import { APIFetchV1, ToAPIURL } from "util/api";
|
||||
|
||||
export function UserProfileDropdown({ user }: { user: PublicUserDocument }) {
|
||||
const { setUser } = useContext(UserContext);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SmallText from "components/util/SmallText";
|
||||
import { useZTable, ZTableSortFn } from "components/util/table/useZTable";
|
||||
import React, { useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { Button } from "react-bootstrap";
|
||||
import { integer } from "tachi-common";
|
||||
import NoDataWrapper from "./NoDataWrapper";
|
||||
@@ -8,6 +8,9 @@ import PageSelector from "./PageSelector";
|
||||
import SortableTH from "./SortableTH";
|
||||
import FilterDirectivesIndicator from "./FilterDirectivesIndicator";
|
||||
import { ComposeSearchFunction, ValueGetterOrHybrid } from "util/ztable/search";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import Icon from "components/util/Icon";
|
||||
import { CopyToClipboard } from "util/misc";
|
||||
|
||||
export interface ZTableTHProps {
|
||||
changeSort: (str: string) => void;
|
||||
@@ -123,6 +126,8 @@ export default function TachiTable<D>({
|
||||
reverseSort,
|
||||
});
|
||||
|
||||
const { settings } = useContext(UserSettingsContext);
|
||||
|
||||
return (
|
||||
<div className="justify-content-center w-100">
|
||||
<div className="row">
|
||||
@@ -156,8 +161,16 @@ export default function TachiTable<D>({
|
||||
</div>
|
||||
<div className="col-12 px-0">
|
||||
<div className="row">
|
||||
<div className="col-lg-6 align-self-center">{displayStr}</div>
|
||||
<div className="col-lg-6 text-right">
|
||||
<div className="col-lg-4 align-self-center">{displayStr}</div>
|
||||
{settings?.preferences.developerMode && (
|
||||
<div className="d-none d-lg-flex col-lg-4 justify-content-center">
|
||||
<Button onClick={() => CopyToClipboard(window)} variant="outline-info">
|
||||
<Icon type="table" />
|
||||
Export Data
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-lg-4 ml-auto text-right">
|
||||
<div className="btn-group">
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FormatBMSTables, 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 { ValueGetterOrHybrid } from "util/ztable/search";
|
||||
import BMSDifficultyCell from "../cells/BMSDifficultyCell";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
@@ -21,6 +22,7 @@ import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import IndicatorHeader from "../headers/IndicatorHeader";
|
||||
import PBLeadingRows from "./PBLeadingRows";
|
||||
|
||||
function GetBMSTableVal(chart: ChartDocument<"bms:7K" | "bms:14K">, key: string) {
|
||||
for (const table of chart.data.tableFolders) {
|
||||
@@ -37,14 +39,18 @@ export default function BMSPBTable({
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
playtype,
|
||||
showUser = false,
|
||||
showChart = true,
|
||||
}: {
|
||||
dataset: PBDataset<"bms:7K" | "bms:14K">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
playtype: "7K" | "14K";
|
||||
showUser?: boolean;
|
||||
showChart?: boolean;
|
||||
}) {
|
||||
const headers: Header<PBDataset<"bms:7K" | "bms:14K">[0]>[] = [
|
||||
[
|
||||
...GetPBLeadingHeaders(showUser, showChart, [
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(
|
||||
@@ -53,9 +59,7 @@ export default function BMSPBTable({
|
||||
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)],
|
||||
@@ -102,7 +106,7 @@ export default function BMSPBTable({
|
||||
dataset={dataset}
|
||||
headers={headers}
|
||||
entryName="PBs"
|
||||
searchFunctions={CreateDefaultPBSearchParams("bms", playtype)}
|
||||
searchFunctions={{ ...CreateDefaultPBSearchParams("bms", playtype), ...tableSearchFns }}
|
||||
defaultSortMode={indexCol ? "#" : undefined}
|
||||
rowFunction={pb => (
|
||||
<Row
|
||||
@@ -110,6 +114,8 @@ export default function BMSPBTable({
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
showPlaycount={showPlaycount}
|
||||
indexCol={indexCol}
|
||||
showUser={showUser}
|
||||
showChart={showChart}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -120,10 +126,14 @@ function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
showPlaycount,
|
||||
showUser,
|
||||
showChart,
|
||||
}: {
|
||||
pb: PBDataset<"bms:7K" | "bms:14K">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
showUser: boolean;
|
||||
showChart: boolean;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
@@ -140,9 +150,13 @@ function Row({
|
||||
}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<BMSDifficultyCell chart={pb.__related.chart} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="bms" />
|
||||
<PBLeadingRows
|
||||
pb={pb}
|
||||
scoreState={scoreState}
|
||||
showChart={showChart}
|
||||
showUser={showUser}
|
||||
overrideDiffCell={<BMSDifficultyCell chart={pb.__related.chart} />}
|
||||
/>
|
||||
<ScoreCell score={pb} />
|
||||
<DeltaCell
|
||||
game="bms"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Playtype } from "types/tachi";
|
||||
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 DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndexCell from "../cells/IndexCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
@@ -19,6 +20,7 @@ import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import IndicatorHeader from "../headers/IndicatorHeader";
|
||||
import PBLeadingRows from "./PBLeadingRows";
|
||||
|
||||
export default function GenericPBTable({
|
||||
dataset,
|
||||
@@ -27,6 +29,8 @@ export default function GenericPBTable({
|
||||
game,
|
||||
playtype,
|
||||
showScore,
|
||||
showUser = false,
|
||||
showChart = true,
|
||||
}: {
|
||||
dataset: PBDataset;
|
||||
indexCol?: boolean;
|
||||
@@ -34,13 +38,17 @@ export default function GenericPBTable({
|
||||
game: Game;
|
||||
playtype: Playtype;
|
||||
showScore?: boolean;
|
||||
showUser?: boolean;
|
||||
showChart?: boolean;
|
||||
}) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const headers: Header<PBDataset[0]>[] = [
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
...GetPBLeadingHeaders(showUser, showChart, [
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(x => x.__related.chart.levelNum),
|
||||
]),
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
[
|
||||
@@ -74,6 +82,8 @@ export default function GenericPBTable({
|
||||
indexCol={indexCol}
|
||||
showScore={showScore}
|
||||
showPlaycount={showPlaycount}
|
||||
showUser={showUser}
|
||||
showChart={showChart}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -85,11 +95,15 @@ function Row({
|
||||
indexCol,
|
||||
showScore,
|
||||
showPlaycount,
|
||||
showUser,
|
||||
showChart,
|
||||
}: {
|
||||
pb: PBDataset[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
showScore?: boolean;
|
||||
showUser: boolean;
|
||||
showChart: boolean;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
@@ -106,9 +120,12 @@ function Row({
|
||||
}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game={pb.game} chart={pb.__related.chart} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game={pb.game} />
|
||||
<PBLeadingRows
|
||||
pb={pb}
|
||||
scoreState={scoreState}
|
||||
showChart={showChart}
|
||||
showUser={showUser}
|
||||
/>
|
||||
<ScoreCell score={pb} showScore={showScore} />
|
||||
<LampCell score={pb} />
|
||||
<RatingCell score={pb} />
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { useContext, useState } from "react";
|
||||
import { PublicUserDocument, ScoreCalculatedDataLookup } from "tachi-common";
|
||||
import { PBDataset } from "types/tables";
|
||||
import { IsNullish } from "util/misc";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import { CreateDefaultPBSearchParams } from "util/tables/create-search";
|
||||
import { GetPBLeadingHeaders } from "util/tables/get-pb-leaders";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
@@ -23,6 +23,7 @@ import SelectableRating from "../components/SelectableRating";
|
||||
import TachiTable, { Header, ZTableTHProps } from "../components/TachiTable";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
import IIDXPBDropdown from "../dropdowns/IIDXPBDropdown";
|
||||
import PBLeadingRows from "./PBLeadingRows";
|
||||
|
||||
export default function IIDXPBTable({
|
||||
dataset,
|
||||
@@ -140,7 +141,7 @@ function Row({
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={pb.userID === user?.id ? "highlighted-row" : ""}
|
||||
// className={pb.userID === user?.id ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<IIDXPBDropdown
|
||||
chart={pb.__related.chart}
|
||||
@@ -152,27 +153,7 @@ function Row({
|
||||
}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
{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" />
|
||||
</>
|
||||
)}
|
||||
<PBLeadingRows {...{ showUser, showChart, pb, scoreState }} />
|
||||
<ScoreCell score={pb} />
|
||||
<DeltaCell
|
||||
game="iidx"
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PublicUserDocument } from "tachi-common";
|
||||
import { PBDataset } from "types/tables";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { CreateDefaultPBSearchParams } from "util/tables/create-search";
|
||||
import { GetPBLeadingHeaders } from "util/tables/get-pb-leaders";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndexCell from "../cells/IndexCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
@@ -18,22 +19,29 @@ import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import IndicatorHeader from "../headers/IndicatorHeader";
|
||||
import PBLeadingRows from "./PBLeadingRows";
|
||||
|
||||
export default function MusecaPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
showUser = false,
|
||||
showChart = true,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"museca:Single">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
showUser?: boolean;
|
||||
showChart?: boolean;
|
||||
playtype: "Single";
|
||||
}) {
|
||||
const headers: Header<PBDataset<"museca:Single">[0]>[] = [
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
...GetPBLeadingHeaders(showUser, showChart, [
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(x => x.__related.chart.levelNum),
|
||||
]),
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near - Miss", "Nr. Ms.", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
@@ -63,6 +71,8 @@ export default function MusecaPBTable({
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
indexCol={indexCol}
|
||||
showPlaycount={showPlaycount}
|
||||
showChart={showChart}
|
||||
showUser={showUser}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -73,10 +83,14 @@ function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
showPlaycount,
|
||||
showChart,
|
||||
showUser,
|
||||
}: {
|
||||
pb: PBDataset<"museca:Single">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
showChart: boolean;
|
||||
showUser: boolean;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
@@ -93,9 +107,7 @@ function Row({
|
||||
}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game="museca" chart={pb.__related.chart} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="museca" />
|
||||
<PBLeadingRows {...{ showUser, showChart, pb, scoreState }} />
|
||||
<MillionsScoreCell score={pb} />
|
||||
<MusecaJudgementCell score={pb} />
|
||||
<LampCell score={pb} />
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import { SetState } from "types/react";
|
||||
import { PBDataset } from "types/tables";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import UserCell from "../cells/UserCell";
|
||||
|
||||
export default function PBLeadingRows({
|
||||
showUser,
|
||||
showChart,
|
||||
pb,
|
||||
scoreState,
|
||||
overrideDiffCell,
|
||||
}: {
|
||||
showUser: boolean;
|
||||
showChart: boolean;
|
||||
pb: PBDataset[0];
|
||||
scoreState: { highlight: boolean; setHighlight: SetState<boolean> };
|
||||
overrideDiffCell?: JSX.Element;
|
||||
}) {
|
||||
const game = pb.game;
|
||||
|
||||
const diffCell = overrideDiffCell || <DifficultyCell chart={pb.__related.chart} game={game} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showUser && showChart && (
|
||||
<>
|
||||
<UserCell game={game} playtype={pb.playtype} user={pb.__related.user!} />
|
||||
{diffCell}
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game={game} />
|
||||
</>
|
||||
)}
|
||||
{showUser && !showChart && (
|
||||
<>
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<UserCell game={game} playtype={pb.playtype} user={pb.__related.user!} />
|
||||
</>
|
||||
)}
|
||||
{!showUser && showChart && (
|
||||
<>
|
||||
{diffCell}
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game={game} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { PBDataset } from "types/tables";
|
||||
import { IsNullish } from "util/misc";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { CreateDefaultPBSearchParams } from "util/tables/create-search";
|
||||
import { GetPBLeadingHeaders } from "util/tables/get-pb-leaders";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import IndexCell from "../cells/IndexCell";
|
||||
import IndicatorsCell from "../cells/IndicatorsCell";
|
||||
@@ -18,22 +19,29 @@ import TachiTable, { Header } from "../components/TachiTable";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import IndicatorHeader from "../headers/IndicatorHeader";
|
||||
import PBLeadingRows from "./PBLeadingRows";
|
||||
|
||||
export default function SDVXPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
playtype,
|
||||
showUser = false,
|
||||
showChart = true,
|
||||
}: {
|
||||
dataset: PBDataset<"sdvx:Single">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
playtype: "7K" | "14K";
|
||||
showUser?: boolean;
|
||||
showChart?: boolean;
|
||||
}) {
|
||||
const headers: Header<PBDataset<"sdvx:Single">[0]>[] = [
|
||||
["Chart", "Chart", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
IndicatorHeader,
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
...GetPBLeadingHeaders(showUser, showChart, [
|
||||
"Chart",
|
||||
"Chart",
|
||||
NumericSOV(x => x.__related.chart.levelNum),
|
||||
]),
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near - Miss", "Nr. Ms.", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
@@ -63,6 +71,8 @@ export default function SDVXPBTable({
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
indexCol={indexCol}
|
||||
showPlaycount={showPlaycount}
|
||||
showChart={showChart}
|
||||
showUser={showUser}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -73,10 +83,14 @@ function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
showPlaycount,
|
||||
showChart,
|
||||
showUser,
|
||||
}: {
|
||||
pb: PBDataset<"sdvx:Single">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
showChart: boolean;
|
||||
showUser: boolean;
|
||||
}) {
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
@@ -93,9 +107,7 @@ function Row({
|
||||
}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game="sdvx" chart={pb.__related.chart} />
|
||||
<IndicatorsCell highlight={scoreState.highlight} />
|
||||
<TitleCell song={pb.__related.song} chart={pb.__related.chart} game="sdvx" />
|
||||
<PBLeadingRows {...{ showUser, showChart, pb, scoreState }} />
|
||||
<MillionsScoreCell score={pb} />
|
||||
<SDVXJudgementCell score={pb} />
|
||||
<LampCell score={pb} />
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import React from "react";
|
||||
import { UserSettingsContext } from "context/UserSettingsContext";
|
||||
import React, { useContext } from "react";
|
||||
import { UnsuccessfulAPIFetchResponse } from "util/api";
|
||||
|
||||
export default function ApiError({ error }: { error: UnsuccessfulAPIFetchResponse }) {
|
||||
return <div>An error has occured: {error.description}</div>;
|
||||
const { settings } = useContext(UserSettingsContext);
|
||||
|
||||
return (
|
||||
<div>
|
||||
An error has occured
|
||||
{settings?.preferences.developerMode ? ` (${error.description})` : ""}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,3 +153,15 @@ export interface UGPTChartLeaderboardAdjacent<I extends IDStrings = IDStrings> {
|
||||
adjacentAbove: PBScoreDocument<I>[];
|
||||
adjacentBelow: PBScoreDocument<I>[];
|
||||
}
|
||||
|
||||
export interface ScoreLeaderboardReturns<I extends IDStrings = IDStrings> {
|
||||
users: PublicUserDocument[];
|
||||
songs: SongDocument<IDStringToGame[I]>[];
|
||||
charts: ChartDocument<I>[];
|
||||
pbs: PBScoreDocument<I>[];
|
||||
}
|
||||
|
||||
export interface UserLeaderboardReturns<I extends IDStrings = IDStrings> {
|
||||
users: PublicUserDocument[];
|
||||
gameStats: UserGameStats<I>[];
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
integer,
|
||||
ScoreDocument,
|
||||
PublicUserDocument,
|
||||
UserGameStats,
|
||||
} from "tachi-common";
|
||||
|
||||
export type PBDataset<I extends IDStrings = IDStrings> = (PBScoreDocument<I> & {
|
||||
@@ -41,3 +42,10 @@ export type ChartLeaderboardDataset<I extends IDStrings = IDStrings> = (PBScoreD
|
||||
user: PublicUserDocument;
|
||||
};
|
||||
})[];
|
||||
|
||||
export type UGSDataset<I extends IDStrings = IDStrings> = (UserGameStats<I> & {
|
||||
__related: {
|
||||
user: PublicUserDocument;
|
||||
index: integer;
|
||||
};
|
||||
})[];
|
||||
|
||||
+38
-1
@@ -1,5 +1,14 @@
|
||||
import { Playtype } from "types/tachi";
|
||||
import { GetGamePTConfig, Game, ScoreCalculatedDataLookup, IDStrings, integer } from "tachi-common";
|
||||
import {
|
||||
GetGamePTConfig,
|
||||
Game,
|
||||
ScoreCalculatedDataLookup,
|
||||
IDStrings,
|
||||
integer,
|
||||
ChartDocument,
|
||||
GamePTConfig,
|
||||
} from "tachi-common";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
export function RFA<T>(arr: T[]): T {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
@@ -127,3 +136,31 @@ export function ComposeInverseExpFn(pow: number) {
|
||||
export function ComposeLogFn(base: number) {
|
||||
return (x: number) => Math.log(x) / Math.log(base);
|
||||
}
|
||||
|
||||
export 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;
|
||||
}
|
||||
|
||||
export function CopyToClipboard(data: unknown) {
|
||||
toast.success("Copied data to clipboard.");
|
||||
navigator.clipboard.writeText(JSON.stringify(data, null, "\t"));
|
||||
}
|
||||
|
||||
@@ -7,11 +7,12 @@ export interface SearchDirective {
|
||||
value: string;
|
||||
}
|
||||
|
||||
type DirectiveModes = "!" | ">" | ">=" | "<" | "<=" | "~";
|
||||
type DirectiveModes = "$" | ">" | ">=" | "<" | "<=" | "~" | "!";
|
||||
|
||||
export interface Directive {
|
||||
key: string;
|
||||
// :! - EXACT
|
||||
// :$ - EXACT
|
||||
// :! - NOT
|
||||
// :> - GREATER THAN
|
||||
// :>= - GREATER THAN OR EQ
|
||||
// :< - LESS THAN
|
||||
@@ -29,7 +30,7 @@ export interface Directive {
|
||||
// VALUE = /("(?:\\"|[^"])+"|[^ ]+)/ - Parses the value.
|
||||
|
||||
export function ParseDirectives(search: string): Directive[] {
|
||||
const parsedSearch = search.matchAll(/([^ ]+):(>=|<=|!|>|<|~)?("(?:\\"|[^"])+"|[^ ]+)/gu);
|
||||
const parsedSearch = search.matchAll(/([^ ]+):(>=|<=|!|\$|>|<|~)?("(?:\\"|[^"])+"|[^ ]+)/gu);
|
||||
|
||||
const searchDirectives = [];
|
||||
for (const ps of parsedSearch) {
|
||||
@@ -62,7 +63,9 @@ type MatchFn = (searchValue: string, dataValue: string | number) => boolean;
|
||||
|
||||
const Matchers: Record<DirectiveModes, MatchFn> = {
|
||||
// Exact: Make sure this string is exactly the data value.
|
||||
"!": (sv, dv) => sv === dv.toString(),
|
||||
$: (sv, dv) => sv === dv.toString(),
|
||||
// Not: this value is not equal to the provided value. Case insensitive.
|
||||
"!": (sv, dv) => sv.toLowerCase() !== dv.toString().toLowerCase(),
|
||||
// LT, LTE, GT, GTE: If the data value is a number, compare numerically. If the data value is a string
|
||||
// compare alphabetically.
|
||||
// equality for alphabetical is implemented as .startsWith case insensitively.
|
||||
|
||||
Reference in New Issue
Block a user