From 0b54d6d862fd06b68391d5125a460a8dca3d4818 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sat, 15 Oct 2022 02:49:41 +0100 Subject: [PATCH] feat: rival ranking in UI --- .../games/_game/_playtype/GPTChartPage.tsx | 1 + client/src/app/routes/GameRoutes.tsx | 5 +- .../components/tables/cells/RankingCell.tsx | 70 +++++++++++++++++-- .../tables/components/SelectableRanking.tsx | 64 +++++++++++++++++ .../components/tables/folders/FolderTable.tsx | 33 ++++++--- .../tables/headers/RankingHeader.tsx | 37 ++++++++++ client/src/components/tables/pbs/PBTable.tsx | 19 ++++- .../tables/rivals/RivalChartTable.tsx | 31 +++++--- client/src/context/UGPTContext.tsx | 11 --- client/src/util/tables/create-search.ts | 8 ++- client/src/util/ztable/search.ts | 12 +++- .../framework/pb/create-pb-doc.ts | 3 +- 12 files changed, 252 insertions(+), 42 deletions(-) create mode 100644 client/src/components/tables/components/SelectableRanking.tsx create mode 100644 client/src/components/tables/headers/RankingHeader.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 495220a8d..3477cb8be 100644 --- a/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx +++ b/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx @@ -261,6 +261,7 @@ function ChartLeaderboardTable({ return ( (); @@ -66,7 +67,9 @@ export default function GameRoutes() { - + + + diff --git a/client/src/components/tables/cells/RankingCell.tsx b/client/src/components/tables/cells/RankingCell.tsx index 48a033dc5..7d1d863b5 100644 --- a/client/src/components/tables/cells/RankingCell.tsx +++ b/client/src/components/tables/cells/RankingCell.tsx @@ -1,15 +1,73 @@ +import useLUGPTSettings from "components/util/useLUGPTSettings"; import React from "react"; +import { integer } from "rg-stats/js/util/types"; import { PBScoreDocument } from "tachi-common"; +export type RankingViewMode = "global" | "rival" | "both-if-self" | "global-no-switch"; + export default function RankingCell({ rankingData, + userID, + rankingViewMode, }: { rankingData: PBScoreDocument["rankingData"]; + userID: integer; + rankingViewMode: RankingViewMode; }) { - return ( - - #{rankingData.rank} - /{rankingData.outOf} - - ); + const { settings } = useLUGPTSettings(); + + switch (rankingViewMode) { + case "global": + case "global-no-switch": + return ( + + #{rankingData.rank} + /{rankingData.outOf} + + ); + case "rival": + if (!settings) { + return ( + + No Settings, yet tried to view rival stats? not possible. how'd you get + here. report this. + + ); + } + + if (settings?.userID !== userID) { + return ( + + N/A + + ); + } + + return ( + + #{rankingData.rivalRank} + /{settings.rivals.length + 1} + + ); + + case "both-if-self": + if (settings?.userID === userID && rankingData.rivalRank !== null) { + return ( + + Global #{rankingData.rank} + /{rankingData.outOf} +
+ Rival #{rankingData.rivalRank} + /{settings.rivals.length + 1} + + ); + } + + return ( + + #{rankingData.rank} + /{rankingData.outOf} + + ); + } } diff --git a/client/src/components/tables/components/SelectableRanking.tsx b/client/src/components/tables/components/SelectableRanking.tsx new file mode 100644 index 000000000..84ddef0da --- /dev/null +++ b/client/src/components/tables/components/SelectableRanking.tsx @@ -0,0 +1,64 @@ +import useLUGPTSettings from "components/util/useLUGPTSettings"; +import React from "react"; +import { SetState } from "types/react"; +import { RankingViewMode } from "../cells/RankingCell"; +import SortableTH from "./SortableTH"; +import { ZTableTHProps } from "./TachiTable"; + +export default function SelectableRanking({ + rankingViewMode, + setRankingViewMode, + changeSort, + currentSortMode, + reverseSort, +}: { + rankingViewMode: RankingViewMode; + setRankingViewMode: SetState; +} & ZTableTHProps) { + const { settings } = useLUGPTSettings(); + + if (rankingViewMode === "both-if-self" || rankingViewMode === "global-no-switch" || !settings) { + return ( + + ); + } + + return ( + + +
+ changeSort("Site Ranking")}> + + + + + ); +} diff --git a/client/src/components/tables/folders/FolderTable.tsx b/client/src/components/tables/folders/FolderTable.tsx index 88ad80192..616d5c15e 100644 --- a/client/src/components/tables/folders/FolderTable.tsx +++ b/client/src/components/tables/folders/FolderTable.tsx @@ -6,9 +6,10 @@ import { Game, IDStrings, ScoreCalculatedDataLookup } from "tachi-common"; import { FolderDataset } from "types/tables"; import { Playtype } from "types/tachi"; import Muted from "components/util/Muted"; +import useLUGPTSettings from "components/util/useLUGPTSettings"; import DifficultyCell from "../cells/DifficultyCell"; import IndicatorsCell from "../cells/IndicatorsCell"; -import RankingCell from "../cells/RankingCell"; +import RankingCell, { RankingViewMode } from "../cells/RankingCell"; import TimestampCell from "../cells/TimestampCell"; import TitleCell from "../cells/TitleCell"; import DropdownRow from "../components/DropdownRow"; @@ -19,6 +20,7 @@ import ScoreCoreCells from "../game-core-cells/ScoreCoreCells"; import ChartHeader from "../headers/ChartHeader"; import { GetGPTCoreHeaders } from "../headers/GameHeaders"; import { EmptyHeader, FolderIndicatorHeader } from "../headers/IndicatorHeader"; +import { CreateRankingHeader } from "../headers/RankingHeader"; export default function FolderTable({ dataset, @@ -31,7 +33,10 @@ export default function FolderTable({ }) { const defaultRating = useScoreRatingAlg(game, playtype); + const { settings } = useLUGPTSettings(); + const [rating, setRating] = useState(defaultRating); + const [rankingViewMode, setRankingViewMode] = useState("global"); const headers: Header[] = [ ChartHeader(game, (k) => k), @@ -45,11 +50,11 @@ export default function FolderTable({ setRating, (x) => x.__related.pb ), - [ - "Site Ranking", - "Site Rank", - NumericSOV((x) => x.__related.pb?.rankingData.rank ?? -Infinity), - ], + CreateRankingHeader( + rankingViewMode, + setRankingViewMode, + (k) => k.__related.pb?.rankingData + ), ["Last Raised", "Last Raised", NumericSOV((x) => x.__related.pb?.timeAchieved ?? 0)], ]; @@ -60,7 +65,13 @@ export default function FolderTable({ entryName="Charts" searchFunctions={CreateDefaultFolderSearchParams(game, playtype)} rowFunction={(data) => ( - + )} /> ); @@ -70,10 +81,12 @@ function Row({ data, rating, game, + rankingViewMode, }: { data: FolderDataset[0]; game: Game; rating: ScoreCalculatedDataLookup[I]; + rankingViewMode: RankingViewMode; }) { const score = data.__related.pb; @@ -110,7 +123,11 @@ function Row({ PB - + ); diff --git a/client/src/components/tables/headers/RankingHeader.tsx b/client/src/components/tables/headers/RankingHeader.tsx new file mode 100644 index 000000000..9f3b28fa8 --- /dev/null +++ b/client/src/components/tables/headers/RankingHeader.tsx @@ -0,0 +1,37 @@ +import { NumericSOV } from "util/sorts"; +import React from "react"; +import { PBScoreDocument } from "tachi-common"; +import { SetState } from "types/react"; +import { RankingViewMode } from "../cells/RankingCell"; +import SelectableRanking from "../components/SelectableRanking"; +import { Header, ZTableTHProps } from "../components/TachiTable"; + +export function CreateRankingHeader( + rankingViewMode: RankingViewMode, + setRankingViewMode: SetState, + kMapToRankingData: (k: T) => PBScoreDocument["rankingData"] | undefined +): Header { + return [ + "Site Ranking", + "Site Ranking", + + NumericSOV((x) => { + const rankingData = kMapToRankingData(x); + + if (!rankingData) { + return -Infinity; + } + + return rankingViewMode === "rival" + ? rankingData.rivalRank ?? -Infinity + : rankingData.rank; + }), + (thProps: ZTableTHProps) => ( + + ), + ]; +} diff --git a/client/src/components/tables/pbs/PBTable.tsx b/client/src/components/tables/pbs/PBTable.tsx index 33091825d..6c859b3d7 100644 --- a/client/src/components/tables/pbs/PBTable.tsx +++ b/client/src/components/tables/pbs/PBTable.tsx @@ -8,7 +8,7 @@ import { PBDataset } from "types/tables"; import { Playtype } from "types/tachi"; import DropdownIndicatorCell from "../cells/DropdownIndicatorCell"; import IndexCell from "../cells/IndexCell"; -import RankingCell from "../cells/RankingCell"; +import RankingCell, { RankingViewMode } from "../cells/RankingCell"; import TimestampCell from "../cells/TimestampCell"; import DropdownRow from "../components/DropdownRow"; import TachiTable, { Header } from "../components/TachiTable"; @@ -18,6 +18,7 @@ import ScoreCoreCells from "../game-core-cells/ScoreCoreCells"; import ChartHeader from "../headers/ChartHeader"; import { GetGPTCoreHeaders } from "../headers/GameHeaders"; import { EmptyHeader } from "../headers/IndicatorHeader"; +import { CreateRankingHeader } from "../headers/RankingHeader"; import PBLeadingRows from "./PBLeadingRows"; export default function PBTable({ @@ -29,6 +30,7 @@ export default function PBTable({ showChart = true, playtype, alg, + defaultRankingViewMode, }: { dataset: PBDataset; indexCol?: boolean; @@ -38,10 +40,14 @@ export default function PBTable({ playtype: Playtype; game: Game; alg?: ScoreCalculatedDataLookup[I]; + defaultRankingViewMode?: RankingViewMode; }) { const defaultRating = useScoreRatingAlg(game, playtype); const [rating, setRating] = useState(alg ?? defaultRating); + const [rankingViewMode, setRankingViewMode] = useState( + defaultRankingViewMode ?? "global" + ); const headers: Header[0]>[] = [ ...GetPBLeadingHeaders( @@ -51,7 +57,7 @@ export default function PBTable({ ), EmptyHeader, ...GetGPTCoreHeaders(game, playtype, rating, setRating, (x) => x), - ["Site Ranking", "Site Rank", NumericSOV((x) => x.rankingData.rank)], + CreateRankingHeader(rankingViewMode, setRankingViewMode, (k) => k.rankingData), ["Last Raised", "Last Raised", NumericSOV((x) => x.timeAchieved ?? 0)], EmptyHeader, ]; @@ -86,6 +92,7 @@ export default function PBTable({ rating={rating} game={game} playtype={playtype} + rankingViewMode={rankingViewMode} /> )} /> @@ -100,6 +107,7 @@ function Row({ showUser, game, rating, + rankingViewMode, }: { pb: PBDataset[0]; indexCol: boolean; @@ -110,6 +118,7 @@ function Row({ playtype: Playtype; // ts bug? rating: any; // ScoreCalculatedDataLookup[I]; + rankingViewMode: RankingViewMode; }) { const scoreState = usePBState(pb); @@ -128,7 +137,11 @@ function Row({ {indexCol && } - + {showPlaycount && {pb.__playcount ?? 0}} diff --git a/client/src/components/tables/rivals/RivalChartTable.tsx b/client/src/components/tables/rivals/RivalChartTable.tsx index 102f98686..03a5495eb 100644 --- a/client/src/components/tables/rivals/RivalChartTable.tsx +++ b/client/src/components/tables/rivals/RivalChartTable.tsx @@ -4,7 +4,7 @@ import React, { useState } from "react"; import { ChartDocument, Game, IDStrings, ScoreCalculatedDataLookup } from "tachi-common"; import { RivalChartDataset } from "types/tables"; import IndexCell from "../cells/IndexCell"; -import RankingCell from "../cells/RankingCell"; +import RankingCell, { RankingViewMode } from "../cells/RankingCell"; import TimestampCell from "../cells/TimestampCell"; import UserCell from "../cells/UserCell"; import DropdownRow from "../components/DropdownRow"; @@ -13,6 +13,7 @@ import { GraphAndJudgementDataComponent } from "../dropdowns/components/Document import { GPTDropdownSettings } from "../dropdowns/GPTDropdownSettings"; import ScoreCoreCells from "../game-core-cells/ScoreCoreCells"; import { GetGPTCoreHeaders } from "../headers/GameHeaders"; +import { CreateRankingHeader } from "../headers/RankingHeader"; export default function RivalChartTable({ dataset, @@ -28,6 +29,7 @@ export default function RivalChartTable({ const defaultRating = useScoreRatingAlg(game, playtype); const [rating, setRating] = useState(defaultRating); + const [rankingViewMode, setRankingViewMode] = useState("global"); const headers: Header[] = [ ["#", "#", NumericSOV((x) => x.__related.index)], @@ -39,11 +41,11 @@ export default function RivalChartTable({ setRating, (x) => x.__related.pb ), - [ - "Site Ranking", - "Site Rank", - NumericSOV((x) => x.__related.pb?.rankingData.rank ?? -Infinity), - ], + CreateRankingHeader( + rankingViewMode, + setRankingViewMode, + (k) => k.__related.pb?.rankingData + ), ["Last Raised", "Last Raised", NumericSOV((x) => x.__related.pb?.timeAchieved ?? 0)], ]; @@ -55,7 +57,14 @@ export default function RivalChartTable({ noTopDisplayStr defaultSortMode="#" rowFunction={(data) => ( - + )} /> ); @@ -66,11 +75,13 @@ function Row({ rating, game, chart, + rankingViewMode, }: { data: RivalChartDataset[0]; game: Game; rating: ScoreCalculatedDataLookup[I]; chart: ChartDocument; + rankingViewMode: RankingViewMode; }) { const pb = data.__related.pb; @@ -98,7 +109,11 @@ function Row({ - + ); diff --git a/client/src/context/UGPTContext.tsx b/client/src/context/UGPTContext.tsx index 897c839c7..8251b3045 100644 --- a/client/src/context/UGPTContext.tsx +++ b/client/src/context/UGPTContext.tsx @@ -4,7 +4,6 @@ import fetchUGPTData, { UGPTData } from "components/util/query/fetchUGPTData"; import React, { createContext, useContext, useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import { JustChildren, SetState } from "types/react"; -import { ErrorPage } from "app/pages/ErrorPage"; import { UserContext } from "./UserContext"; /** @@ -33,7 +32,6 @@ export const UGPTContext = createContext<{ setLoggedInData: () => void 0, viewingData: null, - setViewingData: () => void 0, }); @@ -106,15 +104,6 @@ export function UGPTContextProvider({ children }: JustChildren) { return ; } - if (!viewingData) { - return ( - - ); - } - return ( ( score: (x) => x.scoreData.score, percent: (x) => x.scoreData.percent, ranking: (x) => x.rankingData.rank, + rivalRanking: (x) => x.rankingData.rivalRank, highlight: (x) => !!x.highlight, username: (x) => x.__related.user?.username ?? null, lamp: { @@ -98,9 +99,10 @@ export function CreateDefaultFolderSearchParams title: (x) => x.__related.song.title, difficulty: (x) => x.difficulty, level: (x) => x.levelNum, - score: (x) => x.__related.pb?.scoreData.score ?? -Infinity, - percent: (x) => x.__related.pb?.scoreData.percent ?? -Infinity, - ranking: (x) => x.__related.pb?.rankingData.rank ?? -Infinity, + score: (x) => x.__related.pb?.scoreData.score ?? null, + percent: (x) => x.__related.pb?.scoreData.percent ?? null, + ranking: (x) => x.__related.pb?.rankingData.rank ?? null, + rivalRanking: (x) => x.__related.pb?.rankingData.rivalRank ?? null, highlight: (x) => !!x.__related.pb?.highlight, played: (x) => !!x.__related.pb, lamp: { diff --git a/client/src/util/ztable/search.ts b/client/src/util/ztable/search.ts index 3ce3caf65..71bf0824b 100644 --- a/client/src/util/ztable/search.ts +++ b/client/src/util/ztable/search.ts @@ -204,6 +204,16 @@ export function GetValueGetter(v: ValueGetterOrHybrid) { return v.valueGetter; } +function GetValueInsensitive(record: Record, key: string): T | undefined { + for (const [recKey, value] of Object.entries(record)) { + if (recKey.toLowerCase() === key.toLowerCase()) { + return value; + } + } + + return undefined; +} + export function ComposeSearchFunction( valueGetters: Record> ): ZTableSearchFn { @@ -237,7 +247,7 @@ export function ComposeSearchFunction( } for (const directive of directives) { - const vgOrHybrid = valueGetters[directive.key]; + const vgOrHybrid = GetValueInsensitive(valueGetters, directive.key); if (!vgOrHybrid) { continue; diff --git a/server/src/lib/score-import/framework/pb/create-pb-doc.ts b/server/src/lib/score-import/framework/pb/create-pb-doc.ts index eb25ad973..5fd06d12c 100644 --- a/server/src/lib/score-import/framework/pb/create-pb-doc.ts +++ b/server/src/lib/score-import/framework/pb/create-pb-doc.ts @@ -89,12 +89,13 @@ export async function UpdateChartRanking(game: Game, playtype: Playtype, chartID for (const score of scores) { rank++; + seenUserIDs.push(score.userID); const thisUsersRivals = allRivals[score.userID]; let rivalRank: integer | null = null; - if (thisUsersRivals) { + if (thisUsersRivals && thisUsersRivals.length > 0) { rivalRank = thisUsersRivals.filter((e) => seenUserIDs.includes(e)).length + 1; }