@@ -435,7 +435,7 @@ function getSavedErdDocuments() {
435435 && typeof document . updatedAt === "string"
436436 )
437437 : [ ]
438- ] ) . filter ( ( [ , documents ] ) => documents . length > 0 ) ) ;
438+ ] ) ) ;
439439 } catch {
440440 return { } ;
441441 }
@@ -468,7 +468,9 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
468468 const erdDocuments = erdDocumentsByRepository [ repositoryKey ] ?? createInitialErdDocuments ( repositoryName ) ;
469469 const selectedErdId = selectedErdIdByRepository [ repositoryKey ] ?? erdDocuments [ 0 ] ?. id ;
470470 const selectedErd = erdDocuments . find ( ( document ) => document . id === selectedErdId ) ?? erdDocuments [ 0 ] ;
471- const erdCode = selectedErd ?. code ?? defaultErdCode ;
471+ const selectedErdRenderId = selectedErd ?. id ?? "" ;
472+ const erdCode = selectedErd ?. code ?? "" ;
473+ const [ debouncedErdCode , setDebouncedErdCode ] = useState ( erdCode ) ;
472474 const diagram = useMemo ( ( ) => parseErdCode ( erdCode ) , [ erdCode ] ) ;
473475 const erdDocumentSummaries = useMemo ( ( ) => erdDocuments . map ( ( document ) => ( {
474476 document,
@@ -506,20 +508,56 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
506508
507509 useEffect ( ( ) => {
508510 const initialDocuments = createInitialErdDocuments ( repositoryName ) ;
509- setErdDocumentsByRepository ( ( prev ) => prev [ repositoryKey ] ?. length ? prev : {
511+ setErdDocumentsByRepository ( ( prev ) => Object . prototype . hasOwnProperty . call ( prev , repositoryKey ) ? prev : {
510512 ...prev ,
511513 [ repositoryKey ] : initialDocuments
512514 } ) ;
513- setSelectedErdIdByRepository ( ( prev ) => prev [ repositoryKey ] ? prev : {
514- ...prev ,
515- [ repositoryKey ] : initialDocuments [ 0 ] . id
516- } ) ;
517515 } , [ repositoryKey , repositoryName ] ) ;
518516
517+ useEffect ( ( ) => {
518+ setSelectedErdIdByRepository ( ( prev ) => {
519+ const hasSelectedDocument = selectedErdId && erdDocuments . some ( ( document ) => document . id === selectedErdId ) ;
520+
521+ if ( hasSelectedDocument ) {
522+ return prev ;
523+ }
524+
525+ if ( ! erdDocuments . length ) {
526+ if ( ! Object . prototype . hasOwnProperty . call ( prev , repositoryKey ) ) {
527+ return prev ;
528+ }
529+
530+ const nextSelectedIds = { ...prev } ;
531+ delete nextSelectedIds [ repositoryKey ] ;
532+ return nextSelectedIds ;
533+ }
534+
535+ return {
536+ ...prev ,
537+ [ repositoryKey ] : erdDocuments [ 0 ] . id
538+ } ;
539+ } ) ;
540+ } , [ erdDocuments , repositoryKey , selectedErdId ] ) ;
541+
519542 useEffect ( ( ) => {
520543 saveErdDocuments ( erdDocumentsByRepository ) ;
521544 } , [ erdDocumentsByRepository ] ) ;
522545
546+ useEffect ( ( ) => {
547+ if ( ! selectedErdRenderId ) {
548+ setDebouncedErdCode ( "" ) ;
549+ return ;
550+ }
551+
552+ const debounceId = window . setTimeout ( ( ) => {
553+ setDebouncedErdCode ( erdCode ) ;
554+ } , 250 ) ;
555+
556+ return ( ) => {
557+ window . clearTimeout ( debounceId ) ;
558+ } ;
559+ } , [ erdCode , selectedErdRenderId ] ) ;
560+
523561 useEffect ( ( ) => {
524562 const viewport = diagramViewportRef . current ;
525563 if ( ! viewport ) return ;
@@ -554,13 +592,14 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
554592 } , [ minCanvasZoom , viewportSize . width , viewportSize . height ] ) ;
555593
556594 const updateSelectedErdCode = ( nextCode : string ) => {
595+ if ( ! selectedErdId ) return ;
596+
557597 setErdDocumentsByRepository ( ( prev ) => {
558598 const documents = prev [ repositoryKey ] ?? createInitialErdDocuments ( repositoryName ) ;
559- const targetId = selectedErdId ?? documents [ 0 ] ?. id ;
560599
561600 return {
562601 ...prev ,
563- [ repositoryKey ] : documents . map ( ( document ) => document . id === targetId
602+ [ repositoryKey ] : documents . map ( ( document ) => document . id === selectedErdId
564603 ? { ...document , code : nextCode , updatedAt : "방금 전" }
565604 : document )
566605 } ;
@@ -596,8 +635,6 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
596635 } ;
597636
598637 const handleDeleteErd = ( documentId : string ) => {
599- if ( erdDocuments . length <= 1 ) return ;
600-
601638 const nextDocuments = erdDocuments . filter ( ( document ) => document . id !== documentId ) ;
602639 const nextSelectedId = selectedErd ?. id === documentId
603640 ? nextDocuments [ 0 ] ?. id
@@ -608,15 +645,34 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
608645 [ repositoryKey ] : nextDocuments
609646 } ) ) ;
610647
611- if ( nextSelectedId ) {
612- setSelectedErdIdByRepository ( ( prev ) => ( {
613- ...prev ,
614- [ repositoryKey ] : nextSelectedId
615- } ) ) ;
616- }
648+ setSelectedErdIdByRepository ( ( prev ) => {
649+ const nextSelectedIds = { ...prev } ;
650+
651+ if ( nextSelectedId ) {
652+ nextSelectedIds [ repositoryKey ] = nextSelectedId ;
653+ } else {
654+ delete nextSelectedIds [ repositoryKey ] ;
655+ }
656+
657+ return nextSelectedIds ;
658+ } ) ;
617659 } ;
618660
619661 useEffect ( ( ) => {
662+ if ( ! selectedErdRenderId ) {
663+ setMermaidSvg ( "" ) ;
664+ setMermaidError ( "" ) ;
665+ cleanupMermaidRenderArtifacts ( ) ;
666+ return ;
667+ }
668+
669+ if ( ! debouncedErdCode . trim ( ) ) {
670+ setMermaidSvg ( "" ) ;
671+ setMermaidError ( "Mermaid 코드를 입력하세요." ) ;
672+ cleanupMermaidRenderArtifacts ( ) ;
673+ return ;
674+ }
675+
620676 let cancelled = false ;
621677 const renderId = `codedock-erd-${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 ) } ` ;
622678 const renderContainer = document . createElement ( "div" ) ;
@@ -638,7 +694,7 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
638694
639695 const renderDiagram = async ( ) => {
640696 try {
641- const result = await mermaid . render ( renderId , toMermaidRenderableCode ( erdCode ) , renderContainer ) ;
697+ const result = await mermaid . render ( renderId , toMermaidRenderableCode ( debouncedErdCode ) , renderContainer ) ;
642698 if ( ! cancelled ) {
643699 setMermaidSvg ( result . svg ) ;
644700 setMermaidError ( "" ) ;
@@ -663,7 +719,7 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
663719 renderContainer . remove ( ) ;
664720 cleanupMermaidRenderArtifacts ( ) ;
665721 } ;
666- } , [ erdCode ] ) ;
722+ } , [ debouncedErdCode , selectedErdRenderId ] ) ;
667723
668724 useEffect ( ( ) => {
669725 if ( ! showDownloadMenu ) return ;
@@ -853,14 +909,16 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
853909 < button
854910 type = "button"
855911 onClick = { ( ) => updateSelectedErdCode ( generatedErdCode ) }
912+ disabled = { ! selectedErd }
856913 className = "inline-flex shrink-0 items-center gap-2 rounded-lg border-0 px-3 py-2 tracking-tight"
857914 style = { {
858915 background : "rgba(255, 145, 77, 0.10)" ,
859916 border : "1px solid rgba(255, 145, 77, 0.32)" ,
860917 color : "#ff9a5c" ,
861- cursor : "pointer" ,
918+ cursor : selectedErd ? "pointer" : "not-allowed ",
862919 fontSize : "12px" ,
863- fontWeight : 950
920+ fontWeight : 950 ,
921+ opacity : selectedErd ? 1 : 0.42
864922 } }
865923 >
866924 < Sparkles size = { 15 } />
@@ -869,66 +927,54 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
869927 < button
870928 type = "button"
871929 onClick = { ( ) => updateSelectedErdCode ( defaultErdCode ) }
930+ disabled = { ! selectedErd }
872931 className = "inline-flex shrink-0 items-center gap-2 rounded-lg border-0 px-3 py-2 tracking-tight"
873932 style = { {
874933 background : "rgba(234, 247, 255, 0.06)" ,
875934 border : "1px solid rgba(32, 227, 255, 0.16)" ,
876935 color : "var(--white)" ,
877- cursor : "pointer" ,
936+ cursor : selectedErd ? "pointer" : "not-allowed ",
878937 fontSize : "12px" ,
879- fontWeight : 900
938+ fontWeight : 900 ,
939+ opacity : selectedErd ? 1 : 0.42
880940 } }
881941 >
882942 < RefreshCw size = { 15 } />
883943 기본값
884944 </ button >
885- < button
886- type = "button"
887- onClick = { handleAddErd }
888- className = "inline-flex shrink-0 items-center gap-2 rounded-lg border-0 px-3 py-2 tracking-tight"
889- style = { {
890- background : "rgba(32, 227, 255, 0.10)" ,
891- border : "1px solid rgba(32, 227, 255, 0.24)" ,
892- color : "var(--neon-cyan)" ,
893- cursor : "pointer" ,
894- fontSize : "12px" ,
895- fontWeight : 950
896- } }
897- >
898- < Plus size = { 15 } />
899- 생성
900- </ button >
901945 < button
902946 type = "button"
903947 onClick = { ( ) => selectedErd ?. id && handleDeleteErd ( selectedErd . id ) }
904- disabled = { erdDocuments . length <= 1 }
948+ disabled = { ! selectedErd }
905949 className = "inline-flex shrink-0 items-center gap-2 rounded-lg border-0 px-3 py-2 tracking-tight"
906950 style = { {
907951 background : "rgba(255, 107, 107, 0.10)" ,
908952 border : "1px solid rgba(255, 107, 107, 0.22)" ,
909953 color : "#ff8f8f" ,
910- cursor : erdDocuments . length > 1 ? "pointer" : "not-allowed" ,
954+ cursor : selectedErd ? "pointer" : "not-allowed" ,
911955 fontSize : "12px" ,
912956 fontWeight : 950 ,
913- opacity : erdDocuments . length > 1 ? 1 : 0.42
957+ opacity : selectedErd ? 1 : 0.42
914958 } }
915- title = { erdDocuments . length > 1 ? "현재 ERD 삭제" : "마지막 ERD는 삭제할 수 없어요" }
959+ title = { selectedErd ? "현재 ERD 삭제" : "삭제할 ERD가 없어요" }
916960 >
917961 < Trash2 size = { 15 } />
918962 삭제
919963 </ button >
920964 < div className = "relative shrink-0" ref = { downloadMenuRef } >
921965 < button
922966 type = "button"
923- onClick = { ( ) => setShowDownloadMenu ( ( v ) => ! v ) }
967+ onClick = { ( ) => selectedErd && setShowDownloadMenu ( ( v ) => ! v ) }
968+ disabled = { ! selectedErd }
924969 className = "inline-flex h-9 shrink-0 items-center gap-1.5 rounded-lg border-0 px-3 tracking-tight"
925970 style = { {
926971 background : "rgba(234, 247, 255, 0.06)" ,
927972 border : "1px solid rgba(32, 227, 255, 0.16)" ,
928973 color : "var(--white)" ,
929- cursor : "pointer" ,
974+ cursor : selectedErd ? "pointer" : "not-allowed ",
930975 fontSize : "12px" ,
931976 fontWeight : 900 ,
977+ opacity : selectedErd ? 1 : 0.42
932978 } }
933979 aria-label = "ERD 다운로드"
934980 aria-expanded = { showDownloadMenu }
@@ -1041,10 +1087,28 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
10411087 className = "codedock-scrollbar-hidden mt-3 grid gap-2 overflow-y-auto pr-1"
10421088 style = { { maxHeight : embedded ? 190 : 250 } }
10431089 >
1090+ { erdDocumentSummaries . length === 0 && (
1091+ < div
1092+ className = "rounded-xl px-4 py-5 text-center tracking-tight"
1093+ style = { {
1094+ background : "rgba(234, 247, 255, 0.045)" ,
1095+ border : "1px dashed rgba(32, 227, 255, 0.24)" ,
1096+ color : "var(--white)"
1097+ } }
1098+ >
1099+ < Database size = { 22 } style = { { color : "var(--neon-cyan)" , margin : "0 auto 10px" } } />
1100+ < p className = "m-0" style = { { fontSize : "14px" , fontWeight : 950 } } >
1101+ ERD를 추가하세요
1102+ </ p >
1103+ < p className = "m-0 mt-1" style = { { color : "var(--muted)" , fontSize : "11px" , fontWeight : 800 } } >
1104+ 추가 버튼을 눌러 새 다이어그램을 만들 수 있어요.
1105+ </ p >
1106+ </ div >
1107+ ) }
10441108 { erdDocumentSummaries . map ( ( { document : erdDoc , diagram : documentDiagram } ) => {
10451109 const isSelected = erdDoc . id === selectedErd ?. id ;
10461110 const documentColumnCount = documentDiagram . entities . reduce ( ( sum , entity ) => sum + entity . columns . length , 0 ) ;
1047- const canDeleteErd = erdDocuments . length > 1 ;
1111+ const canDeleteErd = erdDocuments . length > 0 ;
10481112
10491113 return (
10501114 < div
@@ -1100,7 +1164,7 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
11001164 opacity : canDeleteErd ? 1 : 0.38
11011165 } }
11021166 aria-label = { `${ erdDoc . title } 삭제` }
1103- title = { canDeleteErd ? "ERD 삭제" : "마지막 ERD는 삭제할 수 없어요" }
1167+ title = "ERD 삭제"
11041168 >
11051169 < Trash2 size = { 14 } />
11061170 </ button >
@@ -1128,7 +1192,7 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
11281192 fontSize : "11px" ,
11291193 fontWeight : 800
11301194 } } >
1131- { selectedErd ?. title ?? "실시간 렌더링 " }
1195+ { selectedErd ?. title ?? "ERD 없음 " }
11321196 </ span >
11331197 </ div >
11341198
@@ -1151,16 +1215,20 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
11511215 < textarea
11521216 value = { erdCode }
11531217 onChange = { ( event ) => updateSelectedErdCode ( event . target . value ) }
1218+ disabled = { ! selectedErd }
1219+ placeholder = "ERD를 추가하면 Mermaid 코드를 편집할 수 있어요."
11541220 spellCheck = { false }
11551221 className = "codedock-scrollbar-hidden block w-full resize-none border-0 p-4 font-mono outline-none"
11561222 style = { {
11571223 flex : embedded ? "1 1 0" : undefined ,
11581224 minHeight : embedded ? 0 : "720px" ,
11591225 background : "rgba(5, 11, 20, 0.78)" ,
11601226 color : "var(--soft-mint)" ,
1227+ cursor : selectedErd ? "text" : "not-allowed" ,
11611228 fontSize : "12px" ,
11621229 fontWeight : 800 ,
1163- lineHeight : 1.7
1230+ lineHeight : 1.7 ,
1231+ opacity : selectedErd ? 1 : 0.62
11641232 } }
11651233 />
11661234 </ section >
@@ -1180,13 +1248,22 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
11801248 < PreviewStat label = "관계" value = { relationCount } />
11811249 </ div >
11821250 < div className = "flex min-w-0 flex-wrap items-center justify-end gap-3" >
1183- < p className = "m-0 min-w-0 tracking-tight" style = { {
1184- color : "var(--muted)" ,
1185- fontSize : "12px" ,
1186- fontWeight : 800
1187- } } >
1188- erDiagram 코드를 수정하면 아래 다이어그램이 바로 갱신됩니다.
1189- </ p >
1251+ < button
1252+ type = "button"
1253+ onClick = { handleAddErd }
1254+ className = "inline-flex shrink-0 items-center gap-2 rounded-lg border-0 px-3 py-2 tracking-tight transition-all hover:scale-[1.02]"
1255+ style = { {
1256+ background : "rgba(32, 227, 255, 0.10)" ,
1257+ border : "1px solid rgba(32, 227, 255, 0.24)" ,
1258+ color : "var(--neon-cyan)" ,
1259+ cursor : "pointer" ,
1260+ fontSize : "12px" ,
1261+ fontWeight : 950
1262+ } }
1263+ >
1264+ < Plus size = { 15 } />
1265+ 생성
1266+ </ button >
11901267 { zoomControls }
11911268 </ div >
11921269 </ div >
@@ -1228,7 +1305,28 @@ export function ERDPage({ embedded = false, repositoryName = defaultRepositoryNa
12281305 transformOrigin : "0 0"
12291306 } }
12301307 >
1231- { mermaidError ? (
1308+ { ! selectedErd ? (
1309+ < div
1310+ className = "absolute rounded-2xl px-5 py-5 text-center tracking-tight"
1311+ style = { {
1312+ left : diagramFrameOffsetX ,
1313+ top : diagramFrameOffsetY ,
1314+ width : 320 ,
1315+ background : "rgba(11, 22, 40, 0.78)" ,
1316+ border : "1px dashed rgba(32, 227, 255, 0.28)" ,
1317+ color : "var(--white)" ,
1318+ boxShadow : "0 18px 44px rgba(0, 0, 0, 0.24)"
1319+ } }
1320+ >
1321+ < Database size = { 26 } style = { { color : "var(--neon-cyan)" , margin : "0 auto 12px" } } />
1322+ < p className = "m-0" style = { { fontSize : 15 , fontWeight : 950 } } >
1323+ ERD를 추가하세요
1324+ </ p >
1325+ < p className = "m-0 mt-2" style = { { color : "var(--muted)" , fontSize : 12 , fontWeight : 800 , lineHeight : 1.6 } } >
1326+ 새 다이어그램을 만들면 여기에서 바로 미리볼 수 있어요.
1327+ </ p >
1328+ </ div >
1329+ ) : mermaidError ? (
12321330 < div
12331331 className = "absolute rounded-2xl px-5 py-4"
12341332 style = { {
0 commit comments