11import { useEffect , useState } from "react" ;
22import { useDiagram , useEnums , useLayout } from "../../hooks" ;
33import { toDBML } from "../../utils/exportAs/dbml" ;
4+ import { fromDBML } from "../../utils/importFrom/dbml" ;
45import { Button , Tooltip } from "@douyinfe/semi-ui" ;
56import { IconTemplate } from "@douyinfe/semi-icons" ;
67import { useTranslation } from "react-i18next" ;
78import CodeEditor from "../CodeEditor" ;
89
910export default function DBMLEditor ( ) {
10- const { tables : currentTables , relationships } = useDiagram ( ) ;
1111 const diagram = useDiagram ( ) ;
12- const { enums } = useEnums ( ) ;
12+ const {
13+ tables : currentTables ,
14+ relationships,
15+ setTables,
16+ setRelationships,
17+ database,
18+ } = diagram ;
19+ const { enums, setEnums } = useEnums ( ) ;
1320 const [ value , setValue ] = useState ( ( ) => toDBML ( { ...diagram , enums } ) ) ;
1421 const { setLayout } = useLayout ( ) ;
22+ const { setExternalIssues } = diagram ;
1523 const { t } = useTranslation ( ) ;
1624
1725 const toggleDBMLEditor = ( ) => {
1826 setLayout ( ( prev ) => ( { ...prev , dbmlEditor : ! prev . dbmlEditor } ) ) ;
1927 } ;
2028
2129 useEffect ( ( ) => {
22- setValue ( toDBML ( { tables : currentTables , enums, relationships } ) ) ;
23- } , [ currentTables , enums , relationships ] ) ;
30+ const normalized = toDBML ( {
31+ tables : currentTables ,
32+ enums,
33+ relationships,
34+ database,
35+ } ) ;
36+ setValue ( normalized ) ;
37+ } , [ currentTables , enums , relationships , database ] ) ;
38+
39+ useEffect ( ( ) => {
40+ const currentDbml = toDBML ( {
41+ tables : currentTables ,
42+ enums,
43+ relationships,
44+ database,
45+ } ) ;
46+
47+ if ( value === currentDbml ) return ;
48+
49+ const handle = setTimeout ( ( ) => {
50+ try {
51+ const parsed = fromDBML ( value ) ;
52+ // Preserve coordinates when table names match existing ones
53+ const nameToExisting = new Map (
54+ currentTables . map ( ( t ) => [ t . name , { x : t . x , y : t . y } ] ) ,
55+ ) ;
56+ parsed . tables = parsed . tables . map ( ( t ) => {
57+ const coords = nameToExisting . get ( t . name ) ;
58+ return coords ? { ...t , ...coords } : t ;
59+ } ) ;
60+ setTables ( parsed . tables ) ;
61+ setRelationships ( parsed . relationships ) ;
62+ setEnums ( parsed . enums ) ;
63+ // Clear any previous external issues on success
64+ setExternalIssues ( [ ] ) ;
65+ } catch ( err ) {
66+ const message = err ?. message || String ( err ) ;
67+ // Put parse error into Issues panel instead of toast spam
68+ setExternalIssues ( [ message ] ) ;
69+ setEditorMarkersFromError ( message ) ;
70+ }
71+ } , 700 ) ;
72+
73+ return ( ) => clearTimeout ( handle ) ;
74+ } , [
75+ value ,
76+ currentTables ,
77+ enums ,
78+ relationships ,
79+ database ,
80+ setTables ,
81+ setRelationships ,
82+ setEnums ,
83+ setExternalIssues ,
84+ ] ) ;
85+
86+ // Translate DBML parse error message to Monaco markers
87+ const [ markers , setMarkers ] = useState ( [ ] ) ;
88+ const setEditorMarkersFromError = ( message ) => {
89+ // Try to extract line/column like: "line X, column Y" or "(X:Y)"
90+ const lineColMatch =
91+ / l i n e \s + ( \d + ) \s * , \s * c o l u m n \s * ( \d + ) / i. exec ( message ) ||
92+ / \( ( \d + ) \s * [: | , ] \s * ( \d + ) \) / . exec ( message ) ;
93+
94+ const lineNumber = lineColMatch ? parseInt ( lineColMatch [ 1 ] , 10 ) : 1 ;
95+ const column = lineColMatch ? parseInt ( lineColMatch [ 2 ] , 10 ) : 1 ;
96+
97+ setMarkers ( [
98+ {
99+ startLineNumber : lineNumber ,
100+ startColumn : column ,
101+ endLineNumber : lineNumber ,
102+ endColumn : column + 1 ,
103+ message,
104+ } ,
105+ ] ) ;
106+ } ;
24107
25108 return (
26109 < CodeEditor
@@ -29,8 +112,9 @@ export default function DBMLEditor() {
29112 language = "dbml"
30113 onChange = { setValue }
31114 height = "100%"
115+ markers = { markers }
32116 options = { {
33- readOnly : true ,
117+ readOnly : false ,
34118 minimap : { enabled : false } ,
35119 } }
36120 extraControls = {
0 commit comments