diff --git a/client/package.json b/client/package.json
index 0e564407b..1d4d5ac32 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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",
diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml
index cac8da4a1..8c1e5c475 100644
--- a/client/pnpm-lock.yaml
+++ b/client/pnpm-lock.yaml
@@ -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
diff --git a/client/src/app/pages/ErrorPage.tsx b/client/src/app/pages/ErrorPage.tsx
index d8f257360..acad8dcee 100644
--- a/client/src/app/pages/ErrorPage.tsx
+++ b/client/src/app/pages/ErrorPage.tsx
@@ -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;
diff --git a/client/src/app/pages/dashboard/users/UserPage.tsx b/client/src/app/pages/dashboard/users/UserPage.tsx
index cdc65c020..215f7aa57 100644
--- a/client/src/app/pages/dashboard/users/UserPage.tsx
+++ b/client/src/app/pages/dashboard/users/UserPage.tsx
@@ -12,87 +12,5 @@ export default function UserPage({ reqUser }: { reqUser: PublicUserDocument }) {
return ;
}
- // this all sucks
- return (
-
-
-
-
-
})
-
-
-
-
-
-
- {reqUser.socialMedia.discord ? (
- -
- {" "}
- {reqUser.socialMedia.discord}
-
- ) : null}
- {reqUser.socialMedia.discord ? (
- -
- {" "}
- {reqUser.socialMedia.discord}
-
- ) : null}
- {reqUser.socialMedia.discord ? (
- -
- {" "}
- {reqUser.socialMedia.discord}
-
- ) : null}
- {reqUser.socialMedia.discord ? (
- -
- {" "}
- {reqUser.socialMedia.discord}
-
- ) : null}
- {reqUser.socialMedia.discord ? (
- -
- {" "}
- {reqUser.socialMedia.discord}
-
- ) : null}
-
-
-
-
-
-
-
-
-
- );
+ return <>>;
}
diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/ScoresPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/ScoresPage.tsx
index 7c0336714..422ab3684 100644
--- a/client/src/app/pages/dashboard/users/games/_game/_playtype/ScoresPage.tsx
+++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/ScoresPage.tsx
@@ -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({
Recent Scores
+
+
+ Most Played
+
+
+
+ All PBs
+
{scoreSet === "best" ? (
-
- ) : (
+
+ ) : scoreSet === "recent" ? (
+ ) : scoreSet === "all" ? (
+
+ ) : (
+
)}
@@ -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 (
@@ -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"}
/>
diff --git a/client/src/app/routes/UserRoutes.tsx b/client/src/app/routes/UserRoutes.tsx
index 6867a9237..43d22dfb4 100644
--- a/client/src/app/routes/UserRoutes.tsx
+++ b/client/src/app/routes/UserRoutes.tsx
@@ -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 (
-
-
-
-
-
- Settings Page
-
-
+
+
+
+
);
}
+function UserProfileRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
+ return (
+ <>
+
+ }
+ >
+
+
+
+
+
+
+
+ Settings Page
+
+
+ >
+ );
+}
+
function UserGameRoutes({ reqUser }: { reqUser: PublicUserDocument }) {
const { game } = useParams<{ game: string }>();
@@ -153,7 +188,16 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: PublicUserDocument
return (
<>
-
+
+ }
+ >
+
+
-
>
diff --git a/client/src/components/layout/header/SearchBar.tsx b/client/src/components/layout/header/SearchBar.tsx
index efa242126..57e0d3b79 100644
--- a/client/src/components/layout/header/SearchBar.tsx
+++ b/client/src/components/layout/header/SearchBar.tsx
@@ -54,9 +54,7 @@ function SearchResults({ results }: { results: SearchReturns }) {
>
{u.username}
- {u.about.length >= 50
- ? `${u.about.substring(0, 47)}...`
- : u.about}
+ {u.status}
))}
diff --git a/client/src/components/nav/Navbar.tsx b/client/src/components/nav/Navbar.tsx
index 8e67a65c9..a26fd8e43 100644
--- a/client/src/components/nav/Navbar.tsx
+++ b/client/src/components/nav/Navbar.tsx
@@ -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 (
diff --git a/client/src/components/tables/cells/TimestampCell.tsx b/client/src/components/tables/cells/TimestampCell.tsx
index 56e77860e..1d75701ea 100644
--- a/client/src/components/tables/cells/TimestampCell.tsx
+++ b/client/src/components/tables/cells/TimestampCell.tsx
@@ -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 (
-
+ |
{time ? (
<>
{MillisToSince(time)}
@@ -15,6 +22,12 @@ export default function TimestampCell({ time }: { time: integer | null }) {
) : (
"No Data."
)}
+ {service && (
+ <>
+
+ Played On: {service}
+ >
+ )}
|
);
}
diff --git a/client/src/components/tables/cells/TitleCell.tsx b/client/src/components/tables/cells/TitleCell.tsx
index ff4650fcd..71e272f8a 100644
--- a/client/src/components/tables/cells/TitleCell.tsx
+++ b/client/src/components/tables/cells/TitleCell.tsx
@@ -8,11 +8,13 @@ export default function TitleCell({
song,
chart,
noArtist,
+ comment,
}: {
song: SongDocument;
chart: ChartDocument;
game: Game;
noArtist?: boolean;
+ comment?: string | null;
}) {
return (
@@ -25,6 +27,12 @@ export default function TitleCell({
>
)}
+ {comment && (
+ <>
+
+ "{comment}"
+ >
+ )}
|
);
}
diff --git a/client/src/components/tables/components/TachiTable.tsx b/client/src/components/tables/components/TachiTable.tsx
index 5eb0b3fea..5da98e720 100644
--- a/client/src/components/tables/components/TachiTable.tsx
+++ b/client/src/components/tables/components/TachiTable.tsx
@@ -77,12 +77,14 @@ export default function TachiTable({
defaultSortMode,
defaultReverseSort,
searchFunctions,
+ noTopDisplayStr = false,
}: {
dataset: D[];
rowFunction: (data: D) => JSX.Element;
headers: Header[];
- entryName?: string;
+ entryName: string;
pageLen?: integer;
+ noTopDisplayStr?: boolean;
defaultSortMode?: string;
defaultReverseSort?: boolean;
searchFunctions?: Record>;
@@ -123,18 +125,26 @@ export default function TachiTable({
return (
- {searchFunctions && (
-
- setSearch(e.target.value)}
- type="text"
- placeholder={`Filter ${entryName}`}
- value={search}
- />
-
-
- )}
+
+ {!noTopDisplayStr && (
+
{displayStr}
+ )}
+ {searchFunctions && (
+
+ setSearch(e.target.value)}
+ type="text"
+ placeholder={`Filter ${entryName}`}
+ value={search}
+ />
+
+
+ )}
+
diff --git a/client/src/components/tables/components/UseScoreState.tsx b/client/src/components/tables/components/UseScoreState.tsx
new file mode 100644
index 000000000..3083e00bb
--- /dev/null
+++ b/client/src/components/tables/components/UseScoreState.tsx
@@ -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 };
+}
diff --git a/client/src/components/tables/dropdowns/GenericPBDropdown.tsx b/client/src/components/tables/dropdowns/GenericPBDropdown.tsx
index eb4ac9d37..0f599d551 100644
--- a/client/src/components/tables/dropdowns/GenericPBDropdown.tsx
+++ b/client/src/components/tables/dropdowns/GenericPBDropdown.tsx
@@ -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 {
score: ScoreDocument | PBScoreDocument;
scoreState: ScoreState;
+ pbData: UGPTChartPBComposition;
}
export default function GenericPBDropdown({
@@ -44,26 +47,21 @@ export default function GenericPBDropdown({
DocComponent?: (props: {
score: ScoreDocument | PBScoreDocument;
scoreState: ScoreState;
+ showSingleScoreNote?: boolean;
+ 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>(
- `/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>(
+ `/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
);
+ const { isLoading: histIsLoading, error: histError, data: histData } = useApiQuery<
+ ScoreDocument[]
+ >(`/users/${reqUser.id}/games/${game}/${playtype}/scores/${chart.chartID}`);
+
const currentScoreDoc: ScoreDocument | PBScoreDocument | null = useMemo(() => {
if (!data) {
// dont worry about this null, it never gets below the rquery checks
@@ -71,6 +69,12 @@ export default function GenericPBDropdown({
}
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({
);
}
+ // @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 = (
+
+ );
} else if (view === "debug") {
body = ;
} else {
- body = ;
+ body = (
+
+ );
}
return (
@@ -114,17 +136,21 @@ export default function GenericPBDropdown({
PB Info
-
-
- Best Score
-
-
-
- Best Lamp
-
-
+ {!isComposedFromSingleScore && (
+ <>
+
+
+ Best Score
+
+
+
+ Best Lamp
+
+ >
+ )}
+
- Play History
+ Play History{histData && ` (${histData.length})`}
{user?.authLevel === "admin" && (
diff --git a/client/src/components/tables/dropdowns/GenericScoreDropdown.tsx b/client/src/components/tables/dropdowns/GenericScoreDropdown.tsx
index 12e701fb2..02a21bd88 100644
--- a/client/src/components/tables/dropdowns/GenericScoreDropdown.tsx
+++ b/client/src/components/tables/dropdowns/GenericScoreDropdown.tsx
@@ -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({
DocComponent?: (props: {
score: ScoreDocument | PBScoreDocument;
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>(
- `/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>(
+ `/users/${reqUser.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true`
);
+ const { isLoading: histIsLoading, error: histError, data: histData } = useApiQuery<
+ ScoreDocument[]
+ >(`/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({
let body;
if (view === "history") {
- body = <>>; // todo
+ body = (
+
+ );
} else if (view === "debug") {
body = ;
} else if (view === "moreInfo") {
- body = ;
+ body = ;
}
return (
@@ -100,16 +105,16 @@ export default function GenericScoreDropdown({
Versus PB
-
+
Play History
- {user?.authLevel === "admin" && (
+
Debug Info
- )}
+
>
}
>
diff --git a/client/src/components/tables/dropdowns/IIDXPBDropdown.tsx b/client/src/components/tables/dropdowns/IIDXPBDropdown.tsx
index 9950aeafb..4d226ecb4 100644
--- a/client/src/components/tables/dropdowns/IIDXPBDropdown.tsx
+++ b/client/src/components/tables/dropdowns/IIDXPBDropdown.tsx
@@ -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;
};
} & 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>(
- `/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 (
-
-
-
- );
- }
-
return (
| ScoreDocument<"iidx:SP" | "iidx:DP">;
scoreState: ScoreState;
+ pbData: UGPTChartPBComposition<"iidx:SP" | "iidx:DP">;
}) {
return (
<>
diff --git a/client/src/components/tables/dropdowns/IIDXScoreDropdown.tsx b/client/src/components/tables/dropdowns/IIDXScoreDropdown.tsx
index ffae8450b..5e757c766 100644
--- a/client/src/components/tables/dropdowns/IIDXScoreDropdown.tsx
+++ b/client/src/components/tables/dropdowns/IIDXScoreDropdown.tsx
@@ -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
;
};
} & 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>(
- `/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>(
+ `/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 = (
- <>
-
-
-
- {IsScore(thisScore) && }
-
- >
- );
- } else if (view === "debug") {
- content = ;
- }
-
return (
-
-
-
- This Score
-
-
-
- Versus PB
-
-
-
- Play History
-
-
-
- Debug Info
-
- >
+
+ {...{ 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}
-
+ />
);
}
diff --git a/client/src/components/tables/dropdowns/components/DropdownScoreButtons.tsx b/client/src/components/tables/dropdowns/components/DropdownScoreButtons.tsx
index 323b16529..ccbbbfc0d 100644
--- a/client/src/components/tables/dropdowns/components/DropdownScoreButtons.tsx
+++ b/client/src/components/tables/dropdowns/components/DropdownScoreButtons.tsx
@@ -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
>
) : (
+
e.comment)
+ .filter(e => e !== null)
+ .join("; ")}
+ />
)}
diff --git a/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx b/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx
index d09607c1e..7408bf4d3 100644
--- a/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx
+++ b/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx
@@ -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}
/>
-
+
@@ -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
};
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({
<>
-
- No graphs available :(
-
+ {GraphComponent ? (
+
+ ) : (
+
+ No graphs available :(
+
+ )}
+
{IsScore(score) ? (
<>
- {renderScoreInfo &&
}
+ {renderScoreInfo && !showSingleScoreNote &&
}
+ {showSingleScoreNote && (
+
+
+
+
+ In this case, your best lamp and your best score were the
+ same!
+
+
+ )}
) : (
+
e.comment)
+ .filter(e => e !== null)
+ .join("; ")}
+ />
)}
diff --git a/client/src/components/tables/dropdowns/components/IIDXScoreDropdownParts.tsx b/client/src/components/tables/dropdowns/components/IIDXScoreDropdownParts.tsx
index b624dcfdc..e7c6e593f 100644
--- a/client/src/components/tables/dropdowns/components/IIDXScoreDropdownParts.tsx
+++ b/client/src/components/tables/dropdowns/components/IIDXScoreDropdownParts.tsx
@@ -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 (
<>
diff --git a/client/src/components/tables/dropdowns/components/PlayHistory.tsx b/client/src/components/tables/dropdowns/components/PlayHistory.tsx
new file mode 100644
index 000000000..e51bbf8f4
--- /dev/null
+++ b/client/src/components/tables/dropdowns/components/PlayHistory.tsx
@@ -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
;
+ }
+
+ if (error) {
+ return <>{error.description}>;
+ }
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx b/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx
index 69fbdd83e..0dbe1e370 100644
--- a/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx
+++ b/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx
@@ -53,9 +53,12 @@ export default function ScoreEditButtons({
);
@@ -113,12 +120,14 @@ function CommentModal({
comment,
setComment,
scoreID,
+ score,
}: {
show: boolean;
setShow: SetState
;
comment: string | null;
setComment: SetState;
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)}
/>
-
+
Submit
@@ -159,11 +169,12 @@ function CommentModal({
ModifyScore(scoreID, { comment: null }).then(r => {
if (r) {
setComment(null);
+ score.comment = null;
setShow(false);
}
})
diff --git a/client/src/components/tables/game-core-cells/BMSScoreCoreCells.tsx b/client/src/components/tables/game-core-cells/BMSScoreCoreCells.tsx
new file mode 100644
index 000000000..28935a814
--- /dev/null
+++ b/client/src/components/tables/game-core-cells/BMSScoreCoreCells.tsx
@@ -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 (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/client/src/components/tables/game-core-cells/GenericScoreCoreCells.tsx b/client/src/components/tables/game-core-cells/GenericScoreCoreCells.tsx
new file mode 100644
index 000000000..93274f7e5
--- /dev/null
+++ b/client/src/components/tables/game-core-cells/GenericScoreCoreCells.tsx
@@ -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 (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/client/src/components/tables/game-core-cells/IIDXScoreCoreCells.tsx b/client/src/components/tables/game-core-cells/IIDXScoreCoreCells.tsx
new file mode 100644
index 000000000..55bda9a8b
--- /dev/null
+++ b/client/src/components/tables/game-core-cells/IIDXScoreCoreCells.tsx
@@ -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 (
+ <>
+
+
+
+
+ {!IsNullish(sc.calculatedData[rating])
+ ? sc.calculatedData[rating]!.toFixed(2)
+ : "No Data."}
+ |
+ >
+ );
+}
diff --git a/client/src/components/tables/game-core-cells/MusecaScoreCoreCells.tsx b/client/src/components/tables/game-core-cells/MusecaScoreCoreCells.tsx
new file mode 100644
index 000000000..8f64673e8
--- /dev/null
+++ b/client/src/components/tables/game-core-cells/MusecaScoreCoreCells.tsx
@@ -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 (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/client/src/components/tables/game-core-cells/SDVXCoreCells.tsx b/client/src/components/tables/game-core-cells/SDVXCoreCells.tsx
new file mode 100644
index 000000000..c861f054c
--- /dev/null
+++ b/client/src/components/tables/game-core-cells/SDVXCoreCells.tsx
@@ -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 (
+ <>
+
+
+
+ {!IsNullish(sc.calculatedData.VF6) ? sc.calculatedData.VF6!.toFixed(3) : "N/A"} |
+ >
+ );
+}
diff --git a/client/src/components/tables/game-core-cells/ScoreCoreCells.tsx b/client/src/components/tables/game-core-cells/ScoreCoreCells.tsx
new file mode 100644
index 000000000..66e4d0445
--- /dev/null
+++ b/client/src/components/tables/game-core-cells/ScoreCoreCells.tsx
@@ -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 (
+
+ );
+ } else if (game === "bms") {
+ return ;
+ } else if (game === "sdvx" || game === "usc") {
+ return ;
+ } else if (game === "maimai") {
+ return ;
+ } else if (game === "museca") {
+ return ;
+ }
+
+ return ;
+}
diff --git a/client/src/components/tables/history-scores/HistoryScoreTable.tsx b/client/src/components/tables/history-scores/HistoryScoreTable.tsx
new file mode 100644
index 000000000..785a59ce2
--- /dev/null
+++ b/client/src/components/tables/history-scores/HistoryScoreTable.tsx
@@ -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 (
+ 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) => (
+
+ ),
+ ]
+ : ["Rating", "Rating", NumericSOV(x => x.calculatedData[rating] ?? 0)],
+ ["Timestamp", "Timestamp", NumericSOV(x => x.timeAchieved ?? 0)],
+ ]}
+ defaultSortMode="Timestamp"
+ defaultReverseSort
+ rowFunction={sc =>
}
+ />
+ );
+}
+
+function Row({
+ sc,
+ rating,
+ game,
+}: {
+ sc: ScoreDocument;
+ rating: ScoreCalculatedDataLookup[IDStrings];
+ game: Game;
+}) {
+ return (
+
+
+
+
+ );
+}
diff --git a/client/src/components/tables/pbs/BMSPBTable.tsx b/client/src/components/tables/pbs/BMSPBTable.tsx
index 83e32067e..62d408f7a 100644
--- a/client/src/components/tables/pbs/BMSPBTable.tsx
+++ b/client/src/components/tables/pbs/BMSPBTable.tsx
@@ -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[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 (
}
- className={highlight ? "highlighted-row" : ""}
+ className={scoreState.highlight ? "highlighted-row" : ""}
>
{indexCol && }
@@ -159,6 +167,7 @@ function Row({
+ {showPlaycount && {pb.__playcount ?? 0} | }
);
}
diff --git a/client/src/components/tables/pbs/GenericPBTable.tsx b/client/src/components/tables/pbs/GenericPBTable.tsx
index eed1fd7c4..004cc4a8a 100644
--- a/client/src/components/tables/pbs/GenericPBTable.tsx
+++ b/client/src/components/tables/pbs/GenericPBTable.tsx
@@ -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 (
}
- className={highlight ? "highlighted-row" : ""}
+ className={scoreState.highlight ? "highlighted-row" : ""}
>
{indexCol && }
@@ -126,6 +131,7 @@ function Row({
+ {showPlaycount && {pb.__playcount ?? 0} | }
);
}
diff --git a/client/src/components/tables/pbs/IIDXPBTable.tsx b/client/src/components/tables/pbs/IIDXPBTable.tsx
index 62991d355..d89b86171 100644
--- a/client/src/components/tables/pbs/IIDXPBTable.tsx
+++ b/client/src/components/tables/pbs/IIDXPBTable.tsx
@@ -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 (
}
- className={highlight ? "highlighted-row" : ""}
+ className={scoreState.highlight ? "highlighted-row" : ""}
>
{indexCol && }
@@ -148,6 +156,7 @@ function Row({
+ {showPlaycount && {pb.__playcount ?? 0} | }
);
}
diff --git a/client/src/components/tables/pbs/MusecaPBTable.tsx b/client/src/components/tables/pbs/MusecaPBTable.tsx
index 763b8355d..ff6409d29 100644
--- a/client/src/components/tables/pbs/MusecaPBTable.tsx
+++ b/client/src/components/tables/pbs/MusecaPBTable.tsx
@@ -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 (
}
- className={highlight ? "highlighted-row" : ""}
+ className={scoreState.highlight ? "highlighted-row" : ""}
>
{indexCol && }
@@ -114,6 +120,7 @@ function Row({
+ {showPlaycount && {pb.__playcount ?? 0} | }
);
}
diff --git a/client/src/components/tables/pbs/PBTable.tsx b/client/src/components/tables/pbs/PBTable.tsx
index bbad759bb..e430ac8ce 100644
--- a/client/src/components/tables/pbs/PBTable.tsx
+++ b/client/src/components/tables/pbs/PBTable.tsx
@@ -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 ;
diff --git a/client/src/components/tables/pbs/SDVXPBTable.tsx b/client/src/components/tables/pbs/SDVXPBTable.tsx
index 2d899807d..3ec7b409f 100644
--- a/client/src/components/tables/pbs/SDVXPBTable.tsx
+++ b/client/src/components/tables/pbs/SDVXPBTable.tsx
@@ -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 (
}
- className={highlight ? "highlighted-row" : ""}
+ className={scoreState.highlight ? "highlighted-row" : ""}
>
{indexCol && }
@@ -114,6 +122,7 @@ function Row({
+ {showPlaycount && {pb.__playcount ?? 0} | }
);
}
diff --git a/client/src/components/tables/scores/BMSScoreTable.tsx b/client/src/components/tables/scores/BMSScoreTable.tsx
index 4efa37225..49f257743 100644
--- a/client/src/components/tables/scores/BMSScoreTable.tsx
+++ b/client/src/components/tables/scores/BMSScoreTable.tsx
@@ -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 => (
)}
@@ -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 (
-
-
-
-
-
- {!IsNullish(sc.calculatedData.sieglinde)
- ? sc.calculatedData.sieglinde!.toFixed(2)
- : "N/A"}
- |
-
+
+
);
}
diff --git a/client/src/components/tables/scores/GenericScoreTable.tsx b/client/src/components/tables/scores/GenericScoreTable.tsx
index e76801657..d5d99c5bd 100644
--- a/client/src/components/tables/scores/GenericScoreTable.tsx
+++ b/client/src/components/tables/scores/GenericScoreTable.tsx
@@ -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 => (
)}
@@ -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 (
-
-
-
-
-
+
+
);
}
diff --git a/client/src/components/tables/scores/IIDXScoreTable.tsx b/client/src/components/tables/scores/IIDXScoreTable.tsx
index eef1d48a9..b16fd1ccd 100644
--- a/client/src/components/tables/scores/IIDXScoreTable.tsx
+++ b/client/src/components/tables/scores/IIDXScoreTable.tsx
@@ -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 => (
-
-
-
-
- |
- {!IsNullish(sc.calculatedData[rating])
- ? sc.calculatedData[rating]!.toFixed(2)
- : "No Data."}
- |
-
+
+
);
}
diff --git a/client/src/components/tables/scores/MusecaScoreTable.tsx b/client/src/components/tables/scores/MusecaScoreTable.tsx
index 640f63662..ce35b42a9 100644
--- a/client/src/components/tables/scores/MusecaScoreTable.tsx
+++ b/client/src/components/tables/scores/MusecaScoreTable.tsx
@@ -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 =>
}
/>
);
@@ -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 (
-
-
-
-
-
-
+
+
+
);
}
diff --git a/client/src/components/tables/scores/SDVXScoreTable.tsx b/client/src/components/tables/scores/SDVXScoreTable.tsx
index db90c11a2..40a4a26e4 100644
--- a/client/src/components/tables/scores/SDVXScoreTable.tsx
+++ b/client/src/components/tables/scores/SDVXScoreTable.tsx
@@ -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 =>
}
/>
);
}
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 (
[0]; reqUser: Pub
}
>
-
-
-
- {!IsNullish(sc.calculatedData.VF6) ? sc.calculatedData.VF6!.toFixed(3) : "N/A"} |
-
+
+
+
);
}
diff --git a/client/src/components/user/UGPTHeader.tsx b/client/src/components/user/UGPTHeader.tsx
index 0f6d5c849..c3603a801 100644
--- a/client/src/components/user/UGPTHeader.tsx
+++ b/client/src/components/user/UGPTHeader.tsx
@@ -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 (
-
-
-
-
-
- {reqUser.username}'s {FormatGame(game, playtype)} Profile
-
-
-
-
-
-
-
-
-
- | Playcount |
- {stats.totalScores} |
-
-
- | Last Played |
-
- {stats.mostRecentScore === null ||
- stats.mostRecentScore.timeAchieved === null
- ? "Unknown."
- : MillisToSince(stats.mostRecentScore.timeAchieved)}
- |
-
-
- | First Play |
-
- {stats.firstScore === null ||
- stats.firstScore.timeAchieved === null
- ? "Unknown."
- : MillisToSince(stats.firstScore.timeAchieved)}
- |
-
-
-
-
-
- <>
- {(Object.keys(
- gptConfig.classHumanisedFormat
- ) as GameClassSets[IDStrings][]).map(k => (
-
- | {UppercaseFirst(k)} |
-
- {stats.gameStats.classes[k] ? (
-
- ) : (
- "No Data"
- )}
- |
-
- ))}
- {Object.entries(stats.gameStats.ratings).map(([k, v]) => (
-
- | {UppercaseFirst(k)} |
-
- {FormatGPTRating(
- game,
- playtype,
- k as ScoreCalculatedDataLookup[IDStrings],
- v
- )}
- |
-
- ))}
- >
-
-
-
-
-
-
-
-
-
-
+ <>
+
-
-
+
+
+
+
+ | Playcount |
+ {stats.totalScores} |
+
+
+ | Last Played |
+
+ {stats.mostRecentScore === null ||
+ stats.mostRecentScore.timeAchieved === null
+ ? "Unknown."
+ : MillisToSince(stats.mostRecentScore.timeAchieved)}
+ |
+
+
+ | First Play |
+
+ {stats.firstScore === null || stats.firstScore.timeAchieved === null
+ ? "Unknown."
+ : MillisToSince(stats.firstScore.timeAchieved)}
+ |
+
+
-
+
+
+ <>
+ {(Object.keys(
+ gptConfig.classHumanisedFormat
+ ) as GameClassSets[IDStrings][]).map(k => (
+
+ | {UppercaseFirst(k)} |
+
+ {stats.gameStats.classes[k] ? (
+
+ ) : (
+ "No Data"
+ )}
+ |
+
+ ))}
+ {Object.entries(stats.gameStats.ratings).map(([k, v]) => (
+
+ | {UppercaseFirst(k)} |
+
+ {FormatGPTRating(
+ game,
+ playtype,
+ k as ScoreCalculatedDataLookup[IDStrings],
+ v
+ )}
+ |
+
+ ))}
+ >
+
+
+
+
+
+ >
);
}
@@ -169,7 +133,7 @@ function RankingData({ ranking, outOf }: { ranking: number; outOf: number }) {
);
}
-function BottomNav({ baseUrl }: { baseUrl: string }) {
+export function UGPTBottomNav({ baseUrl }: { baseUrl: string }) {
return (
diff --git a/client/src/components/user/UserHeader.tsx b/client/src/components/user/UserHeader.tsx
new file mode 100644
index 000000000..e784fd973
--- /dev/null
+++ b/client/src/components/user/UserHeader.tsx
@@ -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 (
+ <>
+
+ >
+ );
+}
+
+export function UserBottomNav({
+ baseUrl,
+ reqUser,
+}: {
+ baseUrl: string;
+ reqUser: PublicUserDocument;
+}) {
+ const { user } = useContext(UserContext);
+
+ const isRequestedUser = !!(user && user.id === reqUser.id);
+
+ const navItems = [
+
+ About
+ ,
+
+ Games
+ ,
+ ];
+
+ if (isRequestedUser) {
+ navItems.push(
+
+ Settings
+
+ );
+ }
+
+ return (
+
+ {navItems}
+
+ );
+}
diff --git a/client/src/components/user/UserHeaderContainer.tsx b/client/src/components/user/UserHeaderContainer.tsx
new file mode 100644
index 000000000..9c8c241e7
--- /dev/null
+++ b/client/src/components/user/UserHeaderContainer.tsx
@@ -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 (
+
+
+
+
+
{header}
+
+
+
{footer}
+
+
+
+
+ );
+}
diff --git a/client/src/components/util/HasDevModeOn.tsx b/client/src/components/util/HasDevModeOn.tsx
new file mode 100644
index 000000000..5653bb079
--- /dev/null
+++ b/client/src/components/util/HasDevModeOn.tsx
@@ -0,0 +1,7 @@
+import React from "react";
+
+export default function HasDevModeOn({ children }: { children: React.ReactChild }) {
+ // settings mock context?
+
+ return <>{children}>;
+}
diff --git a/client/src/components/util/table/useZTable.tsx b/client/src/components/util/table/useZTable.tsx
index ae0c0864d..1185c7f5e 100644
--- a/client/src/components/util/table/useZTable.tsx
+++ b/client/src/components/util/table/useZTable.tsx
@@ -92,7 +92,9 @@ export function useZTable(originalDataset: D[], providedOptions?: Partial = (PBScoreDocument & {
__related: { chart: ChartDocument; song: SongDocument; index: integer };
+ __playcount?: integer;
})[];
export type ScoreDataset = (ScoreDocument & {
diff --git a/client/src/util/grade-deltas.ts b/client/src/util/grade-deltas.ts
index 285faf413..b8ccd6eed 100644
--- a/client/src/util/grade-deltas.ts
+++ b/client/src/util/grade-deltas.ts
@@ -9,7 +9,7 @@ export function AbsoluteGradeDelta(
) {
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);
diff --git a/client/src/util/tables.ts b/client/src/util/tables.ts
new file mode 100644
index 000000000..1cf0bc9ca
--- /dev/null
+++ b/client/src/util/tables.ts
@@ -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(
+ game: Game,
+ playtype: Playtype
+): Record[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),
+ },
+ };
+}