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+ externalIssues,
19+ } = diagram ;
20+ const { enums, setEnums } = useEnums ( ) ;
1321 const [ value , setValue ] = useState ( ( ) => toDBML ( { ...diagram , enums } ) ) ;
1422 const { setLayout } = useLayout ( ) ;
23+ const { setExternalIssues } = diagram ;
1524 const { t } = useTranslation ( ) ;
1625
26+ // Translate DBML parse errors to issues and Monaco markers
27+ const [ markers , setMarkers ] = useState ( [ ] ) ;
28+
1729 const toggleDBMLEditor = ( ) => {
1830 setLayout ( ( prev ) => ( { ...prev , dbmlEditor : ! prev . dbmlEditor } ) ) ;
1931 } ;
2032
2133 useEffect ( ( ) => {
22- setValue ( toDBML ( { tables : currentTables , enums, relationships } ) ) ;
23- } , [ currentTables , enums , relationships ] ) ;
34+ const normalized = toDBML ( {
35+ tables : currentTables ,
36+ enums,
37+ relationships,
38+ database,
39+ } ) ;
40+ setValue ( normalized ) ;
41+ } , [ currentTables , enums , relationships , database ] ) ;
42+
43+ useEffect ( ( ) => {
44+ const currentDbml = toDBML ( {
45+ tables : currentTables ,
46+ enums,
47+ relationships,
48+ database,
49+ } ) ;
50+
51+ if ( value === currentDbml ) {
52+ // If editor content already matches diagram state,
53+ // ensure any lingering external issues/markers are cleared
54+ if ( externalIssues ?. length ) setExternalIssues ( [ ] ) ;
55+ if ( markers . length ) setMarkers ( [ ] ) ;
56+ return ;
57+ }
58+
59+ const handle = setTimeout ( ( ) => {
60+ try {
61+ const parsed = fromDBML ( value ) ;
62+ // Preserve coordinates when table names match existing ones
63+ const nameToExisting = new Map (
64+ currentTables . map ( ( t ) => [ t . name , { x : t . x , y : t . y } ] ) ,
65+ ) ;
66+ parsed . tables = parsed . tables . map ( ( t ) => {
67+ const coords = nameToExisting . get ( t . name ) ;
68+ return coords ? { ...t , ...coords } : t ;
69+ } ) ;
70+ setTables ( parsed . tables ) ;
71+ setRelationships ( parsed . relationships ) ;
72+ setEnums ( parsed . enums ) ;
73+ // Clear any previous external issues on success
74+ setExternalIssues ( [ ] ) ;
75+ setMarkers ( [ ] ) ;
76+ } catch ( err ) {
77+ const { issues : parsedIssues , markers : parsedMarkers } =
78+ produceDiagnostics ( err ) ;
79+ setExternalIssues ( parsedIssues ) ;
80+ setMarkers ( parsedMarkers ) ;
81+ }
82+ } , 700 ) ;
83+
84+ return ( ) => clearTimeout ( handle ) ;
85+ } , [
86+ value ,
87+ currentTables ,
88+ enums ,
89+ relationships ,
90+ database ,
91+ setTables ,
92+ setRelationships ,
93+ setEnums ,
94+ setExternalIssues ,
95+ externalIssues ?. length ,
96+ markers . length ,
97+ ] ) ;
98+
99+ const produceDiagnostics = ( err ) => {
100+ // Prefer diagnostics from @dbml /core if present
101+ if ( Array . isArray ( err ?. diags ) && err . diags . length > 0 ) {
102+ const issues = err . diags . map ( ( d ) => {
103+ const ln = d ?. location ?. start ?. line ;
104+ const col = d ?. location ?. start ?. column ;
105+ const code = d ?. code ? ` [${ d . code } ]` : "" ;
106+ if ( ln && col ) return `line ${ ln } , col ${ col } : ${ d . message } ${ code } ` ;
107+ return d . message + code ;
108+ } ) ;
109+
110+ const markers = err . diags . map ( ( d ) => {
111+ const start = d ?. location ?. start || { } ;
112+ const end = d ?. location ?. end || { } ;
113+ const startLineNumber = start . line || 1 ;
114+ const startColumn = start . column || 1 ;
115+ const endLineNumber = end . line || startLineNumber ;
116+ const endColumn = end . column || startColumn + 1 ;
117+ return {
118+ startLineNumber,
119+ startColumn,
120+ endLineNumber,
121+ endColumn,
122+ message : d . message ,
123+ } ;
124+ } ) ;
125+ return { issues, markers } ;
126+ }
127+
128+ // Fallbacks
129+ const message =
130+ ( typeof err ?. message === "string" && err . message ) ||
131+ ( typeof err ?. description === "string" && err . description ) ||
132+ ( ( ) => {
133+ try {
134+ return JSON . stringify ( err ) ;
135+ } catch {
136+ return String ( err ) ;
137+ }
138+ } ) ( ) ;
139+
140+ // Try to extract line/column from string messages
141+ const m =
142+ / l i n e \s + ( \d + ) \s * , \s * c o l u m n \s * ( \d + ) / i. exec ( message ) ||
143+ / \( ( \d + ) \s * [: | , ] \s * ( \d + ) \) / . exec ( message ) ;
144+ const ln = m ? parseInt ( m [ 1 ] , 10 ) : 1 ;
145+ const col = m ? parseInt ( m [ 2 ] , 10 ) : 1 ;
146+ return {
147+ issues : [ message ] ,
148+ markers : [
149+ {
150+ startLineNumber : ln ,
151+ startColumn : col ,
152+ endLineNumber : ln ,
153+ endColumn : col + 1 ,
154+ message,
155+ } ,
156+ ] ,
157+ } ;
158+ } ;
24159
25160 return (
26161 < CodeEditor
@@ -29,8 +164,9 @@ export default function DBMLEditor() {
29164 language = "dbml"
30165 onChange = { setValue }
31166 height = "100%"
167+ markers = { markers }
32168 options = { {
33- readOnly : true ,
169+ readOnly : false ,
34170 minimap : { enabled : false } ,
35171 } }
36172 extraControls = {
0 commit comments