Skip to content

Commit bce2f4e

Browse files
committed
feat: add runtime config diff dialog
1 parent c26e6d3 commit bce2f4e

11 files changed

Lines changed: 437 additions & 9 deletions

File tree

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
1919
"@typescript-eslint/no-unused-vars": "warn",
2020
"@typescript-eslint/no-explicit-any": "warn",
21+
"react/jsx-no-undef": "off",
2122
"react/react-in-jsx-scope": "off",
2223
"@typescript-eslint/no-namespace": "off",
2324
"react-compiler/react-compiler": "error",

frontend/interface/service/tauri.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,7 @@ export const save_window_size_state = async () => {
203203
export const collect_envs = async () => {
204204
return await invoke<EnvInfos>("collect_envs");
205205
};
206+
207+
export const getRuntimeYaml = async () => {
208+
return await invoke<string>("get_runtime_yaml");
209+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/* eslint-disable */
2+
/* prettier-ignore */
3+
// @ts-nocheck
4+
// noinspection JSUnusedGlobalSymbols
5+
// Generated by unplugin-auto-import
6+
export {}
7+
declare global {
8+
const IconMdiTextBoxCheckOutline: (typeof import("~icons/mdi/text-box-check-outline.jsx"))["default"];
9+
}

frontend/nyanpasu/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"devDependencies": {
4949
"@emotion/babel-plugin": "11.12.0",
5050
"@emotion/react": "11.13.0",
51+
"@iconify/json": "^2.2.233",
5152
"@types/react": "18.3.3",
5253
"@types/react-dom": "18.3.0",
5354
"@vitejs/plugin-react": "4.3.1",
@@ -56,6 +57,8 @@
5657
"sass": "1.77.8",
5758
"shiki": "1.12.1",
5859
"tailwindcss-textshadow": "2.1.3",
60+
"unplugin-auto-import": "^0.18.2",
61+
"unplugin-icons": "^0.19.1",
5962
"vite": "5.3.5",
6063
"vite-plugin-monaco-editor": "1.1.3",
6164
"vite-plugin-sass-dts": "1.3.25",
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { useAtomValue } from "jotai";
2+
import { useEffect, useRef } from "react";
3+
import { useTranslation } from "react-i18next";
4+
import useSWR from "swr";
5+
import { monaco } from "@/services/monaco";
6+
import { themeMode } from "@/store";
7+
import { getRuntimeYaml, useClash } from "@nyanpasu/interface";
8+
import { BaseDialog } from "@nyanpasu/ui";
9+
10+
export type RuntimeConfigDiffDialogProps = {
11+
open: boolean;
12+
onClose: () => void;
13+
};
14+
15+
export default function RuntimeConfigDiffDialog({
16+
open,
17+
onClose,
18+
}: RuntimeConfigDiffDialogProps) {
19+
const { t } = useTranslation();
20+
const { getProfiles, getProfileFile } = useClash();
21+
const currentProfileUid = getProfiles.data?.current;
22+
const mode = useAtomValue(themeMode);
23+
const {
24+
data: runtimeConfig,
25+
isLoading: isLoadingRuntimeConfig,
26+
error: errorRuntimeConfig,
27+
} = useSWR(open ? "/getRuntimeConfigYaml" : null, getRuntimeYaml);
28+
const {
29+
data: profileConfig,
30+
isLoading: isLoadingProfileConfig,
31+
error: errorProfileConfig,
32+
} = useSWR(
33+
open ? `/readProfileFile?uid=${currentProfileUid}` : null,
34+
async (key) => {
35+
const url = new URL(key, window.location.origin);
36+
return await getProfileFile(url.searchParams.get("uid")!);
37+
},
38+
{
39+
revalidateOnFocus: true,
40+
refreshInterval: 0,
41+
},
42+
);
43+
const monacoRef = useRef<typeof monaco | null>(null);
44+
const editorRef = useRef<monaco.editor.IStandaloneDiffEditor | null>(null);
45+
const domRef = useRef<HTMLDivElement>(null);
46+
useEffect(() => {
47+
if (open && runtimeConfig && profileConfig) {
48+
console.log("init monaco");
49+
const run = async () => {
50+
const { monaco } = await import("@/services/monaco");
51+
monacoRef.current = monaco;
52+
console.log(domRef.current);
53+
editorRef.current = monaco.editor.createDiffEditor(domRef.current!, {
54+
theme: mode === "light" ? "vs" : "vs-dark",
55+
minimap: { enabled: false },
56+
automaticLayout: true,
57+
readOnly: true,
58+
});
59+
editorRef.current.setModel({
60+
original: monaco.editor.createModel(profileConfig, "yaml"),
61+
modified: monaco.editor.createModel(runtimeConfig, "yaml"),
62+
});
63+
};
64+
run().catch(console.error);
65+
}
66+
return () => {
67+
monacoRef.current = null;
68+
editorRef.current?.dispose();
69+
};
70+
}, [mode, open, runtimeConfig, profileConfig]);
71+
console.log(currentProfileUid);
72+
if (!currentProfileUid) {
73+
return null;
74+
}
75+
76+
return (
77+
<BaseDialog title={t("Runtime Config")} open={open} onClose={onClose}>
78+
<div className="xs:w-[95vw] h-full w-[80vw] px-4">
79+
<div ref={domRef} className="h-[75vh] w-full" />
80+
</div>
81+
</BaseDialog>
82+
);
83+
}

frontend/nyanpasu/src/components/setting/setting-clash-core.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import { useTranslation } from "react-i18next";
55
import { message } from "@/utils/notification";
66
import LoadingButton from "@mui/lab/LoadingButton";
77
import { Box, List, ListItem } from "@mui/material";
8-
import { ClashCore, useClash, useNyanpasu } from "@nyanpasu/interface";
8+
import {
9+
ClashCore,
10+
useClash,
11+
useNyanpasu,
12+
VergeConfig,
13+
} from "@nyanpasu/interface";
914
import { BaseCard, ExpandMore } from "@nyanpasu/ui";
1015
import { ClashCoreItem } from "./modules/clash-core";
1116

@@ -107,7 +112,7 @@ export const SettingClashCore = () => {
107112
});
108113

109114
const handleUpdateCore = useLockFn(
110-
async (core: Required<IVergeConfig>["clash_core"]) => {
115+
async (core: Required<VergeConfig>["clash_core"]) => {
111116
try {
112117
loading.mask = true;
113118

frontend/nyanpasu/src/pages/_app.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,14 @@ export default function App() {
3232
const isDrawer = useMemo(() => Boolean(column === 1), [column]);
3333

3434
return (
35-
<SWRConfig value={{ errorRetryCount: 3 }}>
35+
<SWRConfig
36+
value={{
37+
errorRetryCount: 5,
38+
revalidateOnMount: true,
39+
revalidateOnFocus: true,
40+
refreshInterval: 5000,
41+
}}
42+
>
3643
<CssVarsProvider theme={theme}>
3744
<ThemeModeProvider />
3845
<LogProvider />

frontend/nyanpasu/src/pages/profiles.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useAtom } from "jotai";
2+
import { useState } from "react";
23
import { useTranslation } from "react-i18next";
34
import {
45
atomChainsSelected,
@@ -8,10 +9,11 @@ import NewProfileButton from "@/components/profiles/new-profile-button";
89
import ProfileItem from "@/components/profiles/profile-item";
910
import ProfileSide from "@/components/profiles/profile-side";
1011
import { QuickImport } from "@/components/profiles/quick-import";
12+
import RuntimeConfigDiffDialog from "@/components/profiles/runtime-config-diff-dialog";
1113
import { filterProfiles } from "@/components/profiles/utils";
1214
import { Public } from "@mui/icons-material";
1315
import Masonry from "@mui/lab/Masonry";
14-
import { Button } from "@mui/material";
16+
import { Button, IconButton } from "@mui/material";
1517
import { Profile, useClash } from "@nyanpasu/interface";
1618
import { SidePage } from "@nyanpasu/ui";
1719

@@ -46,12 +48,25 @@ export const ProfilePage = () => {
4648
setGlobalChain(false);
4749
};
4850

51+
const [runtimeConfigViewerOpen, setRuntimeConfigViewerOpen] = useState(false);
52+
console.log(runtimeConfigViewerOpen);
4953
return (
5054
<SidePage
5155
title={t("Profiles")}
5256
flexReverse
5357
header={
5458
<div>
59+
<RuntimeConfigDiffDialog
60+
open={runtimeConfigViewerOpen}
61+
onClose={() => setRuntimeConfigViewerOpen(false)}
62+
/>
63+
<IconButton
64+
onClick={() => {
65+
setRuntimeConfigViewerOpen(true);
66+
}}
67+
>
68+
<IconMdiTextBoxCheckOutline />
69+
</IconButton>
5570
<Button
5671
size="small"
5772
variant={globalChain ? "contained" : "outlined"}

frontend/nyanpasu/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@
2222
},
2323
"jsxImportSource": "@emotion/react",
2424
},
25-
"include": ["./src"],
25+
"include": ["./src", "./auto-imports.d.ts"],
2626
"references": [{ "path": "../interface/tsconfig.json" }],
2727
}

frontend/nyanpasu/vite.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import path from "node:path";
2+
import AutoImport from "unplugin-auto-import/vite";
3+
import IconsResolver from "unplugin-icons/resolver";
4+
import Icons from "unplugin-icons/vite";
25
import { defineConfig } from "vite";
36
import monaco from "vite-plugin-monaco-editor";
47
import sassDts from "vite-plugin-sass-dts";
@@ -50,6 +53,17 @@ export default defineConfig(({ command }) => {
5053
// plugins: ["@emotion/babel-plugin"],
5154
// },
5255
}),
56+
AutoImport({
57+
resolvers: [
58+
IconsResolver({
59+
prefix: "Icon",
60+
extension: "jsx",
61+
}),
62+
],
63+
}),
64+
Icons({
65+
compiler: "jsx", // or 'solid'
66+
}),
5367
generouted(),
5468
sassDts({ esmExport: true }),
5569
monaco({ languageWorkers: ["editorWorkerService", "typescript"] }),

0 commit comments

Comments
 (0)