Switch to monaco-editor from codemirror (#403)

* switch to monaco-editor

* fix styles
This commit is contained in:
1ilit
2025-04-12 20:50:47 +04:00
committed by GitHub
parent 00e97653cb
commit adf647483c
8 changed files with 259 additions and 325 deletions

View File

@@ -0,0 +1,47 @@
import { Editor } from "@monaco-editor/react";
import { useDiagram, useSettings } from "../../hooks";
import { Button, Toast } from "@douyinfe/semi-ui";
import { useTranslation } from "react-i18next";
import { IconCopy } from "@douyinfe/semi-icons";
import { setUpDBML } from "./setUpDBML";
import "./styles.css";
export default function CodeEditor({ showCopyButton, ...props }) {
const { settings } = useSettings();
const { database } = useDiagram();
const { t } = useTranslation();
const copyCode = () => {
navigator.clipboard
.writeText(props.value)
.then(() => Toast.success(t("copied_to_clipboard")))
.catch((e) => {
console.log(e);
});
};
const handleEditorMount = (editor, monaco) => {
setUpDBML(monaco, database);
setTimeout(() => {
editor.getAction("editor.action.formatDocument").run();
}, 300);
};
return (
<div className="relative">
<Editor
{...props}
theme={settings.mode === "light" ? "vs" : "vs-dark"}
onMount={handleEditorMount}
/>
{showCopyButton && (
<Button
className="absolute right-6 bottom-2 z-10"
icon={<IconCopy />}
onClick={copyCode}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,152 @@
import { dbToTypes } from "../../data/datatypes";
export function setUpDBML(monaco, database) {
monaco.languages.register({ id: "dbml" });
console.log(Object.keys(dbToTypes[database]));
monaco.languages.setMonarchTokensProvider("dbml", {
defaultToken: "",
tokenPostfix: ".dbml",
ignoreCase: true,
keywords: [
"table",
"tablegroup",
"project",
"enum",
"ref",
"as",
"indexes",
"index",
"note",
"delete",
"update",
"pk",
"increment",
"not",
"null",
"unique",
"default",
],
typeKeywords: Object.keys(dbToTypes[database]),
operators: [
"=",
">",
"<",
"!",
"~",
"?",
":",
"==",
"<=",
">=",
"!=",
"&&",
"||",
"++",
"--",
"+",
"-",
"*",
"/",
"&",
"|",
"^",
],
symbols: /[=><!~?:&|+\-*/^%]+/,
escapes:
/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,
tokenizer: {
root: [
[
/[a-zA-Z_$][\w$]*/,
{
cases: {
"@keywords": "keyword",
"@typeKeywords": "type",
"@default": "identifier",
},
},
],
{ include: "@whitespace" },
[/[{}()[\]]/, "@brackets"],
[/[<>](?!@symbols)/, "@brackets"],
[
/@symbols/,
{
cases: {
"@operators": "operator",
"@default": "",
},
},
],
[/\d*\.\d+([eE][-+]?\d+)?/, "number.float"],
[/0[xX][0-9a-fA-F]+/, "number.hex"],
[/\d+/, "number"],
[/[;,.]/, "delimiter"],
[/"([^"\\]|\\.)*$/, "string.invalid"],
[/'([^'\\]|\\.)*$/, "string.invalid"],
[/"/, { token: "string.quote", bracket: "@open", next: "@dqstring" }],
[/'/, { token: "string.quote", bracket: "@open", next: "@sqstring" }],
],
dqstring: [
[/[^\\"]+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/"/, { token: "string.quote", bracket: "@close", next: "@pop" }],
],
sqstring: [
[/[^\\']+/, "string"],
[/@escapes/, "string.escape"],
[/\\./, "string.escape.invalid"],
[/'/, { token: "string.quote", bracket: "@close", next: "@pop" }],
],
comment: [
[/[^\\/*]+/, "comment"],
[/\/\*/, "comment", "@push"],
["\\*/", "comment", "@pop"],
[/[\\/*]/, "comment"],
],
whitespace: [
[/[ \t\r\n]+/, "white"],
[/\/\*/, "comment", "@comment"],
[/\/\/.*$/, "comment"],
],
},
});
monaco.languages.setLanguageConfiguration("dbml", {
comments: {
lineComment: "//",
blockComment: ["/*", "*/"],
},
brackets: [
["{", "}"],
["[", "]"],
["(", ")"],
],
autoClosingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
],
surroundingPairs: [
{ open: "{", close: "}" },
{ open: "[", close: "]" },
{ open: "(", close: ")" },
{ open: '"', close: '"' },
{ open: "'", close: "'" },
],
});
}

View File

@@ -0,0 +1,4 @@
.monaco-editor,
.monaco-editor > .overflow-guard {
border-radius: 6px;
}

View File

@@ -1,52 +0,0 @@
import { useState } from "react";
import { sql } from "@codemirror/lang-sql";
import { json } from "@codemirror/lang-json";
import { vscodeDark } from "@uiw/codemirror-theme-vscode";
import { githubLight } from "@uiw/codemirror-theme-github";
import { useSettings } from "../../../hooks";
import { useTranslation } from "react-i18next";
import CodeMirror from "@uiw/react-codemirror";
const languageExtension = {
sql: [sql()],
json: [json()],
};
export default function Code({ value, language }) {
const { t } = useTranslation();
const { settings } = useSettings();
const [copied, setCopied] = useState(false);
const copyCode = () => {
navigator.clipboard
.writeText(value)
.then(() => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
})
.catch((e) => {
console.log(e);
});
};
return (
<div className="relative">
<CodeMirror
value={value}
height="360px"
extensions={languageExtension[language]}
editable={false}
theme={settings.mode === "dark" ? vscodeDark : githubLight}
/>
<button
onClick={copyCode}
className={`absolute right-4 top-2 px-2 py-1 rounded-sm ${settings.mode === "dark" ? "bg-zinc-700" : "bg-zinc-200"}`}
>
<i className={`bi bi-clipboard${copied ? "-check" : ""} me-2`} />
{t("copy")}
</button>
</div>
);
}

View File

@@ -34,12 +34,19 @@ import ImportSource from "./ImportSource";
import SetTableWidth from "./SetTableWidth";
import Language from "./Language";
import Share from "./Share";
import Code from "./Code";
import CodeEditor from "../../CodeEditor";
import { useTranslation } from "react-i18next";
import { importSQL } from "../../../utils/importSQL";
import { databases } from "../../../data/databases";
import { isRtl } from "../../../i18n/utils/rtl";
const extensionToLanguage = {
md: "markdown",
sql: "sql",
dbml: "dbml",
json: "json",
};
export default function Modal({
modal,
setModal,
@@ -52,7 +59,8 @@ export default function Modal({
importFrom,
}) {
const { t, i18n } = useTranslation();
const { tables, setTables, setRelationships, database, setDatabase } = useDiagram();
const { tables, setTables, setRelationships, database, setDatabase } =
useDiagram();
const { setNotes } = useNotes();
const { setAreas } = useAreas();
const { setTypes } = useTypes();
@@ -307,7 +315,13 @@ export default function Modal({
{modal === MODAL.IMG ? (
<Image src={exportData.data} alt="Diagram" height={280} />
) : (
<Code value={exportData.data} language={exportData.extension} />
<CodeEditor
height={360}
value={exportData.data}
language={extensionToLanguage[exportData.extension]}
options={{ readOnly: true }}
showCopyButton={true}
/>
)}
<div className="text-sm font-semibold mt-2">{t("filename")}:</div>
<Input

View File

@@ -34,6 +34,7 @@ export const getModalWidth = (modal) => {
switch (modal) {
case MODAL.LANGUAGE:
case MODAL.OPEN:
case MODAL.CODE:
case MODAL.NEW:
return 740;
default: