From 09ba3cb2af3b5ff46dd42ce61358f80e37ed2610 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Thu, 9 Dec 2021 14:02:46 +0000 Subject: [PATCH 01/20] add airbag splashes --- client/src/util/splashes.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/util/splashes.ts b/client/src/util/splashes.ts index 2bd06133c..acb68dfbd 100644 --- a/client/src/util/splashes.ts +++ b/client/src/util/splashes.ts @@ -112,6 +112,8 @@ export const contentiousSplashes = [ "...and good faith is when people agree with me", "Anyone else at this party gay?", "I have the transfeminine urge to pee.", + "I HAVE TO LIE IN THE MIDDLE OF THE FLOOR", + "COMPLETELY MOTIONLESS NOT DARING TO BREATHE", ]; export const heySplashes = [ From 22985d6a7198d112e5b09489da7ab0acedaa319f Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Fri, 10 Dec 2021 01:03:36 +0000 Subject: [PATCH 02/20] Fix bug where 0 would be rendered in some circumstances for classInfo --- .../app/pages/dashboard/games/_game/_playtype/GPTMainPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/app/pages/dashboard/games/_game/_playtype/GPTMainPage.tsx b/client/src/app/pages/dashboard/games/_game/_playtype/GPTMainPage.tsx index 65cdf653b..e0a9d8cef 100644 --- a/client/src/app/pages/dashboard/games/_game/_playtype/GPTMainPage.tsx +++ b/client/src/app/pages/dashboard/games/_game/_playtype/GPTMainPage.tsx @@ -175,7 +175,7 @@ function AchievedClassRow({ playtype={playtype} classValue={classInfo.classValue} /> - {classInfo.classOldValue && ( + {classInfo.classOldValue !== null && ( <> {" "} (Raised from{" "} From 3bc5ecd9c77a6856799c0c70d46377a3f585a855 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 00:47:50 +0000 Subject: [PATCH 03/20] Fix scoreContentDropdown to use ScoreCoreCells instead of anything else. --- .../components/GenericScoreContentDropdown.tsx | 15 +++++++-------- .../dropdowns/components/ScoreEditButtons.tsx | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx b/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx index 595420917..a2f6e91a8 100644 --- a/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx +++ b/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx @@ -2,9 +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 ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells"; import ExternalLink from "components/util/ExternalLink"; import React, { useEffect, useState } from "react"; -import { PBScoreDocument, ScoreDocument } from "tachi-common"; +import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common"; import { UGPTChartPBComposition } from "types/api-returns"; import { SetState } from "types/react"; import { IsScore } from "util/asserts"; @@ -24,15 +25,13 @@ export function ScoreInfo({ score }: { score: ScoreDocument | PBScoreDocument }) - - - {/* @ts-expect-error yeah we know service doesnt necessarily exist */} diff --git a/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx b/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx index 4308e0116..5d24b2f8e 100644 --- a/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx +++ b/client/src/components/tables/dropdowns/components/ScoreEditButtons.tsx @@ -41,7 +41,7 @@ export default function ScoreEditButtons({ onClick={() => setShow(true)} > - Edit + Comment @@ -49,7 +49,7 @@ export default function ScoreEditButtons({ )} From 512e4320d5b807ad50466dcbcf75c92907013086 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:02:39 +0000 Subject: [PATCH 04/20] Tidy up the headers --- .../GenericScoreContentDropdown.tsx | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx b/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx index a2f6e91a8..7470ebb0d 100644 --- a/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx +++ b/client/src/components/tables/dropdowns/components/GenericScoreContentDropdown.tsx @@ -1,37 +1,33 @@ -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 ScoreCoreCells from "components/tables/game-core-cells/ScoreCoreCells"; -import ExternalLink from "components/util/ExternalLink"; +import useScoreRatingAlg from "components/util/useScoreRatingAlg"; import React, { useEffect, useState } from "react"; -import { GetGamePTConfig, PBScoreDocument, ScoreDocument } from "tachi-common"; +import { PBScoreDocument, ScoreDocument } from "tachi-common"; import { UGPTChartPBComposition } from "types/api-returns"; import { SetState } from "types/react"; import { IsScore } from "util/asserts"; +import { UppercaseFirst } from "util/misc"; import CommentContainer from "./CommentContainer"; import JudgementTable from "./JudgementTable"; import PBNote from "./PBNote"; import ScoreEditButtons from "./ScoreEditButtons"; export function ScoreInfo({ score }: { score: ScoreDocument | PBScoreDocument }) { + const rating = useScoreRatingAlg(score.game, score.playtype); + return (
- + + + - + {/* @ts-expect-error yeah we know service doesnt necessarily exist */} From dbe03e497cbf4e721a62bd53cd024bc513fb655d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:28:22 +0000 Subject: [PATCH 05/20] Note that this error is not recoverable. --- client/src/components/util/ErrorBoundary.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/src/components/util/ErrorBoundary.tsx b/client/src/components/util/ErrorBoundary.tsx index 47f317379..47a333b8d 100644 --- a/client/src/components/util/ErrorBoundary.tsx +++ b/client/src/components/util/ErrorBoundary.tsx @@ -1,8 +1,10 @@ +import { TachiConfig } from "lib/config"; import React from "react"; import CenterLayoutPage from "../layout/CenterLayoutPage"; import DebugContent from "./DebugContent"; import Divider from "./Divider"; import ExternalLink from "./ExternalLink"; +import Muted from "./Muted"; export default class ErrorBoundary extends React.Component { constructor(props: any) { @@ -32,6 +34,8 @@ export default class ErrorBoundary extends React.Component + This error is not recoverable. :/ + Go Home ); From 12b9daec80b94be0895d80f4e81fa452efdd3e75 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:28:31 +0000 Subject: [PATCH 06/20] Add user deletability as a thing. --- client/package.json | 2 +- client/pnpm-lock.yaml | 8 ++-- .../dashboard/users/UserSettingsPage.tsx | 14 ++++++ .../tables/dropdowns/GenericScoreDropdown.tsx | 13 +++++- .../dropdowns/components/ManageScore.tsx | 45 +++++++++++++++++++ 5 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 client/src/components/tables/dropdowns/components/ManageScore.tsx diff --git a/client/package.json b/client/package.json index f191f66ff..a119078a6 100644 --- a/client/package.json +++ b/client/package.json @@ -82,7 +82,7 @@ "resolve": "1.12.0", "resolve-url-loader": "3.1.0", "socicon": "3.0.5", - "tachi-common": "0.3.10" + "tachi-common": "0.3.18" }, "devDependencies": { "@types/humanize-duration": "3.25.1", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 4a9356f88..73a9297ee 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -84,7 +84,7 @@ specifiers: serve: 11.2.0 socicon: 3.0.5 style-loader: 1.0.0 - tachi-common: 0.3.10 + tachi-common: 0.3.18 terser-webpack-plugin: 1.4.1 ts-pnp: 1.1.4 typescript: 4.3.5 @@ -156,7 +156,7 @@ dependencies: resolve: 1.12.0 resolve-url-loader: 3.1.0 socicon: 3.0.5 - tachi-common: 0.3.10 + tachi-common: 0.3.18 devDependencies: '@types/humanize-duration': 3.25.1 @@ -11241,8 +11241,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.10: - resolution: {integrity: sha512-OiEnRz8b7ix+m/W5bFwaN+0fgupmbYHKhsn4/IaFb8f2rCEHY/i3t8TGuYAx212Uo2hOsDSX5/yxVOrgcxZ1AA==} + /tachi-common/0.3.18: + resolution: {integrity: sha512-Qom4IbPtLv83+6mW2OU3+thdqZIGrcUJaH9vhTWN6FoFTw60l0FjtBGsB7343qwsuK7l9JqcEaEJ/psKTDZb1w==} dependencies: monk: 7.3.4 transitivePeerDependencies: diff --git a/client/src/app/pages/dashboard/users/UserSettingsPage.tsx b/client/src/app/pages/dashboard/users/UserSettingsPage.tsx index 2ca2bcb98..768da0ad4 100644 --- a/client/src/app/pages/dashboard/users/UserSettingsPage.tsx +++ b/client/src/app/pages/dashboard/users/UserSettingsPage.tsx @@ -172,6 +172,7 @@ function PreferencesForm({ reqUser }: { reqUser: PublicUserDocument }) { invisible: settings?.preferences.invisible ?? false, contentiousContent: settings?.preferences.contentiousContent ?? false, advancedMode: settings?.preferences.advancedMode ?? false, + deletableScores: settings?.preferences.deletableScores ?? false, }, onSubmit: async values => { const res = await APIFetchV1( @@ -224,6 +225,19 @@ function PreferencesForm({ reqUser }: { reqUser: PublicUserDocument }) { /> Hide your last seen status. */} + + + + Enables the option to delete scores. Turn this off if the thought of deleting a + score gives you anxiety. + + ({ user: PublicUserDocument; chart: ChartDocument; scoreState: ScoreState; - defaultView?: "vsPB" | "moreInfo" | "history" | "debug"; + defaultView?: "vsPB" | "moreInfo" | "history" | "debug" | "manage"; thisScore: ScoreDocument; DocComponent?: (props: { score: ScoreDocument | PBScoreDocument; @@ -55,6 +56,8 @@ export default function GenericScoreDropdown({ }) => JSX.Element; } & GamePT) { const [view, setView] = useState(defaultView); + const { user: currentUser } = useContext(UserContext); + const { settings } = useContext(UserSettingsContext); const { isLoading, error, data } = useApiQuery>( `/users/${user.id}/games/${game}/${playtype}/pbs/${chart.chartID}?getComposition=true` @@ -94,6 +97,8 @@ export default function GenericScoreDropdown({ body = ; } else if (view === "vsPB") { body = ; + } else if (view === "manage") { + body = ; } return ( @@ -112,6 +117,12 @@ export default function GenericScoreDropdown({ Play History + {currentUser?.id === user.id && settings?.preferences.deletableScores && ( + + + Manage Score + + )} diff --git a/client/src/components/tables/dropdowns/components/ManageScore.tsx b/client/src/components/tables/dropdowns/components/ManageScore.tsx new file mode 100644 index 000000000..c1271743d --- /dev/null +++ b/client/src/components/tables/dropdowns/components/ManageScore.tsx @@ -0,0 +1,45 @@ +import React, { useMemo, useReducer, useState } from "react"; +import { ScoreDocument } from "tachi-common"; +import { APIFetchV1 } from "util/api"; +import { DelayedPageReload } from "util/misc"; + +export default function ManageScore({ score }: { score: ScoreDocument }) { + const [warn, upgWarn] = useReducer(r => r + 1, 0); + const message = useMemo(() => { + if (warn === 0) { + return "Delete Score (Requires Further Confirmation)"; + } else if (warn === 1) { + return "Are you absolutely sure? This score will be gone. Permanently."; + } else if (warn === 2) { + return "I'm serious. You will lose this score. It will be gone. Are you REALLY sure you want to do this?"; + } else if (warn === 3) { + return "OK. Click me one last time, then."; + } + + return "lol unknown state"; + }, [warn]); + + return ( +
+
{ + if (warn < 3) { + upgWarn(); + } else { + APIFetchV1( + `/scores/${score.scoreID}`, + { + method: "DELETE", + }, + true, + true + ).then(() => DelayedPageReload()); + } + }} + > + {message} +
+
+ ); +} From 5bec211e0ef90335f694a593e23e5320403e9492 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:37:46 +0000 Subject: [PATCH 07/20] Attempt page reload button --- client/src/components/util/ErrorBoundary.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/util/ErrorBoundary.tsx b/client/src/components/util/ErrorBoundary.tsx index 47a333b8d..9bcfd3f3e 100644 --- a/client/src/components/util/ErrorBoundary.tsx +++ b/client/src/components/util/ErrorBoundary.tsx @@ -36,6 +36,9 @@ export default class ErrorBoundary extends React.Component This error is not recoverable. :/ + window.location.reload()}> + Attempt Page Reload + Go Home ); From fc4a9184bfb04677f5e78a1fe867bbf201dfcad6 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:52:06 +0000 Subject: [PATCH 08/20] Fix hethans state issue --- .../users/games/_game/_playtype/UGPTSettingsPage.tsx | 8 ++++++-- client/src/components/util/ErrorBoundary.tsx | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx index 13bcbdc82..36a704cc6 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx @@ -182,13 +182,13 @@ function PreferencesForm({ reqUser, game, playtype }: Props) { } function ShowcaseForm({ reqUser, game, playtype }: Props) { - const { settings } = useContext(UGPTSettingsContext); + const { settings, setSettings } = useContext(UGPTSettingsContext); const [stats, setStats] = useState(settings!.preferences.stats); const [show, setShow] = useState(false); const SaveChanges = async () => { - await APIFetchV1( + const r = await APIFetchV1( `/users/${reqUser.id}/games/${game}/${playtype}/showcase`, { method: "PUT", @@ -198,6 +198,10 @@ function ShowcaseForm({ reqUser, game, playtype }: Props) { true, true ); + + if (r.success) { + setSettings(r.body); + } }; const [isFirstPaint, setIsFirstPaint] = useState(true); diff --git a/client/src/components/util/ErrorBoundary.tsx b/client/src/components/util/ErrorBoundary.tsx index 9bcfd3f3e..cf021923e 100644 --- a/client/src/components/util/ErrorBoundary.tsx +++ b/client/src/components/util/ErrorBoundary.tsx @@ -1,4 +1,3 @@ -import { TachiConfig } from "lib/config"; import React from "react"; import CenterLayoutPage from "../layout/CenterLayoutPage"; import DebugContent from "./DebugContent"; From 0994f93061606d15d2cb7ea3f2fd7ce4773b6569 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 01:56:07 +0000 Subject: [PATCH 09/20] Add percent to folder showcase stats. --- client/src/components/user/UGPTStatShowcase.tsx | 8 +++++--- client/src/util/misc.ts | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client/src/components/user/UGPTStatShowcase.tsx b/client/src/components/user/UGPTStatShowcase.tsx index 454972672..a61dcc96d 100644 --- a/client/src/components/user/UGPTStatShowcase.tsx +++ b/client/src/components/user/UGPTStatShowcase.tsx @@ -25,7 +25,7 @@ import { UGPTPreferenceStatsReturn } from "types/api-returns"; import { GamePT } from "types/react"; import { Playtype } from "types/tachi"; import { APIFetchV1 } from "util/api"; -import { UppercaseFirst } from "util/misc"; +import { ToPercent, UppercaseFirst } from "util/misc"; import UGPTStatContainer from "./UGPTStatContainer"; import UGPTStatCreator from "./UGPTStatCreator"; @@ -431,8 +431,10 @@ export function StatDisplay({

{result.value} - {/* @ts-expect-error temp */} - /{result.outOf} + + {/* @ts-expect-error This property definitely exists.*/} + {/* */}/{result.outOf} ({ToPercent(result.value, result.outOf)}) +

{user && user.id !== reqUser.id && ( diff --git a/client/src/util/misc.ts b/client/src/util/misc.ts index fefb8d715..7a61ee2d0 100644 --- a/client/src/util/misc.ts +++ b/client/src/util/misc.ts @@ -197,3 +197,6 @@ export function HistorySafeGoBack(history: ReturnType) { // history.goBack(); // } } +export function ToPercent(n1: number, n2: number) { + return `${((100 * n1) / n2).toFixed(2)}%`; +} From 9c0e93231c7845147335e636b73549ba2347f8b6 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 02:01:38 +0000 Subject: [PATCH 10/20] Fix bug where navigating to a song with 0 charts of that playtype would cause a crash instead of safely redirecting --- client/src/app/routes/GameRoutes.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/src/app/routes/GameRoutes.tsx b/client/src/app/routes/GameRoutes.tsx index d53ff24d9..6882c7f6c 100644 --- a/client/src/app/routes/GameRoutes.tsx +++ b/client/src/app/routes/GameRoutes.tsx @@ -164,6 +164,7 @@ function SongChartRoutes({ game, playtype }: GamePT) { useEffect(() => { setActiveChart(null); }, [game, playtype]); + if (error) { return ; } @@ -172,6 +173,14 @@ function SongChartRoutes({ game, playtype }: GamePT) { return ; } + if (data.charts.every(c => c.playtype !== playtype)) { + return ( + + ); + } + return ( <> Date: Sun, 12 Dec 2021 02:17:40 +0000 Subject: [PATCH 11/20] Up GPT leaderboard limit to 500. --- .../dashboard/games/_game/_playtype/GPTLeaderboardsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a41ab1916..d9377ea30 100644 --- a/client/src/app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage.tsx +++ b/client/src/app/pages/dashboard/games/_game/_playtype/GPTLeaderboardsPage.tsx @@ -79,7 +79,7 @@ function ProfileLeaderboard({ game, playtype }: GamePT) { ) : null; const { data, isLoading, error } = useApiQuery( - `/games/${game}/${playtype}/leaderboard?alg=${alg}` + `/games/${game}/${playtype}/leaderboard?alg=${alg}&limit=500` ); if (error) { From 5bae11652a55a21e6c0f5cc4dbde7f58c3c756b0 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 13:19:43 +0000 Subject: [PATCH 12/20] Leaderboards now honor the proper gameprofile formatter. --- .../users/games/_game/_playtype/LeaderboardsPage.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 137bf1109..e90fefd94 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 @@ -12,6 +12,7 @@ import { COLOUR_SET, FormatGame, GetGameConfig, + GetGamePTConfig, IDStrings, integer, PublicUserDocument, @@ -92,6 +93,8 @@ function LeaderboardsPageContent({ } & GamePT) { const { stats, leaderboard } = data; + const gptConfig = GetGamePTConfig(game, playtype); + const userMap = new Map(); for (const u of stats.users) { @@ -131,7 +134,13 @@ function LeaderboardsPageContent({ {userMap.get(s.userID)?.username} -
+ {/* temp */} - - - {i === 0 && ( - - )} - - ))} - - ); -} - -function FolderThermometer({ - keys, - values, - max, - colours, -}: { - keys: T[]; - values: Record; - colours: Record; - max: integer; -}) { - return ( - - ); -} diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx index 8d10a8eaa..5b5585f5b 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx @@ -1,5 +1,6 @@ import { ResponsiveBar } from "@nivo/bar"; import { BarChartTooltip } from "components/charts/ChartTooltip"; +import FolderInfoHeader from "components/game/folder/FolderInfoHeader"; import QuickTooltip from "components/layout/misc/QuickTooltip"; import Card from "components/layout/page/Card"; import FolderTable from "components/tables/folders/FolderTable"; @@ -94,7 +95,7 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) { return ( x.data.value, true)); } - -function FolderInfoHeader({ game, playtype, reqUser, folderDataset, data }: InfoProps) { - const scoreBucket = useBucket(game, playtype); - const [view, setView] = useState<"grade" | "lamp">(scoreBucket); - - return ( - -
-
- - - Score Info - - - - Lamp Info - -
-
-
- -
- - {view === "grade" ? ( - - ) : ( - <> - )} -
- ); -} - -function ScoreDistributionChart({ game, playtype, folderDataset }: InfoProps) { - const dataMap = CreateChartIDMap(folderDataset); - const gptConfig = GetGamePTConfig(game, playtype); - - const dataset = []; - - const expScale = GetGradeChartExpScale(game); - - const expFn = ComposeExpFn(expScale); - const invExpFn = ComposeInverseExpFn(expScale); - - for (const data of folderDataset) { - const value = data.__related.pb ? expFn(data.__related.pb.scoreData.percent) : 0; - dataset.push({ - chartID: data.chartID, - expValue: expFn(value), - value, - grade: data.__related.pb?.scoreData.grade, - }); - } - - dataset.sort(NumericSOV(x => x.expValue)); - - return ( -
- ( - { - const data = dataMap.get(d.indexValue as string)!; - - return ( -
- {data.__related.song.title} -
- {data.__related.pb?.scoreData.percent.toFixed(2)}% -
- ); - }} - /> - )} - key={"value"} - colors={k => { - if (!k.data.grade) { - return "black"; - } - // @ts-expect-error temp - return ChangeOpacity(gptConfig.gradeColours[k.data.grade], 0.5); - }} - // @ts-expect-error temp - borderColor={k => gptConfig.gradeColours[k.data.grade]} - borderWidth={1} - padding={0.2} - // @ts-expect-error temp - data={dataset} - minValue={0} - maxValue={expFn(100)} - margin={{ left: 50, top: 20, bottom: 20 }} - valueFormat={e => `${invExpFn(e).toFixed(2)}%`} - axisLeft={{ - tickValues: gptConfig.gradeBoundaries.map(e => (e === 0 ? 0 : expFn(e))), - format: x => { - let nearest; - - const lgv = invExpFn(x); - - for (const [i, gradeBnd] of gptConfig.gradeBoundaries.entries()) { - if (Math.abs(gradeBnd - lgv) < 0.00005) { - nearest = i; - break; - } - } - - if (nearest === undefined) { - return null; - } - - return gptConfig.grades[nearest]; - }, - }} - axisBottom={null} - {...DEFAULT_BAR_PROPS} - labelSkipWidth={40} - /> -
- ); -} diff --git a/client/src/components/game/folder/FolderDistributionTable.tsx b/client/src/components/game/folder/FolderDistributionTable.tsx new file mode 100644 index 000000000..7705581d1 --- /dev/null +++ b/client/src/components/game/folder/FolderDistributionTable.tsx @@ -0,0 +1,100 @@ +import { ResponsiveBar } from "@nivo/bar"; +import { BarChartTooltip } from "components/charts/ChartTooltip"; +import MiniTable from "components/tables/components/MiniTable"; +import Muted from "components/util/Muted"; +import React from "react"; +import { integer } from "tachi-common"; +import { ChangeOpacity } from "util/color-opacity"; +import { TACHI_CHART_THEME } from "util/constants/chart-theme"; +import { Reverse, StepFromToMax, PercentFrom } from "util/misc"; + +export default function FolderDistributionTable({ + keys, + values, + colours, + max, +}: { + keys: T[]; + values: Record; + colours: Record; + max: integer; +}) { + const cumulativeValues: Record = {} as Record; + + let total = 0; + for (const k of keys) { + total += values[k] ?? 0; + cumulativeValues[k] = total; + } + + return ( + + {keys.map((k, i) => ( +
+ + + {i === 0 && ( + + )} + + ))} + + ); +} + +function FolderThermometer({ + keys, + values, + max, + colours, +}: { + keys: T[]; + values: Record; + colours: Record; + max: integer; +}) { + return ( + + ); +} diff --git a/client/src/components/game/folder/FolderInfoHeader.tsx b/client/src/components/game/folder/FolderInfoHeader.tsx new file mode 100644 index 000000000..f96e2b174 --- /dev/null +++ b/client/src/components/game/folder/FolderInfoHeader.tsx @@ -0,0 +1,73 @@ +import Card from "components/layout/page/Card"; +import Divider from "components/util/Divider"; +import Icon from "components/util/Icon"; +import SelectButton from "components/util/SelectButton"; +import { useBucket } from "components/util/useBucket"; +import React, { useState } from "react"; +import { PublicUserDocument } from "tachi-common"; +import { GamePT } from "types/react"; +import { FolderDataset } from "types/tables"; +import FolderMinimap from "./FolderMinimap"; +import FolderScoreDistributionChart from "./FolderScoreDistributionChart"; + +export default function FolderInfoHeader({ + game, + playtype, + reqUser, + folderDataset, + folderTitle, +}: { + reqUser: PublicUserDocument; + folderDataset: FolderDataset; + folderTitle: string; +} & GamePT) { + const scoreBucket = useBucket(game, playtype); + const [view, setView] = useState<"grade" | "lamp">(scoreBucket); + const [breakdownInfo, setBreakdownInfo] = useState<"graph" | "switchboard">("switchboard"); + + return ( + +
+
+ + + Score Info + + + + Lamp Info + +
+
+
+ +
+
+ +
+ + {breakdownInfo === "graph" ? ( + + ) : ( + + )} +
+ ); +} diff --git a/client/src/components/game/folder/FolderMinimap.tsx b/client/src/components/game/folder/FolderMinimap.tsx new file mode 100644 index 000000000..feaf3efdb --- /dev/null +++ b/client/src/components/game/folder/FolderMinimap.tsx @@ -0,0 +1,150 @@ +import QuickTooltip from "components/layout/misc/QuickTooltip"; +import Divider from "components/util/Divider"; +import React, { useEffect, useMemo, useState } from "react"; +import { Form } from "react-bootstrap"; +import { Link } from "react-router-dom"; +import { ChartDocument, FormatChart, Game, GamePTConfig, GetGamePTConfig } from "tachi-common"; +import { GamePT } from "types/react"; +import { FolderDataset } from "types/tables"; +import { ChangeOpacity } from "util/color-opacity"; +import { CreateChartLink } from "util/data"; +import { NumericSOV } from "util/sorts"; + +type Props = { + folderDataset: FolderDataset; + view: "grade" | "lamp"; +} & GamePT; + +export default function FolderMinimap(props: Props) { + return ( + <> +
+ +
+
+ Sadly, Switchboard view doesn't work very well on mobile at the moment. +
+ + ); +} + +function FolderMinimapMain({ game, playtype, folderDataset, view }: Props) { + const gptConfig = GetGamePTConfig(game, playtype); + + const [sortAlg, setSortAlg] = useState(view === "grade" ? "score" : "lamp"); + + useEffect(() => { + if (view === "grade") { + setSortAlg("score"); + } else { + setSortAlg("lamp"); + } + }, [view]); + + const getterFn = useMemo<(f: FolderDataset[0]) => number | undefined>(() => { + if (sortAlg === "score") { + return c => c.__related.pb?.scoreData.percent; + } else if (sortAlg === "lamp") { + return c => c.__related.pb?.scoreData.lampIndex; + } else if (sortAlg.startsWith("tierlist:")) { + const v = sortAlg.split("tierlist:")[1]; + + return c => c.tierlistInfo[v as keyof ChartDocument["tierlistInfo"]]?.value; + } + + return () => 0; + }, [sortAlg]); + + const sortedDataset = useMemo( + () => folderDataset.slice(0).sort(NumericSOV(a => getterFn(a) ?? -Infinity, true)), + [getterFn, folderDataset, sortAlg] + ); + + return ( +
+
+
+ {sortedDataset.map(d => ( + + ))} +
+ + + Sorting charts on: + setSortAlg(e.target.value)} + > + {view === "grade" && } + {view === "lamp" && } + {gptConfig.tierlists.map(e => ( + + ))} + + +
+
+ ); +} + +function MinimapElement({ + data, + gptConfig, + view, + game, +}: { + data: FolderDataset[0]; + view: "grade" | "lamp"; + gptConfig: GamePTConfig; + game: Game; +}) { + const colour = useMemo(() => { + if (!data.__related.pb) { + return null; + } + + // @ts-expect-error unhinged dynamic access (i dont care) + return gptConfig[`${view}Colours`][data.__related.pb.scoreData[view]]; + }, [data.__related.pb, gptConfig, view]); + + return ( + + {FormatChart(game, data.__related.song, data)} + + {data.__related.pb?.scoreData.lamp ?? "Not Played"} + {data.__related.pb && ( + <> +
+ {data.__related.pb.scoreData.grade} +
+ + {data.__related.pb.scoreData.score} ( + {data.__related.pb.scoreData.percent.toFixed(2)}%) + + + )} + + } + > + +
+ + + ); +} diff --git a/client/src/components/game/folder/FolderScoreDistributionChart.tsx b/client/src/components/game/folder/FolderScoreDistributionChart.tsx new file mode 100644 index 000000000..6df7a4003 --- /dev/null +++ b/client/src/components/game/folder/FolderScoreDistributionChart.tsx @@ -0,0 +1,157 @@ +import { ResponsiveBar } from "@nivo/bar"; +import { BarChartTooltip } from "components/charts/ChartTooltip"; +import React, { useMemo } from "react"; +import { GetGamePTConfig } from "tachi-common"; +import { GamePT } from "types/react"; +import { FolderDataset } from "types/tables"; +import { DEFAULT_BAR_PROPS } from "util/charts"; +import { ChangeOpacity } from "util/color-opacity"; +import { CreateChartIDMap } from "util/data"; +import { ComposeExpFn, ComposeInverseExpFn, CountElements, Reverse } from "util/misc"; +import { GetGradeChartExpScale } from "util/scales"; +import { NumericSOV } from "util/sorts"; +import FolderDistributionTable from "./FolderDistributionTable"; + +type Props = { + folderDataset: FolderDataset; + view: "grade" | "lamp"; +} & GamePT; + +export default function FolderScoreDistributionChart({ + game, + playtype, + folderDataset, + view, +}: Props) { + const gptConfig = GetGamePTConfig(game, playtype); + + const values = useMemo(() => { + if (view === "grade") { + return CountElements(folderDataset, x => x.__related.pb?.scoreData.grade ?? null); + } + + return CountElements(folderDataset, x => x.__related.pb?.scoreData.lamp ?? null); + }, [folderDataset, view]); + + return ( +
+
+ {view === "grade" ? ( + + ) : ( + + )} +
+ {/*
+ +
*/} +
+ ); +} + +function ScoreDistributionChart({ game, folderDataset, playtype, view }: Props) { + if (view === "grade") { + return ; + } + + return <>LAMP TODO; +} + +function ScoreBarChart({ game, playtype, folderDataset }: Omit) { + const dataMap = CreateChartIDMap(folderDataset); + const gptConfig = GetGamePTConfig(game, playtype); + + const expScale = GetGradeChartExpScale(game); + const expFn = ComposeExpFn(expScale); + const invExpFn = ComposeInverseExpFn(expScale); + + const dataset = []; + + for (const data of folderDataset) { + const value = data.__related.pb ? expFn(data.__related.pb.scoreData.percent) : 0; + dataset.push({ + chartID: data.chartID, + expValue: expFn(value), + value, + grade: data.__related.pb?.scoreData.grade, + }); + } + + dataset.sort(NumericSOV(x => x.expValue)); + + return ( +
+ ( + { + const data = dataMap.get(d.indexValue as string)!; + + return ( +
+ {data.__related.song.title} +
+ {data.__related.pb?.scoreData.percent.toFixed(2)}% +
+ ); + }} + /> + )} + key={"value"} + colors={k => { + if (!k.data.grade) { + return "black"; + } + // @ts-expect-error temp + return ChangeOpacity(gptConfig.gradeColours[k.data.grade], 0.5); + }} + // @ts-expect-error temp + borderColor={k => gptConfig.gradeColours[k.data.grade]} + borderWidth={1} + padding={0.2} + // @ts-expect-error temp + data={dataset} + minValue={0} + maxValue={expFn(100)} + margin={{ left: 50, top: 20, bottom: 20 }} + valueFormat={e => `${invExpFn(e).toFixed(2)}%`} + axisLeft={{ + tickValues: gptConfig.gradeBoundaries.map(e => (e === 0 ? 0 : expFn(e))), + format: x => { + let nearest; + + const lgv = invExpFn(x); + + for (const [i, gradeBnd] of gptConfig.gradeBoundaries.entries()) { + if (Math.abs(gradeBnd - lgv) < 0.00005) { + nearest = i; + break; + } + } + + if (nearest === undefined) { + return null; + } + + return gptConfig.grades[nearest]; + }, + }} + axisBottom={null} + {...DEFAULT_BAR_PROPS} + labelSkipWidth={40} + /> +
+ ); +} diff --git a/client/src/util/color-opacity.ts b/client/src/util/color-opacity.ts index 5fbd6c2ba..2883fbd11 100644 --- a/client/src/util/color-opacity.ts +++ b/client/src/util/color-opacity.ts @@ -3,6 +3,10 @@ * @param opacity A value between 0 and 1. */ export function ChangeOpacity(str: string, opacity: number) { + if (!str) { + throw new Error(`Str given to ChangeOpacity was undefined.`); + } + if (str.startsWith("#")) { if (str.length === 7) { return str + Math.floor(opacity * 255).toString(16); diff --git a/client/src/util/misc.ts b/client/src/util/misc.ts index 7a61ee2d0..4b94fdb99 100644 --- a/client/src/util/misc.ts +++ b/client/src/util/misc.ts @@ -200,3 +200,23 @@ export function HistorySafeGoBack(history: ReturnType) { export function ToPercent(n1: number, n2: number) { return `${((100 * n1) / n2).toFixed(2)}%`; } + +export function CountElements(data: T[], collector: (element: T) => string | null) { + const counts: Record = {}; + + for (const element of data) { + const key = collector(element); + + if (key === null) { + continue; + } + + if (!counts[key]) { + counts[key] = 1; + } else { + counts[key]++; + } + } + + return counts; +} From b3180bd955298c1374ea18dc02e48af9920c565f Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 01:24:23 +0000 Subject: [PATCH 16/20] Add raise detection --- .../game/folder/FolderInfoHeader.tsx | 1 + .../components/game/folder/FolderMinimap.tsx | 96 ++++++++++++++++++- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/client/src/components/game/folder/FolderInfoHeader.tsx b/client/src/components/game/folder/FolderInfoHeader.tsx index f96e2b174..bd1bd1bbd 100644 --- a/client/src/components/game/folder/FolderInfoHeader.tsx +++ b/client/src/components/game/folder/FolderInfoHeader.tsx @@ -62,6 +62,7 @@ export default function FolderInfoHeader({ /> ) : ( ( + `/users/${props.reqUser.id}/games/${props.game}/${props.playtype}/sessions/last` + ); + + const session = useMemo(() => { + if (error && error.statusCode === 404) { + return null; + } else if (data) { + return data; + } + + return null; + }, [data, error]); + + if (error && error.statusCode !== 404) { + return ; + } + + if (isLoading || !data) { + return ; + } + return ( <>
- +
Sadly, Switchboard view doesn't work very well on mobile at the moment. @@ -28,7 +63,13 @@ export default function FolderMinimap(props: Props) { ); } -function FolderMinimapMain({ game, playtype, folderDataset, view }: Props) { +function FolderMinimapMain({ + game, + playtype, + folderDataset, + view, + recentSession, +}: Props & { recentSession: SessionDocument | null }) { const gptConfig = GetGamePTConfig(game, playtype); const [sortAlg, setSortAlg] = useState(view === "grade" ? "score" : "lamp"); @@ -60,6 +101,16 @@ function FolderMinimapMain({ game, playtype, folderDataset, view }: Props) { [getterFn, folderDataset, sortAlg] ); + const recentlyTouched = useMemo(() => { + if (!recentSession) { + return []; + } + + return recentSession.scoreInfo + .filter(e => e.isNewScore || e.gradeDelta > 0 || e.lampDelta > 0) + .map(e => e.scoreID); + }, [recentSession]); + return (
@@ -71,6 +122,15 @@ function FolderMinimapMain({ game, playtype, folderDataset, view }: Props) { gptConfig={gptConfig} view={view} game={game} + wasRecent={ + (d.__related.pb && + recentlyTouched.some( + scoreID => + d.__related.pb!.composedFrom.scorePB === scoreID || + d.__related.pb!.composedFrom.lampPB === scoreID + )) ?? + false + } /> ))}
@@ -101,12 +161,26 @@ function MinimapElement({ gptConfig, view, game, + wasRecent, }: { data: FolderDataset[0]; view: "grade" | "lamp"; gptConfig: GamePTConfig; game: Game; + wasRecent: boolean; }) { + let icon = "level-up-alt"; + + // if this user recently cleared mare nectaris + // tell them to fuck off + if ( + data.chartID === "924bf011fdd8334b609b02e382123f9f5440d16d" && + data.__related.pb && + data.__related.pb?.scoreData.lampIndex >= IIDX_LAMPS.EASY_CLEAR + ) { + icon = "hand-middle-finger"; + } + const colour = useMemo(() => { if (!data.__related.pb) { return null; @@ -122,6 +196,12 @@ function MinimapElement({
{FormatChart(game, data.__related.song, data)} + {wasRecent && ( + <> + You raised this recently! +
+ + )} {data.__related.pb?.scoreData.lamp ?? "Not Played"} {data.__related.pb && ( <> @@ -139,11 +219,17 @@ function MinimapElement({ >
+ > + {wasRecent && ( + + )} +
); From cfd01862bfcdd0db9cfd9d9fa25f791ea24ce3c9 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 17:37:50 +0000 Subject: [PATCH 17/20] Add dropshadow to tooltips From 64e235ac3563b6c2e88e5b9758934c695fb03f88 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 17:42:09 +0000 Subject: [PATCH 18/20] Add folders as a link on statshowcase --- client/src/components/user/UGPTStatShowcase.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/components/user/UGPTStatShowcase.tsx b/client/src/components/user/UGPTStatShowcase.tsx index a61dcc96d..beb98cd30 100644 --- a/client/src/components/user/UGPTStatShowcase.tsx +++ b/client/src/components/user/UGPTStatShowcase.tsx @@ -424,7 +424,12 @@ export function StatDisplay({ header={
Folder
} > <> -

{headerStr}

+ +

{headerStr}

+
{UppercaseFirst(stat.property)} >={" "} {FormatPropertyGTE(game, playtype, stat.property, stat.gte)} From 8620325a0b4cfc7336533e1518b2e082f3730080 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 17:48:54 +0000 Subject: [PATCH 19/20] Fixes #71 --- .../_game/_playtype/folders/FolderTablePage.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx index 49975bdcf..af78c1d9f 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx @@ -254,7 +254,10 @@ function TableBarChart({ } arr.push({ - folder: data.folder.title, + folder: + data.folder.title.length > 10 + ? `${data.folder.title.slice(0, 10)}...` + : data.folder.title, ...realData, }); } @@ -327,7 +330,11 @@ function OverviewBarChart({ }) { const longestTitle = useMemo( () => - dataset.map((e: any) => e.folder).sort(NumericSOV(x => x.length, true))[0] ?? 0, + Math.min( + dataset.map((e: any) => e.folder).sort(NumericSOV(x => x.length, true))[0] + ?.length ?? 0, + 10 + ), [dataset] ); @@ -360,7 +367,7 @@ function OverviewBarChart({ minValue={0} maxValue={100} valueFormat={v => `${v.toFixed(2)}%`} - margin={{ left: 10 + longestTitle.length * 7, bottom: 50, top: 20, right: 20 }} + margin={{ left: 10 + longestTitle * 8, bottom: 50, top: 20, right: 20 }} layout="horizontal" axisBottom={{ tickSize: 5, From 4583ec15877d903ef9267fef01ac84fda706cc1d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:10:22 +0000 Subject: [PATCH 20/20] Release v2.0.2 --- client/package.json | 2 +- client/src/util/constants/version.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/package.json b/client/package.json index c86c84454..6e996592d 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "tachi-client", - "version": "2.0.1", + "version": "2.0.2", "private": true, "scripts": { "start": "node scripts/start.js -o", diff --git a/client/src/util/constants/version.ts b/client/src/util/constants/version.ts index 477d5a065..1e1acecb2 100644 --- a/client/src/util/constants/version.ts +++ b/client/src/util/constants/version.ts @@ -1,6 +1,6 @@ const MAJOR = 2; const MINOR = 0; -const PATCH = 1; +const PATCH = 2; // As is with all front-facing zkldi projects, the version names for tachi-client // are from an album I like. In this case, the album is The Cure - Disintegration.
Score InfoScore Info{UppercaseFirst(rating)}Timestamp
{(s.ratings[alg] ?? 0).toFixed(2)} + {s.ratings[alg] + ? gptConfig.profileRatingAlgFormatters[alg] + ? gptConfig.profileRatingAlgFormatters[alg]!(s.ratings[alg]!) + : s.ratings[alg]!.toFixed(2) + : "No Data."} + {Object.entries(s.classes).length From 10fd62bbb89e69a7575e6162ca59a7c9089c7d3d Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 14:54:23 +0000 Subject: [PATCH 13/20] Update everything to honor new scoreBucket --- client/package.json | 2 +- client/pnpm-lock.yaml | 8 ++-- .../_playtype/folders/FolderSelectPage.tsx | 5 ++- .../_playtype/folders/FolderTablePage.tsx | 6 +-- .../_playtype/folders/SpecificFolderPage.tsx | 41 ++++++++++++++----- client/src/components/util/useBucket.tsx | 16 ++++++++ 6 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 client/src/components/util/useBucket.tsx diff --git a/client/package.json b/client/package.json index a119078a6..c86c84454 100644 --- a/client/package.json +++ b/client/package.json @@ -82,7 +82,7 @@ "resolve": "1.12.0", "resolve-url-loader": "3.1.0", "socicon": "3.0.5", - "tachi-common": "0.3.18" + "tachi-common": "0.3.19" }, "devDependencies": { "@types/humanize-duration": "3.25.1", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 73a9297ee..24bf998a4 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -84,7 +84,7 @@ specifiers: serve: 11.2.0 socicon: 3.0.5 style-loader: 1.0.0 - tachi-common: 0.3.18 + tachi-common: 0.3.19 terser-webpack-plugin: 1.4.1 ts-pnp: 1.1.4 typescript: 4.3.5 @@ -156,7 +156,7 @@ dependencies: resolve: 1.12.0 resolve-url-loader: 3.1.0 socicon: 3.0.5 - tachi-common: 0.3.18 + tachi-common: 0.3.19 devDependencies: '@types/humanize-duration': 3.25.1 @@ -11241,8 +11241,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.18: - resolution: {integrity: sha512-Qom4IbPtLv83+6mW2OU3+thdqZIGrcUJaH9vhTWN6FoFTw60l0FjtBGsB7343qwsuK7l9JqcEaEJ/psKTDZb1w==} + /tachi-common/0.3.19: + resolution: {integrity: sha512-N3/n9emh2X9pA+3jQ1UfkLX9V8hk/oHGE1GPlkReRmyIR9gRAxhf3uZCiorPqibSkPzx9LrGLJcO52FR5NQ8oA==} dependencies: monk: 7.3.4 transitivePeerDependencies: diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx index 8fc323802..c3c73fd0e 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx @@ -10,6 +10,7 @@ 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 { useBucket } from "components/util/useBucket"; import useUGPTBase from "components/util/useUGPTBase"; import { UserContext } from "context/UserContext"; import React, { useContext, useMemo, useState } from "react"; @@ -89,7 +90,9 @@ export function FolderInfoComponent({ }: Props & { folder: FolderDocument; folderStats: FolderStatsInfo }) { const gptConfig = GetGamePTConfig(game, playtype); - const [elements, setElements] = useState<"grade" | "lamp">(gptConfig.scoreBucket); + const scoreBucket = useBucket(game, playtype); + + const [elements, setElements] = useState<"grade" | "lamp">(scoreBucket); const base = useUGPTBase({ reqUser, game, playtype }); diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx index ddbec68f7..49975bdcf 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderTablePage.tsx @@ -10,6 +10,7 @@ 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 { useBucket } from "components/util/useBucket"; import { UserContext } from "context/UserContext"; import React, { useContext, useEffect, useMemo, useState } from "react"; import { Form, InputGroup } from "react-bootstrap"; @@ -218,10 +219,9 @@ function TableBarChart({ dataMap: Map; }) { const gptConfig = GetGamePTConfig(table.game, table.playtype); + const bucket = useBucket(table.game, table.playtype); - const [mode, setMode] = useState<"grades" | "lamps">( - gptConfig.scoreBucket === "grade" ? "grades" : "lamps" - ); + const [mode, setMode] = useState<"grades" | "lamps">(bucket === "grade" ? "grades" : "lamps"); const colours = useMemo(() => { if (mode === "grades") { diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx index d1a8e2f9a..8d10a8eaa 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/SpecificFolderPage.tsx @@ -10,6 +10,7 @@ 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 { useBucket } from "components/util/useBucket"; import { useFormik } from "formik"; import { nanoid } from "nanoid"; import React, { useEffect, useMemo, useState } from "react"; @@ -111,9 +112,6 @@ export default function SpecificFolderPage({ reqUser, game, playtype }: Props) { return (
-
- -
{folderInfoHeader}
@@ -660,15 +658,38 @@ function FolderDatasetToTierlistInfo( } function FolderInfoHeader({ game, playtype, reqUser, folderDataset, data }: InfoProps) { + const scoreBucket = useBucket(game, playtype); + const [view, setView] = useState<"grade" | "lamp">(scoreBucket); + return ( - +
+
+ + + Score Info + + + + Lamp Info + +
+
+
+ +
+ + {view === "grade" ? ( + + ) : ( + <> + )}
); } diff --git a/client/src/components/util/useBucket.tsx b/client/src/components/util/useBucket.tsx new file mode 100644 index 000000000..d66e23ac1 --- /dev/null +++ b/client/src/components/util/useBucket.tsx @@ -0,0 +1,16 @@ +import { UGPTSettingsContext } from "context/UGPTSettingsContext"; +import { useContext } from "react"; +import { Game, GetGamePTConfig } from "tachi-common"; +import { Playtype } from "types/tachi"; + +export function useBucket(game: Game, playtype: Playtype) { + const { settings } = useContext(UGPTSettingsContext); + + if (!settings?.preferences.scoreBucket) { + const gptConfig = GetGamePTConfig(game, playtype); + + return gptConfig.scoreBucket; + } + + return settings.preferences.scoreBucket; +} From e3a3190f9e2914aa419769a66da12ff9f752e401 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 12 Dec 2021 15:38:37 +0000 Subject: [PATCH 14/20] Add the option to change your score bucket --- .../_game/_playtype/UGPTSettingsPage.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx index 36a704cc6..20b2ef33d 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage.tsx @@ -10,6 +10,7 @@ import useQueryString from "components/util/useQueryString"; import { UGPTSettingsContext } from "context/UGPTSettingsContext"; import deepmerge from "deepmerge"; import { useFormik } from "formik"; +import { TachiConfig } from "lib/config"; import React, { useContext, useEffect, useState } from "react"; import { Button, Form } from "react-bootstrap"; import { @@ -83,6 +84,7 @@ function PreferencesForm({ reqUser, game, playtype }: Props) { preferredSessionAlg: settings!.preferences.preferredSessionAlg || gptConfig.defaultSessionRatingAlg, gameSpecific: settings!.preferences.gameSpecific, + scoreBucket: settings!.preferences.scoreBucket ?? gptConfig.scoreBucket, }, onSubmit: async values => { const rj = await APIFetchV1( @@ -157,6 +159,24 @@ function PreferencesForm({ reqUser, game, playtype }: Props) { used for things like leaderboards. + + Preferred Score Info + + + + + + What should {TachiConfig.name} prefer to show you about scores? +
+ Note: This will only affect defaults, such as what graph is shown in the folder + breakdown. You can still view all the same stats! +
+
{game === "iidx" && ( Date: Sun, 12 Dec 2021 18:19:01 +0000 Subject: [PATCH 15/20] Make folder switchboards and thermometers --- .../_playtype/folders/FolderSelectPage.tsx | 92 +--------- .../_playtype/folders/SpecificFolderPage.tsx | 130 +-------------- .../game/folder/FolderDistributionTable.tsx | 100 +++++++++++ .../game/folder/FolderInfoHeader.tsx | 73 ++++++++ .../components/game/folder/FolderMinimap.tsx | 150 +++++++++++++++++ .../folder/FolderScoreDistributionChart.tsx | 157 ++++++++++++++++++ client/src/util/color-opacity.ts | 4 + client/src/util/misc.ts | 20 +++ 8 files changed, 507 insertions(+), 219 deletions(-) create mode 100644 client/src/components/game/folder/FolderDistributionTable.tsx create mode 100644 client/src/components/game/folder/FolderInfoHeader.tsx create mode 100644 client/src/components/game/folder/FolderMinimap.tsx create mode 100644 client/src/components/game/folder/FolderScoreDistributionChart.tsx diff --git a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx index c3c73fd0e..2df7972f8 100644 --- a/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx +++ b/client/src/app/pages/dashboard/users/games/_game/_playtype/folders/FolderSelectPage.tsx @@ -1,5 +1,6 @@ import { ResponsiveBar } from "@nivo/bar"; import { BarChartTooltip } from "components/charts/ChartTooltip"; +import DistributionTable from "components/game/folder/FolderDistributionTable"; import Card from "components/layout/page/Card"; import MiniTable from "components/tables/components/MiniTable"; import DebounceSearch from "components/util/DebounceSearch"; @@ -163,94 +164,3 @@ export function FolderInfoComponent({
); } - -function DistributionTable({ - keys, - values, - colours, - max, -}: { - keys: T[]; - values: Record; - colours: Record; - max: integer; -}) { - const cumulativeValues: Record = {} as Record; - - let total = 0; - for (const k of keys) { - total += values[k] ?? 0; - cumulativeValues[k] = total; - } - - return ( - - {keys.map((k, i) => ( -
{k} - {values[k] ?? 0} ({cumulativeValues[k]}) -
- ChangeOpacity(colours[k.id], 0.5)} - // @ts-expect-error Keys are appended with "." for some reason. - borderColor={k => colours[k.data.id]} - labelTextColor="black" - labelSkipHeight={12} - maxValue={max} - padding={0.33} - borderWidth={1} - valueScale={{ type: "linear" }} - axisRight={{ - tickSize: 5, - tickPadding: 5, - tickValues: StepFromToMax(max), - }} - margin={{ right: 50, bottom: 10, top: 20 }} - tooltip={d => ( - ( -
- {d.label} - {d.value} ({PercentFrom(d.value, max)}) -
- )} - /> - )} - motionConfig="stiff" - /> -
{k} + {values[k] ?? 0} ({cumulativeValues[k]}) +
+ ChangeOpacity(colours[k.id], 0.5)} + // @ts-expect-error Keys are appended with "." for some reason. + borderColor={k => colours[k.data.id]} + labelTextColor="black" + labelSkipHeight={12} + maxValue={max} + padding={0.33} + borderWidth={1} + valueScale={{ type: "linear" }} + axisRight={{ + tickSize: 5, + tickPadding: 5, + tickValues: StepFromToMax(max), + }} + margin={{ right: 50, bottom: 10, top: 20 }} + tooltip={d => ( + ( +
+ {d.label} + {d.value} ({PercentFrom(d.value, max)}) +
+ )} + /> + )} + motionConfig="stiff" + /> +