From 2a6aa6b8796deacc70371dd3f3dbc64887ba6bbb Mon Sep 17 00:00:00 2001 From: zkldi Date: Thu, 30 Sep 2021 20:35:07 +0100 Subject: [PATCH] More? --- .../games/_game/_playtype/GPTChartPage.tsx | 49 ++-- .../_game/_playtype/GPTLeaderboardsPage.tsx | 238 +++++++++++++++++- .../games/_game/_playtype/GPTSongPage.tsx | 29 --- .../_game/_playtype/LeaderboardsPage.tsx | 2 +- client/src/app/routes/GameRoutes.tsx | 52 +++- client/src/app/routes/UserRoutes.tsx | 4 +- .../layout/header/UserProfileDropdown.tsx | 16 +- .../tables/components/TachiTable.tsx | 19 +- .../src/components/tables/pbs/BMSPBTable.tsx | 30 ++- .../components/tables/pbs/GenericPBTable.tsx | 29 ++- .../src/components/tables/pbs/IIDXPBTable.tsx | 27 +- .../components/tables/pbs/MusecaPBTable.tsx | 24 +- .../components/tables/pbs/PBLeadingRows.tsx | 51 ++++ .../src/components/tables/pbs/SDVXPBTable.tsx | 24 +- client/src/components/util/ApiError.tsx | 12 +- client/src/types/api-returns.ts | 12 + client/src/types/tables.ts | 8 + client/src/util/misc.ts | 39 ++- client/src/util/ztable/search.ts | 11 +- 19 files changed, 538 insertions(+), 138 deletions(-) delete mode 100644 client/src/app/pages/dashboard/games/_game/_playtype/GPTSongPage.tsx create mode 100644 client/src/components/tables/pbs/PBLeadingRows.tsx diff --git a/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx b/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx index f174ce81f..385548067 100644 --- a/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx +++ b/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx @@ -9,6 +9,7 @@ import ApiError from "components/util/ApiError"; import Divider from "components/util/Divider"; import Icon from "components/util/Icon"; import Loading from "components/util/Loading"; +import Muted from "components/util/Muted"; import SelectButton from "components/util/SelectButton"; import { UserContext } from "context/UserContext"; import React, { useContext, useMemo, useState } from "react"; @@ -31,6 +32,7 @@ import { GamePT, SetState } from "types/react"; import { PBDataset } from "types/tables"; import { APIFetchV1, UnsuccessfulAPIFetchResponse } from "util/api"; import { CreateUserMap } from "util/data"; +import { SelectRightChart } from "util/misc"; // This component forms a wrapper around the Real GPT Chart Page // which handles the case where activeChart == null. @@ -60,9 +62,9 @@ export default function GPTChartPage({ `${formatSongTitle} (${formatDiff})` ); - if (!chart) { - setActiveChart(SelectRightChart(gptConfig, difficulty, allCharts)); + setActiveChart(SelectRightChart(gptConfig, difficulty, allCharts)); + if (!chart) { return ; } @@ -158,7 +160,12 @@ function InternalGPTChartPage({ Best 100 - + Your Position @@ -241,7 +248,7 @@ function TopShowcase({ const bestUser = userMap.get(bestPlay.userID)!; // User hasn't played, or isn't logged in or something. - if (!data.adjacent || user?.id === bestPlay.userID) { + if (user?.id === bestPlay.userID) { return ( @@ -249,6 +256,21 @@ function TopShowcase({ ); } + if (!data.adjacent) { + return ( + <> + + + + + + You've not played this chart. + + + + ); + } + const thisUsersPlay = data.adjacent.pb; return ( @@ -304,22 +326,3 @@ function PlayCard({ ); } - -function SelectRightChart(gptConfig: GamePTConfig, chartID: string, charts: ChartDocument[]) { - if (gptConfig.difficulties.includes(chartID as any)) { - for (const chart of charts) { - if (chart.difficulty === chartID && chart.isPrimary) { - return chart; - } - } - } else { - // else, its a chart ID. - for (const chart of charts) { - if (chartID === chart.chartID) { - return chart; - } - } - } - - return null; -} diff --git a/client/src/app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage.tsx b/client/src/app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage.tsx index 223f8afb5..ed127fbae 100644 --- a/client/src/app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage.tsx +++ b/client/src/app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage.tsx @@ -1,7 +1,28 @@ +import ClassBadge from "components/game/ClassBadge"; import useSetSubheader from "components/layout/header/useSetSubheader"; -import React from "react"; -import { FormatGame, GetGameConfig } from "tachi-common"; +import IndexCell from "components/tables/cells/IndexCell"; +import UserCell from "components/tables/cells/UserCell"; +import TachiTable, { Header } from "components/tables/components/TachiTable"; +import PBTable from "components/tables/pbs/PBTable"; +import ApiError from "components/util/ApiError"; +import DebugContent from "components/util/DebugContent"; +import Divider from "components/util/Divider"; +import Icon from "components/util/Icon"; +import Loading from "components/util/Loading"; +import Muted from "components/util/Muted"; +import useApiQuery from "components/util/query/useApiQuery"; +import SelectButton from "components/util/SelectButton"; +import useScoreRatingAlg, { useProfileRatingAlg } from "components/util/useScoreRatingAlg"; +import { UGPTSettingsContext } from "context/UGPTSettingsContext"; +import React, { useContext, useState } from "react"; +import { Col, Row, Form } from "react-bootstrap"; +import { FormatGame, GetGameConfig, GetGamePTConfig } from "tachi-common"; +import { ScoreLeaderboardReturns, UserLeaderboardReturns } from "types/api-returns"; import { GamePT } from "types/react"; +import { PBDataset, UGSDataset } from "types/tables"; +import { CreateChartMap, CreateSongMap, CreateUserMap } from "util/data"; +import { UppercaseFirst } from "util/misc"; +import { NumericSOV, StrSOV } from "util/sorts"; export default function GPTLeaderboardsPage({ game, playtype }: GamePT) { useSetSubheader( @@ -10,5 +31,216 @@ export default function GPTLeaderboardsPage({ game, playtype }: GamePT) { `${FormatGame(game, playtype)} Leaderboards` ); - return
leaderboard
; + const { settings } = useContext(UGPTSettingsContext); + + const [mode, setMode] = useState<"profile" | "score">("profile"); + + return ( + + +
+ + + User Leaderboards + + + + Score Leaderboards + +
+ + + + {mode === "profile" ? ( + + ) : ( + + )} + +
+ ); +} + +function ProfileLeaderboard({ game, playtype }: GamePT) { + const gptConfig = GetGamePTConfig(game, playtype); + + const defaultAlg = useProfileRatingAlg(game, playtype); + + const [alg, setAlg] = useState(defaultAlg); + + const SelectComponent = + gptConfig.profileRatingAlgs.length > 1 ? ( + setAlg(e.target.value as any)}> + {gptConfig.profileRatingAlgs.map(e => ( + + ))} + + ) : null; + + const { data, isLoading, error } = useApiQuery( + `/games/${game}/${playtype}/leaderboard?alg=${alg}` + ); + + if (error) { + return ( + <> + {SelectComponent} + + + ); + } + + if (!data || isLoading) { + return ( + <> + {SelectComponent} + + + ); + } + + const userMap = CreateUserMap(data.users); + + const userDataset: UGSDataset = []; + + for (const [index, gs] of data.gameStats.entries()) { + userDataset.push({ + ...gs, + __related: { + user: userMap.get(gs.userID)!, + index, + }, + }); + } + + return ( + <> + {SelectComponent} + + x.__related.index)], + ["User", "User", StrSOV(x => x.__related.user.username)], + ...gptConfig.profileRatingAlgs.map( + e => + [ + UppercaseFirst(e), + UppercaseFirst(e), + NumericSOV(x => x.ratings[e] ?? -Infinity), + ] as Header + ), + ["Classes", "Classes"], + ]} + rowFunction={r => ( + + + + {gptConfig.profileRatingAlgs.map(e => ( + + {r.ratings[e] + ? gptConfig.profileRatingAlgFormatters[e] + ? gptConfig.profileRatingAlgFormatters[e]!(r.ratings[e]!) + : r.ratings[e]!.toFixed(2) + : "No Data."} + + ))} + + {Object.keys(r.classes).length === 0 ? ( + None + ) : ( + Object.entries(r.classes).map(([k, v]) => ( + + )) + )} + + + )} + /> + + ); +} + +function ScoreLeaderboard({ game, playtype }: GamePT) { + const gptConfig = GetGamePTConfig(game, playtype); + + const defaultAlg = useScoreRatingAlg(game, playtype); + + const [alg, setAlg] = useState(defaultAlg); + + const SelectComponent = + gptConfig.scoreRatingAlgs.length > 1 ? ( + setAlg(e.target.value as any)}> + {gptConfig.scoreRatingAlgs.map(e => ( + + ))} + + ) : null; + + const { data, isLoading, error } = useApiQuery( + `/games/${game}/${playtype}/score-leaderboard?alg=${alg}` + ); + + if (error) { + return ( + <> + {SelectComponent} + + + ); + } + + if (!data || isLoading) { + return ( + <> + {SelectComponent} + + + ); + } + + const songMap = CreateSongMap(data.songs); + const chartMap = CreateChartMap(data.charts); + const userMap = CreateUserMap(data.users); + + const pbDataset: PBDataset = []; + + for (const [index, pb] of data.pbs.entries()) { + pbDataset.push({ + ...pb, + __related: { + chart: chartMap.get(pb.chartID)!, + song: songMap.get(pb.songID)!, + index, + user: userMap.get(pb.userID)!, + }, + }); + } + + return ( + <> + {SelectComponent} + + + + ); } diff --git a/client/src/app/pages/dashboard/games/_game/_playtype/GPTSongPage.tsx b/client/src/app/pages/dashboard/games/_game/_playtype/GPTSongPage.tsx deleted file mode 100644 index 6493bba73..000000000 --- a/client/src/app/pages/dashboard/games/_game/_playtype/GPTSongPage.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import useSetSubheader from "components/layout/header/useSetSubheader"; -import LinkButton from "components/util/LinkButton"; -import React, { useEffect } from "react"; -import { Button, Row } from "react-bootstrap"; -import { ChartDocument, FormatDifficulty, GetGameConfig, GetGamePTConfig } from "tachi-common"; -import { SongsReturn } from "types/api-returns"; -import { GamePT, SetState } from "types/react"; -import { ChangeOpacity } from "util/color-opacity"; -import { NumericSOV } from "util/sorts"; - -export default function GPTSongPage({ - game, - playtype, - song, - charts, - setActiveChart, -}: { setActiveChart: SetState } & SongsReturn & GamePT) { - const formatSongTitle = `${song.artist} - ${song.title}`; - - useSetSubheader( - ["Games", GetGameConfig(game).name, playtype, "Songs", formatSongTitle], - [game, playtype], - formatSongTitle - ); - - setActiveChart(null); - - return
You gotta select a chart to see leaderboards!
; -} diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx index 4b8b0ebc3..e3a480941 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage.tsx @@ -155,7 +155,7 @@ function LeaderboardsPageContent({ header={"Leaderboard"} footer={ View Global Leaderboards diff --git a/client/src/app/routes/GameRoutes.tsx b/client/src/app/routes/GameRoutes.tsx index 37765bb3f..150dea992 100644 --- a/client/src/app/routes/GameRoutes.tsx +++ b/client/src/app/routes/GameRoutes.tsx @@ -3,7 +3,6 @@ import GPTChartPage from "app/pages/dashboard/games/_game/_playtype/GPTChartPage import GPTDevInfo from "app/pages/dashboard/games/_game/_playtype/GPTDevInfo"; import GPTLeaderboardsPage from "app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage"; import GPTMainPage from "app/pages/dashboard/games/_game/_playtype/GPTMainPage"; -import GPTSongPage from "app/pages/dashboard/games/_game/_playtype/GPTSongPage"; import GPTSongsPage from "app/pages/dashboard/games/_game/_playtype/GPTSongsPage"; import { ErrorPage } from "app/pages/ErrorPage"; import ChartInfoFormat from "components/game/charts/ChartInfoFormat"; @@ -15,6 +14,8 @@ import Divider from "components/util/Divider"; import LinkButton from "components/util/LinkButton"; import Loading from "components/util/Loading"; import useApiQuery from "components/util/query/useApiQuery"; +import { UGPTSettingsContext, UGPTSettingsContextProvider } from "context/UGPTSettingsContext"; +import { UserContext } from "context/UserContext"; import { UserSettingsContext } from "context/UserSettingsContext"; import React, { useContext, useEffect, useState } from "react"; import { Col, Row } from "react-bootstrap"; @@ -25,9 +26,11 @@ import { Game, GetGameConfig, GetGamePTConfig, + UGPTSettings, } from "tachi-common"; import { SongsReturn } from "types/api-returns"; import { GamePT, SetState } from "types/react"; +import { APIFetchV1 } from "util/api"; import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts"; import { ChangeOpacity } from "util/color-opacity"; import { NumericSOV } from "util/sorts"; @@ -57,7 +60,9 @@ export default function GameRoutes() { - + + + @@ -79,6 +84,30 @@ function GamePlaytypeRoutes({ game }: { game: Game }) { ); } + const { user } = useContext(UserContext); + const { setSettings } = useContext(UGPTSettingsContext); + + useEffect(() => { + (async () => { + if (user) { + const settingsRes = await APIFetchV1( + `/users/${user.id}/games/${game}/${playtype}/settings` + ); + + if (!settingsRes.success) { + setSettings(null); + return; + } + + setSettings(settingsRes.body); + } + })(); + + return () => { + setSettings(null); + }; + }, [user, game, playtype]); + return ( <>
@@ -122,12 +151,13 @@ function SongChartRoutes({ game, playtype }: GamePT) { const { settings } = useContext(UserSettingsContext); + const gptConfig = GetGamePTConfig(game, playtype); + const [activeChart, setActiveChart] = useState(null); useEffect(() => { setActiveChart(null); }, [game, playtype]); - if (error) { return ; } @@ -148,12 +178,12 @@ function SongChartRoutes({ game, playtype }: GamePT) { - x.levelNum, true))[0] + .difficulty + }`} /> @@ -193,10 +223,6 @@ function SongInfoHeader({ setActiveChart, }: { activeChart: ChartDocument | null; setActiveChart: SetState } & GamePT & SongsReturn) { - // @TODO: Custom game song formatting functions, since for some games - // (like popn) players use the genres to discuss them. - const formatSongTitle = `${song.artist} - ${song.title}`; - const [imgShow, setImgShow] = useState(true); const gptConfig = GetGamePTConfig(game, playtype); diff --git a/client/src/app/routes/UserRoutes.tsx b/client/src/app/routes/UserRoutes.tsx index bcea10762..1e8bb9c11 100644 --- a/client/src/app/routes/UserRoutes.tsx +++ b/client/src/app/routes/UserRoutes.tsx @@ -165,8 +165,8 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: PublicUserDocument ); if (!settingsRes.success) { - console.error(settingsRes); - throw settingsRes; + setSettings(null); + return; } setSettings(settingsRes.body); diff --git a/client/src/components/layout/header/UserProfileDropdown.tsx b/client/src/components/layout/header/UserProfileDropdown.tsx index ec17ab874..f4b17ce95 100644 --- a/client/src/components/layout/header/UserProfileDropdown.tsx +++ b/client/src/components/layout/header/UserProfileDropdown.tsx @@ -1,13 +1,13 @@ -import React, { useContext, useState } from "react"; -import { Link } from "react-router-dom"; -import Dropdown from "react-bootstrap/Dropdown"; import Divider from "components/util/Divider"; -import { PublicUserDocument } from "tachi-common"; -import { Button } from "react-bootstrap"; -import { APIFetchV1, ToAPIURL } from "util/api"; -import toast from "react-hot-toast"; -import { UserContext } from "context/UserContext"; import DropdownToggleOverride from "components/util/DropdownToggleOverride"; +import { UserContext } from "context/UserContext"; +import React, { useContext } from "react"; +import { Button } from "react-bootstrap"; +import Dropdown from "react-bootstrap/Dropdown"; +import toast from "react-hot-toast"; +import { Link } from "react-router-dom"; +import { PublicUserDocument } from "tachi-common"; +import { APIFetchV1, ToAPIURL } from "util/api"; export function UserProfileDropdown({ user }: { user: PublicUserDocument }) { const { setUser } = useContext(UserContext); diff --git a/client/src/components/tables/components/TachiTable.tsx b/client/src/components/tables/components/TachiTable.tsx index 5da98e720..6ce1d06f6 100644 --- a/client/src/components/tables/components/TachiTable.tsx +++ b/client/src/components/tables/components/TachiTable.tsx @@ -1,6 +1,6 @@ import SmallText from "components/util/SmallText"; import { useZTable, ZTableSortFn } from "components/util/table/useZTable"; -import React, { useState } from "react"; +import React, { useContext, useState } from "react"; import { Button } from "react-bootstrap"; import { integer } from "tachi-common"; import NoDataWrapper from "./NoDataWrapper"; @@ -8,6 +8,9 @@ import PageSelector from "./PageSelector"; import SortableTH from "./SortableTH"; import FilterDirectivesIndicator from "./FilterDirectivesIndicator"; import { ComposeSearchFunction, ValueGetterOrHybrid } from "util/ztable/search"; +import { UserSettingsContext } from "context/UserSettingsContext"; +import Icon from "components/util/Icon"; +import { CopyToClipboard } from "util/misc"; export interface ZTableTHProps { changeSort: (str: string) => void; @@ -123,6 +126,8 @@ export default function TachiTable({ reverseSort, }); + const { settings } = useContext(UserSettingsContext); + return (
@@ -156,8 +161,16 @@ export default function TachiTable({
-
{displayStr}
-
+
{displayStr}
+ {settings?.preferences.developerMode && ( +
+ +
+ )} +