mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-05 13:28:08 +03:00
Huge Updates...
This commit is contained in:
+1
-2
@@ -70,11 +70,10 @@
|
||||
"react-query": "3.18.1",
|
||||
"react-router": "5.2.0",
|
||||
"react-router-dom": "5.1.2",
|
||||
"react-table": "7.7.0",
|
||||
"resolve": "1.12.0",
|
||||
"resolve-url-loader": "3.1.0",
|
||||
"socicon": "3.0.5",
|
||||
"tachi-common": "0.1.31"
|
||||
"tachi-common": "0.1.40"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/humanize-duration": "3.25.1",
|
||||
|
||||
Generated
+4
-14
@@ -69,7 +69,6 @@ specifiers:
|
||||
react-query: 3.18.1
|
||||
react-router: 5.2.0
|
||||
react-router-dom: 5.1.2
|
||||
react-table: 7.7.0
|
||||
resolve: 1.12.0
|
||||
resolve-url-loader: 3.1.0
|
||||
sass: 1.32.8
|
||||
@@ -77,7 +76,7 @@ specifiers:
|
||||
serve: 11.2.0
|
||||
socicon: 3.0.5
|
||||
style-loader: 1.0.0
|
||||
tachi-common: 0.1.31
|
||||
tachi-common: 0.1.40
|
||||
terser-webpack-plugin: 1.4.1
|
||||
ts-pnp: 1.1.4
|
||||
typescript: 4.3.5
|
||||
@@ -137,11 +136,10 @@ dependencies:
|
||||
react-query: 3.18.1_react-dom@16.12.0+react@17.0.2
|
||||
react-router: 5.2.0_react@17.0.2
|
||||
react-router-dom: 5.1.2_react@17.0.2
|
||||
react-table: 7.7.0_react@17.0.2
|
||||
resolve: 1.12.0
|
||||
resolve-url-loader: 3.1.0
|
||||
socicon: 3.0.5
|
||||
tachi-common: 0.1.31_typescript@4.3.5
|
||||
tachi-common: 0.1.40_typescript@4.3.5
|
||||
|
||||
devDependencies:
|
||||
'@types/humanize-duration': 3.25.1
|
||||
@@ -10220,14 +10218,6 @@ packages:
|
||||
tiny-warning: 1.0.3
|
||||
dev: false
|
||||
|
||||
/react-table/7.7.0_react@17.0.2:
|
||||
resolution: {integrity: sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==}
|
||||
peerDependencies:
|
||||
react: ^16.8.3 || ^17.0.0-0
|
||||
dependencies:
|
||||
react: 17.0.2
|
||||
dev: false
|
||||
|
||||
/react-transition-group/4.4.2_react-dom@16.12.0+react@17.0.2:
|
||||
resolution: {integrity: sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==}
|
||||
peerDependencies:
|
||||
@@ -11483,8 +11473,8 @@ packages:
|
||||
strip-ansi: 6.0.0
|
||||
dev: true
|
||||
|
||||
/tachi-common/0.1.31_typescript@4.3.5:
|
||||
resolution: {integrity: sha512-jaJhv/zW+ye/GAKrYBiJb31shP4hSFhpssM56LUccZJKppDtd56lUu6fFtPWgydt3EIG7fVLqmTQJtDC0WRugg==}
|
||||
/tachi-common/0.1.40_typescript@4.3.5:
|
||||
resolution: {integrity: sha512-oj52sHcb2x+Jcg/qyKx1ras/b/9AiW5n1yYa+1pw/6SD8ZcHtzXkSFnUhq0oFbcwPvfy+4m7ygczMfhw9BsN2g==}
|
||||
dependencies:
|
||||
monk: 7.3.4
|
||||
tap: 15.0.9_typescript@4.3.5
|
||||
|
||||
@@ -29,6 +29,9 @@ export function ErrorPage({
|
||||
case 404:
|
||||
message = "This page does not exist. If you think it should exist, let us know!";
|
||||
break;
|
||||
case 429:
|
||||
message = "You're being rate limited. If this was unexpected, please report this.";
|
||||
break;
|
||||
case 500:
|
||||
message = "Looks like the server has failed. This has been reported!";
|
||||
break;
|
||||
|
||||
@@ -12,87 +12,5 @@ export default function UserPage({ reqUser }: { reqUser: PublicUserDocument }) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
// this all sucks
|
||||
return (
|
||||
<div className="card mb-5 mb-xxl-8 w-100">
|
||||
<div className="card-body pt-9 pb-0">
|
||||
<div className="d-flex flex-wrap flex-sm-nowrap mx-4 align-items-center">
|
||||
<div className="mr-6">
|
||||
<img
|
||||
src={ToAPIURL(`/users/${reqUser.id}/pfp`)}
|
||||
alt={`${reqUser.username}'s Profile Picture`}
|
||||
className="rounded"
|
||||
style={{
|
||||
width: "128px",
|
||||
height: "128px",
|
||||
boxShadow: "0px 0px 10px 0px #000000",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="align-self-start">
|
||||
<div className="font-weight-bolder font-size-h1">
|
||||
<div>{reqUser.username}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="ml-auto align-self-center">
|
||||
<ul>
|
||||
{reqUser.socialMedia.discord ? (
|
||||
<li>
|
||||
<i className="flaticon2 flaticon-twitter-logo"></i>{" "}
|
||||
{reqUser.socialMedia.discord}
|
||||
</li>
|
||||
) : null}
|
||||
{reqUser.socialMedia.discord ? (
|
||||
<li>
|
||||
<i className="flaticon2 flaticon-twitter-logo"></i>{" "}
|
||||
{reqUser.socialMedia.discord}
|
||||
</li>
|
||||
) : null}
|
||||
{reqUser.socialMedia.discord ? (
|
||||
<li>
|
||||
<i className="flaticon2 flaticon-twitter-logo"></i>{" "}
|
||||
{reqUser.socialMedia.discord}
|
||||
</li>
|
||||
) : null}
|
||||
{reqUser.socialMedia.discord ? (
|
||||
<li>
|
||||
<i className="flaticon2 flaticon-twitter-logo"></i>{" "}
|
||||
{reqUser.socialMedia.discord}
|
||||
</li>
|
||||
) : null}
|
||||
{reqUser.socialMedia.discord ? (
|
||||
<li>
|
||||
<i className="flaticon2 flaticon-twitter-logo"></i>{" "}
|
||||
{reqUser.socialMedia.discord}
|
||||
</li>
|
||||
) : null}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider className="mt-4 mb-4" />
|
||||
|
||||
<div className="d-flex overflow-auto h-55px">
|
||||
<ul
|
||||
className="nav flex-nowrap d-flex w-100"
|
||||
style={{ justifyContent: "space-evenly" }}
|
||||
>
|
||||
<li className="nav-item active">
|
||||
<a className="nav-link text-active-primary me-6 active" href="#">
|
||||
Overview
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className="nav-item">
|
||||
<a className="nav-link text-active-primary me-6" href="#">
|
||||
Games
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <></>;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function ScoresPage({
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
} & GamePT) {
|
||||
const [scoreSet, setScoreSet] = useState<"recent" | "best">("best");
|
||||
const [scoreSet, setScoreSet] = useState<"recent" | "best" | "all" | "playcount">("best");
|
||||
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
@@ -52,13 +52,41 @@ export default function ScoresPage({
|
||||
<Icon type="history" />
|
||||
Recent Scores
|
||||
</SelectButton>
|
||||
<SelectButton id="playcount" setValue={setScoreSet} value={scoreSet}>
|
||||
<Icon type="mortar-pestle" />
|
||||
Most Played
|
||||
</SelectButton>
|
||||
<SelectButton id="all" setValue={setScoreSet} value={scoreSet}>
|
||||
<Icon type="database" />
|
||||
All PBs
|
||||
</SelectButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 mt-4">
|
||||
{scoreSet === "best" ? (
|
||||
<PBsOverview {...{ reqUser, game, playtype }} />
|
||||
) : (
|
||||
<PBsOverview
|
||||
url={`/users/${reqUser.id}/games/${game}/${playtype}/pbs/best`}
|
||||
{...{ reqUser, game, playtype }}
|
||||
/>
|
||||
) : scoreSet === "recent" ? (
|
||||
<ScoresOverview {...{ reqUser, game, playtype }} />
|
||||
) : scoreSet === "all" ? (
|
||||
<PBsOverview
|
||||
url={`/users/${reqUser.id}/games/${game}/${playtype}/pbs/all`}
|
||||
reqUser={reqUser}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
indexCol={false}
|
||||
/>
|
||||
) : (
|
||||
<PBsOverview
|
||||
url={`/users/${reqUser.id}/games/${game}/${playtype}/most-played`}
|
||||
reqUser={reqUser}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
indexCol
|
||||
showPlaycount
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,12 +111,22 @@ function useFetchPBs(url: string) {
|
||||
return { isLoading, error: error as UnsuccessfulAPIResponse, data };
|
||||
}
|
||||
|
||||
function PBsOverview({ reqUser, game, playtype }: { reqUser: PublicUserDocument } & GamePT) {
|
||||
function PBsOverview({
|
||||
reqUser,
|
||||
game,
|
||||
playtype,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
url,
|
||||
}: {
|
||||
reqUser: PublicUserDocument;
|
||||
url: string;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
} & GamePT) {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { isLoading, error, data } = useFetchPBs(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/best`
|
||||
);
|
||||
const { isLoading, error, data } = useFetchPBs(url);
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
@@ -109,6 +147,8 @@ function PBsOverview({ reqUser, game, playtype }: { reqUser: PublicUserDocument
|
||||
reqUser={reqUser}
|
||||
dataset={data!}
|
||||
game={game}
|
||||
showPlaycount={showPlaycount}
|
||||
indexCol={indexCol}
|
||||
playtype={playtype as "SP" | "DP"}
|
||||
/>
|
||||
</LoadingWrapper>
|
||||
|
||||
@@ -4,13 +4,22 @@ import OverviewPage from "app/pages/dashboard/users/games/_game/_playtype/Overvi
|
||||
import SessionsPage from "app/pages/dashboard/users/games/_game/_playtype/SessionsPage";
|
||||
import { ErrorPage } from "app/pages/ErrorPage";
|
||||
import RequireAuthAsUserParam from "components/auth/RequireAuthAsUserParam";
|
||||
import UGPTHeader from "components/user/UGPTHeader";
|
||||
import { UGPTBottomNav, UGPTHeaderBody } from "components/user/UGPTHeader";
|
||||
import { UserBottomNav, UserHeaderBody } from "components/user/UserHeader";
|
||||
import UserHeaderContainer from "components/user/UserHeaderContainer";
|
||||
import Loading from "components/util/Loading";
|
||||
import { BackgroundContext } from "context/BackgroundContext";
|
||||
import React, { useContext, useEffect, useState } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { Redirect, Route, Switch, useParams } from "react-router-dom";
|
||||
import { Game, GetGameConfig, PublicUserDocument, UserGameStats, UGPTSettings } from "tachi-common";
|
||||
import {
|
||||
Game,
|
||||
GetGameConfig,
|
||||
PublicUserDocument,
|
||||
UserGameStats,
|
||||
UGPTSettings,
|
||||
FormatGame,
|
||||
} from "tachi-common";
|
||||
import { UGPTStatsReturn } from "types/api-returns";
|
||||
import { APIFetchV1, APIFetchV1Return, ToAPIURL } from "util/api";
|
||||
import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts";
|
||||
@@ -30,6 +39,10 @@ export default function UserRoutes() {
|
||||
} else {
|
||||
setReqUser(res.body);
|
||||
}
|
||||
|
||||
return () => {
|
||||
setReqUser(null);
|
||||
};
|
||||
})();
|
||||
}, [params.userID]);
|
||||
|
||||
@@ -50,21 +63,43 @@ export default function UserRoutes() {
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/users/:userID">
|
||||
<UserPage reqUser={reqUser} />
|
||||
</Route>
|
||||
|
||||
<Route exact path="/dashboard/users/:userID/settings">
|
||||
<RequireAuthAsUserParam>Settings Page</RequireAuthAsUserParam>
|
||||
</Route>
|
||||
|
||||
<Route path="/dashboard/users/:userID/games/:game">
|
||||
<UserGameRoutes reqUser={reqUser} />
|
||||
</Route>
|
||||
|
||||
<Route path="/dashboard/users/:userID">
|
||||
<UserProfileRoutes reqUser={reqUser} />
|
||||
</Route>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
function UserProfileRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
|
||||
return (
|
||||
<>
|
||||
<UserHeaderContainer
|
||||
header={`${reqUser.username}'s Profile`}
|
||||
footer={
|
||||
<UserBottomNav
|
||||
reqUser={reqUser}
|
||||
baseUrl={`/dashboard/users/${reqUser.username}`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<UserHeaderBody reqUser={reqUser} />
|
||||
</UserHeaderContainer>
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/users/:userID">
|
||||
<UserPage reqUser={reqUser} />
|
||||
</Route>
|
||||
<Route exact path="/dashboard/users/:userID/settings">
|
||||
<RequireAuthAsUserParam>Settings Page</RequireAuthAsUserParam>
|
||||
</Route>
|
||||
</Switch>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function UserGameRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
|
||||
const { game } = useParams<{ game: string }>();
|
||||
|
||||
@@ -153,7 +188,16 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: PublicUserDocument
|
||||
|
||||
return (
|
||||
<>
|
||||
<UGPTHeader reqUser={reqUser} game={game} playtype={playtype} stats={stats} />
|
||||
<UserHeaderContainer
|
||||
header={`${reqUser.username}'s ${FormatGame(game, playtype)} Profile`}
|
||||
footer={
|
||||
<UGPTBottomNav
|
||||
baseUrl={`/dashboard/users/${reqUser.username}/games/${game}/${playtype}`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<UGPTHeaderBody reqUser={reqUser} game={game} playtype={playtype} stats={stats} />
|
||||
</UserHeaderContainer>
|
||||
<Switch>
|
||||
<Route exact path="/dashboard/users/:userID/games/:game/:playtype">
|
||||
<OverviewPage
|
||||
|
||||
@@ -4,7 +4,6 @@ import { HeaderMobile } from "./header/HeaderMobile";
|
||||
import { Footer } from "./footer/Footer";
|
||||
import { LayoutInit } from "../../_metronic/layout/components/LayoutInit";
|
||||
import { SubHeader } from "./subheader/SubHeader";
|
||||
import { QuickPanel } from "../../_metronic/layout/components/extras/offcanvas/QuickPanel";
|
||||
import { AnimateLoading } from "../../_metronic/_partials/controls";
|
||||
import { JustChildren } from "types/react";
|
||||
import BackgroundImage from "components/layout/misc/BackgroundImage";
|
||||
@@ -39,7 +38,6 @@ export function Layout({ children }: JustChildren) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<QuickPanel />
|
||||
|
||||
<LayoutInit />
|
||||
</>
|
||||
|
||||
@@ -54,9 +54,7 @@ function SearchResults({ results }: { results: SearchReturns }) {
|
||||
>
|
||||
<strong>{u.username}</strong>
|
||||
<span className="font-size-sm font-weight-bold text-muted">
|
||||
{u.about.length >= 50
|
||||
? `${u.about.substring(0, 47)}...`
|
||||
: u.about}
|
||||
{u.status}
|
||||
</span>
|
||||
</SearchResult>
|
||||
))}
|
||||
|
||||
@@ -4,18 +4,26 @@ import { useLocation } from "react-router-dom";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
|
||||
export default function Navbar({ children }: { children: JSX.Element[] }) {
|
||||
const links = children.map(e =>
|
||||
e.props.to[e.props.to.length - 1] === "/"
|
||||
? e.props.to.substring(0, e.props.to.length - 1)
|
||||
: e.props.to
|
||||
);
|
||||
const links = children
|
||||
.filter(e => e.props.to)
|
||||
.map(e =>
|
||||
e.props.to[e.props.to.length - 1] === "/"
|
||||
? e.props.to.substring(0, e.props.to.length - 1)
|
||||
: e.props.to
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const value = useMemo(() => {
|
||||
const loc = location.pathname.split(/([#?]|\/$)/u)[0];
|
||||
|
||||
return links.indexOf(loc);
|
||||
const x = links.indexOf(loc);
|
||||
|
||||
if (x === -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return x;
|
||||
}, [location]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import React from "react";
|
||||
import { Badge } from "react-bootstrap";
|
||||
import { integer } from "tachi-common";
|
||||
import { FormatTime, MillisToSince } from "util/time";
|
||||
|
||||
export default function TimestampCell({ time }: { time: integer | null }) {
|
||||
export default function TimestampCell({
|
||||
time,
|
||||
service,
|
||||
}: {
|
||||
time: integer | null;
|
||||
service?: string;
|
||||
}) {
|
||||
return (
|
||||
<td>
|
||||
<td style={{ minWidth: "140px", maxWidth: "200px" }}>
|
||||
{time ? (
|
||||
<>
|
||||
{MillisToSince(time)}
|
||||
@@ -15,6 +22,12 @@ export default function TimestampCell({ time }: { time: integer | null }) {
|
||||
) : (
|
||||
"No Data."
|
||||
)}
|
||||
{service && (
|
||||
<>
|
||||
<br />
|
||||
<small className="text-muted">Played On: {service}</small>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@ export default function TitleCell({
|
||||
song,
|
||||
chart,
|
||||
noArtist,
|
||||
comment,
|
||||
}: {
|
||||
song: SongDocument;
|
||||
chart: ChartDocument;
|
||||
game: Game;
|
||||
noArtist?: boolean;
|
||||
comment?: string | null;
|
||||
}) {
|
||||
return (
|
||||
<td style={{ textAlign: "left", minWidth: "140px", maxWidth: "300px" }}>
|
||||
@@ -25,6 +27,12 @@ export default function TitleCell({
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
{comment && (
|
||||
<>
|
||||
<br />
|
||||
<small>"{comment}"</small>
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -77,12 +77,14 @@ export default function TachiTable<D>({
|
||||
defaultSortMode,
|
||||
defaultReverseSort,
|
||||
searchFunctions,
|
||||
noTopDisplayStr = false,
|
||||
}: {
|
||||
dataset: D[];
|
||||
rowFunction: (data: D) => JSX.Element;
|
||||
headers: Header<D>[];
|
||||
entryName?: string;
|
||||
entryName: string;
|
||||
pageLen?: integer;
|
||||
noTopDisplayStr?: boolean;
|
||||
defaultSortMode?: string;
|
||||
defaultReverseSort?: boolean;
|
||||
searchFunctions?: Record<string, ValueGetterOrHybrid<D>>;
|
||||
@@ -123,18 +125,26 @@ export default function TachiTable<D>({
|
||||
|
||||
return (
|
||||
<div className="justify-content-center w-100">
|
||||
{searchFunctions && (
|
||||
<div className="col-12 col-lg-3 px-0 ml-auto input-group">
|
||||
<input
|
||||
className="form-control filter-directives-enabled"
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
type="text"
|
||||
placeholder={`Filter ${entryName}`}
|
||||
value={search}
|
||||
/>
|
||||
<FilterDirectivesIndicator searchFunctions={searchFunctions} doc={dataset[0]} />
|
||||
</div>
|
||||
)}
|
||||
<div className="row">
|
||||
{!noTopDisplayStr && (
|
||||
<div className="d-none d-lg-flex col-lg-6 align-self-center">{displayStr}</div>
|
||||
)}
|
||||
{searchFunctions && (
|
||||
<div className="col-12 col-lg-3 ml-auto input-group">
|
||||
<input
|
||||
className="form-control filter-directives-enabled"
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
type="text"
|
||||
placeholder={`Filter ${entryName}`}
|
||||
value={search}
|
||||
/>
|
||||
<FilterDirectivesIndicator
|
||||
searchFunctions={searchFunctions}
|
||||
doc={dataset[0]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="col-12 px-0 mt-4 mb-4">
|
||||
<table className="table table-striped table-hover table-vertical-center text-center table-responsive-md">
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { PBDataset, ScoreDataset } from "types/tables";
|
||||
|
||||
export function useScoreState(sc: ScoreDataset[0]) {
|
||||
const [highlight, setHighlight] = useState(sc.highlight);
|
||||
const [comment, setComment] = useState(sc.comment);
|
||||
|
||||
useEffect(() => {
|
||||
sc.comment = comment;
|
||||
sc.highlight = highlight;
|
||||
}, [comment, highlight]);
|
||||
|
||||
return { highlight, comment, setHighlight, setComment };
|
||||
}
|
||||
|
||||
export function usePBState(pb: PBDataset[0]) {
|
||||
const [highlight, setHighlight] = useState(pb.highlight);
|
||||
|
||||
useEffect(() => {
|
||||
pb.highlight = highlight;
|
||||
}, [highlight]);
|
||||
|
||||
return { highlight, setHighlight };
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import Loading from "components/util/Loading";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import GenericScoreContentDropdown from "./components/GenericScoreContentDropdown";
|
||||
import PlayHistory from "./components/PlayHistory";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
|
||||
export interface ScoreState {
|
||||
highlight: boolean;
|
||||
@@ -26,6 +28,7 @@ export interface ScoreState {
|
||||
export interface ScoreDropdownProps<I extends IDStrings = IDStrings> {
|
||||
score: ScoreDocument<I> | PBScoreDocument<I>;
|
||||
scoreState: ScoreState;
|
||||
pbData: UGPTChartPBComposition<I>;
|
||||
}
|
||||
|
||||
export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
@@ -44,26 +47,21 @@ export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
DocComponent?: (props: {
|
||||
score: ScoreDocument<I> | PBScoreDocument<I>;
|
||||
scoreState: ScoreState;
|
||||
showSingleScoreNote?: boolean;
|
||||
pbData: UGPTChartPBComposition<I>;
|
||||
}) => JSX.Element;
|
||||
} & GamePT) {
|
||||
const { user } = useContext(UserContext);
|
||||
const [view, setView] = useState(defaultView);
|
||||
|
||||
const { isLoading, error, data } = useQuery(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`,
|
||||
async () => {
|
||||
const res = await APIFetchV1<UGPTChartPBComposition<I>>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error(res.description);
|
||||
}
|
||||
|
||||
return res.body;
|
||||
}
|
||||
const { isLoading, error, data } = useApiQuery<UGPTChartPBComposition<I>>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
const { isLoading: histIsLoading, error: histError, data: histData } = useApiQuery<
|
||||
ScoreDocument<I>[]
|
||||
>(`/users/${reqUser.id}/games/${game}/${playtype}/scores/${chart.chartID}`);
|
||||
|
||||
const currentScoreDoc: ScoreDocument<I> | PBScoreDocument<I> | null = useMemo(() => {
|
||||
if (!data) {
|
||||
// dont worry about this null, it never gets below the rquery checks
|
||||
@@ -71,6 +69,12 @@ export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
}
|
||||
|
||||
if (view === "pb") {
|
||||
if (data.pb.composedFrom.lampPB === data.pb.composedFrom.scorePB) {
|
||||
// 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.pb;
|
||||
}
|
||||
|
||||
@@ -96,14 +100,32 @@ export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
);
|
||||
}
|
||||
|
||||
// @TODO this doesn't support alternate PBs.
|
||||
const isComposedFromSingleScore = data.pb.composedFrom.lampPB === data.pb.composedFrom.scorePB;
|
||||
|
||||
let body;
|
||||
|
||||
if (view === "history") {
|
||||
body = <></>; // todo
|
||||
body = (
|
||||
<PlayHistory
|
||||
data={histData}
|
||||
error={histError}
|
||||
isLoading={histIsLoading}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
/>
|
||||
);
|
||||
} else if (view === "debug") {
|
||||
body = <DebugContent data={data} />;
|
||||
} else {
|
||||
body = <DocComponent score={currentScoreDoc!} scoreState={scoreState} />;
|
||||
body = (
|
||||
<DocComponent
|
||||
score={currentScoreDoc!}
|
||||
showSingleScoreNote={isComposedFromSingleScore}
|
||||
pbData={data}
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -114,17 +136,21 @@ export default function GenericPBDropdown<I extends IDStrings = IDStrings>({
|
||||
<Icon type="trophy" />
|
||||
PB Info
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="scorePB">
|
||||
<Icon type="star-half-alt" />
|
||||
Best Score
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="lampPB">
|
||||
<Icon type="lightbulb" />
|
||||
Best Lamp
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="history" disabled>
|
||||
{!isComposedFromSingleScore && (
|
||||
<>
|
||||
<SelectButton setValue={setView} value={view} id="scorePB">
|
||||
<Icon type="star-half-alt" />
|
||||
Best Score
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="lampPB">
|
||||
<Icon type="lightbulb" />
|
||||
Best Lamp
|
||||
</SelectButton>
|
||||
</>
|
||||
)}
|
||||
<SelectButton setValue={setView} value={view} id="history">
|
||||
<Icon type="history" />
|
||||
Play History
|
||||
Play History{histData && ` (${histData.length})`}
|
||||
</SelectButton>
|
||||
{user?.authLevel === "admin" && (
|
||||
<SelectButton setValue={setView} value={view} id="debug">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import Icon from "components/util/Icon";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import React, { useContext, useMemo, useState } from "react";
|
||||
import React, { useContext, useState } from "react";
|
||||
import DropdownStructure from "./components/DropdownStructure";
|
||||
import {
|
||||
PublicUserDocument,
|
||||
@@ -17,6 +17,9 @@ import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import Loading from "components/util/Loading";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import GenericScoreContentDropdown from "./components/GenericScoreContentDropdown";
|
||||
import PlayHistory from "./components/PlayHistory";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import HasDevModeOn from "components/util/HasDevModeOn";
|
||||
|
||||
export interface ScoreState {
|
||||
highlight: boolean;
|
||||
@@ -46,26 +49,20 @@ export default function GenericScoreDropdown<I extends IDStrings = IDStrings>({
|
||||
DocComponent?: (props: {
|
||||
score: ScoreDocument<I> | PBScoreDocument<I>;
|
||||
scoreState: ScoreState;
|
||||
pbData: UGPTChartPBComposition;
|
||||
}) => JSX.Element;
|
||||
} & GamePT) {
|
||||
const { user } = useContext(UserContext);
|
||||
const [view, setView] = useState(defaultView);
|
||||
|
||||
const { isLoading, error, data } = useQuery(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`,
|
||||
async () => {
|
||||
const res = await APIFetchV1<UGPTChartPBComposition<I>>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error(res.description);
|
||||
}
|
||||
|
||||
return res.body;
|
||||
}
|
||||
const { isLoading, error, data } = useApiQuery<UGPTChartPBComposition<I>>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
const { isLoading: histIsLoading, error: histError, data: histData } = useApiQuery<
|
||||
ScoreDocument<I>[]
|
||||
>(`/users/${reqUser.id}/games/${game}/${playtype}/scores/${chart.chartID}`);
|
||||
|
||||
if (error) {
|
||||
return <>An error has occured. Whoops.</>;
|
||||
}
|
||||
@@ -81,11 +78,19 @@ export default function GenericScoreDropdown<I extends IDStrings = IDStrings>({
|
||||
let body;
|
||||
|
||||
if (view === "history") {
|
||||
body = <></>; // todo
|
||||
body = (
|
||||
<PlayHistory
|
||||
error={histError}
|
||||
isLoading={histIsLoading}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
data={histData}
|
||||
/>
|
||||
);
|
||||
} else if (view === "debug") {
|
||||
body = <DebugContent data={data} />;
|
||||
} else if (view === "moreInfo") {
|
||||
body = <DocComponent score={thisScore as any} scoreState={scoreState} />;
|
||||
body = <DocComponent score={thisScore as any} scoreState={scoreState} pbData={data} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -100,16 +105,16 @@ export default function GenericScoreDropdown<I extends IDStrings = IDStrings>({
|
||||
<Icon type="balance-scale-right" />
|
||||
Versus PB
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="history" disabled>
|
||||
<SelectButton setValue={setView} value={view} id="history">
|
||||
<Icon type="history" />
|
||||
Play History
|
||||
</SelectButton>
|
||||
{user?.authLevel === "admin" && (
|
||||
<HasDevModeOn>
|
||||
<SelectButton setValue={setView} value={view} id="debug">
|
||||
<Icon type="bug" />
|
||||
Debug Info
|
||||
</SelectButton>
|
||||
)}
|
||||
</HasDevModeOn>
|
||||
</>
|
||||
}
|
||||
>
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import Icon from "components/util/Icon";
|
||||
import Loading from "components/util/Loading";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import React, { useMemo, useState, useEffect } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import React from "react";
|
||||
import { ChartDocument, PBScoreDocument, PublicUserDocument, ScoreDocument } from "tachi-common";
|
||||
import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import { IsScore } from "util/asserts";
|
||||
import CommentContainer from "./components/CommentContainer";
|
||||
import DropdownScoreButtons from "./components/DropdownScoreButtons";
|
||||
import DropdownStructure from "./components/DropdownStructure";
|
||||
import { IIDXGraphsComponent, ModsTable, ScoreInfo } from "./components/IIDXScoreDropdownParts";
|
||||
import { IIDXGraphsComponent } from "./components/IIDXScoreDropdownParts";
|
||||
import JudgementTable from "./components/JudgementTable";
|
||||
import PBNote from "./components/PBNote";
|
||||
import ScoreEditButtons from "./components/ScoreEditButtons";
|
||||
import GenericPBDropdown, { ScoreState } from "./GenericPBDropdown";
|
||||
|
||||
export default function IIDXPBDropdown({
|
||||
@@ -32,37 +21,6 @@ export default function IIDXPBDropdown({
|
||||
setHighlight: SetState<boolean>;
|
||||
};
|
||||
} & GamePT) {
|
||||
const [view, setView] = useState<"pb" | "scorePB" | "lampPB" | "bpPB" | "history" | "debug">(
|
||||
"pb"
|
||||
);
|
||||
|
||||
const { isLoading, error, data } = useQuery(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`,
|
||||
async () => {
|
||||
const res = await APIFetchV1<UGPTChartPBComposition<"iidx:SP">>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error(res.description);
|
||||
}
|
||||
|
||||
return res.body;
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <>An error has occured. Nice.</>;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
return (
|
||||
<div style={{ height: "200px" }} className="d-flex align-items-center">
|
||||
<Loading />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<GenericPBDropdown
|
||||
{...{ game, playtype, chart, reqUser, scoreState }}
|
||||
@@ -74,16 +32,18 @@ export default function IIDXPBDropdown({
|
||||
function DocumentDisplay({
|
||||
score,
|
||||
scoreState,
|
||||
pbData,
|
||||
}: {
|
||||
score: PBScoreDocument<"iidx:SP" | "iidx:DP"> | ScoreDocument<"iidx:SP" | "iidx:DP">;
|
||||
scoreState: ScoreState;
|
||||
pbData: UGPTChartPBComposition<"iidx:SP" | "iidx:DP">;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="col-9">
|
||||
<div className="row">
|
||||
<IIDXGraphsComponent score={score} />
|
||||
<DropdownScoreButtons {...{ score, scoreState }} />
|
||||
<DropdownScoreButtons {...{ score, scoreState, pbData }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-3 align-self-center">
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
import Icon from "components/util/Icon";
|
||||
import Loading from "components/util/Loading";
|
||||
import SelectButton from "components/util/SelectButton";
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import DropdownStructure from "./components/DropdownStructure";
|
||||
import { PublicUserDocument, ChartDocument, ScoreDocument } from "tachi-common";
|
||||
import { useQuery } from "react-query";
|
||||
import { IsScore } from "util/asserts";
|
||||
import { IIDXGraphsComponent, ModsTable } from "./components/IIDXScoreDropdownParts";
|
||||
import JudgementTable from "./components/JudgementTable";
|
||||
import CommentContainer from "./components/CommentContainer";
|
||||
import ScoreEditButtons from "./components/ScoreEditButtons";
|
||||
import DebugContent from "components/util/DebugContent";
|
||||
import { IIDXGraphsComponent } from "./components/IIDXScoreDropdownParts";
|
||||
import GenericScoreDropdown from "./GenericScoreDropdown";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import GenericScoreContentDropdown from "./components/GenericScoreContentDropdown";
|
||||
|
||||
export default function IIDXScoreDropdown({
|
||||
thisScore,
|
||||
@@ -33,21 +26,8 @@ export default function IIDXScoreDropdown({
|
||||
setComment: SetState<string | null>;
|
||||
};
|
||||
} & GamePT) {
|
||||
const [view, setView] = useState<"vsPB" | "moreInfo" | "history" | "debug">("moreInfo");
|
||||
|
||||
const { isLoading, error, data } = useQuery(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`,
|
||||
async () => {
|
||||
const res = await APIFetchV1<UGPTChartPBComposition<"iidx:SP">>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
if (!res.success) {
|
||||
throw new Error(res.description);
|
||||
}
|
||||
|
||||
return res.body;
|
||||
}
|
||||
const { isLoading, error, data } = useApiQuery<UGPTChartPBComposition<"iidx:SP" | "iidx:DP">>(
|
||||
`/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
@@ -62,52 +42,18 @@ export default function IIDXScoreDropdown({
|
||||
);
|
||||
}
|
||||
|
||||
let content = null;
|
||||
|
||||
if (view === "moreInfo") {
|
||||
content = (
|
||||
<>
|
||||
<div className="col-9">
|
||||
<div className="row">
|
||||
<IIDXGraphsComponent score={thisScore} />
|
||||
<CommentContainer comment={scoreState.comment} />
|
||||
<ScoreEditButtons score={thisScore} scoreState={scoreState} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-3 align-self-center">
|
||||
<JudgementTable score={thisScore} />
|
||||
{IsScore(thisScore) && <ModsTable score={thisScore} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
} else if (view === "debug") {
|
||||
content = <DebugContent data={data} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownStructure
|
||||
buttons={
|
||||
<>
|
||||
<SelectButton setValue={setView} value={view} id="moreInfo">
|
||||
<Icon type="chart-bar" />
|
||||
This Score
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="vsPB">
|
||||
<Icon type="balance-scale-right" />
|
||||
Versus PB
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="history" disabled>
|
||||
<Icon type="history" />
|
||||
Play History
|
||||
</SelectButton>
|
||||
<SelectButton setValue={setView} value={view} id="debug">
|
||||
<Icon type="bug" />
|
||||
Debug Info
|
||||
</SelectButton>
|
||||
</>
|
||||
<GenericScoreDropdown<"iidx:SP" | "iidx:DP">
|
||||
{...{ game, playtype, chart, reqUser, scoreState, thisScore }}
|
||||
DocComponent={props =>
|
||||
GenericScoreContentDropdown({
|
||||
...props,
|
||||
renderScoreInfo: false,
|
||||
// let the record show that i tried fixing this
|
||||
// for a while, but gave up.
|
||||
GraphComponent: IIDXGraphsComponent as any,
|
||||
})
|
||||
}
|
||||
>
|
||||
{content}
|
||||
</DropdownStructure>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ScoreInfo } from "./GenericScoreContentDropdown";
|
||||
import PBNote from "./PBNote";
|
||||
import ScoreEditButtons from "./ScoreEditButtons";
|
||||
|
||||
export default function DropdownScoreButtons({ score, scoreState }: ScoreDropdownProps) {
|
||||
export default function DropdownScoreButtons({ score, scoreState, pbData }: ScoreDropdownProps) {
|
||||
const [comment, setComment] = useState(IsScore(score) ? score.comment : null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -26,6 +26,12 @@ export default function DropdownScoreButtons({ score, scoreState }: ScoreDropdow
|
||||
</>
|
||||
) : (
|
||||
<div className="col-12">
|
||||
<CommentContainer
|
||||
comment={pbData.scores
|
||||
.map(e => e.comment)
|
||||
.filter(e => e !== null)
|
||||
.join("; ")}
|
||||
/>
|
||||
<PBNote />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -2,8 +2,10 @@ import DeltaCell from "components/tables/cells/DeltaCell";
|
||||
import LampCell from "components/tables/cells/LampCell";
|
||||
import ScoreCell from "components/tables/cells/ScoreCell";
|
||||
import TimestampCell from "components/tables/cells/TimestampCell";
|
||||
import ExternalLink from "components/util/ExternalLink";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { PBScoreDocument, ScoreDocument } from "tachi-common";
|
||||
import { UGPTChartPBComposition } from "types/api-returns";
|
||||
import { SetState } from "types/react";
|
||||
import { IsScore } from "util/asserts";
|
||||
import CommentContainer from "./CommentContainer";
|
||||
@@ -31,7 +33,7 @@ export function ScoreInfo({ score }: { score: ScoreDocument }) {
|
||||
grade={score.scoreData.grade}
|
||||
/>
|
||||
<LampCell score={score} />
|
||||
<TimestampCell time={score.timeAchieved} />
|
||||
<TimestampCell time={score.timeAchieved} service={score.service} />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -43,10 +45,18 @@ export default function GenericScoreContentDropdown({
|
||||
score,
|
||||
scoreState,
|
||||
renderScoreInfo = true,
|
||||
showSingleScoreNote = false,
|
||||
GraphComponent = null,
|
||||
pbData,
|
||||
}: {
|
||||
score: ScoreDocument | PBScoreDocument;
|
||||
scoreState: { highlight: boolean; setHighlight: SetState<boolean> };
|
||||
renderScoreInfo?: boolean;
|
||||
showSingleScoreNote?: boolean;
|
||||
pbData: UGPTChartPBComposition;
|
||||
GraphComponent?:
|
||||
| (({ score }: { score: ScoreDocument | PBScoreDocument }) => JSX.Element)
|
||||
| null;
|
||||
}) {
|
||||
const [comment, setComment] = useState(IsScore(score) ? score.comment : null);
|
||||
|
||||
@@ -58,13 +68,28 @@ export default function GenericScoreContentDropdown({
|
||||
<>
|
||||
<div className="col-9">
|
||||
<div className="row h-100 justify-content-center">
|
||||
<div className="d-flex align-items-center" style={{ height: "200px" }}>
|
||||
<span className="text-muted">No graphs available :(</span>
|
||||
</div>
|
||||
{GraphComponent ? (
|
||||
<GraphComponent score={score} />
|
||||
) : (
|
||||
<div className="d-flex align-items-center" style={{ height: "200px" }}>
|
||||
<span className="text-muted">No graphs available :(</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{IsScore(score) ? (
|
||||
<>
|
||||
{renderScoreInfo && <ScoreInfo score={score} />}
|
||||
{renderScoreInfo && !showSingleScoreNote && <ScoreInfo score={score} />}
|
||||
<CommentContainer comment={comment} />
|
||||
{showSingleScoreNote && (
|
||||
<div className="col-12">
|
||||
<PBNote />
|
||||
<br />
|
||||
<small>
|
||||
In this case, your best lamp and your best score were the
|
||||
same!
|
||||
</small>
|
||||
</div>
|
||||
)}
|
||||
<ScoreEditButtons
|
||||
score={score}
|
||||
scoreState={{ ...scoreState, comment, setComment }}
|
||||
@@ -72,6 +97,12 @@ export default function GenericScoreContentDropdown({
|
||||
</>
|
||||
) : (
|
||||
<div className="col-12 align-self-end">
|
||||
<CommentContainer
|
||||
comment={pbData.scores
|
||||
.map(e => e.comment)
|
||||
.filter(e => e !== null)
|
||||
.join("; ")}
|
||||
/>
|
||||
<PBNote />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3,9 +3,9 @@ import IIDXLampCell from "components/tables/cells/IIDXLampCell";
|
||||
import ScoreCell from "components/tables/cells/ScoreCell";
|
||||
import TimestampCell from "components/tables/cells/TimestampCell";
|
||||
import SelectNav from "components/util/SelectNav";
|
||||
import React, { useState } from "react";
|
||||
import { ScoreDocument, PBScoreDocument, Lamps } from "tachi-common";
|
||||
import { Nav } from ".pnpm/react-bootstrap@1.0.1_react-dom@16.12.0+react@17.0.2/node_modules/react-bootstrap";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ScoreDocument, PBScoreDocument } from "tachi-common";
|
||||
import { Nav } from "react-bootstrap";
|
||||
import IIDXLampChart from "components/charts/IIDXLampChart";
|
||||
import MiniTable from "components/tables/components/MiniTable";
|
||||
import { IsScore } from "util/asserts";
|
||||
@@ -92,6 +92,10 @@ export function IIDXGraphsComponent({
|
||||
return true;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setLamp(LampToKey(score));
|
||||
}, [score]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="col-12 d-flex justify-content-center">
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import HistoryScoreTable from "components/tables/history-scores/HistoryScoreTable";
|
||||
import Loading from "components/util/Loading";
|
||||
import React from "react";
|
||||
import { ScoreDocument } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import { UnsuccessfulAPIFetchResponse } from "util/api";
|
||||
|
||||
export default function PlayHistory({
|
||||
data,
|
||||
isLoading,
|
||||
error,
|
||||
game,
|
||||
playtype,
|
||||
}: {
|
||||
data?: ScoreDocument[];
|
||||
isLoading: boolean;
|
||||
error: UnsuccessfulAPIFetchResponse | null;
|
||||
} & GamePT) {
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <>{error.description}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="col-12 col-lg-10">
|
||||
<HistoryScoreTable dataset={data} game={game} playtype={playtype} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -53,9 +53,12 @@ export default function ScoreEditButtons({
|
||||
<Button
|
||||
variant="success"
|
||||
onClick={() =>
|
||||
ModifyScore(score.scoreID, { highlight: false }).then(
|
||||
r => r && setHighlight(false)
|
||||
)
|
||||
ModifyScore(score.scoreID, { highlight: false }).then(r => {
|
||||
if (r) {
|
||||
setHighlight(false);
|
||||
score.highlight = false;
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Icon noPad type="star" />
|
||||
@@ -66,9 +69,12 @@ export default function ScoreEditButtons({
|
||||
<Button
|
||||
variant="outline-secondary"
|
||||
onClick={() =>
|
||||
ModifyScore(score.scoreID, { highlight: true }).then(
|
||||
r => r && setHighlight(true)
|
||||
)
|
||||
ModifyScore(score.scoreID, { highlight: true }).then(r => {
|
||||
if (r) {
|
||||
setHighlight(true);
|
||||
score.highlight = true;
|
||||
}
|
||||
})
|
||||
}
|
||||
>
|
||||
<Icon noPad type="star" />
|
||||
@@ -82,6 +88,7 @@ export default function ScoreEditButtons({
|
||||
comment={comment}
|
||||
setComment={setComment}
|
||||
scoreID={score.scoreID}
|
||||
score={score}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -113,12 +120,14 @@ function CommentModal({
|
||||
comment,
|
||||
setComment,
|
||||
scoreID,
|
||||
score,
|
||||
}: {
|
||||
show: boolean;
|
||||
setShow: SetState<boolean>;
|
||||
comment: string | null;
|
||||
setComment: SetState<string | null>;
|
||||
scoreID: string;
|
||||
score: ScoreDocument;
|
||||
}) {
|
||||
const [innerComment, setInnerComment] = useState(comment ?? "");
|
||||
|
||||
@@ -135,6 +144,7 @@ function CommentModal({
|
||||
ModifyScore(scoreID, { comment: innerComment }).then(r => {
|
||||
if (r) {
|
||||
setComment(innerComment);
|
||||
score.comment = innerComment;
|
||||
setShow(false);
|
||||
}
|
||||
});
|
||||
@@ -150,7 +160,7 @@ function CommentModal({
|
||||
onChange={e => setInnerComment(e.target.value)}
|
||||
/>
|
||||
<div className="input-group-append">
|
||||
<Button variant="outline-primary" type="submit">
|
||||
<Button variant="primary" type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
@@ -159,11 +169,12 @@ function CommentModal({
|
||||
|
||||
<QuickTooltip text="Remove your comment on this score.">
|
||||
<Button
|
||||
variant="outline-secondary"
|
||||
variant="outline-danger"
|
||||
onClick={() =>
|
||||
ModifyScore(scoreID, { comment: null }).then(r => {
|
||||
if (r) {
|
||||
setComment(null);
|
||||
score.comment = null;
|
||||
setShow(false);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import React from "react";
|
||||
import { ScoreDocument } from "tachi-common";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
import LampCell from "../cells/LampCell";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
|
||||
export default function BMSScoreCoreCells({ sc }: { sc: ScoreDocument<"bms:7K" | "bms:14K"> }) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} />
|
||||
<DeltaCell
|
||||
game="bms"
|
||||
playtype={sc.playtype}
|
||||
score={sc.scoreData.score}
|
||||
percent={sc.scoreData.percent}
|
||||
grade={sc.scoreData.grade}
|
||||
/>
|
||||
<LampCell score={sc} />
|
||||
<RatingCell score={sc} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { ScoreDocument } from "tachi-common";
|
||||
import LampCell from "../cells/LampCell";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
|
||||
export default function GenericScoreCoreCells({
|
||||
sc,
|
||||
showScore,
|
||||
}: {
|
||||
sc: ScoreDocument;
|
||||
showScore?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} showScore={showScore} />
|
||||
<LampCell score={sc} />
|
||||
<RatingCell score={sc} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import { ScoreDocument, ScoreCalculatedDataLookup } from "tachi-common";
|
||||
import { IsNullish } from "util/misc";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
import IIDXLampCell from "../cells/IIDXLampCell";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
|
||||
export default function IIDXScoreCoreCells({
|
||||
sc,
|
||||
rating,
|
||||
}: {
|
||||
sc: ScoreDocument<"iidx:SP" | "iidx:DP">;
|
||||
rating: ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"];
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<ScoreCell score={sc} />
|
||||
<DeltaCell
|
||||
game="iidx"
|
||||
playtype={sc.playtype}
|
||||
score={sc.scoreData.score}
|
||||
percent={sc.scoreData.percent}
|
||||
grade={sc.scoreData.grade}
|
||||
/>
|
||||
<IIDXLampCell sc={sc} />
|
||||
<td>
|
||||
{!IsNullish(sc.calculatedData[rating])
|
||||
? sc.calculatedData[rating]!.toFixed(2)
|
||||
: "No Data."}
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { ScoreDocument } from "tachi-common";
|
||||
import LampCell from "../cells/LampCell";
|
||||
import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import MusecaJudgementCell from "../cells/MusecaJudgementCell";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
|
||||
export default function MusecaScoreCoreCells({ sc }: { sc: ScoreDocument<"museca:Single"> }) {
|
||||
return (
|
||||
<>
|
||||
<MillionsScoreCell score={sc} />
|
||||
<MusecaJudgementCell score={sc} />
|
||||
<LampCell score={sc} />
|
||||
<RatingCell score={sc} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from "react";
|
||||
import { ScoreDocument } from "tachi-common";
|
||||
import { IsNullish } from "util/misc";
|
||||
import LampCell from "../cells/LampCell";
|
||||
import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import SDVXJudgementCell from "../cells/SDVXJudgementCell";
|
||||
|
||||
export default function SDVXScoreCoreCells({ sc }: { sc: ScoreDocument<"sdvx:Single"> }) {
|
||||
return (
|
||||
<>
|
||||
<MillionsScoreCell score={sc} />
|
||||
<SDVXJudgementCell score={sc} />
|
||||
<LampCell score={sc} />
|
||||
<td>{!IsNullish(sc.calculatedData.VF6) ? sc.calculatedData.VF6!.toFixed(3) : "N/A"}</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from "react";
|
||||
import { Game, IDStrings, ScoreCalculatedDataLookup, ScoreDocument } from "tachi-common";
|
||||
import { GamePT } from "types/react";
|
||||
import BMSScoreCoreCells from "./BMSScoreCoreCells";
|
||||
import GenericScoreCoreCells from "./GenericScoreCoreCells";
|
||||
import IIDXScoreCoreCells from "./IIDXScoreCoreCells";
|
||||
import MusecaScoreCoreCells from "./MusecaScoreCoreCells";
|
||||
import SDVXScoreCoreCells from "./SDVXCoreCells";
|
||||
|
||||
export default function ScoreCoreCells({
|
||||
game,
|
||||
score,
|
||||
rating,
|
||||
}: {
|
||||
score: ScoreDocument;
|
||||
rating: ScoreCalculatedDataLookup[IDStrings];
|
||||
game: Game;
|
||||
}) {
|
||||
const sc = score as any; // lazy hack
|
||||
|
||||
if (game === "iidx") {
|
||||
return (
|
||||
<IIDXScoreCoreCells
|
||||
rating={rating as ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"]}
|
||||
sc={sc}
|
||||
/>
|
||||
);
|
||||
} else if (game === "bms") {
|
||||
return <BMSScoreCoreCells sc={sc} />;
|
||||
} else if (game === "sdvx" || game === "usc") {
|
||||
return <SDVXScoreCoreCells sc={sc} />;
|
||||
} else if (game === "maimai") {
|
||||
return <GenericScoreCoreCells showScore={false} sc={sc} />;
|
||||
} else if (game === "museca") {
|
||||
return <MusecaScoreCoreCells sc={sc} />;
|
||||
}
|
||||
|
||||
return <GenericScoreCoreCells sc={sc} />;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { nanoid } from "nanoid";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
Game,
|
||||
GetGamePTConfig,
|
||||
IDStrings,
|
||||
integer,
|
||||
ScoreCalculatedDataLookup,
|
||||
ScoreDocument,
|
||||
} from "tachi-common";
|
||||
import { SetState } from "types/react";
|
||||
import { Playtype } from "types/tachi";
|
||||
import { NumericSOV } from "util/sorts";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import SelectableRating from "../components/SelectableRating";
|
||||
import TachiTable, { ZTableTHProps } from "../components/TachiTable";
|
||||
import IIDXScoreCoreCells from "../game-core-cells/IIDXScoreCoreCells";
|
||||
import ScoreCoreCells from "../game-core-cells/ScoreCoreCells";
|
||||
|
||||
export default function HistoryScoreTable({
|
||||
dataset,
|
||||
pageLen = 5,
|
||||
playtype,
|
||||
game,
|
||||
}: {
|
||||
dataset: ScoreDocument[];
|
||||
pageLen?: integer;
|
||||
playtype: Playtype;
|
||||
game: Game;
|
||||
}) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const [rating, setRating] = useState(gptConfig.defaultScoreRatingAlg);
|
||||
|
||||
return (
|
||||
<TachiTable
|
||||
dataset={dataset}
|
||||
pageLen={pageLen}
|
||||
noTopDisplayStr
|
||||
entryName="Scores"
|
||||
headers={[
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Info", "Info", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
game === "iidx"
|
||||
? [
|
||||
"Rating",
|
||||
"Rating",
|
||||
NumericSOV(x => x.calculatedData[rating] ?? 0),
|
||||
(thProps: ZTableTHProps) => (
|
||||
<SelectableRating
|
||||
key={nanoid()}
|
||||
game="iidx"
|
||||
playtype={playtype}
|
||||
rating={rating}
|
||||
setRating={setRating}
|
||||
{...thProps}
|
||||
/>
|
||||
),
|
||||
]
|
||||
: ["Rating", "Rating", NumericSOV(x => x.calculatedData[rating] ?? 0)],
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
]}
|
||||
defaultSortMode="Timestamp"
|
||||
defaultReverseSort
|
||||
rowFunction={sc => <Row key={sc.scoreID} sc={sc} game={game} rating={rating} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Row({
|
||||
sc,
|
||||
rating,
|
||||
game,
|
||||
}: {
|
||||
sc: ScoreDocument;
|
||||
rating: ScoreCalculatedDataLookup[IDStrings];
|
||||
game: Game;
|
||||
}) {
|
||||
return (
|
||||
<tr className={sc.highlight ? "highlighted-row" : ""}>
|
||||
<ScoreCoreCells score={sc} game={game} rating={rating as any} />
|
||||
<TimestampCell time={sc.timeAchieved} service={sc.service} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import BMSDifficultyCell from "../cells/BMSDifficultyCell";
|
||||
import { BMS_TABLES } from "util/constants/bms-tables";
|
||||
import { ValueGetterOrHybrid } from "util/ztable/search";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
|
||||
function GetBMSTableVal(chart: ChartDocument<"bms:7K" | "bms:14K">, key: string) {
|
||||
for (const table of chart.data.tableFolders) {
|
||||
@@ -31,12 +32,14 @@ function GetBMSTableVal(chart: ChartDocument<"bms:7K" | "bms:14K">, key: string)
|
||||
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";
|
||||
}) {
|
||||
const headers: Header<PBDataset<"bms:7K" | "bms:14K">[0]>[] = [
|
||||
@@ -46,10 +49,14 @@ export default function BMSPBTable({
|
||||
["Deltas", "Deltas", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["Sieglinde", "sgl.", NumericSOV(x => x.calculatedData.sieglinde ?? 0)],
|
||||
["Ranking", "Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Site Ranking", "Site Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Last Raised", "Last Raised", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (showPlaycount) {
|
||||
headers.push(["Playcount", "Plays", NumericSOV(x => x.__playcount ?? 0)]);
|
||||
}
|
||||
|
||||
if (indexCol) {
|
||||
headers.unshift(["#", "#", NumericSOV(x => x.__related.index)]);
|
||||
}
|
||||
@@ -107,6 +114,7 @@ export default function BMSPBTable({
|
||||
pb={pb}
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
showPlaycount={showPlaycount}
|
||||
indexCol={indexCol}
|
||||
/>
|
||||
)}
|
||||
@@ -118,14 +126,14 @@ function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
reqUser,
|
||||
showPlaycount,
|
||||
}: {
|
||||
pb: PBDataset<"bms:7K" | "bms:14K">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(pb.highlight);
|
||||
|
||||
const scoreState = { highlight, setHighlight };
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
@@ -138,7 +146,7 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<BMSDifficultyCell chart={pb.__related.chart} />
|
||||
@@ -159,6 +167,7 @@ function Row({
|
||||
</td>
|
||||
<RankingCell rankingData={pb.rankingData} />
|
||||
<TimestampCell time={pb.timeAchieved} />
|
||||
{showPlaycount && <td>{pb.__playcount ?? 0}</td>}
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import RankingCell from "../cells/RankingCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
@@ -9,19 +9,18 @@ import TachiTable, { Header } from "../components/TachiTable";
|
||||
import IndexCell from "../cells/IndexCell";
|
||||
import { HumanFriendlyStrToGradeIndex, HumanFriendlyStrToLampIndex } from "util/str-to-num";
|
||||
import DropdownRow from "../components/DropdownRow";
|
||||
import { IsNullish } from "util/misc";
|
||||
import LampCell from "../cells/LampCell";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import SDVXJudgementCell from "../cells/SDVXJudgementCell";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
import { Playtype } from "types/tachi";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
|
||||
export default function GenericPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
game,
|
||||
playtype,
|
||||
@@ -29,6 +28,7 @@ export default function GenericPBTable({
|
||||
}: {
|
||||
dataset: PBDataset;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
game: Game;
|
||||
playtype: Playtype;
|
||||
@@ -46,10 +46,14 @@ export default function GenericPBTable({
|
||||
gptConfig.defaultScoreRatingAlg,
|
||||
NumericSOV(x => x.calculatedData[gptConfig.defaultScoreRatingAlg] ?? 0),
|
||||
],
|
||||
["Ranking", "Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Site Ranking", "Site Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Last Raised", "Last Raised", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (showPlaycount) {
|
||||
headers.push(["Playcount", "Plays", NumericSOV(x => x.__playcount ?? 0)]);
|
||||
}
|
||||
|
||||
if (indexCol) {
|
||||
headers.unshift(["#", "#", NumericSOV(x => x.__related.index)]);
|
||||
}
|
||||
@@ -84,6 +88,7 @@ export default function GenericPBTable({
|
||||
reqUser={reqUser}
|
||||
indexCol={indexCol}
|
||||
showScore={showScore}
|
||||
showPlaycount={showPlaycount}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -95,15 +100,15 @@ function Row({
|
||||
indexCol,
|
||||
reqUser,
|
||||
showScore,
|
||||
showPlaycount,
|
||||
}: {
|
||||
pb: PBDataset[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
showScore?: boolean;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(pb.highlight);
|
||||
|
||||
const scoreState = { highlight, setHighlight };
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
@@ -116,7 +121,7 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game={pb.game} chart={pb.__related.chart} />
|
||||
@@ -126,6 +131,7 @@ function Row({
|
||||
<RatingCell score={pb} />
|
||||
<RankingCell rankingData={pb.rankingData} />
|
||||
<TimestampCell time={pb.timeAchieved} />
|
||||
{showPlaycount && <td>{pb.__playcount ?? 0}</td>}
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,15 +18,18 @@ import DropdownRow from "../components/DropdownRow";
|
||||
import IIDXPBDropdown from "../dropdowns/IIDXPBDropdown";
|
||||
import IIDXLampCell from "../cells/IIDXLampCell";
|
||||
import { IsNullish } from "util/misc";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
|
||||
export default function IIDXPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"iidx:SP" | "iidx:DP">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
playtype: "SP" | "DP";
|
||||
}) {
|
||||
@@ -57,10 +60,14 @@ export default function IIDXPBTable({
|
||||
/>
|
||||
),
|
||||
],
|
||||
["Ranking", "Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Site Ranking", "Site Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Last Raised", "Last Raised", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (showPlaycount) {
|
||||
headers.push(["Playcount", "Plays", NumericSOV(x => x.__playcount ?? 0)]);
|
||||
}
|
||||
|
||||
if (indexCol) {
|
||||
headers.unshift(["#", "#", NumericSOV(x => x.__related.index)]);
|
||||
}
|
||||
@@ -93,6 +100,7 @@ export default function IIDXPBTable({
|
||||
pb={pb}
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
showPlaycount={showPlaycount}
|
||||
indexCol={indexCol}
|
||||
rating={rating}
|
||||
/>
|
||||
@@ -106,15 +114,15 @@ function Row({
|
||||
indexCol,
|
||||
reqUser,
|
||||
rating,
|
||||
showPlaycount,
|
||||
}: {
|
||||
pb: PBDataset<"iidx:SP" | "iidx:DP">[0];
|
||||
indexCol: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
showPlaycount: boolean;
|
||||
rating: ScoreCalculatedDataLookup["iidx:SP"];
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(pb.highlight);
|
||||
|
||||
const scoreState = { highlight, setHighlight };
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
@@ -127,7 +135,7 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell chart={pb.__related.chart} game={"iidx"} />
|
||||
@@ -148,6 +156,7 @@ function Row({
|
||||
</td>
|
||||
<RankingCell rankingData={pb.rankingData} />
|
||||
<TimestampCell time={pb.timeAchieved} />
|
||||
{showPlaycount && <td>{pb.__playcount ?? 0}</td>}
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import RankingCell from "../cells/RankingCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
@@ -9,23 +9,24 @@ import TachiTable, { Header } from "../components/TachiTable";
|
||||
import IndexCell from "../cells/IndexCell";
|
||||
import { HumanFriendlyStrToGradeIndex, HumanFriendlyStrToLampIndex } from "util/str-to-num";
|
||||
import DropdownRow from "../components/DropdownRow";
|
||||
import { IsNullish } from "util/misc";
|
||||
import LampCell from "../cells/LampCell";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import SDVXJudgementCell from "../cells/SDVXJudgementCell";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
import MusecaJudgementCell from "../cells/MusecaJudgementCell";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
|
||||
export default function MusecaPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"museca:Single">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
playtype: "Single";
|
||||
}) {
|
||||
@@ -33,13 +34,17 @@ export default function MusecaPBTable({
|
||||
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Judgements", "Judge", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near - Miss", "Nr-Ms", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["ktRating", "ktRating", NumericSOV(x => x.calculatedData.ktRating ?? 0)],
|
||||
["Ranking", "Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Site Ranking", "Site Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Last Raised", "Last Raised", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (showPlaycount) {
|
||||
headers.push(["Playcount", "Plays", NumericSOV(x => x.__playcount ?? 0)]);
|
||||
}
|
||||
|
||||
if (indexCol) {
|
||||
headers.unshift(["#", "#", NumericSOV(x => x.__related.index)]);
|
||||
}
|
||||
@@ -73,6 +78,7 @@ export default function MusecaPBTable({
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
indexCol={indexCol}
|
||||
showPlaycount={showPlaycount}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -83,14 +89,14 @@ function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
reqUser,
|
||||
showPlaycount,
|
||||
}: {
|
||||
pb: PBDataset<"museca:Single">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(pb.highlight);
|
||||
|
||||
const scoreState = { highlight, setHighlight };
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
@@ -103,7 +109,7 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game="museca" chart={pb.__related.chart} />
|
||||
@@ -114,6 +120,7 @@ function Row({
|
||||
<RatingCell score={pb} />
|
||||
<RankingCell rankingData={pb.rankingData} />
|
||||
<TimestampCell time={pb.timeAchieved} />
|
||||
{showPlaycount && <td>{pb.__playcount ?? 0}</td>}
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,17 +13,19 @@ export default function PBTable({
|
||||
indexCol = true,
|
||||
reqUser,
|
||||
playtype,
|
||||
showPlaycount,
|
||||
game,
|
||||
}: {
|
||||
dataset: PBDataset;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
} & 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 } as any;
|
||||
const props = { dataset, indexCol, reqUser, playtype, showPlaycount } as any;
|
||||
|
||||
if (game === "iidx") {
|
||||
return <IIDXPBTable {...props} />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import RankingCell from "../cells/RankingCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
@@ -15,15 +15,18 @@ import DifficultyCell from "../cells/DifficultyCell";
|
||||
import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import GenericPBDropdown from "../dropdowns/GenericPBDropdown";
|
||||
import SDVXJudgementCell from "../cells/SDVXJudgementCell";
|
||||
import { usePBState } from "../components/UseScoreState";
|
||||
|
||||
export default function SDVXPBTable({
|
||||
dataset,
|
||||
indexCol = true,
|
||||
showPlaycount = false,
|
||||
reqUser,
|
||||
playtype,
|
||||
}: {
|
||||
dataset: PBDataset<"sdvx:Single">;
|
||||
indexCol?: boolean;
|
||||
showPlaycount?: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
playtype: "7K" | "14K";
|
||||
}) {
|
||||
@@ -31,13 +34,17 @@ export default function SDVXPBTable({
|
||||
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Judgements", "Judge", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near - Miss", "Nr-Ms", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["VF6", "VF6", NumericSOV(x => x.calculatedData.VF6 ?? 0)],
|
||||
["Ranking", "Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Site Ranking", "Site Rank", NumericSOV(x => x.rankingData.rank)],
|
||||
["Last Raised", "Last Raised", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
];
|
||||
|
||||
if (showPlaycount) {
|
||||
headers.push(["Playcount", "Plays", NumericSOV(x => x.__playcount ?? 0)]);
|
||||
}
|
||||
|
||||
if (indexCol) {
|
||||
headers.unshift(["#", "#", NumericSOV(x => x.__related.index)]);
|
||||
}
|
||||
@@ -71,6 +78,7 @@ export default function SDVXPBTable({
|
||||
key={`${pb.chartID}:${pb.userID}`}
|
||||
reqUser={reqUser}
|
||||
indexCol={indexCol}
|
||||
showPlaycount={showPlaycount}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -80,15 +88,15 @@ export default function SDVXPBTable({
|
||||
function Row({
|
||||
pb,
|
||||
indexCol,
|
||||
showPlaycount,
|
||||
reqUser,
|
||||
}: {
|
||||
pb: PBDataset<"sdvx:Single">[0];
|
||||
indexCol: boolean;
|
||||
showPlaycount: boolean;
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(pb.highlight);
|
||||
|
||||
const scoreState = { highlight, setHighlight };
|
||||
const scoreState = usePBState(pb);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
@@ -101,7 +109,7 @@ function Row({
|
||||
scoreState={scoreState}
|
||||
/>
|
||||
}
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
>
|
||||
{indexCol && <IndexCell index={pb.__related.index} />}
|
||||
<DifficultyCell game="sdvx" chart={pb.__related.chart} />
|
||||
@@ -114,6 +122,7 @@ function Row({
|
||||
</td>
|
||||
<RankingCell rankingData={pb.rankingData} />
|
||||
<TimestampCell time={pb.timeAchieved} />
|
||||
{showPlaycount && <td>{pb.__playcount ?? 0}</td>}
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import TitleCell from "../cells/TitleCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { ScoreDataset } from "types/tables";
|
||||
import { integer, PublicUserDocument, Playtypes } from "tachi-common";
|
||||
import { integer, PublicUserDocument, Playtypes, ScoreDocument } from "tachi-common";
|
||||
import TachiTable from "../components/TachiTable";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
import DeltaCell from "../cells/DeltaCell";
|
||||
@@ -16,6 +16,10 @@ import BMSDifficultyCell from "../cells/BMSDifficultyCell";
|
||||
import { Playtype } from "types/tachi";
|
||||
import GenericScoreDropdown from "../dropdowns/GenericScoreDropdown";
|
||||
import SDVXJudgementCell from "../cells/SDVXJudgementCell";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
import BMSScoreCoreCells from "../game-core-cells/BMSScoreCoreCells";
|
||||
import { CreateDefaultScoreSearch } from "util/tables";
|
||||
|
||||
export default function BMSScoreTable({
|
||||
reqUser,
|
||||
@@ -43,22 +47,7 @@ export default function BMSScoreTable({
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
]}
|
||||
entryName="Scores"
|
||||
searchFunctions={{
|
||||
artist: x => x.__related.song.artist,
|
||||
title: x => x.__related.song.title,
|
||||
difficulty: x => FormatDifficulty(x.__related.chart, "bms"),
|
||||
level: x => x.__related.chart.levelNum,
|
||||
score: x => x.scoreData.score,
|
||||
percent: x => x.scoreData.percent,
|
||||
lamp: {
|
||||
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
|
||||
strToNum: HumanFriendlyStrToLampIndex("bms", playtype),
|
||||
},
|
||||
grade: {
|
||||
valueGetter: x => [x.scoreData.grade, x.scoreData.gradeIndex],
|
||||
strToNum: HumanFriendlyStrToGradeIndex("bms", playtype),
|
||||
},
|
||||
}}
|
||||
searchFunctions={CreateDefaultScoreSearch<"bms:7K" | "bms:14K">("bms", playtype)}
|
||||
rowFunction={sc => (
|
||||
<Row playtype={playtype} key={sc.scoreID} sc={sc} reqUser={reqUser} />
|
||||
)}
|
||||
@@ -75,14 +64,11 @@ function Row({
|
||||
reqUser: PublicUserDocument;
|
||||
playtype: Playtype;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(sc.highlight);
|
||||
const [comment, setComment] = useState(sc.comment);
|
||||
|
||||
const scoreState = { highlight, comment, setHighlight, setComment };
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
@@ -95,22 +81,14 @@ function Row({
|
||||
}
|
||||
>
|
||||
<BMSDifficultyCell chart={sc.__related.chart} />
|
||||
<TitleCell song={sc.__related.song} chart={sc.__related.chart} game="bms" />
|
||||
<ScoreCell score={sc} />
|
||||
<DeltaCell
|
||||
<TitleCell
|
||||
song={sc.__related.song}
|
||||
chart={sc.__related.chart}
|
||||
game="bms"
|
||||
playtype={playtype}
|
||||
score={sc.scoreData.score}
|
||||
percent={sc.scoreData.percent}
|
||||
grade={sc.scoreData.grade}
|
||||
comment={sc.comment}
|
||||
/>
|
||||
<LampCell score={sc} />
|
||||
<td>
|
||||
{!IsNullish(sc.calculatedData.sieglinde)
|
||||
? sc.calculatedData.sieglinde!.toFixed(2)
|
||||
: "N/A"}
|
||||
</td>
|
||||
<TimestampCell time={sc.timeAchieved} />
|
||||
<BMSScoreCoreCells sc={sc} />
|
||||
<TimestampCell time={sc.timeAchieved} service={sc.scoreMeta.client} />
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import GenericScoreDropdown from "../dropdowns/GenericScoreDropdown";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
import { Playtype } from "types/tachi";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
import GenericScoreCoreCells from "../game-core-cells/GenericScoreCoreCells";
|
||||
import { CreateDefaultScoreSearch } from "util/tables";
|
||||
|
||||
export default function GenericScoreTable({
|
||||
reqUser,
|
||||
@@ -51,22 +54,7 @@ export default function GenericScoreTable({
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
]}
|
||||
entryName="Scores"
|
||||
searchFunctions={{
|
||||
artist: x => x.__related.song.artist,
|
||||
title: x => x.__related.song.title,
|
||||
difficulty: x => FormatDifficulty(x.__related.chart, game),
|
||||
level: x => x.__related.chart.levelNum,
|
||||
score: x => x.scoreData.score,
|
||||
percent: x => x.scoreData.percent,
|
||||
lamp: {
|
||||
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
|
||||
strToNum: HumanFriendlyStrToLampIndex(game, playtype),
|
||||
},
|
||||
grade: {
|
||||
valueGetter: x => [x.scoreData.grade, x.scoreData.gradeIndex],
|
||||
strToNum: HumanFriendlyStrToGradeIndex(game, playtype),
|
||||
},
|
||||
}}
|
||||
searchFunctions={CreateDefaultScoreSearch(game, playtype)}
|
||||
rowFunction={sc => (
|
||||
<Row key={sc.scoreID} sc={sc} reqUser={reqUser} showScore={showScore} />
|
||||
)}
|
||||
@@ -83,14 +71,11 @@ function Row({
|
||||
reqUser: PublicUserDocument;
|
||||
showScore?: boolean;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(sc.highlight);
|
||||
const [comment, setComment] = useState(sc.comment);
|
||||
|
||||
const scoreState = { highlight, comment, setHighlight, setComment };
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
@@ -103,11 +88,8 @@ function Row({
|
||||
}
|
||||
>
|
||||
<DifficultyCell chart={sc.__related.chart} game={sc.game} />
|
||||
<TitleCell song={sc.__related.song} chart={sc.__related.chart} game={sc.game} />
|
||||
<ScoreCell score={sc} showScore={showScore} />
|
||||
<LampCell score={sc} />
|
||||
<RatingCell score={sc} />
|
||||
<TimestampCell time={sc.timeAchieved} />
|
||||
<GenericScoreCoreCells sc={sc} showScore={showScore} />
|
||||
<TimestampCell time={sc.timeAchieved} service={sc.service} />
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
ScoreCalculatedDataLookup,
|
||||
PublicUserDocument,
|
||||
Playtypes,
|
||||
ScoreDocument,
|
||||
} from "tachi-common";
|
||||
import TachiTable, { ZTableTHProps } from "../components/TachiTable";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
@@ -23,6 +24,10 @@ import DropdownRow from "../components/DropdownRow";
|
||||
import IIDXScoreDropdown from "../dropdowns/IIDXScoreDropdown";
|
||||
import { IsNullish } from "util/misc";
|
||||
import { Playtype } from "types/tachi";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
import IIDXScoreCoreCells from "../game-core-cells/IIDXScoreCoreCells";
|
||||
import { CreateDefaultScoreSearch } from "util/tables";
|
||||
import RankingCell from "../cells/RankingCell";
|
||||
|
||||
export default function IIDXScoreTable({
|
||||
reqUser,
|
||||
@@ -69,22 +74,7 @@ export default function IIDXScoreTable({
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
]}
|
||||
entryName="Scores"
|
||||
searchFunctions={{
|
||||
artist: x => x.__related.song.artist,
|
||||
title: x => x.__related.song.title,
|
||||
difficulty: x => FormatDifficulty(x.__related.chart, "iidx"),
|
||||
level: x => x.__related.chart.levelNum,
|
||||
score: x => x.scoreData.score,
|
||||
percent: x => x.scoreData.percent,
|
||||
lamp: {
|
||||
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
|
||||
strToNum: HumanFriendlyStrToLampIndex("iidx", playtype),
|
||||
},
|
||||
grade: {
|
||||
valueGetter: x => [x.scoreData.grade, x.scoreData.gradeIndex],
|
||||
strToNum: HumanFriendlyStrToGradeIndex("iidx", playtype),
|
||||
},
|
||||
}}
|
||||
searchFunctions={CreateDefaultScoreSearch<"iidx:SP" | "iidx:DP">("iidx", playtype)}
|
||||
rowFunction={sc => (
|
||||
<Row
|
||||
key={sc.scoreID}
|
||||
@@ -109,14 +99,11 @@ function Row({
|
||||
rating: ScoreCalculatedDataLookup["iidx:SP" | "iidx:DP"];
|
||||
playtype: Playtype;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(sc.highlight);
|
||||
const [comment, setComment] = useState(sc.comment);
|
||||
|
||||
const scoreState = { highlight, comment, setHighlight, setComment };
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<IIDXScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
@@ -129,22 +116,14 @@ function Row({
|
||||
}
|
||||
>
|
||||
<DifficultyCell chart={sc.__related.chart} game="iidx" />
|
||||
<TitleCell song={sc.__related.song} chart={sc.__related.chart} game="iidx" />
|
||||
<ScoreCell score={sc} />
|
||||
<DeltaCell
|
||||
<TitleCell
|
||||
song={sc.__related.song}
|
||||
comment={sc.comment}
|
||||
chart={sc.__related.chart}
|
||||
game="iidx"
|
||||
playtype={playtype}
|
||||
score={sc.scoreData.score}
|
||||
percent={sc.scoreData.percent}
|
||||
grade={sc.scoreData.grade}
|
||||
/>
|
||||
<IIDXLampCell sc={sc} />
|
||||
<td>
|
||||
{!IsNullish(sc.calculatedData[rating])
|
||||
? sc.calculatedData[rating]!.toFixed(2)
|
||||
: "No Data."}
|
||||
</td>
|
||||
<TimestampCell time={sc.timeAchieved} />
|
||||
<IIDXScoreCoreCells sc={sc} rating={rating} />
|
||||
<TimestampCell time={sc.timeAchieved} service={sc.service} />
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FormatDifficulty } from "tachi-common/js/utils/util";
|
||||
import TitleCell from "../cells/TitleCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { ScoreDataset } from "types/tables";
|
||||
import { integer, PublicUserDocument, Playtypes } from "tachi-common";
|
||||
import { integer, PublicUserDocument, Playtypes, ScoreDocument } from "tachi-common";
|
||||
import TachiTable from "../components/TachiTable";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
@@ -16,6 +16,9 @@ import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import GenericScoreDropdown from "../dropdowns/GenericScoreDropdown";
|
||||
import RatingCell from "../cells/RatingCell";
|
||||
import MusecaJudgementCell from "../cells/MusecaJudgementCell";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
import MusecaScoreCoreCells from "../game-core-cells/MusecaScoreCoreCells";
|
||||
import { CreateDefaultScoreSearch } from "util/tables";
|
||||
|
||||
export default function MusecaScoreTable({
|
||||
reqUser,
|
||||
@@ -35,29 +38,14 @@ export default function MusecaScoreTable({
|
||||
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Judgements", "Judge", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near-Miss", "Nr-Ms", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["KtRating", "KtRating", NumericSOV(x => x.calculatedData.ktRating ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
]}
|
||||
entryName="Scores"
|
||||
searchFunctions={{
|
||||
artist: x => x.__related.song.artist,
|
||||
title: x => x.__related.song.title,
|
||||
difficulty: x => FormatDifficulty(x.__related.chart, "museca"),
|
||||
level: x => x.__related.chart.levelNum,
|
||||
score: x => x.scoreData.score,
|
||||
percent: x => x.scoreData.percent,
|
||||
lamp: {
|
||||
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
|
||||
strToNum: HumanFriendlyStrToLampIndex("museca", "Single"),
|
||||
},
|
||||
grade: {
|
||||
valueGetter: x => [x.scoreData.grade, x.scoreData.gradeIndex],
|
||||
strToNum: HumanFriendlyStrToGradeIndex("museca", "Single"),
|
||||
},
|
||||
}}
|
||||
searchFunctions={CreateDefaultScoreSearch<"museca:Single">("museca", "Single")}
|
||||
rowFunction={sc => <Row key={sc.scoreID} sc={sc} reqUser={reqUser} />}
|
||||
/>
|
||||
);
|
||||
@@ -70,14 +58,11 @@ function Row({
|
||||
sc: ScoreDataset<"museca:Single">[0];
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const [highlight, setHighlight] = useState(sc.highlight);
|
||||
const [comment, setComment] = useState(sc.comment);
|
||||
|
||||
const scoreState = { highlight, comment, setHighlight, setComment };
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
@@ -90,12 +75,14 @@ function Row({
|
||||
}
|
||||
>
|
||||
<DifficultyCell chart={sc.__related.chart} game="museca" />
|
||||
<TitleCell song={sc.__related.song} chart={sc.__related.chart} game="museca" />
|
||||
<MillionsScoreCell score={sc} />
|
||||
<MusecaJudgementCell score={sc} />
|
||||
<LampCell score={sc} />
|
||||
<RatingCell score={sc} />
|
||||
<TimestampCell time={sc.timeAchieved} />
|
||||
<TitleCell
|
||||
song={sc.__related.song}
|
||||
comment={sc.comment}
|
||||
chart={sc.__related.chart}
|
||||
game="museca"
|
||||
/>
|
||||
<MusecaScoreCoreCells sc={sc} />
|
||||
<TimestampCell time={sc.timeAchieved} service={sc.service} />
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import TitleCell from "../cells/TitleCell";
|
||||
import TimestampCell from "../cells/TimestampCell";
|
||||
import { NumericSOV, StrSOV } from "util/sorts";
|
||||
import { ScoreDataset } from "types/tables";
|
||||
import { integer, PublicUserDocument, Playtypes } from "tachi-common";
|
||||
import { integer, PublicUserDocument, Playtypes, ScoreDocument } from "tachi-common";
|
||||
import TachiTable from "../components/TachiTable";
|
||||
import DifficultyCell from "../cells/DifficultyCell";
|
||||
import ScoreCell from "../cells/ScoreCell";
|
||||
@@ -14,6 +14,10 @@ import { IsNullish } from "util/misc";
|
||||
import LampCell from "../cells/LampCell";
|
||||
import MillionsScoreCell from "../cells/MillionsScoreCell";
|
||||
import GenericScoreDropdown from "../dropdowns/GenericScoreDropdown";
|
||||
import SDVXJudgementCell from "../cells/SDVXJudgementCell";
|
||||
import { useScoreState } from "../components/UseScoreState";
|
||||
import SDVXScoreCoreCells from "../game-core-cells/SDVXCoreCells";
|
||||
import { CreateDefaultScoreSearch } from "util/tables";
|
||||
|
||||
export default function SDVXScoreTable({
|
||||
reqUser,
|
||||
@@ -33,42 +37,25 @@ export default function SDVXScoreTable({
|
||||
["Chart", "Ch.", NumericSOV(x => x.__related.chart.levelNum)],
|
||||
["Song", "Song", StrSOV(x => x.__related.song.title)],
|
||||
["Score", "Score", NumericSOV(x => x.scoreData.percent)],
|
||||
["Near - Miss", "Nr-Ms", NumericSOV(x => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV(x => x.scoreData.lampIndex)],
|
||||
["VF6", "VF6", NumericSOV(x => x.calculatedData.VF6 ?? 0)],
|
||||
|
||||
["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
|
||||
]}
|
||||
entryName="Scores"
|
||||
searchFunctions={{
|
||||
artist: x => x.__related.song.artist,
|
||||
title: x => x.__related.song.title,
|
||||
difficulty: x => FormatDifficulty(x.__related.chart, "sdvx"),
|
||||
level: x => x.__related.chart.levelNum,
|
||||
score: x => x.scoreData.score,
|
||||
percent: x => x.scoreData.percent,
|
||||
lamp: {
|
||||
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
|
||||
strToNum: HumanFriendlyStrToLampIndex("sdvx", "Single"),
|
||||
},
|
||||
grade: {
|
||||
valueGetter: x => [x.scoreData.grade, x.scoreData.gradeIndex],
|
||||
strToNum: HumanFriendlyStrToGradeIndex("sdvx", "Single"),
|
||||
},
|
||||
}}
|
||||
searchFunctions={CreateDefaultScoreSearch<"sdvx:Single">("sdvx", "Single")}
|
||||
rowFunction={sc => <Row key={sc.scoreID} sc={sc} reqUser={reqUser} />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function Row({ sc, reqUser }: { sc: ScoreDataset<"sdvx:Single">[0]; reqUser: PublicUserDocument }) {
|
||||
const [highlight, setHighlight] = useState(sc.highlight);
|
||||
const [comment, setComment] = useState(sc.comment);
|
||||
|
||||
const scoreState = { highlight, comment, setHighlight, setComment };
|
||||
const scoreState = useScoreState(sc);
|
||||
|
||||
return (
|
||||
<DropdownRow
|
||||
className={highlight ? "highlighted-row" : ""}
|
||||
className={scoreState.highlight ? "highlighted-row" : ""}
|
||||
dropdown={
|
||||
<GenericScoreDropdown
|
||||
chart={sc.__related.chart}
|
||||
@@ -81,11 +68,14 @@ function Row({ sc, reqUser }: { sc: ScoreDataset<"sdvx:Single">[0]; reqUser: Pub
|
||||
}
|
||||
>
|
||||
<DifficultyCell chart={sc.__related.chart} game="sdvx" />
|
||||
<TitleCell song={sc.__related.song} chart={sc.__related.chart} game="sdvx" />
|
||||
<MillionsScoreCell score={sc} />
|
||||
<LampCell score={sc} />
|
||||
<td>{!IsNullish(sc.calculatedData.VF6) ? sc.calculatedData.VF6!.toFixed(3) : "N/A"}</td>
|
||||
<TimestampCell time={sc.timeAchieved} />
|
||||
<TitleCell
|
||||
song={sc.__related.song}
|
||||
comment={sc.comment}
|
||||
chart={sc.__related.chart}
|
||||
game="sdvx"
|
||||
/>
|
||||
<SDVXScoreCoreCells sc={sc} />
|
||||
<TimestampCell time={sc.timeAchieved} service={sc.service} />
|
||||
</DropdownRow>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { FormatGPTRating, UppercaseFirst } from "util/misc";
|
||||
import ProfileBadges from "./ProfileBadges";
|
||||
import { GameClassSets } from "tachi-common/js/game-classes";
|
||||
|
||||
export default function UGPTHeader({
|
||||
export function UGPTHeaderBody({
|
||||
reqUser,
|
||||
game,
|
||||
playtype,
|
||||
@@ -34,124 +34,88 @@ export default function UGPTHeader({
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="card card-custom">
|
||||
<div className="card-header">
|
||||
<h4>
|
||||
{reqUser.username}'s {FormatGame(game, playtype)} Profile
|
||||
</h4>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">
|
||||
<div className="col-12 col-lg-3">
|
||||
<div className="d-flex justify-content-center mb-3">
|
||||
<ProfilePicture user={reqUser} />
|
||||
</div>
|
||||
<div
|
||||
className="d-flex align-items-center"
|
||||
style={{ flexDirection: "column" }}
|
||||
>
|
||||
<ProfileBadges badges={reqUser.badges} />
|
||||
</div>
|
||||
<div className="d-block d-lg-none">
|
||||
<Divider className="mt-4 mb-4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-md-6 col-lg-3">
|
||||
<MiniTable
|
||||
className="table-sm text-center"
|
||||
headers={["Player Info"]}
|
||||
colSpan={2}
|
||||
>
|
||||
<tr>
|
||||
<td>Playcount</td>
|
||||
<td>{stats.totalScores}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Played</td>
|
||||
<td>
|
||||
{stats.mostRecentScore === null ||
|
||||
stats.mostRecentScore.timeAchieved === null
|
||||
? "Unknown."
|
||||
: MillisToSince(stats.mostRecentScore.timeAchieved)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>First Play</td>
|
||||
<td>
|
||||
{stats.firstScore === null ||
|
||||
stats.firstScore.timeAchieved === null
|
||||
? "Unknown."
|
||||
: MillisToSince(stats.firstScore.timeAchieved)}
|
||||
</td>
|
||||
</tr>
|
||||
</MiniTable>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-lg-3">
|
||||
<MiniTable
|
||||
className="table-sm text-center"
|
||||
headers={["Player Stats"]}
|
||||
colSpan={2}
|
||||
>
|
||||
<>
|
||||
{(Object.keys(
|
||||
gptConfig.classHumanisedFormat
|
||||
) as GameClassSets[IDStrings][]).map(k => (
|
||||
<tr key={k}>
|
||||
<td>{UppercaseFirst(k)}</td>
|
||||
<td>
|
||||
{stats.gameStats.classes[k] ? (
|
||||
<ClassBadge
|
||||
showSetOnHover={false}
|
||||
key={`${k}:${stats.gameStats.classes[k]}`}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
classSet={k}
|
||||
classValue={stats.gameStats.classes[k]!}
|
||||
/>
|
||||
) : (
|
||||
"No Data"
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{Object.entries(stats.gameStats.ratings).map(([k, v]) => (
|
||||
<tr key={k}>
|
||||
<td>{UppercaseFirst(k)}</td>
|
||||
<td>
|
||||
{FormatGPTRating(
|
||||
game,
|
||||
playtype,
|
||||
k as ScoreCalculatedDataLookup[IDStrings],
|
||||
v
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
</MiniTable>
|
||||
</div>
|
||||
<div className="col-12 col-lg-3">
|
||||
<RankingData
|
||||
ranking={stats.rankingData.ranking}
|
||||
outOf={stats.rankingData.outOf}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-footer pb-0 pt-0">
|
||||
<BottomNav
|
||||
baseUrl={`/dashboard/users/${reqUser.username}/games/${game}/${playtype}`}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
<div className="col-12 col-lg-3">
|
||||
<div className="d-flex justify-content-center mb-3">
|
||||
<ProfilePicture user={reqUser} />
|
||||
</div>
|
||||
<div className="d-flex align-items-center" style={{ flexDirection: "column" }}>
|
||||
<ProfileBadges badges={reqUser.badges} />
|
||||
</div>
|
||||
<div className="d-block d-lg-none">
|
||||
<Divider className="mt-4 mb-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Divider className="mt-8 mb-4" />
|
||||
|
||||
<div className="col-12 col-md-6 col-lg-3">
|
||||
<MiniTable className="table-sm text-center" headers={["Player Info"]} colSpan={2}>
|
||||
<tr>
|
||||
<td>Playcount</td>
|
||||
<td>{stats.totalScores}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Played</td>
|
||||
<td>
|
||||
{stats.mostRecentScore === null ||
|
||||
stats.mostRecentScore.timeAchieved === null
|
||||
? "Unknown."
|
||||
: MillisToSince(stats.mostRecentScore.timeAchieved)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>First Play</td>
|
||||
<td>
|
||||
{stats.firstScore === null || stats.firstScore.timeAchieved === null
|
||||
? "Unknown."
|
||||
: MillisToSince(stats.firstScore.timeAchieved)}
|
||||
</td>
|
||||
</tr>
|
||||
</MiniTable>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-md-6 col-lg-3">
|
||||
<MiniTable className="table-sm text-center" headers={["Player Stats"]} colSpan={2}>
|
||||
<>
|
||||
{(Object.keys(
|
||||
gptConfig.classHumanisedFormat
|
||||
) as GameClassSets[IDStrings][]).map(k => (
|
||||
<tr key={k}>
|
||||
<td>{UppercaseFirst(k)}</td>
|
||||
<td>
|
||||
{stats.gameStats.classes[k] ? (
|
||||
<ClassBadge
|
||||
showSetOnHover={false}
|
||||
key={`${k}:${stats.gameStats.classes[k]}`}
|
||||
game={game}
|
||||
playtype={playtype}
|
||||
classSet={k}
|
||||
classValue={stats.gameStats.classes[k]!}
|
||||
/>
|
||||
) : (
|
||||
"No Data"
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{Object.entries(stats.gameStats.ratings).map(([k, v]) => (
|
||||
<tr key={k}>
|
||||
<td>{UppercaseFirst(k)}</td>
|
||||
<td>
|
||||
{FormatGPTRating(
|
||||
game,
|
||||
playtype,
|
||||
k as ScoreCalculatedDataLookup[IDStrings],
|
||||
v
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
</MiniTable>
|
||||
</div>
|
||||
<div className="col-12 col-lg-3">
|
||||
<RankingData ranking={stats.rankingData.ranking} outOf={stats.rankingData.outOf} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,7 +133,7 @@ function RankingData({ ranking, outOf }: { ranking: number; outOf: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
function BottomNav({ baseUrl }: { baseUrl: string }) {
|
||||
export function UGPTBottomNav({ baseUrl }: { baseUrl: string }) {
|
||||
return (
|
||||
<div className="row align-items-center mb-0">
|
||||
<Navbar>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import Divider from "components/util/Divider";
|
||||
import React, { useContext } from "react";
|
||||
import { PublicUserDocument } from "tachi-common";
|
||||
import ProfilePicture from "./ProfilePicture";
|
||||
import Navbar from "components/nav/Navbar";
|
||||
import NavItem from "components/nav/NavItem";
|
||||
import ProfileBadges from "./ProfileBadges";
|
||||
import { UserContext } from "context/UserContext";
|
||||
|
||||
export function UserHeaderBody({ reqUser }: { reqUser: PublicUserDocument }) {
|
||||
return (
|
||||
<>
|
||||
<div className="col-12 col-lg-3">
|
||||
<div className="d-flex justify-content-center mb-3">
|
||||
<ProfilePicture user={reqUser} />
|
||||
</div>
|
||||
<div className="d-flex align-items-center" style={{ flexDirection: "column" }}>
|
||||
<ProfileBadges badges={reqUser.badges} />
|
||||
</div>
|
||||
<div className="d-block d-lg-none">
|
||||
<Divider className="mt-4 mb-4" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function UserBottomNav({
|
||||
baseUrl,
|
||||
reqUser,
|
||||
}: {
|
||||
baseUrl: string;
|
||||
reqUser: PublicUserDocument;
|
||||
}) {
|
||||
const { user } = useContext(UserContext);
|
||||
|
||||
const isRequestedUser = !!(user && user.id === reqUser.id);
|
||||
|
||||
const navItems = [
|
||||
<NavItem key="about" to={`${baseUrl}/`}>
|
||||
About
|
||||
</NavItem>,
|
||||
<NavItem key="games" to={`${baseUrl}/games`}>
|
||||
Games
|
||||
</NavItem>,
|
||||
];
|
||||
|
||||
if (isRequestedUser) {
|
||||
navItems.push(
|
||||
<NavItem key="settings" to={`${baseUrl}/settings`}>
|
||||
Settings
|
||||
</NavItem>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="row align-items-center mb-0">
|
||||
<Navbar>{navItems}</Navbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import Divider from "components/util/Divider";
|
||||
import React from "react";
|
||||
|
||||
export default function UserHeaderContainer({
|
||||
header,
|
||||
footer,
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactChild;
|
||||
footer: React.ReactChild;
|
||||
header: React.ReactChild;
|
||||
}) {
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="card card-custom">
|
||||
<div className="card-header">
|
||||
<h4>{header}</h4>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<div className="row align-items-center">{children}</div>
|
||||
</div>
|
||||
<div className="card-footer pb-0 pt-0">{footer}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Divider className="mt-8 mb-4" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
export default function HasDevModeOn({ children }: { children: React.ReactChild }) {
|
||||
// settings mock context?
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
@@ -92,7 +92,9 @@ export function useZTable<D>(originalDataset: D[], providedOptions?: Partial<ZTa
|
||||
return `Displaying ${(page - 1) * pageLen + 1} to ${Math.min(
|
||||
page * pageLen,
|
||||
dataset.length
|
||||
)} of ${dataset.length} ${entryName.toLowerCase()}${search !== "" ? " (Filtered)" : ""}.`;
|
||||
)} of ${dataset.length} ${entryName.toLowerCase()}${
|
||||
search !== "" ? ` (Filtered from ${originalDataset.length})` : ""
|
||||
}.`;
|
||||
}, [page, dataset]);
|
||||
|
||||
// Create a sliding window that can be used for pagination.
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
|
||||
export type PBDataset<I extends IDStrings = IDStrings> = (PBScoreDocument<I> & {
|
||||
__related: { chart: ChartDocument<I>; song: SongDocument<IDStringToGame[I]>; index: integer };
|
||||
__playcount?: integer;
|
||||
})[];
|
||||
|
||||
export type ScoreDataset<I extends IDStrings = IDStrings> = (ScoreDocument<I> & {
|
||||
|
||||
@@ -9,7 +9,7 @@ export function AbsoluteGradeDelta<I extends IDStrings = IDStrings>(
|
||||
) {
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
const maxScore = Math.floor(score * (100 / percent));
|
||||
const maxScore = Math.round(score * (100 / percent));
|
||||
|
||||
const gradeIndex =
|
||||
typeof gradeOrIndex === "number" ? gradeOrIndex : gptConfig.grades.indexOf(gradeOrIndex);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { FormatDifficulty, Game, IDStrings } from "tachi-common";
|
||||
import { ScoreDataset } from "types/tables";
|
||||
import { Playtype } from "types/tachi";
|
||||
import { HumanFriendlyStrToLampIndex, HumanFriendlyStrToGradeIndex } from "./str-to-num";
|
||||
import { ValueGetterOrHybrid } from "./ztable/search";
|
||||
|
||||
export function CreateDefaultScoreSearch<I extends IDStrings = IDStrings>(
|
||||
game: Game,
|
||||
playtype: Playtype
|
||||
): Record<string, ValueGetterOrHybrid<ScoreDataset<I>[0]>> {
|
||||
return {
|
||||
artist: x => x.__related.song.artist,
|
||||
title: x => x.__related.song.title,
|
||||
difficulty: x => FormatDifficulty(x.__related.chart, game),
|
||||
level: x => x.__related.chart.levelNum,
|
||||
score: x => x.scoreData.score,
|
||||
percent: x => x.scoreData.percent,
|
||||
lamp: {
|
||||
valueGetter: x => [x.scoreData.lamp, x.scoreData.lampIndex],
|
||||
strToNum: HumanFriendlyStrToLampIndex(game, playtype),
|
||||
},
|
||||
grade: {
|
||||
valueGetter: x => [x.scoreData.grade, x.scoreData.gradeIndex],
|
||||
strToNum: HumanFriendlyStrToGradeIndex(game, playtype),
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user