1
1
import { useEffect , useState } from "react" ;
2
2
import { useDiagram , useEnums , useLayout } from "../../hooks" ;
3
3
import { toDBML } from "../../utils/exportAs/dbml" ;
4
+ import { fromDBML } from "../../utils/importFrom/dbml" ;
4
5
import { Button , Tooltip } from "@douyinfe/semi-ui" ;
5
6
import { IconTemplate } from "@douyinfe/semi-icons" ;
6
7
import { useTranslation } from "react-i18next" ;
7
8
import CodeEditor from "../CodeEditor" ;
8
9
9
10
export default function DBMLEditor ( ) {
10
- const { tables : currentTables , relationships } = useDiagram ( ) ;
11
11
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 ( ) ;
13
21
const [ value , setValue ] = useState ( ( ) => toDBML ( { ...diagram , enums } ) ) ;
14
22
const { setLayout } = useLayout ( ) ;
23
+ const { setExternalIssues } = diagram ;
15
24
const { t } = useTranslation ( ) ;
16
25
26
+ // Translate DBML parse errors to issues and Monaco markers
27
+ const [ markers , setMarkers ] = useState ( [ ] ) ;
28
+
17
29
const toggleDBMLEditor = ( ) => {
18
30
setLayout ( ( prev ) => ( { ...prev , dbmlEditor : ! prev . dbmlEditor } ) ) ;
19
31
} ;
20
32
21
33
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
+ } ;
24
159
25
160
return (
26
161
< CodeEditor
@@ -29,8 +164,9 @@ export default function DBMLEditor() {
29
164
language = "dbml"
30
165
onChange = { setValue }
31
166
height = "100%"
167
+ markers = { markers }
32
168
options = { {
33
- readOnly : true ,
169
+ readOnly : false ,
34
170
minimap : { enabled : false } ,
35
171
} }
36
172
extraControls = {
0 commit comments