텍스트 인라인 편집 기능 추가
This commit is contained in:
@@ -105,6 +105,7 @@ interface ReportDesignerContextType {
|
||||
copyStyles: () => void; // Ctrl+Shift+C 스타일만 복사
|
||||
pasteStyles: () => void; // Ctrl+Shift+V 스타일만 붙여넣기
|
||||
duplicateAtPosition: (componentIds: string[], offsetX?: number, offsetY?: number) => string[]; // Alt+드래그 복제용
|
||||
fitSelectedToContent: () => void; // Ctrl+Shift+F 텍스트 크기 자동 맞춤
|
||||
|
||||
// Undo/Redo
|
||||
undo: () => void;
|
||||
@@ -1503,6 +1504,114 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
||||
[currentPageId],
|
||||
);
|
||||
|
||||
// 텍스트 컴포넌트 크기 자동 맞춤 (Ctrl+Shift+F)
|
||||
const fitSelectedToContent = useCallback(() => {
|
||||
const MM_TO_PX = 4; // 고정 스케일 팩터
|
||||
|
||||
// 선택된 컴포넌트 ID 결정
|
||||
const targetIds =
|
||||
selectedComponentIds.length > 0
|
||||
? selectedComponentIds
|
||||
: selectedComponentId
|
||||
? [selectedComponentId]
|
||||
: [];
|
||||
|
||||
if (targetIds.length === 0) return;
|
||||
|
||||
// 텍스트/레이블 컴포넌트만 필터링
|
||||
const textComponents = components.filter(
|
||||
(c) =>
|
||||
targetIds.includes(c.id) &&
|
||||
(c.type === "text" || c.type === "label") &&
|
||||
!c.locked
|
||||
);
|
||||
|
||||
if (textComponents.length === 0) {
|
||||
toast({
|
||||
title: "크기 조정 불가",
|
||||
description: "선택된 텍스트 컴포넌트가 없습니다.",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 현재 페이지 설정 가져오기
|
||||
const page = currentPage;
|
||||
if (!page) return;
|
||||
|
||||
const canvasWidthPx = page.width * MM_TO_PX;
|
||||
const canvasHeightPx = page.height * MM_TO_PX;
|
||||
const marginRightPx = (page.margins?.right || 10) * MM_TO_PX;
|
||||
const marginBottomPx = (page.margins?.bottom || 10) * MM_TO_PX;
|
||||
|
||||
// 각 텍스트 컴포넌트 크기 조정
|
||||
textComponents.forEach((comp) => {
|
||||
const displayValue = comp.defaultValue || (comp.type === "text" ? "텍스트 입력" : "레이블 텍스트");
|
||||
const fontSize = comp.fontSize || 14;
|
||||
|
||||
// 최대 크기 (여백 고려)
|
||||
const maxWidth = canvasWidthPx - marginRightPx - comp.x;
|
||||
const maxHeight = canvasHeightPx - marginBottomPx - comp.y;
|
||||
|
||||
// 줄바꿈으로 분리하여 각 줄의 너비 측정
|
||||
const lines = displayValue.split("\n");
|
||||
let maxLineWidth = 0;
|
||||
|
||||
lines.forEach((line: string) => {
|
||||
const measureEl = document.createElement("span");
|
||||
measureEl.style.position = "absolute";
|
||||
measureEl.style.visibility = "hidden";
|
||||
measureEl.style.whiteSpace = "nowrap";
|
||||
measureEl.style.fontSize = `${fontSize}px`;
|
||||
measureEl.style.fontWeight = comp.fontWeight || "normal";
|
||||
measureEl.style.fontFamily = "system-ui, -apple-system, sans-serif";
|
||||
measureEl.textContent = line || " ";
|
||||
document.body.appendChild(measureEl);
|
||||
|
||||
const lineWidth = measureEl.getBoundingClientRect().width;
|
||||
maxLineWidth = Math.max(maxLineWidth, lineWidth);
|
||||
document.body.removeChild(measureEl);
|
||||
});
|
||||
|
||||
// 패딩 및 높이 계산
|
||||
const horizontalPadding = 24;
|
||||
const verticalPadding = 20;
|
||||
const lineHeight = fontSize * 1.5;
|
||||
const totalHeight = lines.length * lineHeight;
|
||||
|
||||
const finalWidth = Math.min(maxLineWidth + horizontalPadding, maxWidth);
|
||||
const finalHeight = Math.min(totalHeight + verticalPadding, maxHeight);
|
||||
|
||||
const newWidth = Math.max(50, finalWidth);
|
||||
const newHeight = Math.max(30, finalHeight);
|
||||
|
||||
// 크기 업데이트 - setLayoutConfig 직접 사용
|
||||
setLayoutConfig((prev) => ({
|
||||
pages: prev.pages.map((p) =>
|
||||
p.page_id === currentPageId
|
||||
? {
|
||||
...p,
|
||||
components: p.components.map((c) =>
|
||||
c.id === comp.id
|
||||
? {
|
||||
...c,
|
||||
width: snapToGrid ? Math.round(newWidth / gridSize) * gridSize : newWidth,
|
||||
height: snapToGrid ? Math.round(newHeight / gridSize) * gridSize : newHeight,
|
||||
}
|
||||
: c
|
||||
),
|
||||
}
|
||||
: p
|
||||
),
|
||||
}));
|
||||
});
|
||||
|
||||
toast({
|
||||
title: "크기 조정 완료",
|
||||
description: `${textComponents.length}개의 컴포넌트 크기가 조정되었습니다.`,
|
||||
});
|
||||
}, [selectedComponentId, selectedComponentIds, components, currentPage, currentPageId, snapToGrid, gridSize, toast]);
|
||||
|
||||
// 컴포넌트 삭제 (현재 페이지에서)
|
||||
const removeComponent = useCallback(
|
||||
(id: string) => {
|
||||
@@ -1889,6 +1998,7 @@ export function ReportDesignerProvider({ reportId, children }: { reportId: strin
|
||||
copyStyles,
|
||||
pasteStyles,
|
||||
duplicateAtPosition,
|
||||
fitSelectedToContent,
|
||||
// Undo/Redo
|
||||
undo,
|
||||
redo,
|
||||
|
||||
Reference in New Issue
Block a user