Commit 2bb8cff2 authored by timel's avatar timel

refactor: route

parent 9dc08cb0
...@@ -25,3 +25,7 @@ declare global { ...@@ -25,3 +25,7 @@ declare global {
) => (...args: Parameters<console.log>) => void; ) => (...args: Parameters<console.log>) => void;
} }
} }
declare module "react-router-dom" {
export declare function useLoaderData<T>(): T;
}
...@@ -40,7 +40,7 @@ body { ...@@ -40,7 +40,7 @@ body {
font-size: 14px; font-size: 14px;
display: flex; display: flex;
margin: 0; margin: 0;
place-items: center; // place-items: center;
min-width: 320px; min-width: 320px;
position: fixed; position: fixed;
top: 0; top: 0;
......
export const Component = () => <>build</>;
Component.displayName = "Build";
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
flex-wrap: wrap; flex-wrap: wrap;
align-content: center; align-content: center;
padding: 0 2rem; padding: 0 2rem;
gap: 1rem; gap: 1.5rem;
box-sizing: border-box; box-sizing: border-box;
.logo { .logo {
width: 60px; width: 60px;
......
import "@/styles/core.scss";
import { NavLink, Outlet } from "react-router-dom"; import { NavLink, Outlet } from "react-router-dom";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
...@@ -19,7 +20,7 @@ export const NeosLayout = () => { ...@@ -19,7 +20,7 @@ export const NeosLayout = () => {
<NavLink to="/match">匹配</NavLink> <NavLink to="/match">匹配</NavLink>
<NavLink to="/build">组卡</NavLink> <NavLink to="/build">组卡</NavLink>
<span style={{ flexGrow: 1 }} /> <span style={{ flexGrow: 1 }} />
<NavLink to="/home">个人中心</NavLink> <NavLink to="/profile">个人中心</NavLink>
</nav> </nav>
<Outlet /> <Outlet />
</> </>
......
...@@ -9,14 +9,14 @@ import { ...@@ -9,14 +9,14 @@ import {
import { NeosLayout } from "./Layout"; import { NeosLayout } from "./Layout";
import LazyLoad, { Loading } from "./LazyLoad"; import LazyLoad, { Loading } from "./LazyLoad";
const Start = React.lazy(() => import("./Start")); // const Match = React.lazy(() => import("./Match"));
const Match = React.lazy(() => import("./Match")); // const WaitRoom = React.lazy(() => import("./WaitRoom"));
const WaitRoom = React.lazy(() => import("./WaitRoom")); // const Mora = React.lazy(() => import("./Mora"));
const Mora = React.lazy(() => import("./Mora")); // const NeosDuel = React.lazy(() => import("./Duel/Main"));
const NeosDuel = React.lazy(() => import("./Duel/Main")); // const Replay = React.lazy(() => import("./Replay"));
const Replay = React.lazy(() => import("./Replay")); // const SSO = React.lazy(() => import("./SSO"));
const SSO = React.lazy(() => import("./SSO")); // const Profile = React.lazy(() => import("./Profile"));
const Home = React.lazy(() => import("./Home")); // const Build = React.lazy(() => import("./Build"));
// TODO: finish this // TODO: finish this
const _router = createBrowserRouter([ const _router = createBrowserRouter([
...@@ -26,51 +26,66 @@ const _router = createBrowserRouter([ ...@@ -26,51 +26,66 @@ const _router = createBrowserRouter([
children: [ children: [
{ {
path: "/", path: "/",
element: <LazyLoad lazy={<Start />} />, lazy: () => import("./Start"),
}, },
{ {
path: "/match", path: "/match/*",
element: <LazyLoad lazy={<Match />} />, lazy: () => import("./NewMatch"),
},
{
path: "/build",
lazy: () => import("./Build"),
},
{
path: "/profile",
lazy: () => import("./Profile"),
},
{
path: "/waitroom/:ip/:player/:passWd",
lazy: () => import("./NewWaitRoom"),
},
{
path: "/duel/:ip/:player/:passWd",
}, },
], ],
}, },
]); ]);
// 暂且先这么写,为后重构为createBrowserRouter打基础 // 暂且先这么写,为后重构为createBrowserRouter打基础
const router = createBrowserRouter( // const router = createBrowserRouter(
createRoutesFromElements( // createRoutesFromElements(
<Route path="/" element={<NeosLayout />}> // <Route path="/" element={<NeosLayout />}>
<Route path="/" element={<LazyLoad lazy={<Start />} />} /> // <Route path="/" element={<LazyLoad lazy={<Start />} />} />
<Route path="/home" element={<LazyLoad lazy={<Home />} />} /> // <Route path="/profile" element={<LazyLoad lazy={<Profile />} />} />
<Route path="/match" element={<LazyLoad lazy={<Match />} />} /> // <Route path="/match" element={<LazyLoad lazy={<Match />} />} />
<Route // <Route
path="/room/:player/:passWd/:ip" // path="/room/:player/:passWd/:ip"
element={ // element={
<Suspense fallback={<Loading />}> // <Suspense fallback={<Loading />}>
<WaitRoom /> // <WaitRoom />
</Suspense> // </Suspense>
} // }
/> // />
<Route // <Route
path="/mora/:player/:passWd/:ip" // path="/mora/:player/:passWd/:ip"
element={ // element={
<Suspense fallback={<Loading />}> // <Suspense fallback={<Loading />}>
<Mora /> // <Mora />
</Suspense> // </Suspense>
} // }
/> // />
<Route path="/replay" element={<LazyLoad lazy={<Replay />} />} /> // <Route path="/replay" element={<LazyLoad lazy={<Replay />} />} />
<Route // <Route
path="/duel/:player/:passWd/:ip" // path="/duel/:player/:passWd/:ip"
element={ // element={
<Suspense fallback={<Loading />}> // <Suspense fallback={<Loading />}>
<NeosDuel /> // <NeosDuel />
</Suspense> // </Suspense>
} // }
/> // />
<Route path="/sso/*" element={<LazyLoad lazy={<SSO />} />} /> // <Route path="/sso/*" element={<LazyLoad lazy={<SSO />} />} />
</Route> // </Route>
) // )
); // );
export const NeosRouter = () => <RouterProvider router={router} />; export const NeosRouter = () => <RouterProvider router={_router} />;
import { Link, type LoaderFunction } from "react-router-dom";
import { accountStore, type User } from "@/stores";
export const loader: LoaderFunction = () => {
const sso = new URLSearchParams(location.search).get("sso");
const user = sso ? getSSOUser(new URLSearchParams(atob(sso))) : undefined;
user && accountStore.login(user);
return null;
};
export const Component: React.FC = () => (
<>
new match
<Link to="/waitroom/1/2/3">waitroom</Link>
</>
);
Component.displayName = "Match";
/** 从SSO跳转回的URL之中,解析用户信息 */
function getSSOUser(searchParams: URLSearchParams): User {
return Object.fromEntries(searchParams) as unknown as User;
}
import { useLoaderData, type LoaderFunction } from "react-router-dom";
type Params = {
player?: string;
passWd?: string;
ip?: string;
};
export const loader: LoaderFunction = async ({ params }) => params;
export const Component: React.FC = () => {
const params = useLoaderData<Params>();
return <>{JSON.stringify(params)}</>;
};
...@@ -11,7 +11,7 @@ import styles from "./index.module.scss"; ...@@ -11,7 +11,7 @@ import styles from "./index.module.scss";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
const DefaultUserName = "Guest"; const DefaultUserName = "Guest";
const Home: React.FC = () => { export const Component: React.FC = () => {
const { user } = useSnapshot(accountStore); const { user } = useSnapshot(accountStore);
const navigate = useNavigate(); const navigate = useNavigate();
...@@ -86,5 +86,4 @@ const Home: React.FC = () => { ...@@ -86,5 +86,4 @@ const Home: React.FC = () => {
</> </>
); );
}; };
Component.displayName = "Profile";
export default Home;
.hint {
font-size: 20px;
font-family: "Gill Sans", sans-serif;
color: white;
text-align: center;
margin-top: 100px;
}
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useConfig } from "@/config";
import { accountStore, User } from "@/stores";
import styles from "./index.module.scss";
const NeosConfig = useConfig();
const SSO: React.FC = () => {
const sso = new URL(location.href).searchParams.get("sso");
const user: User | undefined = sso
? getSSOUser(new URLSearchParams(atob(sso)))
: undefined;
const [logined, setLogined] = useState(false);
const navigate = useNavigate();
useEffect(() => {
if (!sso) {
const ssoUrl = getSSOUrl(location.href);
window.location.href = ssoUrl;
}
if (user) {
accountStore.login(user);
setLogined(true);
navigate("/home");
}
}, []);
return (
<div className={styles.hint}>
{logined ? "登录成功,正在跳转......" : "登录萌卡账号中......"}
</div>
);
};
function getSSOUrl(callbackUrl: string): string {
let params = new URLSearchParams();
params.set("return_sso_url", callbackUrl);
const payload = btoa(params.toString());
const url = new URL(NeosConfig.accountUrl);
params = url.searchParams;
params.set("sso", payload);
return url.toString();
}
function getSSOUser(searchParams: URLSearchParams): User {
const sso = {};
for (const [key, value] of searchParams) {
// @ts-ignore
sso[key] = value;
}
return sso as any;
}
export default SSO;
import { RightOutlined } from "@ant-design/icons"; import { RightOutlined } from "@ant-design/icons";
import React from "react"; import React from "react";
import { Link } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { accountStore } from "@/stores";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
import { useSnapshot } from "valtio";
const NeosConfig = useConfig(); const NeosConfig = useConfig();
const Start: React.FC = () => { export const Component: React.FC = () => {
const { user } = useSnapshot(accountStore);
return ( return (
<> <>
<div className={styles.wrapper}> <div className={styles.wrapper}>
...@@ -31,13 +33,41 @@ const Start: React.FC = () => { ...@@ -31,13 +33,41 @@ const Start: React.FC = () => {
alt="neos" alt="neos"
/> />
</main> </main>
<Link to="/sso" className={styles["start-btn"]}> <LoginBtn logined={Boolean(user)} />
<span>登录游戏</span>
<RightOutlined />
</Link>
</div> </div>
</> </>
); );
}; };
Component.displayName = "Start";
const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => {
const navigate = useNavigate();
const loginViaSSO = () =>
// 跳转回match页
location.replace(getSSOUrl(`${location.origin}/match/}`));
const goToMatch = () => navigate("/match");
return (
<span
className={styles["start-btn"]}
onClick={logined ? goToMatch : loginViaSSO}
>
{logined ? <span>开始游戏</span> : <span>登录游戏</span>}
<RightOutlined />
</span>
);
};
/** 构建一个单点登录(Single Sign-On,简称SSO)的URL */
function getSSOUrl(callbackUrl: string): string {
const params = new URLSearchParams({
sso: btoa(new URLSearchParams({ return_sso_url: callbackUrl }).toString()),
});
const url = new URL(NeosConfig.accountUrl);
url.search = params.toString();
export default Start; return url.toString();
}
...@@ -20,7 +20,7 @@ import { ...@@ -20,7 +20,7 @@ import {
Upload, Upload,
} from "antd"; } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom"; import { useNavigate, useLoaderData } from "react-router-dom";
import rustInit from "rust-src"; import rustInit from "rust-src";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import YGOProDeck from "ygopro-deck-encode"; import YGOProDeck from "ygopro-deck-encode";
...@@ -41,20 +41,22 @@ const { ...@@ -41,20 +41,22 @@ const {
automation: { isAiMode }, automation: { isAiMode },
} = useConfig(); } = useConfig();
const WaitRoom = () => { type Params = {
player?: string;
passWd?: string;
ip?: string;
};
export const Loader = (params: Params) => params;
export const Component = () => {
const snapJoin = useSnapshot(joinStore); const snapJoin = useSnapshot(joinStore);
const snapChat = useSnapshot(chatStore); const snapChat = useSnapshot(chatStore);
const snapMora = useSnapshot(moraStore); const snapMora = useSnapshot(moraStore);
const snapPlayer = useSnapshot(playerStore); const snapPlayer = useSnapshot(playerStore);
const params = useParams<{
player?: string;
passWd?: string;
ip?: string;
}>();
const [choseDeck, setChoseDeck] = useState<boolean>(false); const [choseDeck, setChoseDeck] = useState<boolean>(false);
const { player, passWd, ip } = params; const { player, passWd, ip } = useLoaderData<Params>();
useEffect(() => { useEffect(() => {
if (ip && player && player.length != 0 && passWd && passWd.length != 0) { if (ip && player && player.length != 0 && passWd && passWd.length != 0) {
...@@ -284,5 +286,4 @@ const WaitRoom = () => { ...@@ -284,5 +286,4 @@ const WaitRoom = () => {
</> </>
); );
}; };
Component.displayName = "WaitRoom";
export default WaitRoom;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment