feat: squish tools and insights together

This commit is contained in:
zkldi
2022-12-20 06:04:13 +00:00
parent 7a4dc9f706
commit 48c3a9d23b
11 changed files with 146 additions and 240 deletions
@@ -1,80 +0,0 @@
import useSetSubheader from "components/layout/header/useSetSubheader";
import Card from "components/layout/page/Card";
import { GetGPTInsights } from "components/tools-insights/ToolsAndInsights";
import Divider from "components/util/Divider";
import LinkButton from "components/util/LinkButton";
import React from "react";
import { Col, Row } from "react-bootstrap";
import { Link, Route, Switch } from "react-router-dom";
import { FormatGame, GetGameConfig } from "tachi-common";
import { UGPT } from "types/react";
export default function InsightsPage({ reqUser, game, playtype }: UGPT) {
const gameConfig = GetGameConfig(game);
useSetSubheader(
["Users", reqUser.username, "Games", gameConfig.name, playtype, "Insights"],
[reqUser, game, playtype],
`${reqUser.username}'s ${FormatGame(game, playtype)} Insights`
);
const insights = GetGPTInsights(game, playtype);
if (insights.length === 0) {
return (
<div className="text-center w-100">
{FormatGame(game, playtype)} has no insights. How'd you get here!
</div>
);
}
return (
<Row>
<Switch>
{insights.map((tool) => (
<Route
key={tool.urlPath}
exact
path={`/u/:userID/games/:game/:playtype/insights/${tool.urlPath}`}
>
<Col xs={12} className="mt-4">
<Card
header={tool.name}
footer={
<Link
to={`/u/${reqUser.username}/games/${game}/${playtype}/insights`}
className="text-muted text-hover-white"
>
&lt; Back to all insights...
</Link>
}
>
{tool.description}
</Card>
<Divider />
</Col>
<Col xs={12}>{tool.component({ reqUser, game, playtype })}</Col>
</Route>
))}
<Route exact path="/u/:userID/games/:game/:playtype/insights/">
{insights.map((tool) => (
<Col key={tool.urlPath} xs={12} className="my-4" lg={6}>
<Card
header={tool.name}
footer={
<div className="d-flex w-100 justify-content-end">
<LinkButton to={`insights/${tool.urlPath}`}>
View
</LinkButton>
</div>
}
>
{tool.description}
</Card>
</Col>
))}
</Route>
</Switch>
</Row>
);
}
@@ -1,47 +1,67 @@
import useSetSubheader from "components/layout/header/useSetSubheader";
import Card from "components/layout/page/Card";
import { GetGPTTools } from "components/tools-insights/ToolsAndInsights";
import { GetGPTUtils, GetGPTUtilsName } from "components/gpt-utils/GPTUtils";
import Divider from "components/util/Divider";
import LinkButton from "components/util/LinkButton";
import React from "react";
import { UserContext } from "context/UserContext";
import React, { useContext } from "react";
import { Col, Row } from "react-bootstrap";
import { Link, Route, Switch } from "react-router-dom";
import { FormatGame, GetGameConfig } from "tachi-common";
import { UGPT } from "types/react";
import { GPTUtility } from "types/ugpt";
export default function ToolsPage({ reqUser, game, playtype }: UGPT) {
export default function UGPTUtilsPage({ reqUser, game, playtype }: UGPT) {
const gameConfig = GetGameConfig(game);
const { user } = useContext(UserContext);
const isViewingOwnProfile = user?.id === reqUser.id;
const utils = GetGPTUtils(game, playtype);
const pageName = GetGPTUtilsName(game, playtype, isViewingOwnProfile);
useSetSubheader(
["Users", reqUser.username, "Games", gameConfig.name, playtype, "Tools"],
["Users", reqUser.username, "Games", gameConfig.name, playtype, pageName ?? "Utils"],
[reqUser, game, playtype],
`${reqUser.username}'s ${FormatGame(game, playtype)} Tools`
`${reqUser.username}'s ${FormatGame(game, playtype)} ${pageName ?? "Utils"}`
);
const tools = GetGPTTools(game, playtype);
if (tools.length === 0) {
return (
<div className="text-center w-100">
{FormatGame(game, playtype)} has no tools. How'd you get here!
</div>
);
}
return (
<Row>
<Switch>
{tools.map((tool) => (
<Route exact path="/u/:userID/games/:game/:playtype/utils">
{utils.map((util) => (
<Col key={util.urlPath} xs={12} className="my-4" lg={6}>
<Card
header={util.name}
footer={
<div className="d-flex w-100 justify-content-end">
<LinkButton
to={`/u/${reqUser.username}/games/${game}/${playtype}/utils/${util.urlPath}`}
>
View
</LinkButton>
</div>
}
>
{util.description}
</Card>
</Col>
))}
</Route>
{utils.map((tool) => (
<Route
key={tool.urlPath}
exact
path={`/u/:userID/games/:game/:playtype/tools/${tool.urlPath}`}
path={`/u/:userID/games/:game/:playtype/utils/${tool.urlPath}`}
>
<Col xs={12} className="mt-4">
<Card
header={tool.name}
footer={
<Link
to={`/u/${reqUser.username}/games/${game}/${playtype}/tools`}
to={`/u/${reqUser.username}/games/${game}/${playtype}/utils`}
className="text-muted text-hover-white"
>
&lt; Back to all tools...
@@ -55,25 +75,6 @@ export default function ToolsPage({ reqUser, game, playtype }: UGPT) {
<Col xs={12}>{tool.component({ reqUser, game, playtype })}</Col>
</Route>
))}
<Route exact path="/u/:userID/games/:game/:playtype/tools/">
{tools.map((tool) => (
<Col key={tool.urlPath} xs={12} className="my-4" lg={6}>
<Card
header={tool.name}
footer={
<div className="d-flex w-100 justify-content-end">
<LinkButton to={`tools/${tool.urlPath}`}>
Use Tool
</LinkButton>
</div>
}
>
{tool.description}
</Card>
</Col>
))}
</Route>
</Switch>
</Row>
);
+15 -19
View File
@@ -1,19 +1,20 @@
import { APIFetchV1, APIFetchV1Return, ToAPIURL } from "util/api";
import { IsSupportedGame, IsSupportedPlaytype } from "util/asserts";
import { ErrorPage } from "app/pages/ErrorPage";
import PlaytypeSelect from "app/pages/dashboard/games/_game/PlaytypeSelect";
import FoldersMainPage from "app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage";
import LeaderboardsPage from "app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage";
import OverviewPage from "app/pages/dashboard/users/games/_game/_playtype/OverviewPage";
import RivalsMainPage from "app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage";
import SessionsPage from "app/pages/dashboard/users/games/_game/_playtype/SessionsPage";
import SpecificSessionPage from "app/pages/dashboard/users/games/_game/_playtype/SpecificSessionPage";
import TargetsPage from "app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage";
import UGPTSettingsPage from "app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage";
import UserGamesPage from "app/pages/dashboard/users/UserGamesPage";
import UserImportsPage from "app/pages/dashboard/users/UserImportsPage";
import UserIntegrationsPage from "app/pages/dashboard/users/UserIntegrationsPage";
import UserInvitesPage from "app/pages/dashboard/users/UserInvitesPage";
import UserSettingsPage from "app/pages/dashboard/users/UserSettingsPage";
import { ErrorPage } from "app/pages/ErrorPage";
import LeaderboardsPage from "app/pages/dashboard/users/games/_game/_playtype/LeaderboardsPage";
import OverviewPage from "app/pages/dashboard/users/games/_game/_playtype/OverviewPage";
import SessionsPage from "app/pages/dashboard/users/games/_game/_playtype/SessionsPage";
import SpecificSessionPage from "app/pages/dashboard/users/games/_game/_playtype/SpecificSessionPage";
import UGPTSettingsPage from "app/pages/dashboard/users/games/_game/_playtype/UGPTSettingsPage";
import FoldersMainPage from "app/pages/dashboard/users/games/_game/_playtype/folders/FoldersMainPage";
import RivalsMainPage from "app/pages/dashboard/users/games/_game/_playtype/rivals/RivalsMainPage";
import TargetsPage from "app/pages/dashboard/users/games/_game/_playtype/targets/TargetsPage";
import RequireAuthAsUserParam from "components/auth/RequireAuthAsUserParam";
import LayoutHeaderContainer from "components/layout/LayoutHeaderContainer";
import { UGPTBottomNav, UGPTHeaderBody } from "components/user/UGPTHeader";
@@ -21,6 +22,7 @@ import { UserBottomNav, UserHeaderBody } from "components/user/UserHeader";
import Loading from "components/util/Loading";
import useApiQuery from "components/util/query/useApiQuery";
import { BackgroundContext } from "context/BackgroundContext";
import { TargetsContextProvider } from "context/TargetsContext";
import { UGPTContextProvider } from "context/UGPTContext";
import { UserContext } from "context/UserContext";
import { UserSettingsContext } from "context/UserSettingsContext";
@@ -29,12 +31,9 @@ import { useQuery } from "react-query";
import { Redirect, Route, Switch, useHistory, useParams } from "react-router-dom";
import { FormatGame, Game, GetGameConfig, UserDocument, UserGameStats } from "tachi-common";
import { UGPTStatsReturn } from "types/api-returns";
import UserImportsPage from "app/pages/dashboard/users/UserImportsPage";
import { TargetsContextProvider } from "context/TargetsContext";
import ToolsPage from "app/pages/dashboard/users/games/_game/_playtype/tools/ToolsPage";
import InsightsPage from "app/pages/dashboard/users/games/_game/_playtype/insights/InsightsPage";
import ScoresPage from "../pages/dashboard/users/games/_game/_playtype/ScoresPage";
import UGPTUtilsPage from "app/pages/dashboard/users/games/_game/_playtype/utils/UGPTUtilsPage";
import UserPage from "../pages/dashboard/users/UserPage";
import ScoresPage from "../pages/dashboard/users/games/_game/_playtype/ScoresPage";
export default function UserRoutes() {
const params = useParams<{ userID: string }>();
@@ -265,11 +264,8 @@ function UserGamePlaytypeRoutes({ reqUser, game }: { reqUser: UserDocument; game
<Route exact path="/u/:userID/games/:game/:playtype/leaderboard">
<LeaderboardsPage reqUser={reqUser} game={game} playtype={playtype} />
</Route>
<Route path="/u/:userID/games/:game/:playtype/tools">
<ToolsPage reqUser={reqUser} game={game} playtype={playtype} />
</Route>
<Route path="/u/:userID/games/:game/:playtype/insights">
<InsightsPage reqUser={reqUser} game={game} playtype={playtype} />
<Route path="/u/:userID/games/:game/:playtype/utils">
<UGPTUtilsPage reqUser={reqUser} game={game} playtype={playtype} />
</Route>
<RequireAuthAsUserParam>
<Route exact path="/u/:userID/games/:game/:playtype/settings">
@@ -0,0 +1,62 @@
import { Game, IDStrings, Playtype } from "tachi-common";
import { GPTUtility } from "types/ugpt";
import { BMSCustomTablesTool } from "./tools/BMSCustomTablesTool";
import { IIDXEamusementExportTool } from "./tools/IIDXEamusementExportTool";
import { IIDXPlaylistsTool } from "./tools/IIDXPlaylistsTool";
import { JubilityBreakdownInsight } from "./insights/JubilityBreakdownInsight";
// What utils does each game support?
const GPT_UTILS: Record<IDStrings, Array<GPTUtility>> = {
"bms:7K": [BMSCustomTablesTool],
"bms:14K": [BMSCustomTablesTool],
"chunithm:Single": [],
"gitadora:Dora": [],
"gitadora:Gita": [],
"iidx:DP": [IIDXEamusementExportTool, IIDXPlaylistsTool],
"iidx:SP": [IIDXEamusementExportTool, IIDXPlaylistsTool],
"itg:Stamina": [],
"jubeat:Single": [JubilityBreakdownInsight],
"museca:Single": [],
"pms:Controller": [],
"pms:Keyboard": [],
"popn:9B": [],
"sdvx:Single": [],
"usc:Controller": [],
"usc:Keyboard": [],
"wacca:Single": [],
};
export function GetGPTUtils(game: Game, playtype: Playtype) {
const idString = `${game}:${playtype}` as IDStrings;
return GPT_UTILS[idString];
}
export function GetGPTUtilsName(game: Game, playtype: Playtype, isViewingOwnProfile: boolean) {
const utils = GetGPTUtils(game, playtype);
// things for personal use only are called "tools"
// things everyone might want to view about a person are called "insights".
// an example of an insight would be a jubility breakdown
// an example of a tool would be a eamusement csv export thing.
const tools = utils.filter((e) => e.personalUseOnly);
const insights = utils.filter((e) => e.personalUseOnly !== true);
if (isViewingOwnProfile) {
if (tools.length > 0 && insights.length > 0) {
return "Tools & Insights";
} else if (insights.length > 0) {
return "Insights";
} else if (tools.length > 0) {
return "Tools";
}
return null;
}
if (insights.length > 0) {
return "Insights";
}
return null;
}
@@ -12,7 +12,7 @@ import { Col, Row } from "react-bootstrap";
import { ChartDocument, CreateSongMap, PBScoreDocument, SongDocument } from "tachi-common";
import { UGPT } from "types/react";
import { PBDataset } from "types/tables";
import { GPTToolOrInsight } from "types/ugpt";
import { GPTUtility } from "types/ugpt";
function Component({ game, playtype, reqUser }: UGPT) {
const { data, error } = useApiQuery<{
@@ -115,7 +115,7 @@ function Component({ game, playtype, reqUser }: UGPT) {
);
}
export const JubilityBreakdownInsight: GPTToolOrInsight = {
export const JubilityBreakdownInsight: GPTUtility = {
name: "Jubility Breakdown",
urlPath: "jubility",
description: `See what PBs are going into profile jubility!`,
@@ -8,7 +8,7 @@ import { TachiConfig } from "lib/config";
import React from "react";
import { Col, Row } from "react-bootstrap";
import { UGPT } from "types/react";
import { GPTToolOrInsight } from "types/ugpt";
import { GPTUtility } from "types/ugpt";
import Icon from "components/util/Icon";
import Divider from "components/util/Divider";
@@ -66,9 +66,10 @@ function Component({ game, playtype, reqUser }: UGPT) {
);
}
export const BMSCustomTablesTool: GPTToolOrInsight = {
export const BMSCustomTablesTool: GPTUtility = {
name: `${TachiConfig.name} BMS Tables`,
urlPath: "custom-tables",
description: `${TachiConfig.name} has its own BMS tables that you can use in-game!`,
component: Component,
personalUseOnly: true,
};
@@ -1,6 +1,5 @@
import { CopyToClipboard } from "util/misc";
import ApiError from "components/util/ApiError";
import DebugContent from "components/util/DebugContent";
import Divider from "components/util/Divider";
import Loading from "components/util/Loading";
import useApiQuery from "components/util/query/useApiQuery";
@@ -8,7 +7,7 @@ import { TachiConfig } from "lib/config";
import React from "react";
import { Alert, Button, Col, Row } from "react-bootstrap";
import { UGPT } from "types/react";
import { GPTToolOrInsight } from "types/ugpt";
import { GPTUtility } from "types/ugpt";
function Component({ game, playtype, reqUser }: UGPT) {
const { data, error } = useApiQuery<string>(
@@ -60,9 +59,10 @@ function Component({ game, playtype, reqUser }: UGPT) {
);
}
export const IIDXEamusementExportTool: GPTToolOrInsight = {
export const IIDXEamusementExportTool: GPTUtility = {
name: `e-amusement CSV Export`,
urlPath: "eam-csv-export",
description: `Export your ${TachiConfig.name} scores into e-amusement format.`,
component: Component,
personalUseOnly: true,
};
@@ -1,5 +1,4 @@
import { ToAPIURL } from "util/api";
import { CopyToClipboard } from "util/misc";
import Card from "components/layout/page/Card";
import ApiError from "components/util/ApiError";
import Loading from "components/util/Loading";
@@ -8,9 +7,7 @@ import { TachiConfig } from "lib/config";
import React from "react";
import { Col, Row } from "react-bootstrap";
import { UGPT } from "types/react";
import { GPTToolOrInsight } from "types/ugpt";
import Icon from "components/util/Icon";
import Divider from "components/util/Divider";
import { GPTUtility } from "types/ugpt";
function Component({ game, playtype, reqUser }: UGPT) {
const { data, error } = useApiQuery<
@@ -76,9 +73,10 @@ function Component({ game, playtype, reqUser }: UGPT) {
);
}
export const IIDXPlaylistsTool: GPTToolOrInsight = {
export const IIDXPlaylistsTool: GPTUtility = {
name: `${TachiConfig.name} IIDX Playlists`,
urlPath: "custom-tables",
urlPath: "playlists",
description: `${TachiConfig.name} has its own IIDX playlists that you can use in-game via playlister!`,
component: Component,
personalUseOnly: true,
};
@@ -1,67 +0,0 @@
import { Game, GetIDString, IDStrings, Playtype } from "tachi-common";
import { GPTToolOrInsight } from "types/ugpt";
import { BMSCustomTablesTool } from "./tools/BMSCustomTablesTool";
import { IIDXEamusementExportTool } from "./tools/IIDXEamusementExportTool";
import { IIDXPlaylistsTool } from "./tools/IIDXPlaylistsTool";
import { JubilityBreakdownInsight } from "./insights/JubilityBreakdownInsight";
// A tool is something that users can use, like exporting to a specific format
// or having things integrate with the game in a neat way.
// Tools are **NOT** displayed on someone elses profile. For that, you want
// Insights (Fancy score renderers, basically).
// What tools does each game support?
const GPT_TOOLS: Record<IDStrings, Array<GPTToolOrInsight>> = {
"bms:7K": [BMSCustomTablesTool],
"bms:14K": [BMSCustomTablesTool],
"chunithm:Single": [],
"gitadora:Dora": [],
"gitadora:Gita": [],
"iidx:DP": [IIDXEamusementExportTool, IIDXPlaylistsTool],
"iidx:SP": [IIDXEamusementExportTool, IIDXPlaylistsTool],
"itg:Stamina": [],
"jubeat:Single": [],
"museca:Single": [],
"pms:Controller": [],
"pms:Keyboard": [],
"popn:9B": [],
"sdvx:Single": [],
"usc:Controller": [],
"usc:Keyboard": [],
"wacca:Single": [],
};
// An insight is a page for a unique kind of data viz that tachi (generically) does not
// pick up. Good examples of this would be something like viewing what goes into your
// jubility.
const GPT_INSIGHTS: Record<IDStrings, Array<GPTToolOrInsight>> = {
"bms:7K": [],
"bms:14K": [],
"chunithm:Single": [],
"gitadora:Dora": [],
"gitadora:Gita": [],
"iidx:DP": [],
"iidx:SP": [],
"itg:Stamina": [],
"jubeat:Single": [JubilityBreakdownInsight],
"museca:Single": [],
"pms:Controller": [],
"pms:Keyboard": [],
"popn:9B": [],
"sdvx:Single": [],
"usc:Controller": [],
"usc:Keyboard": [],
"wacca:Single": [],
};
export function GetGPTTools(game: Game, playtype: Playtype) {
const idString = GetIDString(game, playtype);
return GPT_TOOLS[idString];
}
export function GetGPTInsights(game: Game, playtype: Playtype) {
const idString = GetIDString(game, playtype);
return GPT_INSIGHTS[idString];
}
+17 -23
View File
@@ -13,7 +13,7 @@ import { UGPTStatsReturn } from "types/api-returns";
import useLUGPTSettings from "components/util/useLUGPTSettings";
import { UserSettingsContext } from "context/UserSettingsContext";
import FollowUserButton from "components/util/FollowUserButton";
import { GetGPTInsights, GetGPTTools } from "components/tools-insights/ToolsAndInsights";
import { GetGPTUtilsName } from "components/gpt-utils/GPTUtils";
import ProfileBadges from "./ProfileBadges";
import ProfilePicture from "./ProfilePicture";
import RankingData from "./UGPTRankingData";
@@ -202,10 +202,23 @@ export function UGPTBottomNav({
</NavItem>,
];
if (GetGPTInsights(game, playtype).length > 0) {
const utilsName = GetGPTUtilsName(game, playtype, isRequestedUser);
if (utilsName) {
navItems.push(
<NavItem key="insights" to={`${baseUrl}/insights`}>
Insights
<NavItem key="targets" to={`${baseUrl}/utils`}>
{utilsName}
</NavItem>
);
}
if (isRequestedUser) {
navItems.push(
<NavItem key="rivals" to={`${baseUrl}/rivals`}>
Rivals
</NavItem>,
<NavItem key="targets" to={`${baseUrl}/targets`}>
Goals & Quests
</NavItem>
);
}
@@ -217,25 +230,6 @@ export function UGPTBottomNav({
);
if (isRequestedUser) {
navItems.push(
<NavItem key="rivals" to={`${baseUrl}/rivals`}>
Rivals
</NavItem>,
<NavItem key="targets" to={`${baseUrl}/targets`}>
Goals & Quests
</NavItem>
);
const tools = GetGPTTools(game, playtype);
if (tools.length > 0) {
navItems.push(
<NavItem key="targets" to={`${baseUrl}/tools`}>
Tools
</NavItem>
);
}
navItems.push(
<NavItem key="settings" to={`${baseUrl}/settings`}>
Settings
+2 -1
View File
@@ -1,9 +1,10 @@
import React from "react";
import { UGPT } from "./react";
export interface GPTToolOrInsight {
export interface GPTUtility {
urlPath: string;
name: string;
description: React.ReactChild;
component: (ugpt: UGPT) => JSX.Element;
personalUseOnly?: boolean;
}