From 6752e68ab44cfdcd51a0cc02fccd57c4207832e8 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Sun, 14 Nov 2021 06:06:40 +0000 Subject: [PATCH] ARC support. --- client/package.json | 2 +- client/pnpm-lock.yaml | 8 +- .../games/_game/_playtype/GPTChartPage.tsx | 9 ++ .../dashboard/users/ARCIntegrationPage.tsx | 123 ++++++++++++++++++ .../dashboard/users/UserIntegrationsPage.tsx | 18 ++- .../dashboard/users/UserSettingsPage.tsx | 17 ++- client/src/app/routes/ImportRoutes.tsx | 9 ++ .../auth/RequireAuthAsUserParam.tsx | 2 +- .../src/components/imports/ARCImportPage.tsx | 123 ++++++++++++++++++ .../components/imports/KAIIntegrationPage.tsx | 1 - .../tables/components/TachiTable.tsx | 42 +++--- .../src/components/util/SelectLinkButton.tsx | 3 +- client/src/context/UGPTSettingsContext.tsx | 2 +- client/src/index.tsx | 6 +- client/src/util/splashes.ts | 1 + 15 files changed, 324 insertions(+), 42 deletions(-) create mode 100644 client/src/app/pages/dashboard/users/ARCIntegrationPage.tsx create mode 100644 client/src/components/imports/ARCImportPage.tsx diff --git a/client/package.json b/client/package.json index 94c0bb950..163145022 100644 --- a/client/package.json +++ b/client/package.json @@ -80,7 +80,7 @@ "resolve": "1.12.0", "resolve-url-loader": "3.1.0", "socicon": "3.0.5", - "tachi-common": "0.3.0" + "tachi-common": "0.3.2" }, "devDependencies": { "@types/humanize-duration": "3.25.1", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index a50d78c8e..7a10a1102 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -82,7 +82,7 @@ specifiers: serve: 11.2.0 socicon: 3.0.5 style-loader: 1.0.0 - tachi-common: 0.3.0 + tachi-common: 0.3.2 terser-webpack-plugin: 1.4.1 ts-pnp: 1.1.4 typescript: 4.3.5 @@ -152,7 +152,7 @@ dependencies: resolve: 1.12.0 resolve-url-loader: 3.1.0 socicon: 3.0.5 - tachi-common: 0.3.0 + tachi-common: 0.3.2 devDependencies: '@types/humanize-duration': 3.25.1 @@ -11211,8 +11211,8 @@ packages: strip-ansi: 6.0.0 dev: true - /tachi-common/0.3.0: - resolution: {integrity: sha512-ch7Msau97/on1d18bqRMUNUQhF9ZixzvNxMsLJNN14DBfS1AVxaoAfIRiQzWQ1jdfvwmcpP4Ge0x1w1prPZVAQ==} + /tachi-common/0.3.2: + resolution: {integrity: sha512-fX1ffleAJlP6WtjnGxVDfLsz7PQDArIFCMcvOjLB0+PwJ5FwhAO/8CX2B8ksYGuzp8PGWLxQGsPahwN9HV7YVQ==} dependencies: monk: 7.3.4 transitivePeerDependencies: 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 51210fde7..f6534112a 100644 --- a/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx +++ b/client/src/app/pages/dashboard/games/_game/_playtype/GPTChartPage.tsx @@ -1,4 +1,5 @@ import useSetSubheader from "components/layout/header/useSetSubheader"; +import QuickTooltip from "components/layout/misc/QuickTooltip"; import Card from "components/layout/page/Card"; import LampCell from "components/tables/cells/LampCell"; import ScoreCell from "components/tables/cells/ScoreCell"; @@ -31,6 +32,7 @@ import { PBDataset } from "types/tables"; import { APIFetchV1, UnsuccessfulAPIFetchResponse } from "util/api"; import { CreateUserMap } from "util/data"; import { SelectRightChart } from "util/misc"; +import { FormatDate, MillisToSince } from "util/time"; // This component forms a wrapper around the Real GPT Chart Page // which handles the case where activeChart == null. @@ -319,6 +321,13 @@ function PlayCard({ +
+ + {pb.timeAchieved + ? MillisToSince(pb.timeAchieved) ?? "" + : "No Timestamp Info"} + +
diff --git a/client/src/app/pages/dashboard/users/ARCIntegrationPage.tsx b/client/src/app/pages/dashboard/users/ARCIntegrationPage.tsx new file mode 100644 index 000000000..40e17bc98 --- /dev/null +++ b/client/src/app/pages/dashboard/users/ARCIntegrationPage.tsx @@ -0,0 +1,123 @@ +import ApiError from "components/util/ApiError"; +import Divider from "components/util/Divider"; +import Loading from "components/util/Loading"; +import Muted from "components/util/Muted"; +import useApiQuery from "components/util/query/useApiQuery"; +import React, { useEffect, useState } from "react"; +import { Button, Col, Form } from "react-bootstrap"; +import { ARCSavedProfileDocument, PublicUserDocument } from "tachi-common"; +import { SetState } from "types/react"; +import { APIFetchV1 } from "util/api"; + +export default function ARCIntegrationPage({ reqUser }: { reqUser: PublicUserDocument }) { + const [iidxID, setIIDXID] = useState(""); + const [sdvxID, setSDVXID] = useState(""); + + const { data, isLoading, error } = useApiQuery<{ + iidx: ARCSavedProfileDocument | null; + sdvx: ARCSavedProfileDocument | null; + }>(`/users/${reqUser.id}/integrations/arc`); + + useEffect(() => { + if (data) { + setIIDXID(data.iidx?.accountID ?? ""); + setSDVXID(data.sdvx?.accountID ?? ""); + } + }, [data]); + + if (error) { + return ; + } + + if (!data || isLoading) { + return ; + } + + return ( + <> + +

ARC Integration

+ To import scores from ARC, just put your AccountIDs in the tabs below. +
+ + Note: Only the latest versions of IIDX and SDVX are supported for imports. + + +
How to get your AccountID
+
    +
  1. Open the ARC website.
  2. +
  3. Go to the game you care about, and select My Bests.
  4. +
  5. + In the URL, after /profiles, there'll be a string that looks + like H4JxPV3vQH_. Copy that into the text fields below! +
  6. +
  7. To unset an integration, remove the account ID and hit save.
  8. +
+ + + + {iidxID && iidxID.length !== 11 && ( + + Invalid IIDX ID, it should be 11 characters long. + + )} + + + {sdvxID && sdvxID.length !== 11 && ( + + Invalid SDVX ID, it should be 11 characters long. + + )} + + + + + + + ); +} + +function IntegrationStatus({ + name, + value, + setValue, +}: { + name: string; + value: string; + setValue: SetState; +}) { + return ( + + {`${name} Account ID`} + setValue(e.target.value)} + /> + + ); +} diff --git a/client/src/app/pages/dashboard/users/UserIntegrationsPage.tsx b/client/src/app/pages/dashboard/users/UserIntegrationsPage.tsx index e6f0fa930..d1323c8a4 100644 --- a/client/src/app/pages/dashboard/users/UserIntegrationsPage.tsx +++ b/client/src/app/pages/dashboard/users/UserIntegrationsPage.tsx @@ -1,7 +1,6 @@ import useSetSubheader from "components/layout/header/useSetSubheader"; import Card from "components/layout/page/Card"; import ApiError from "components/util/ApiError"; -import DebugContent from "components/util/DebugContent"; import Divider from "components/util/Divider"; import ExternalLink from "components/util/ExternalLink"; import FormInput from "components/util/FormInput"; @@ -16,12 +15,13 @@ import { Alert, Button, Col, Form, Modal, Row } from "react-bootstrap"; import { APIPermissions, APITokenDocument, - TachiAPIClientDocument, PublicUserDocument, + TachiAPIClientDocument, } from "tachi-common"; import { SetState } from "types/react"; import { APIFetchV1 } from "util/api"; import { allPermissions, DelayedPageReload } from "util/misc"; +import ARCIntegrationPage from "./ARCIntegrationPage"; import FervidexIntegrationPage from "./FervidexIntegrationPage"; export default function UserIntegrationsPage({ reqUser }: { reqUser: PublicUserDocument }) { @@ -41,7 +41,7 @@ export default function UserIntegrationsPage({ reqUser }: { reqUser: PublicUserD {mode !== "btchi" && ( - Integrations + Service Configuration )} @@ -57,7 +57,7 @@ export default function UserIntegrationsPage({ reqUser }: { reqUser: PublicUserD {page === "services" ? ( - + ) : page === "api-keys" ? ( ) : ( @@ -605,7 +605,7 @@ function EditClientModal({ ); } -function IntegrationsPage({ reqUser }: { reqUser: PublicUserDocument }) { +function ServicesPage({ reqUser }: { reqUser: PublicUserDocument }) { if (mode === "btchi") { return ( @@ -619,7 +619,7 @@ function IntegrationsPage({ reqUser }: { reqUser: PublicUserDocument }) { return ( -

Services

+

Service Configuration

Some services have had their names truncated to their first three characters for privacy reasons. @@ -637,7 +637,11 @@ function IntegrationsPage({ reqUser }: { reqUser: PublicUserDocument }) { - {page === "fervidex" ? : <>} + {page === "fervidex" ? ( + + ) : ( + + )}
); } diff --git a/client/src/app/pages/dashboard/users/UserSettingsPage.tsx b/client/src/app/pages/dashboard/users/UserSettingsPage.tsx index 007ba31c6..d2b664461 100644 --- a/client/src/app/pages/dashboard/users/UserSettingsPage.tsx +++ b/client/src/app/pages/dashboard/users/UserSettingsPage.tsx @@ -76,6 +76,7 @@ function PreferencesForm({ reqUser }: { reqUser: PublicUserDocument }) { developerMode: settings?.preferences.developerMode ?? false, invisible: settings?.preferences.invisible ?? false, contentiousContent: settings?.preferences.contentiousContent ?? false, + advancedMode: settings?.preferences.advancedMode ?? false, }, onSubmit: async values => { const res = await APIFetchV1( @@ -107,6 +108,18 @@ function PreferencesForm({ reqUser }: { reqUser: PublicUserDocument }) { Enable debug information and other useful debugging buttons. + + + Enable advanced stuff, like being able to copy a tables contents into a CSV. + + + {/* Hide your last seen status. - + */} diff --git a/client/src/app/routes/ImportRoutes.tsx b/client/src/app/routes/ImportRoutes.tsx index 1fee1bae0..979bf80bf 100644 --- a/client/src/app/routes/ImportRoutes.tsx +++ b/client/src/app/routes/ImportRoutes.tsx @@ -12,6 +12,7 @@ import MerJSONPage from "app/pages/dashboard/import/MerJSONPage"; import SSSXMLPage from "app/pages/dashboard/import/SSSXMLPage"; import USCDBPage from "app/pages/dashboard/import/USCDBPage"; import USCIRPage from "app/pages/dashboard/import/USCIRPage"; +import ARCImportPage from "components/imports/ARCImportPage"; import KAIIntegrationPage from "components/imports/KAIIntegrationPage"; import Divider from "components/util/Divider"; import { UserContext } from "context/UserContext"; @@ -98,6 +99,14 @@ export default function ImportRoutes() { + + + + + + + + No user? How did you get here!; + } + + const [importState, setImportState] = useState(NotStartedState); + + const { data, isLoading, error } = useApiQuery<{ + iidx: ARCSavedProfileDocument | null; + sdvx: ARCSavedProfileDocument | null; + }>(`/users/${user.id}/integrations/arc`); + + if (error) { + return ; + } + + if (!data || isLoading) { + return ; + } + + if (!data[game]) { + return ( +
+

You are not authenticated for ARC {GetGameConfig(game).name}.

+
+

+ Please go to the{" "} + Service Settings for ARC, and + link your account. +

+
+ ); + } + + return ( +
+

Authenticated with ARC!

+ +
+ +
+ + + Play on ARC a lot? Please consider setting up{" "} + {game === "iidx" ? ( + + Fervidex + + ) : ( + + Barbatos + + )}{" "} + instead. +
+ ARC exports poor quality data, and can only save one score on a chart at a time. +
+ In an ideal world, you should only have to use this sync once, and then should + switch to an import method that exports scores better! +
+ + +
+ ); +} diff --git a/client/src/components/imports/KAIIntegrationPage.tsx b/client/src/components/imports/KAIIntegrationPage.tsx index c982b2296..ff2eaa9e4 100644 --- a/client/src/components/imports/KAIIntegrationPage.tsx +++ b/client/src/components/imports/KAIIntegrationPage.tsx @@ -14,7 +14,6 @@ import { Button, Form, InputGroup } from "react-bootstrap"; import { APIImportTypes, GetGameConfig, ImportDocument } from "tachi-common"; import { ImportStates, NotStartedState } from "types/import"; import { APIFetchV1 } from "util/api"; -import ImportInfo from "./ImportInfo"; import ImportStateRenderer from "./ImportStateRenderer"; interface Props { diff --git a/client/src/components/tables/components/TachiTable.tsx b/client/src/components/tables/components/TachiTable.tsx index a9a2f4d57..fd20881d7 100644 --- a/client/src/components/tables/components/TachiTable.tsx +++ b/client/src/components/tables/components/TachiTable.tsx @@ -165,27 +165,29 @@ export default function TachiTable({
{displayStr}
- + CopyToClipboard( + // Faulty types with json2csv. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await json2csvAsync(data as any, { + sortHeader: true, + }) + ); + }} + variant="outline-info" + className="w-50" + > + + Export {search !== "" ? "Filtered Data" : "Table"} (CSV) + + )} {settings?.preferences.developerMode && (