editor-viewer.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import { useEffect, useRef } from "react";
  2. import { useLockFn } from "ahooks";
  3. import { useRecoilValue } from "recoil";
  4. import { useTranslation } from "react-i18next";
  5. import {
  6. Button,
  7. Dialog,
  8. DialogActions,
  9. DialogContent,
  10. DialogTitle,
  11. } from "@mui/material";
  12. import { atomThemeMode } from "@/services/states";
  13. import { readProfileFile, saveProfileFile } from "@/services/cmds";
  14. import { Notice } from "@/components/base";
  15. import { nanoid } from "nanoid";
  16. import * as monaco from "monaco-editor";
  17. import { editor } from "monaco-editor/esm/vs/editor/editor.api";
  18. import { configureMonacoYaml } from "monaco-yaml";
  19. import { type JSONSchema7 } from "json-schema";
  20. import metaSchema from "meta-json-schema/schemas/meta-json-schema.json";
  21. import mergeSchema from "meta-json-schema/schemas/clash-verge-merge-json-schema.json";
  22. interface Props {
  23. uid: string;
  24. open: boolean;
  25. language: "yaml" | "javascript";
  26. schema?: "clash" | "merge";
  27. onClose: () => void;
  28. onChange?: () => void;
  29. }
  30. // yaml worker
  31. configureMonacoYaml(monaco, {
  32. validate: true,
  33. enableSchemaRequest: true,
  34. schemas: [
  35. {
  36. uri: "http://example.com/meta-json-schema.json",
  37. fileMatch: ["**/*.clash.yaml"],
  38. schema: metaSchema as JSONSchema7,
  39. },
  40. {
  41. uri: "http://example.com/clash-verge-merge-json-schema.json",
  42. fileMatch: ["**/*.merge.yaml"],
  43. schema: mergeSchema as JSONSchema7,
  44. },
  45. ],
  46. });
  47. export const EditorViewer = (props: Props) => {
  48. const { uid, open, language, schema, onClose, onChange } = props;
  49. const { t } = useTranslation();
  50. const editorRef = useRef<any>();
  51. const instanceRef = useRef<editor.IStandaloneCodeEditor | null>(null);
  52. const themeMode = useRecoilValue(atomThemeMode);
  53. useEffect(() => {
  54. if (!open) return;
  55. readProfileFile(uid).then((data) => {
  56. const dom = editorRef.current;
  57. if (!dom) return;
  58. if (instanceRef.current) instanceRef.current.dispose();
  59. const uri = monaco.Uri.parse(`${nanoid()}.${schema}.${language}`);
  60. const model = monaco.editor.createModel(data, language, uri);
  61. instanceRef.current = editor.create(editorRef.current, {
  62. model: model,
  63. language: language,
  64. tabSize: ["yaml", "javascript"].includes(language) ? 2 : 4, // 根据语言类型设置缩进
  65. theme: themeMode === "light" ? "vs" : "vs-dark",
  66. minimap: { enabled: dom.clientWidth >= 1000 }, // 超过一定宽度显示minimap滚动条
  67. mouseWheelZoom: true, // Ctrl+滚轮调节缩放
  68. quickSuggestions: {
  69. strings: true, // 字符串类型的建议
  70. comments: true, // 注释类型的建议
  71. other: true, // 其他类型的建议
  72. },
  73. });
  74. });
  75. return () => {
  76. if (instanceRef.current) {
  77. instanceRef.current.dispose();
  78. instanceRef.current = null;
  79. }
  80. };
  81. }, [open]);
  82. const onSave = useLockFn(async () => {
  83. const value = instanceRef.current?.getValue();
  84. if (value == null) return;
  85. try {
  86. await saveProfileFile(uid, value);
  87. onChange?.();
  88. onClose();
  89. } catch (err: any) {
  90. Notice.error(err.message || err.toString());
  91. }
  92. });
  93. return (
  94. <Dialog open={open} onClose={onClose} maxWidth="xl" fullWidth>
  95. <DialogTitle>{t("Edit File")}</DialogTitle>
  96. <DialogContent
  97. sx={{ width: "94%", height: "100vh", pb: 1, userSelect: "text" }}
  98. >
  99. <div style={{ width: "100%", height: "100%" }} ref={editorRef} />
  100. </DialogContent>
  101. <DialogActions>
  102. <Button onClick={onClose} variant="outlined">
  103. {t("Cancel")}
  104. </Button>
  105. <Button onClick={onSave} variant="contained">
  106. {t("Save")}
  107. </Button>
  108. </DialogActions>
  109. </Dialog>
  110. );
  111. };