Skip to content

Commit abc6f17

Browse files
myh7754claude
andcommitted
feat: 연결선은 항상 직각, 맵 모양은 상자 / 글자만 / 밑줄 셋으로
직각 연결은 고르는 모양이 아니라 기본으로 들어간다. '글자만'은 중심 주제를 뺀 모든 노드에서 상자를 없앤다. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 0804c66 commit abc6f17

4 files changed

Lines changed: 37 additions & 70 deletions

File tree

‎src/components/MindMapCanvas/BezierEdge.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { MindMapEdge } from '../../types';
33
import { useMindMapStore } from '../../store/useMindMapStore';
44
import { edgeLook, edgePath, treeMeta } from '../../utils/mapTheme';
55

6-
// 이름은 BezierEdge지만 맵 모양(테마)에 따라 직각으로 꺾인 선도 그린다.
6+
// 이름은 BezierEdge지만 지금은 직각으로 꺾인 선을 그린다 (edgePath).
77
export function BezierEdge({
88
sourceX, sourceY, targetX, targetY,
99
target,
@@ -18,7 +18,7 @@ export function BezierEdge({
1818

1919
return (
2020
<path
21-
d={edgePath(theme, sourceX, sourceY, targetX, targetY)}
21+
d={edgePath(sourceX, sourceY, targetX, targetY)}
2222
stroke={isPreview ? '#f59e0b' : look.color}
2323
strokeWidth={isPreview ? 2.5 : look.width}
2424
fill="none"

‎src/components/MindMapCanvas/MindMapCanvas.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { TextNode } from './TextNode';
2020
import { TableNode } from './TableNode';
2121
import { BezierEdge } from './BezierEdge';
2222
import { ChordPanController } from './ChordPanController';
23+
import { edgePath } from '../../utils/mapTheme';
2324

2425
const nodeTypes: NodeTypes = {
2526
textNode: TextNode,
@@ -330,8 +331,7 @@ function Flow() {
330331
const sy = parent.position.y + ph / 2;
331332
const tx = slotX;
332333
const ty = slotY + gh / 2;
333-
const mx = (sx + tx) / 2;
334-
const path = `M ${sx} ${sy} C ${mx} ${sy}, ${mx} ${ty}, ${tx} ${ty}`;
334+
const path = edgePath(sx, sy, tx, ty);
335335
return { slotX, slotY, gw, gh, path };
336336
}, [rfNodes, draggingId, drop, mindMapData.children]);
337337

‎src/utils/mapTheme.test.ts‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ describe('mapTheme', () => {
2121
});
2222

2323
it('직접 칠한 색은 어느 테마에서든 테마 색보다 우선한다', () => {
24-
expect(nodeLook('branch', 1, 0, '#ff0000').style.border).toContain('#ff0000');
25-
expect(nodeLook('branch', 3, 0, '#ff0000').plain).toBe(false); // 글자만 있던 노드도 색이 보여야 한다
26-
expect(edgeLook('elbow', 1, 0, '#ff0000').color).toBe('#ff0000');
24+
expect(nodeLook('classic', 1, 0, '#ff0000').style.background).toBe('#ff0000');
25+
expect(nodeLook('plain', 3, 0, '#ff0000').plain).toBe(false); // 글자만 있던 노드도 색이 보여야 한다
26+
expect(edgeLook('plain', 1, 0, '#ff0000').color).toBe('#ff0000');
2727
});
2828

2929
it('같은 가지의 노드와 선은 같은 색이다', () => {
@@ -35,17 +35,22 @@ describe('mapTheme', () => {
3535
it('밑줄형만 선이 노드 아래쪽에 붙는다', () => {
3636
expect(nodeLook('underline', 1, 0).anchor).toBe('bottom');
3737
expect(nodeLook('underline', 0, -1).anchor).toBe('mid');
38-
expect(nodeLook('branch', 1, 0).anchor).toBe('mid');
38+
expect(nodeLook('plain', 1, 0).anchor).toBe('mid');
3939
});
4040

41-
it('직각 테마만 꺾인 선을 그린다', () => {
42-
expect(edgePath('elbow', 0, 0, 100, 50)).toContain('V');
43-
expect(edgePath('elbow', 0, 10, 100, 10)).toBe('M 0 10 H 100');
44-
expect(edgePath('classic', 0, 0, 100, 50)).toContain('C');
41+
it('글자만 모양은 중심 주제 말고는 상자가 없다', () => {
42+
expect(nodeLook('plain', 0, -1).plain).toBe(false);
43+
expect(nodeLook('plain', 1, 0).plain).toBe(true);
44+
expect(nodeLook('plain', 4, 0).plain).toBe(true);
45+
});
46+
47+
it('선은 직각으로 꺾인다 — 높이가 같으면 직선', () => {
48+
expect(edgePath(0, 0, 100, 50)).toContain('V');
49+
expect(edgePath(0, 10, 100, 10)).toBe('M 0 10 H 100');
4550
});
4651

4752
it('저장된 값이 이상하면 기본 테마로 돌아간다', () => {
48-
expect(readMapTheme('elbow')).toBe('elbow');
53+
expect(readMapTheme('plain')).toBe('plain');
4954
expect(readMapTheme('nope')).toBe('classic');
5055
expect(readMapTheme(null)).toBe('classic');
5156
});

‎src/utils/mapTheme.ts‎

Lines changed: 19 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ import type { CSSProperties } from 'react';
88
* 페이지 방문자는 주인이 고른 모양이 아니라 기본 모양으로 본다. 주인이 고른 모양을
99
* 보여 줘야 하면 맵 데이터에 넣는다.
1010
*/
11-
export type MapTheme = 'classic' | 'branch' | 'underline' | 'elbow';
11+
export type MapTheme = 'classic' | 'plain' | 'underline';
1212

1313
export const MAP_THEMES: { id: MapTheme; label: string }[] = [
14-
{ id: 'classic', label: '기본' },
15-
{ id: 'branch', label: '가지 색' },
14+
{ id: 'classic', label: '상자' },
15+
{ id: 'plain', label: '글자만' },
1616
{ id: 'underline', label: '밑줄' },
17-
{ id: 'elbow', label: '직각' },
1817
];
1918

2019
export const readMapTheme = (v: string | null): MapTheme =>
@@ -92,37 +91,7 @@ export function nodeLook(theme: MapTheme, depth: number, branch: number, custom?
9291

9392
const c = custom ?? branchColor(branch);
9493

95-
// 세부 항목: 상자 없이 글자만. 직접 색을 칠했으면 그 색이 보이도록 옅은 상자를 준다.
96-
const leaf: NodeLook = {
97-
plain: !custom,
98-
anchor: 'mid',
99-
style: {
100-
background: custom ? custom + '26' : undefined,
101-
border: `1px solid ${custom ?? 'transparent'}`,
102-
color: '#cbd5e1',
103-
padding: '3px 8px',
104-
borderRadius: 6,
105-
...text(13, 18, 400),
106-
},
107-
};
108-
109-
if (theme === 'elbow') {
110-
if (depth >= 2) return leaf;
111-
return {
112-
plain: false,
113-
anchor: 'mid',
114-
style: {
115-
background: custom ?? '#151c36',
116-
border: depth === 0 ? '1.5px solid #818cf8' : `1px solid ${custom ?? '#3b4667'}`,
117-
color: depth === 0 ? '#e0e7ff' : '#f1f5f9',
118-
padding: depth === 0 ? '10px 18px' : '7px 12px',
119-
borderRadius: 4,
120-
...(depth === 0 ? text(17, 24, 700) : text(14, 20, 600)),
121-
},
122-
};
123-
}
124-
125-
// branch / underline 공통: 중심 주제는 크게 채운다
94+
// 중심 주제는 어느 모양에서든 크게 채운다
12695
if (depth === 0) {
12796
return {
12897
plain: false,
@@ -138,32 +107,29 @@ export function nodeLook(theme: MapTheme, depth: number, branch: number, custom?
138107
};
139108
}
140109

110+
const top = depth === 1;
111+
const font = top ? text(15, 20, 600) : text(13, 18, 400);
112+
const color = top ? '#f8fafc' : '#cbd5e1';
113+
141114
if (theme === 'underline') {
142-
const top = depth === 1;
143115
return {
144116
plain: true,
145117
anchor: 'bottom',
146-
style: {
147-
borderBottom: `${top ? 3 : 1.5}px solid ${c}`,
148-
color: top ? '#f8fafc' : '#cbd5e1',
149-
padding: top ? '5px 6px' : '4px 6px',
150-
...(top ? text(15, 20, 600) : text(13, 18, 400)),
151-
},
118+
style: { borderBottom: `${top ? 3 : 1.5}px solid ${c}`, color, padding: top ? '5px 6px' : '4px 6px', ...font },
152119
};
153120
}
154121

155-
// branch
156-
if (depth >= 2) return leaf;
122+
// plain: 상자 없이 글자만. 직접 색을 칠했으면 그 색이 보이도록 옅은 상자를 준다.
157123
return {
158-
plain: false,
124+
plain: !custom,
159125
anchor: 'mid',
160126
style: {
161-
background: c + '26',
162-
border: `1.5px solid ${c}`,
163-
color: '#f8fafc',
164-
padding: '8px 14px',
165-
borderRadius: 9,
166-
...text(15, 20, 600),
127+
background: custom ? custom + '26' : undefined,
128+
border: `1px solid ${custom ?? 'transparent'}`,
129+
color,
130+
padding: top ? '4px 8px' : '3px 8px',
131+
borderRadius: 6,
132+
...font,
167133
},
168134
};
169135
}
@@ -181,16 +147,12 @@ export function edgeLook(
181147
width: depth === 0 ? 2 : depth === 1 ? 1.5 : 1,
182148
};
183149
}
184-
if (theme === 'elbow') return { color: custom ?? '#4a5880', width: 1.25 };
185150
// 밑줄형은 선 굵기를 밑줄 굵기와 맞춰 한 줄로 이어 보이게 한다
186151
return { color: custom ?? branchColor(branch), width: depth === 0 ? (theme === 'underline' ? 3 : 2.5) : 1.5 };
187152
}
188153

189-
/** 부모(x1,y1) → 자식(x2,y2) 경로. 직각 테마만 꺾인 선, 나머지는 곡선. */
190-
export function edgePath(theme: MapTheme, x1: number, y1: number, x2: number, y2: number): string {
191-
if (theme !== 'elbow') {
192-
return `M ${x1} ${y1} C ${x1 + 60} ${y1}, ${x2 - 60} ${y2}, ${x2} ${y2}`;
193-
}
154+
/** 부모(x1,y1) → 자식(x2,y2) 경로. 어느 모양에서든 직각으로 꺾인 선이다. */
155+
export function edgePath(x1: number, y1: number, x2: number, y2: number): string {
194156
// 꺾이는 자리를 자식 쪽 기준으로 잡는다 → 폭이 다른 부모들도 같은 세로줄에서 꺾인다
195157
const mx = x2 - 40;
196158
const dy = y2 - y1;

0 commit comments

Comments
 (0)