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.username}'s -
- -
-
-
{reqUser.username}
-
-
- -
-
    - {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)} />
-
@@ -159,11 +169,12 @@ function CommentModal({