mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 20:38:17 +03:00
Merge pull request #65 from TNG-dev/release-v2.0.1
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tachi-client",
|
||||
"version": "2.0.0-prerelease",
|
||||
"version": "2.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node scripts/start.js -o",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import ApiError from "components/util/ApiError";
|
||||
import Divider from "components/util/Divider";
|
||||
import Loading from "components/util/Loading";
|
||||
import OAuthMoreInfo from "components/util/OAuthMoreInfo";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import { UserContext } from "context/UserContext";
|
||||
import { TachiConfig } from "lib/config";
|
||||
@@ -89,6 +90,10 @@ export default function ClientFileFlowPage() {
|
||||
Create the key.
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-12" style={{ fontSize: "1.3rem" }}>
|
||||
<Divider />
|
||||
<OAuthMoreInfo client={data} />
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import CenterLayoutPage from "components/layout/CenterLayoutPage";
|
||||
import ApiError from "components/util/ApiError";
|
||||
import Divider from "components/util/Divider";
|
||||
import Loading from "components/util/Loading";
|
||||
import OAuthMoreInfo from "components/util/OAuthMoreInfo";
|
||||
import useApiQuery from "components/util/query/useApiQuery";
|
||||
import useQueryString from "components/util/useQueryString";
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { integer, TachiAPIClientDocument } from "tachi-common";
|
||||
import { APIFetchV1 } from "util/api";
|
||||
|
||||
export default function OAuthRequestAuthPage() {
|
||||
const params = useQueryString();
|
||||
|
||||
const clientID = params.get("clientID");
|
||||
|
||||
if (!clientID) {
|
||||
return (
|
||||
<CenterLayoutPage>
|
||||
No ClientID. Whoever gave you this link likely messed up somehow. <br />
|
||||
<Link to="/">Go Home.</Link>
|
||||
</CenterLayoutPage>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<CenterLayoutPage>
|
||||
<OAuthRequestAuthLoader clientID={clientID} />
|
||||
</CenterLayoutPage>
|
||||
);
|
||||
}
|
||||
|
||||
function OAuthRequestAuthLoader({ clientID }: { clientID: string }) {
|
||||
const { isLoading, error, data } = useApiQuery<Omit<TachiAPIClientDocument, "clientSecret">>(
|
||||
`/clients/${clientID}`
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!data.redirectUri) {
|
||||
return (
|
||||
<>
|
||||
This client does not support OAuth Flows.
|
||||
<br />
|
||||
Whoever gave you this link likely messed up somehow. <br />
|
||||
<Link to="/">Go Home.</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return <OAuthRequestAuthMain client={data} />;
|
||||
}
|
||||
|
||||
function OAuthRequestAuthMain({
|
||||
client,
|
||||
}: {
|
||||
client: Omit<TachiAPIClientDocument, "clientSecret">;
|
||||
}) {
|
||||
const params = useQueryString();
|
||||
|
||||
const context = params.get("context");
|
||||
|
||||
return (
|
||||
<div className="row">
|
||||
<>
|
||||
<div className="col-12">
|
||||
{client.name} is requesting you authenticate with them.
|
||||
<Divider />
|
||||
</div>
|
||||
<div className="col-12">
|
||||
The key will have the following permissions.
|
||||
<ul>
|
||||
{client.requestedPermissions.map(e => (
|
||||
<li key={e}>{e}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="col-12 d-flex" style={{ justifyContent: "space-evenly" }}>
|
||||
<Link className="btn btn-danger" to="/">
|
||||
No thanks.
|
||||
</Link>
|
||||
<button
|
||||
className="btn btn-success"
|
||||
onClick={async () => {
|
||||
const tokenRes = await APIFetchV1<{
|
||||
code: string;
|
||||
userID: integer;
|
||||
createdOn: number;
|
||||
}>(
|
||||
"/oauth/create-code",
|
||||
{
|
||||
method: "POST",
|
||||
},
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
if (!tokenRes.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const redirectURL = new URL(client.redirectUri!);
|
||||
|
||||
redirectURL.searchParams.set("code", tokenRes.body.code);
|
||||
|
||||
if (context) {
|
||||
redirectURL.searchParams.set("context", context);
|
||||
}
|
||||
|
||||
window.location.href = redirectURL.toString();
|
||||
}}
|
||||
>
|
||||
I want to authenticate with {client.name}.
|
||||
</button>
|
||||
</div>
|
||||
<div className="col-12" style={{ fontSize: "1.3rem" }}>
|
||||
<Divider />
|
||||
<OAuthMoreInfo client={client} />
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -21,10 +21,7 @@ export default function BarbatosPage() {
|
||||
<ol className="instructions-list">
|
||||
<li>
|
||||
Download <code>barbatos.dll</code> from{" "}
|
||||
<ExternalLink href="#">
|
||||
here (No download yet. Barbatos is not supported.)
|
||||
</ExternalLink>
|
||||
.
|
||||
<ExternalLink href="https://f.wcal.xyz/vC3dW7cn">here</ExternalLink>.
|
||||
</li>
|
||||
<li>
|
||||
Download your config file{" "}
|
||||
@@ -32,8 +29,8 @@ export default function BarbatosPage() {
|
||||
<b>This file contains an API Key, which is meant to be kept secret!</b>
|
||||
</li>
|
||||
<li>
|
||||
Place all of these files inside your SDVX folder, next to your <code>.bat</code>{" "}
|
||||
file.
|
||||
Place <code>barbatos.dll</code> and the above config file inside your SDVX
|
||||
folder, next to your <code>.bat</code> file.
|
||||
</li>
|
||||
<ul className="instructions-list">
|
||||
<li>
|
||||
|
||||
@@ -36,7 +36,14 @@ export default function BeatorajaIRPage() {
|
||||
Place the IR <code>.jar</code> file in the <code>ir/</code> folder.
|
||||
</li>
|
||||
<li>Open the game options, and navigate to the IR section.</li>
|
||||
<li>Select {TachiConfig.name} IR.</li>
|
||||
<li>
|
||||
Select {TachiConfig.name} IR.
|
||||
<br />
|
||||
<span className="text-warning">
|
||||
If the IR isn't showing up, make sure you're launching the game with the{" "}
|
||||
<code>beatoraja-config.bat</code> file. Otherwise, IRs will never load.
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
Get an API token for the IR by clicking{" "}
|
||||
<ExternalLink href="/client-file-flow/CXBeatorajaIR">this link</ExternalLink>.
|
||||
|
||||
@@ -133,13 +133,13 @@ function ImportInfoDisplayer({ game }: { game: Game }) {
|
||||
);
|
||||
} else if (game === "sdvx") {
|
||||
Content.unshift(
|
||||
// <ImportInfoCard
|
||||
// name="Barbatos"
|
||||
// href="barbatos"
|
||||
// desc="Automatically import scores, whenever you get them!"
|
||||
// moreInfo="This is the recommended way to import SDVX scores, as it provides high quality data in real-time."
|
||||
// key="Barbatos"
|
||||
// />,
|
||||
<ImportInfoCard
|
||||
name="Barbatos"
|
||||
href="barbatos"
|
||||
desc="Automatically import scores, whenever you get them!"
|
||||
moreInfo="This is the recommended way to import SDVX scores, as it provides high quality data in real-time."
|
||||
key="Barbatos"
|
||||
/>,
|
||||
// <ImportInfoCard
|
||||
// name="Konaste Hook"
|
||||
// href="ks-hook"
|
||||
|
||||
@@ -358,6 +358,12 @@ function OAuthClientRow({ client, clients, setClients }: OAuthClientProps) {
|
||||
{window.location.origin}/client-file-flow/{client.clientID}
|
||||
</code>
|
||||
</h6>
|
||||
<h6>
|
||||
OAuth Flow Link: <br />
|
||||
<code>
|
||||
{window.location.origin}/oauth/request-auth?clientID={client.clientID}
|
||||
</code>
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
@@ -23,6 +23,7 @@ import { GPTLeaderboard, UGPTLeaderboardAdjacent } from "types/api-returns";
|
||||
import { GamePT, SetState } from "types/react";
|
||||
import { APIFetchV1, UnsuccessfulAPIFetchResponse } from "util/api";
|
||||
import { ChangeOpacity } from "util/color-opacity";
|
||||
import { UppercaseFirst } from "util/misc";
|
||||
|
||||
interface LeaderboardsData {
|
||||
stats: UGPTLeaderboardAdjacent;
|
||||
@@ -161,7 +162,10 @@ function LeaderboardsPageContent({
|
||||
</Link>
|
||||
}
|
||||
>
|
||||
<MiniTable className="text-center" headers={["Position", "User", alg, "Classes"]}>
|
||||
<MiniTable
|
||||
className="text-center"
|
||||
headers={["Position", "User", UppercaseFirst(alg), "Classes"]}
|
||||
>
|
||||
<>
|
||||
{bestNearbyUser >= 1 &&
|
||||
leaderboard.gameStats
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ErrorPage } from "app/pages/ErrorPage";
|
||||
import ForgotPasswordPage from "app/pages/ForgotPasswordPage";
|
||||
import LoginPage from "app/pages/LoginPage";
|
||||
import OAuthRequestAuthPage from "app/pages/OAuthRequestAuthPage";
|
||||
import RegisterPage from "app/pages/RegisterPage";
|
||||
import ResetPasswordPage from "app/pages/ResetPasswordPage";
|
||||
import VerifyEmailPage from "app/pages/VerifyEmailPage";
|
||||
@@ -51,6 +52,10 @@ export function Routes() {
|
||||
<Route path="/oauth2-callback">
|
||||
<OAuth2CallbackRoutes />
|
||||
</Route>
|
||||
<Route path="/oauth/request-auth">
|
||||
<OAuthRequestAuthPage />
|
||||
</Route>
|
||||
|
||||
<Route path="/client-file-flow">
|
||||
<ClientFileFlowRoutes />
|
||||
</Route>
|
||||
|
||||
@@ -91,7 +91,7 @@ export default function ARCImportPage({ game }: { game: "sdvx" | "iidx" }) {
|
||||
</Link>
|
||||
) : (
|
||||
<Link style={{ color: "purple" }} to="/dashboard/import/barbatos">
|
||||
Barbatos (Although it's not supported yet, sorry.)
|
||||
Barbatos
|
||||
</Link>
|
||||
)}{" "}
|
||||
instead.
|
||||
|
||||
@@ -1,10 +1,24 @@
|
||||
import Divider from "components/util/Divider";
|
||||
import ExternalLink from "components/util/ExternalLink";
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { ServerStatus } from "types/api-returns";
|
||||
import { APIFetchV1 } from "util/api";
|
||||
import { FORMATTED_VERSION } from "util/constants/version";
|
||||
|
||||
export function Footer() {
|
||||
const [serverVersion, setServerVersion] = useState("Loading...");
|
||||
|
||||
useEffect(() => {
|
||||
APIFetchV1<ServerStatus>("/status").then(r => {
|
||||
if (!r.success) {
|
||||
setServerVersion("Error Fetching data!");
|
||||
} else {
|
||||
setServerVersion(r.body.version);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Divider className="mt-8" />
|
||||
@@ -14,12 +28,20 @@ export function Footer() {
|
||||
<div className="order-2 order-md-1">
|
||||
{/* is there a better way to do this? mt-md-3 is the intent */}
|
||||
<div className="d-block d-lg-none mt-3"></div>
|
||||
Client:{" "}
|
||||
<ExternalLink
|
||||
href="https://www.youtube.com/watch?v=JOb5f-TG6iI&list=PLGFFTE0Sw8tStwmuVsAoeOEdmWBLceK9b"
|
||||
className="gentle-link px-3"
|
||||
href="https://en.wikipedia.org/wiki/Disintegration_(The_Cure_album)"
|
||||
className="gentle-link"
|
||||
>
|
||||
{FORMATTED_VERSION}
|
||||
</ExternalLink>
|
||||
, Server:{" "}
|
||||
<ExternalLink
|
||||
href="https://en.wikipedia.org/wiki/Dummy_(album)"
|
||||
className="gentle-link"
|
||||
>
|
||||
{serverVersion}
|
||||
</ExternalLink>
|
||||
</div>
|
||||
|
||||
<div className="nav nav-dark order-1 order-md-2 justify-content-center">
|
||||
|
||||
@@ -14,6 +14,7 @@ import AllGames from "./AllGames";
|
||||
import ImportScoresLink from "./ImportScoresLink";
|
||||
import MenuDropdown from "./MenuDropdown";
|
||||
import MenuLink from "./MenuLink";
|
||||
import SearchBar from "./SearchBar";
|
||||
|
||||
export function HeaderMenu() {
|
||||
const { user } = useContext(UserContext);
|
||||
@@ -95,6 +96,13 @@ export function HeaderMenu() {
|
||||
</div>
|
||||
</div>
|
||||
<ul className="menu-nav">
|
||||
<li className="menu-item d-block d-lg-none">
|
||||
<div className="d-flex w-100 mb-8 px-4">
|
||||
<SearchBar />
|
||||
</div>
|
||||
<Divider />
|
||||
</li>
|
||||
|
||||
{user && ugs && ugs.length !== 0 && (
|
||||
<MenuDropdown name="Your Profiles">{userProfileLinks}</MenuDropdown>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import { PublicUserDocument, TachiAPIClientDocument } from "tachi-common";
|
||||
import ApiError from "./ApiError";
|
||||
import Loading from "./Loading";
|
||||
import Muted from "./Muted";
|
||||
import useApiQuery from "./query/useApiQuery";
|
||||
|
||||
export default function OAuthMoreInfo({
|
||||
client,
|
||||
}: {
|
||||
client: Omit<TachiAPIClientDocument, "clientSecret">;
|
||||
}) {
|
||||
const { data, isLoading, error } = useApiQuery<PublicUserDocument>(`/users/${client.author}`);
|
||||
|
||||
if (error) {
|
||||
return <ApiError error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
return <Muted>API Client Author: {data.username}.</Muted>;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
APIPermissions,
|
||||
ChartDocument,
|
||||
ClassAchievementDocument,
|
||||
FolderDocument,
|
||||
@@ -162,3 +163,11 @@ export interface UserRecentSummary {
|
||||
recentSessions: SessionDocument[];
|
||||
recentFolders: FolderDocument[];
|
||||
}
|
||||
|
||||
export interface ServerStatus {
|
||||
serverTime: number;
|
||||
startTime: number;
|
||||
version: string;
|
||||
whoami: integer | null;
|
||||
permissions: APIPermissions[];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const MAJOR = 2;
|
||||
const MINOR = 0;
|
||||
const PATCH = 0;
|
||||
const PATCH = 1;
|
||||
|
||||
// As is with all front-facing zkldi projects, the version names for tachi-server
|
||||
// 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.
|
||||
|
||||
const THE_CURE_DISINTEGRATION = [
|
||||
|
||||
@@ -97,6 +97,7 @@ export const loggedInSplashes = [
|
||||
"I'm thinking this one's brown.",
|
||||
"My god is the sun!",
|
||||
"...Like Clockwork",
|
||||
"brooooyyddg",
|
||||
];
|
||||
|
||||
// Stuff that's bad for the kids, you know?
|
||||
|
||||
Reference in New Issue
Block a user