From 7d8846cae3bf682a0b8d344a339dfbb6da7d8945 Mon Sep 17 00:00:00 2001 From: KARM99 Date: Sun, 17 Aug 2025 19:10:46 +0400 Subject: [PATCH] changes by gpt5, most of which I don't understand yet --- src/components/CodeEditor/index.jsx | 37 +++++ src/components/EditorSidePanel/DBMLEditor.jsx | 146 +++++++++++++++++- src/components/EditorSidePanel/Issues.jsx | 52 ++++--- src/context/DiagramContext.jsx | 3 + 4 files changed, 213 insertions(+), 25 deletions(-) diff --git a/src/components/CodeEditor/index.jsx b/src/components/CodeEditor/index.jsx index df34824ee..26409170e 100644 --- a/src/components/CodeEditor/index.jsx +++ b/src/components/CodeEditor/index.jsx @@ -1,4 +1,5 @@ import { Editor } from "@monaco-editor/react"; +import { useEffect, useRef } from "react"; import { useDiagram, useSettings } from "../../hooks"; import { Button, Toast } from "@douyinfe/semi-ui"; import { useTranslation } from "react-i18next"; @@ -9,6 +10,7 @@ import "./styles.css"; export default function CodeEditor({ showCopyButton, extraControls, + markers = [], ...props }) { const { settings } = useSettings(); @@ -24,14 +26,49 @@ export default function CodeEditor({ }); }; + const editorRef = useRef(null); + const monacoRef = useRef(null); + const handleEditorMount = (editor, monaco) => { setUpDBML(monaco, database); setTimeout(() => { editor.getAction("editor.action.formatDocument").run(); }, 300); + + editorRef.current = editor; + monacoRef.current = monaco; + + // Apply existing markers after mount + try { + const model = editor.getModel(); + if (model) { + const normalized = markers.map((m) => ({ + severity: m.severity ?? monaco.MarkerSeverity.Error, + ...m, + })); + monaco.editor.setModelMarkers(model, "dbml", normalized); + } + } catch (err) { + console.warn("Failed to set initial markers", err); + } }; + useEffect(() => { + if (!editorRef.current || !monacoRef.current) return; + try { + const model = editorRef.current.getModel(); + if (!model) return; + const normalized = markers.map((m) => ({ + severity: m.severity ?? monacoRef.current.MarkerSeverity.Error, + ...m, + })); + monacoRef.current.editor.setModelMarkers(model, "dbml", normalized); + } catch (err) { + console.warn("Failed to update markers", err); + } + }, [markers]); + return (
toDBML({ ...diagram, enums })); const { setLayout } = useLayout(); + const { setExternalIssues } = diagram; const { t } = useTranslation(); + // Translate DBML parse errors to issues and Monaco markers + const [markers, setMarkers] = useState([]); + const toggleDBMLEditor = () => { setLayout((prev) => ({ ...prev, dbmlEditor: !prev.dbmlEditor })); }; useEffect(() => { - setValue(toDBML({ tables: currentTables, enums, relationships })); - }, [currentTables, enums, relationships]); + const normalized = toDBML({ + tables: currentTables, + enums, + relationships, + database, + }); + setValue(normalized); + }, [currentTables, enums, relationships, database]); + + useEffect(() => { + const currentDbml = toDBML({ + tables: currentTables, + enums, + relationships, + database, + }); + + if (value === currentDbml) { + // If editor content already matches diagram state, + // ensure any lingering external issues/markers are cleared + if (externalIssues?.length) setExternalIssues([]); + if (markers.length) setMarkers([]); + return; + } + + const handle = setTimeout(() => { + try { + const parsed = fromDBML(value); + // Preserve coordinates when table names match existing ones + const nameToExisting = new Map( + currentTables.map((t) => [t.name, { x: t.x, y: t.y }]), + ); + parsed.tables = parsed.tables.map((t) => { + const coords = nameToExisting.get(t.name); + return coords ? { ...t, ...coords } : t; + }); + setTables(parsed.tables); + setRelationships(parsed.relationships); + setEnums(parsed.enums); + // Clear any previous external issues on success + setExternalIssues([]); + setMarkers([]); + } catch (err) { + const { issues: parsedIssues, markers: parsedMarkers } = + produceDiagnostics(err); + setExternalIssues(parsedIssues); + setMarkers(parsedMarkers); + } + }, 700); + + return () => clearTimeout(handle); + }, [ + value, + currentTables, + enums, + relationships, + database, + setTables, + setRelationships, + setEnums, + setExternalIssues, + externalIssues?.length, + markers.length, + ]); + + const produceDiagnostics = (err) => { + // Prefer diagnostics from @dbml/core if present + if (Array.isArray(err?.diags) && err.diags.length > 0) { + const issues = err.diags.map((d) => { + const ln = d?.location?.start?.line; + const col = d?.location?.start?.column; + const code = d?.code ? ` [${d.code}]` : ""; + if (ln && col) return `line ${ln}, col ${col}: ${d.message}${code}`; + return d.message + code; + }); + + const markers = err.diags.map((d) => { + const start = d?.location?.start || {}; + const end = d?.location?.end || {}; + const startLineNumber = start.line || 1; + const startColumn = start.column || 1; + const endLineNumber = end.line || startLineNumber; + const endColumn = end.column || startColumn + 1; + return { + startLineNumber, + startColumn, + endLineNumber, + endColumn, + message: d.message, + }; + }); + return { issues, markers }; + } + + // Fallbacks + const message = + (typeof err?.message === "string" && err.message) || + (typeof err?.description === "string" && err.description) || + (() => { + try { + return JSON.stringify(err); + } catch { + return String(err); + } + })(); + + // Try to extract line/column from string messages + const m = + /line\s+(\d+)\s*,\s*column\s*(\d+)/i.exec(message) || + /\((\d+)\s*[:|,]\s*(\d+)\)/.exec(message); + const ln = m ? parseInt(m[1], 10) : 1; + const col = m ? parseInt(m[2], 10) : 1; + return { + issues: [message], + markers: [ + { + startLineNumber: ln, + startColumn: col, + endLineNumber: ln, + endColumn: col + 1, + message, + }, + ], + }; + }; return ( { - const findIssues = async () => { - const newIssues = getIssues({ - tables: tables, - relationships: relationships, - types: types, - database: database, - enums: enums, - }); + const newIssues = getIssues({ + tables: tables, + relationships: relationships, + types: types, + database: database, + enums: enums, + }); - if (!arrayIsEqual(newIssues, issues)) { - setIssues(newIssues); - } - }; + const combined = [...externalIssues, ...newIssues]; - findIssues(); - }, [tables, relationships, issues, types, database, enums]); + if (!arrayIsEqual(combined, issues)) { + setIssues(combined); + } + }, [tables, relationships, issues, types, database, enums, externalIssues]); return ( @@ -54,11 +52,25 @@ export default function Issues() {
{t("strict_mode_is_on_no_issues")}
) : issues.length > 0 ? ( <> - {issues.map((e, i) => ( -
- {e} -
- ))} + {issues.map((e, i) => { + const text = + typeof e === "string" + ? e + : typeof e?.message === "string" + ? e.message + : (() => { + try { + return JSON.stringify(e); + } catch { + return String(e); + } + })(); + return ( +
+ {text} +
+ ); + })} ) : (
{t("no_issues")}
diff --git a/src/context/DiagramContext.jsx b/src/context/DiagramContext.jsx index 5e6b54e0a..e7abcdbad 100644 --- a/src/context/DiagramContext.jsx +++ b/src/context/DiagramContext.jsx @@ -12,6 +12,7 @@ export default function DiagramContextProvider({ children }) { const [database, setDatabase] = useState(DB.GENERIC); const [tables, setTables] = useState([]); const [relationships, setRelationships] = useState([]); + const [externalIssues, setExternalIssues] = useState([]); const { transform } = useTransform(); const { setUndoStack, setRedoStack } = useUndoRedo(); const { selectedElement, setSelectedElement } = useSelect(); @@ -244,6 +245,8 @@ export default function DiagramContextProvider({ children }) { setDatabase, tablesCount: tables.length, relationshipsCount: relationships.length, + externalIssues, + setExternalIssues, }} > {children}