Skip to content

Commit 0eb6681

Browse files
committed
fix: erd svg 짤리는 버그 픽스
1 parent ceb0adf commit 0eb6681

2 files changed

Lines changed: 205 additions & 65 deletions

File tree

src/app/pages/ERDPage.tsx

Lines changed: 155 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)