feat: Clash配置、Merge配置提供JSON Schema语法支持、[连接]界面调整 (#887)

This commit is contained in:
dongchengjie 2024-04-17 21:19:37 +08:00 committed by GitHub
parent d0e8f450fd
commit 402299e9c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 67 additions and 20 deletions

View File

@ -32,7 +32,8 @@
"dayjs": "1.11.5", "dayjs": "1.11.5",
"i18next": "^23.7.16", "i18next": "^23.7.16",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"monaco-editor": "^0.34.1", "monaco-editor": "^0.47.0",
"monaco-yaml": "^5.1.1",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@ -3,8 +3,8 @@ import { forwardRef, useImperativeHandle, useState } from "react";
import { useLockFn } from "ahooks"; import { useLockFn } from "ahooks";
import { Box, Button, Snackbar } from "@mui/material"; import { Box, Button, Snackbar } from "@mui/material";
import { deleteConnection } from "@/services/api"; import { deleteConnection } from "@/services/api";
import { truncateStr } from "@/utils/truncate-str";
import parseTraffic from "@/utils/parse-traffic"; import parseTraffic from "@/utils/parse-traffic";
import { t } from "i18next";
export interface ConnectionDetailRef { export interface ConnectionDetailRef {
open: (detail: IConnectionsItem) => void; open: (detail: IConnectionsItem) => void;
@ -69,7 +69,9 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
{ label: "Rule", value: rule }, { label: "Rule", value: rule },
{ {
label: "Process", label: "Process",
value: truncateStr(metadata.process || metadata.processPath), value: `${metadata.process}${
metadata.processPath ? `(${metadata.processPath})` : ""
}`,
}, },
{ label: "Time", value: dayjs(data.start).fromNow() }, { label: "Time", value: dayjs(data.start).fromNow() },
{ label: "Source", value: `${metadata.sourceIP}:${metadata.sourcePort}` }, { label: "Source", value: `${metadata.sourceIP}:${metadata.sourcePort}` },
@ -96,7 +98,7 @@ const InnerConnectionDetail = ({ data, onClose }: InnerProps) => {
onClose?.(); onClose?.();
}} }}
> >
Close {t("Close")}
</Button> </Button>
</Box> </Box>
</Box> </Box>

View File

@ -12,23 +12,39 @@ import {
import { atomThemeMode } from "@/services/states"; import { atomThemeMode } from "@/services/states";
import { readProfileFile, saveProfileFile } from "@/services/cmds"; import { readProfileFile, saveProfileFile } from "@/services/cmds";
import { Notice } from "@/components/base"; import { Notice } from "@/components/base";
import { nanoid } from "nanoid";
import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js"; import * as monaco from "monaco-editor";
import "monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution.js";
import "monaco-editor/esm/vs/editor/contrib/folding/browser/folding.js";
import { editor } from "monaco-editor/esm/vs/editor/editor.api"; import { editor } from "monaco-editor/esm/vs/editor/editor.api";
import { configureMonacoYaml } from "monaco-yaml";
interface Props { interface Props {
uid: string; uid: string;
open: boolean; open: boolean;
mode: "yaml" | "javascript"; language: "yaml" | "javascript";
schema?: "clash" | "merge";
onClose: () => void; onClose: () => void;
onChange?: () => void; onChange?: () => void;
} }
export const EditorViewer = (props: Props) => { // yaml worker
const { uid, open, mode, onClose, onChange } = props; configureMonacoYaml(monaco, {
validate: true,
enableSchemaRequest: true,
schemas: [
{
uri: "https://fastly.jsdelivr.net/gh/dongchengjie/meta-json-schema@main/schemas/meta-json-schema.json",
fileMatch: ["**/*.clash.yaml"],
},
{
uri: "https://fastly.jsdelivr.net/gh/dongchengjie/meta-json-schema@main/schemas/clash-verge-merge-json-schema.json",
fileMatch: ["**/*.merge.yaml"],
},
],
});
export const EditorViewer = (props: Props) => {
const { uid, open, language, schema, onClose, onChange } = props;
const { t } = useTranslation(); const { t } = useTranslation();
const editorRef = useRef<any>(); const editorRef = useRef<any>();
const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null); const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
@ -41,13 +57,21 @@ export const EditorViewer = (props: Props) => {
const dom = editorRef.current; const dom = editorRef.current;
if (!dom) return; if (!dom) return;
if (instanceRef.current) instanceRef.current.dispose(); if (instanceRef.current) instanceRef.current.dispose();
const uri = monaco.Uri.parse(`${nanoid()}.${schema}.${language}`);
const model = monaco.editor.createModel(data, language, uri);
instanceRef.current = editor.create(editorRef.current, { instanceRef.current = editor.create(editorRef.current, {
value: data, model: model,
language: mode, language: language,
theme: themeMode === "light" ? "vs" : "vs-dark", theme: themeMode === "light" ? "vs" : "vs-dark",
minimap: { enabled: false }, minimap: { enabled: dom.clientWidth >= 1000 }, // 超过一定宽度显示minimap滚动条
quickSuggestions: {
strings: true, // 字符串类型的建议
comments: true, // 注释类型的建议
other: true, // 其他类型的建议
},
}); });
}); });
@ -77,8 +101,10 @@ export const EditorViewer = (props: Props) => {
<Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth> <Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
<DialogTitle>{t("Edit File")}</DialogTitle> <DialogTitle>{t("Edit File")}</DialogTitle>
<DialogContent sx={{ width: "95%", pb: 1, userSelect: "text" }}> <DialogContent
<div style={{ width: "100%", height: "500px" }} ref={editorRef} /> sx={{ width: "95%", height: "100vh", pb: 1, userSelect: "text" }}
>
<div style={{ width: "100%", height: "100%" }} ref={editorRef} />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>

View File

@ -386,7 +386,8 @@ export const ProfileItem = (props: Props) => {
<EditorViewer <EditorViewer
uid={uid} uid={uid}
open={fileOpen} open={fileOpen}
mode="yaml" language="yaml"
schema="clash"
onClose={() => setFileOpen(false)} onClose={() => setFileOpen(false)}
/> />
<ConfirmViewer <ConfirmViewer

View File

@ -235,7 +235,8 @@ export const ProfileMore = (props: Props) => {
<EditorViewer <EditorViewer
uid={uid} uid={uid}
open={fileOpen} open={fileOpen}
mode={type === "merge" ? "yaml" : "javascript"} language={type === "merge" ? "yaml" : "javascript"}
schema={type === "merge" ? "merge" : undefined}
onClose={() => setFileOpen(false)} onClose={() => setFileOpen(false)}
/> />
<ConfirmViewer <ConfirmViewer

View File

@ -29,6 +29,7 @@
"New": "New", "New": "New",
"Create Profile": "Create Profile", "Create Profile": "Create Profile",
"Choose File": "Choose File", "Choose File": "Choose File",
"Close": "Close",
"Close All": "Close All", "Close All": "Close All",
"Home": "Home", "Home": "Home",
"Select": "Select", "Select": "Select",

View File

@ -29,6 +29,7 @@
"New": "Новый", "New": "Новый",
"Create Profile": "Создать профиль", "Create Profile": "Создать профиль",
"Choose File": "Выбрать файл", "Choose File": "Выбрать файл",
"Close": "Закрыть",
"Close All": "Закрыть всё", "Close All": "Закрыть всё",
"Home": "Главная", "Home": "Главная",
"Select": "Выбрать", "Select": "Выбрать",

View File

@ -29,6 +29,7 @@
"New": "新建", "New": "新建",
"Create Profile": "新建订阅", "Create Profile": "新建订阅",
"Choose File": "选择文件", "Choose File": "选择文件",
"Close": "关闭",
"Close All": "关闭全部", "Close All": "关闭全部",
"Home": "首页", "Home": "首页",
"Select": "使用", "Select": "使用",

View File

@ -38,7 +38,12 @@ const ConnectionsPage = () => {
const isTableLayout = setting.layout === "table"; const isTableLayout = setting.layout === "table";
const orderOpts: Record<string, OrderFunc> = { const orderOpts: Record<string, OrderFunc> = {
Default: (list) => list, Default: (list) =>
list.sort(
(a, b) =>
new Date(b.start || "0").getTime()! -
new Date(a.start || "0").getTime()!
),
"Upload Speed": (list) => list.sort((a, b) => b.curUpload! - a.curUpload!), "Upload Speed": (list) => list.sort((a, b) => b.curUpload! - a.curUpload!),
"Download Speed": (list) => "Download Speed": (list) =>
list.sort((a, b) => b.curDownload! - a.curDownload!), list.sort((a, b) => b.curDownload! - a.curDownload!),

View File

@ -2,7 +2,7 @@ import { defineConfig } from "vite";
import path from "path"; import path from "path";
import svgr from "vite-plugin-svgr"; import svgr from "vite-plugin-svgr";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import monaco from "vite-plugin-monaco-editor"; import monacoEditor from "vite-plugin-monaco-editor";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@ -11,7 +11,15 @@ export default defineConfig({
plugins: [ plugins: [
svgr(), svgr(),
react(), react(),
monaco({ languageWorkers: ["editorWorkerService", "typescript"] }), monacoEditor({
languageWorkers: ["editorWorkerService", "typescript"],
customWorkers: [
{
label: "yaml",
entry: "monaco-yaml/yaml.worker",
},
],
}),
], ],
build: { build: {
outDir: "../dist", outDir: "../dist",