mirror of
https://github.com/ReaJason/MemShellParty.git
synced 2026-09-22 07:00:43 +08:00
refactor: use react router
This commit is contained in:
Binary file not shown.
+9
-12
@@ -15,17 +15,16 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@tanstack/router-plugin": "^1.114.32",
|
||||
"@types/node": "^22.13.17",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/react-copy-to-clipboard": "^5.0.7",
|
||||
"@types/react-dom": "^19.1.1",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"rimraf": "^6.0.1",
|
||||
"tailwindcss": "^4.1.1",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.4",
|
||||
"tailwindcss": "^4.1.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.2.5",
|
||||
"vite-bundle-visualizer": "^1.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -56,19 +55,15 @@
|
||||
"@radix-ui/react-toggle": "^1.1.2",
|
||||
"@radix-ui/react-toggle-group": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@tailwindcss/vite": "^4.1.1",
|
||||
"@tanstack/react-query": "^5.71.3",
|
||||
"@tanstack/react-router": "^1.114.32",
|
||||
"@tanstack/router-devtools": "^1.114.32",
|
||||
"@tailwindcss/vite": "^4.1.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"embla-carousel-react": "^8.5.2",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"i18next": "^24.2.3",
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.487.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.1.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-day-picker": "9.6.4",
|
||||
@@ -76,8 +71,10 @@
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-i18next": "^15.4.1",
|
||||
"react-resizable-panels": "^2.1.7",
|
||||
"react-router": "^7.5.0",
|
||||
"react-router-dom": "^7.5.0",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"recharts": "^2.15.1",
|
||||
"recharts": "^2.15.2",
|
||||
"sonner": "^2.0.3",
|
||||
"tailwind-merge": "^3.1.0",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
||||
import { ModeToggle } from "@/components/mode-toggle.tsx";
|
||||
import { ThemeProvider } from "@/components/theme-provider.tsx";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import VersionBadge from "@/components/version-badge";
|
||||
import { GitHubIcon } from "@/icon";
|
||||
import { Outlet, createRootRoute } from "@tanstack/react-router";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Outlet } from "react-router-dom";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
export default function RootLayout() {
|
||||
return (
|
||||
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
||||
<Toaster />
|
||||
@@ -42,4 +39,4 @@ function RootComponent() {
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
+5
-19
@@ -1,37 +1,23 @@
|
||||
import { RouterProvider, createRouter } from "@tanstack/react-router";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { routeTree } from "./routeTree.gen";
|
||||
import "./index.css";
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator.tsx";
|
||||
import { env } from "@/config.ts";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import i18n from "./i18n/i18n";
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const router = createRouter({
|
||||
routeTree,
|
||||
basepath: env.BASE_PATH,
|
||||
defaultPreload: "intent",
|
||||
});
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface Register {
|
||||
router: typeof router;
|
||||
}
|
||||
}
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { router } from "./router";
|
||||
import { QueryProvider } from "./providers/query-client-provider";
|
||||
|
||||
const rootElement = document.getElementById("app") as HTMLElement;
|
||||
|
||||
if (!rootElement.innerHTML) {
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
root.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<QueryProvider>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<RouterProvider router={router} />
|
||||
{env.MODE !== "production" && <TailwindIndicator />}
|
||||
</I18nextProvider>
|
||||
</QueryClientProvider>,
|
||||
</QueryProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,41 +10,56 @@ import {
|
||||
ConfigResponseType,
|
||||
GenerateResponse,
|
||||
GenerateResult,
|
||||
MainConfig,
|
||||
PackerConfig,
|
||||
ServerConfig,
|
||||
ShellToolType,
|
||||
} from "@/types/shell.ts";
|
||||
import { transformToPostData } from "@/utils/transformer.ts";
|
||||
import { customValidation, transformToPostData } from "@/utils/transformer.ts";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { LoaderCircle, WandSparklesIcon } from "lucide-react";
|
||||
import { useState, useTransition } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: IndexComponent,
|
||||
});
|
||||
export default function IndexPage() {
|
||||
const { data: serverConfig } = useQuery<ServerConfig>({
|
||||
queryKey: ["serverConfig"],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${env.API_URL}/config/servers`);
|
||||
return await response.json();
|
||||
},
|
||||
});
|
||||
|
||||
function IndexComponent() {
|
||||
const { data } = useQuery<ConfigResponseType>({
|
||||
queryKey: ["config"],
|
||||
const { data: mainConfig } = useQuery<MainConfig>({
|
||||
queryKey: ["mainConfig"],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${env.API_URL}/config`);
|
||||
return await response.json();
|
||||
},
|
||||
});
|
||||
|
||||
const { data: packerConfig } = useQuery<PackerConfig>({
|
||||
queryKey: ["packerConfig"],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${env.API_URL}/config/packers`);
|
||||
return await response.json();
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<FormSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
server: "",
|
||||
server: "Tomcat",
|
||||
targetJdkVersion: "50",
|
||||
debug: false,
|
||||
bypassJavaModule: false,
|
||||
shellClassName: "",
|
||||
shellTool: "Godzilla",
|
||||
shellType: "",
|
||||
shellType: "Listener",
|
||||
urlPattern: "/*",
|
||||
godzillaPass: "pass",
|
||||
godzillaKey: "key",
|
||||
@@ -64,40 +79,11 @@ function IndexComponent() {
|
||||
const [packMethod, setPackMethod] = useState<string>("");
|
||||
const [isActionPending, startTransition] = useTransition();
|
||||
|
||||
function customValidation(values: FormSchema) {
|
||||
if (values.shellType.endsWith("Servlet") && (values.urlPattern === "/*" || !values.urlPattern)) {
|
||||
toast.warning(t("tips.servletUrlPattern"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.shellType.endsWith("ControllerHandler") && (values.urlPattern === "/*" || !values.urlPattern)) {
|
||||
toast.warning(t("tips.controllerUrlPattern"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
(values.shellType === "HandlerMethod" || values.shellType === "HandlerFunction") &&
|
||||
(values.urlPattern === "/*" || !values.urlPattern)
|
||||
) {
|
||||
toast.warning(t("tips.handlerUrlPattern"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.shellTool === ShellToolType.Custom && !values.shellClassBase64) {
|
||||
toast.warning(t("tips.customShellClass"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function onSubmit(values: FormSchema) {
|
||||
const onSubmit = async (data: FormSchema) => {
|
||||
startTransition(async () => {
|
||||
if (!customValidation(values)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const postData = transformToPostData(values);
|
||||
try {
|
||||
customValidation(t, data);
|
||||
const postData = transformToPostData(data);
|
||||
const response = await fetch(`${env.API_URL}/generate`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -105,31 +91,30 @@ function IndexComponent() {
|
||||
},
|
||||
body: JSON.stringify(postData),
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
if (response.ok) {
|
||||
const json: GenerateResponse = await response.json();
|
||||
setPackResult(json.packResult);
|
||||
setAllPackResults(json.allPackResults);
|
||||
setGenerateResult(json.generateResult);
|
||||
setPackMethod(values.packingMethod);
|
||||
toast.success(t("success.generated"));
|
||||
} else {
|
||||
|
||||
if (!response.ok) {
|
||||
const json: APIErrorResponse = await response.json();
|
||||
toast.error(t("errors.generationFailed", { error: json.error }));
|
||||
}
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
toast.error(t("errors.generationFailed", { error: error.message }));
|
||||
|
||||
const result = (await response.json()) as GenerateResponse;
|
||||
setGenerateResult(result.generateResult);
|
||||
setPackResult(result.packResult);
|
||||
setAllPackResults(result.allPackResults);
|
||||
setPackMethod(data.packingMethod);
|
||||
toast.success(t("success.generated"));
|
||||
} catch (error) {
|
||||
toast.error(t("errors.generationFailed", { error: (error as Error).message }));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col xl:flex-row gap-4 p-4">
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col xl:flex-row gap-4 p-4">
|
||||
<div className="w-full xl:w-1/2 space-y-4">
|
||||
<MainConfigCard servers={data?.servers} mainConfig={data?.core} form={form} />
|
||||
<PackageConfigCard packerConfig={data?.packers} form={form} />
|
||||
<MainConfigCard servers={serverConfig} mainConfig={mainConfig} form={form} />
|
||||
<PackageConfigCard packerConfig={packerConfig} form={form} />
|
||||
<Button className="w-full" type="submit" disabled={isActionPending}>
|
||||
{isActionPending ? <LoaderCircle className="animate-spin" /> : <WandSparklesIcon />}
|
||||
{t("buttons.generate")}
|
||||
@@ -146,4 +131,4 @@ function IndexComponent() {
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
retry: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from "./routes/__root";
|
||||
import { Route as IndexImport } from "./routes/index";
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
id: "/",
|
||||
path: "/",
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any);
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface FileRoutesByPath {
|
||||
"/": {
|
||||
id: "/";
|
||||
path: "/";
|
||||
fullPath: "/";
|
||||
preLoaderRoute: typeof IndexImport;
|
||||
parentRoute: typeof rootRoute;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
"/": typeof IndexRoute;
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
"/": typeof IndexRoute;
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute;
|
||||
"/": typeof IndexRoute;
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath;
|
||||
fullPaths: "/";
|
||||
fileRoutesByTo: FileRoutesByTo;
|
||||
to: "/";
|
||||
id: "__root__" | "/";
|
||||
fileRoutesById: FileRoutesById;
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute;
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
};
|
||||
|
||||
export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileTypes<FileRouteTypes>();
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
||||
@@ -0,0 +1,22 @@
|
||||
import { createBrowserRouter } from "react-router-dom";
|
||||
import { env } from "@/config";
|
||||
import RootLayout from "@/components/layouts/root-layout";
|
||||
import IndexPage from "@/pages";
|
||||
|
||||
export const router = createBrowserRouter(
|
||||
[
|
||||
{
|
||||
path: "/",
|
||||
element: <RootLayout />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <IndexPage />,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{
|
||||
basename: env.BASE_PATH,
|
||||
}
|
||||
);
|
||||
@@ -1,5 +1,27 @@
|
||||
import { FormSchema } from "@/types/schema.ts";
|
||||
import { InjectorConfig, ShellConfig, ShellToolConfig } from "@/types/shell.ts";
|
||||
import { InjectorConfig, ShellConfig, ShellToolConfig, ShellToolType } from "@/types/shell.ts";
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
export function customValidation(t: TFunction<"translation", undefined>, values: FormSchema) {
|
||||
if (values.shellType.endsWith("Servlet") && (values.urlPattern === "/*" || !values.urlPattern)) {
|
||||
throw new Error(t("tips.servletUrlPattern"));
|
||||
}
|
||||
|
||||
if (values.shellType.endsWith("ControllerHandler") && (values.urlPattern === "/*" || !values.urlPattern)) {
|
||||
throw new Error(t("tips.controllerUrlPattern"));
|
||||
}
|
||||
|
||||
if (
|
||||
(values.shellType === "HandlerMethod" || values.shellType === "HandlerFunction") &&
|
||||
(values.urlPattern === "/*" || !values.urlPattern)
|
||||
) {
|
||||
throw new Error(t("tips.handlerUrlPattern"));
|
||||
}
|
||||
|
||||
if (values.shellTool === ShellToolType.Custom && !values.shellClassBase64) {
|
||||
throw new Error(t("tips.customShellClass"));
|
||||
}
|
||||
}
|
||||
|
||||
export function transformToPostData(formValue: FormSchema) {
|
||||
const shellConfig: ShellConfig = {
|
||||
|
||||
+1
-2
@@ -1,12 +1,11 @@
|
||||
import path from "node:path";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [TanStackRouterVite({ autoCodeSplitting: true }), react(), tailwindcss()],
|
||||
plugins: [react(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
|
||||
Reference in New Issue
Block a user