컴포넌트 배치문제 해결
This commit is contained in:
parent
db782eb9c9
commit
4736dd87b6
|
|
@ -230,6 +230,7 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
|
// 컴포넌트 영역 내에서만 클릭 이벤트 처리
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onClick?.(e);
|
onClick?.(e);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1738,6 +1738,10 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 캔버스 내부의 상대 좌표 계산 (스크롤 없는 고정 캔버스)
|
||||||
|
const relativeMouseX = event.clientX - rect.left;
|
||||||
|
const relativeMouseY = event.clientY - rect.top;
|
||||||
|
|
||||||
// 다중 선택된 컴포넌트들 확인
|
// 다중 선택된 컴포넌트들 확인
|
||||||
const isDraggedComponentSelected = groupState.selectedComponents.includes(component.id);
|
const isDraggedComponentSelected = groupState.selectedComponents.includes(component.id);
|
||||||
const componentsToMove = isDraggedComponentSelected
|
const componentsToMove = isDraggedComponentSelected
|
||||||
|
|
@ -1745,6 +1749,18 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
: [component];
|
: [component];
|
||||||
|
|
||||||
console.log("드래그 시작:", component.id, "이동할 컴포넌트 수:", componentsToMove.length);
|
console.log("드래그 시작:", component.id, "이동할 컴포넌트 수:", componentsToMove.length);
|
||||||
|
console.log("마우스 위치:", {
|
||||||
|
clientX: event.clientX,
|
||||||
|
clientY: event.clientY,
|
||||||
|
rectLeft: rect.left,
|
||||||
|
rectTop: rect.top,
|
||||||
|
relativeX: relativeMouseX,
|
||||||
|
relativeY: relativeMouseY,
|
||||||
|
componentX: component.position.x,
|
||||||
|
componentY: component.position.y,
|
||||||
|
grabOffsetX: relativeMouseX - component.position.x,
|
||||||
|
grabOffsetY: relativeMouseY - component.position.y,
|
||||||
|
});
|
||||||
|
|
||||||
setDragState({
|
setDragState({
|
||||||
isDragging: true,
|
isDragging: true,
|
||||||
|
|
@ -1761,8 +1777,8 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
z: (component.position as Position).z || 1,
|
z: (component.position as Position).z || 1,
|
||||||
},
|
},
|
||||||
grabOffset: {
|
grabOffset: {
|
||||||
x: event.clientX - rect.left - component.position.x,
|
x: relativeMouseX - component.position.x,
|
||||||
y: event.clientY - rect.top - component.position.y,
|
y: relativeMouseY - component.position.y,
|
||||||
},
|
},
|
||||||
justFinishedDrag: false,
|
justFinishedDrag: false,
|
||||||
});
|
});
|
||||||
|
|
@ -1776,9 +1792,14 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
if (!dragState.isDragging || !dragState.draggedComponent || !canvasRef.current) return;
|
if (!dragState.isDragging || !dragState.draggedComponent || !canvasRef.current) return;
|
||||||
|
|
||||||
const rect = canvasRef.current.getBoundingClientRect();
|
const rect = canvasRef.current.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 캔버스 내부의 상대 좌표 계산 (스크롤 없는 고정 캔버스)
|
||||||
|
const relativeMouseX = event.clientX - rect.left;
|
||||||
|
const relativeMouseY = event.clientY - rect.top;
|
||||||
|
|
||||||
const newPosition = {
|
const newPosition = {
|
||||||
x: event.clientX - rect.left - dragState.grabOffset.x,
|
x: relativeMouseX - dragState.grabOffset.x,
|
||||||
y: event.clientY - rect.top - dragState.grabOffset.y,
|
y: relativeMouseY - dragState.grabOffset.y,
|
||||||
z: (dragState.draggedComponent.position as Position).z || 1,
|
z: (dragState.draggedComponent.position as Position).z || 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2874,108 +2895,95 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<RealtimePreview
|
||||||
key={component.id}
|
key={component.id}
|
||||||
className="absolute"
|
component={displayComponent}
|
||||||
style={{
|
isSelected={
|
||||||
left: `${displayComponent.position.x}px`,
|
selectedComponent?.id === component.id || groupState.selectedComponents.includes(component.id)
|
||||||
top: `${displayComponent.position.y}px`,
|
}
|
||||||
width: displayComponent.style?.width || `${displayComponent.size.width}px`,
|
onClick={(e) => handleComponentClick(component, e)}
|
||||||
height: displayComponent.style?.height || `${displayComponent.size.height}px`,
|
onDragStart={(e) => startComponentDrag(component, e)}
|
||||||
zIndex: displayComponent.position.z || 1,
|
onDragEnd={endDrag}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<RealtimePreview
|
{/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 */}
|
||||||
component={displayComponent}
|
{(component.type === "group" || component.type === "container" || component.type === "area") &&
|
||||||
isSelected={
|
layout.components
|
||||||
selectedComponent?.id === component.id || groupState.selectedComponents.includes(component.id)
|
.filter((child) => child.parentId === component.id)
|
||||||
}
|
.map((child) => {
|
||||||
onClick={(e) => handleComponentClick(component, e)}
|
// 자식 컴포넌트에도 드래그 피드백 적용
|
||||||
onDragStart={(e) => startComponentDrag(component, e)}
|
const isChildDraggingThis =
|
||||||
onDragEnd={endDrag}
|
dragState.isDragging && dragState.draggedComponent?.id === child.id;
|
||||||
>
|
const isChildBeingDragged =
|
||||||
{/* 컨테이너, 그룹, 영역의 자식 컴포넌트들 렌더링 */}
|
dragState.isDragging &&
|
||||||
{(component.type === "group" || component.type === "container" || component.type === "area") &&
|
dragState.draggedComponents.some((dragComp) => dragComp.id === child.id);
|
||||||
layout.components
|
|
||||||
.filter((child) => child.parentId === component.id)
|
|
||||||
.map((child) => {
|
|
||||||
// 자식 컴포넌트에도 드래그 피드백 적용
|
|
||||||
const isChildDraggingThis =
|
|
||||||
dragState.isDragging && dragState.draggedComponent?.id === child.id;
|
|
||||||
const isChildBeingDragged =
|
|
||||||
dragState.isDragging &&
|
|
||||||
dragState.draggedComponents.some((dragComp) => dragComp.id === child.id);
|
|
||||||
|
|
||||||
let displayChild = child;
|
let displayChild = child;
|
||||||
|
|
||||||
|
if (isChildBeingDragged) {
|
||||||
|
if (isChildDraggingThis) {
|
||||||
|
// 주 드래그 자식 컴포넌트
|
||||||
|
displayChild = {
|
||||||
|
...child,
|
||||||
|
position: dragState.currentPosition,
|
||||||
|
style: {
|
||||||
|
...child.style,
|
||||||
|
opacity: 0.8,
|
||||||
|
transform: "scale(1.02)",
|
||||||
|
transition: "none",
|
||||||
|
zIndex: 9999,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 다른 선택된 자식 컴포넌트들
|
||||||
|
const originalChildComponent = dragState.draggedComponents.find(
|
||||||
|
(dragComp) => dragComp.id === child.id,
|
||||||
|
);
|
||||||
|
if (originalChildComponent) {
|
||||||
|
const deltaX = dragState.currentPosition.x - dragState.originalPosition.x;
|
||||||
|
const deltaY = dragState.currentPosition.y - dragState.originalPosition.y;
|
||||||
|
|
||||||
if (isChildBeingDragged) {
|
|
||||||
if (isChildDraggingThis) {
|
|
||||||
// 주 드래그 자식 컴포넌트
|
|
||||||
displayChild = {
|
displayChild = {
|
||||||
...child,
|
...child,
|
||||||
position: dragState.currentPosition,
|
position: {
|
||||||
|
x: originalChildComponent.position.x + deltaX,
|
||||||
|
y: originalChildComponent.position.y + deltaY,
|
||||||
|
z: originalChildComponent.position.z || 1,
|
||||||
|
} as Position,
|
||||||
style: {
|
style: {
|
||||||
...child.style,
|
...child.style,
|
||||||
opacity: 0.8,
|
opacity: 0.8,
|
||||||
transform: "scale(1.02)",
|
|
||||||
transition: "none",
|
transition: "none",
|
||||||
zIndex: 9999,
|
zIndex: 8888,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
// 다른 선택된 자식 컴포넌트들
|
|
||||||
const originalChildComponent = dragState.draggedComponents.find(
|
|
||||||
(dragComp) => dragComp.id === child.id,
|
|
||||||
);
|
|
||||||
if (originalChildComponent) {
|
|
||||||
const deltaX = dragState.currentPosition.x - dragState.originalPosition.x;
|
|
||||||
const deltaY = dragState.currentPosition.y - dragState.originalPosition.y;
|
|
||||||
|
|
||||||
displayChild = {
|
|
||||||
...child,
|
|
||||||
position: {
|
|
||||||
x: originalChildComponent.position.x + deltaX,
|
|
||||||
y: originalChildComponent.position.y + deltaY,
|
|
||||||
z: originalChildComponent.position.z || 1,
|
|
||||||
} as Position,
|
|
||||||
style: {
|
|
||||||
...child.style,
|
|
||||||
opacity: 0.8,
|
|
||||||
transition: "none",
|
|
||||||
zIndex: 8888,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
// 자식 컴포넌트의 위치를 부모 기준 상대 좌표로 조정
|
||||||
<div
|
const relativeChildComponent = {
|
||||||
key={child.id}
|
...displayChild,
|
||||||
className="absolute"
|
position: {
|
||||||
style={{
|
x: displayChild.position.x - component.position.x,
|
||||||
left: `${displayChild.position.x - component.position.x}px`,
|
y: displayChild.position.y - component.position.y,
|
||||||
top: `${displayChild.position.y - component.position.y}px`,
|
z: displayChild.position.z || 1,
|
||||||
width: `${displayChild.size.width}px`,
|
},
|
||||||
height: `${displayChild.size.height}px`, // 순수 컴포넌트 높이만 사용
|
};
|
||||||
zIndex: displayChild.position.z || 1,
|
|
||||||
}}
|
return (
|
||||||
>
|
<RealtimePreview
|
||||||
<RealtimePreview
|
key={child.id}
|
||||||
component={displayChild}
|
component={relativeChildComponent}
|
||||||
isSelected={
|
isSelected={
|
||||||
selectedComponent?.id === child.id ||
|
selectedComponent?.id === child.id || groupState.selectedComponents.includes(child.id)
|
||||||
groupState.selectedComponents.includes(child.id)
|
}
|
||||||
}
|
onClick={(e) => handleComponentClick(child, e)}
|
||||||
onClick={(e) => handleComponentClick(child, e)}
|
onDragStart={(e) => startComponentDrag(child, e)}
|
||||||
onDragStart={(e) => startComponentDrag(child, e)}
|
onDragEnd={endDrag}
|
||||||
onDragEnd={endDrag}
|
/>
|
||||||
/>
|
);
|
||||||
</div>
|
})}
|
||||||
);
|
</RealtimePreview>
|
||||||
})}
|
|
||||||
</RealtimePreview>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,21 @@ export function snapToGrid(position: Position, gridInfo: GridInfo, gridSettings:
|
||||||
const { columnWidth } = gridInfo;
|
const { columnWidth } = gridInfo;
|
||||||
const { gap, padding } = gridSettings;
|
const { gap, padding } = gridSettings;
|
||||||
|
|
||||||
// 격자 기준으로 위치 계산
|
// 격자 셀 크기 (컬럼 너비 + 간격을 하나의 격자 단위로 계산)
|
||||||
const gridX = Math.round((position.x - padding) / (columnWidth + gap));
|
const cellWidth = columnWidth + gap;
|
||||||
const rowHeight = Math.max(20, gap); // gap과 최소 20px 중 큰 값으로 행 높이 설정
|
const cellHeight = Math.max(40, gap * 2); // 행 높이를 더 크게 설정
|
||||||
const gridY = Math.round((position.y - padding) / rowHeight); // 동적 행 높이로 세로 스냅
|
|
||||||
|
// 패딩을 제외한 상대 위치
|
||||||
|
const relativeX = position.x - padding;
|
||||||
|
const relativeY = position.y - padding;
|
||||||
|
|
||||||
|
// 격자 기준으로 위치 계산 (가장 가까운 격자점으로 스냅)
|
||||||
|
const gridX = Math.round(relativeX / cellWidth);
|
||||||
|
const gridY = Math.round(relativeY / cellHeight);
|
||||||
|
|
||||||
// 실제 픽셀 위치로 변환
|
// 실제 픽셀 위치로 변환
|
||||||
const snappedX = Math.max(padding, padding + gridX * (columnWidth + gap));
|
const snappedX = Math.max(padding, padding + gridX * cellWidth);
|
||||||
const snappedY = Math.max(padding, padding + gridY * rowHeight);
|
const snappedY = Math.max(padding, padding + gridY * cellHeight);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x: snappedX,
|
x: snappedX,
|
||||||
|
|
@ -172,25 +179,22 @@ export function generateGridLines(
|
||||||
const gridInfo = calculateGridInfo(containerWidth, containerHeight, gridSettings);
|
const gridInfo = calculateGridInfo(containerWidth, containerHeight, gridSettings);
|
||||||
const { columnWidth } = gridInfo;
|
const { columnWidth } = gridInfo;
|
||||||
|
|
||||||
// 세로 격자선 (컬럼 경계)
|
// 격자 셀 크기 (스냅 로직과 동일하게)
|
||||||
|
const cellWidth = columnWidth + gap;
|
||||||
|
const cellHeight = Math.max(40, gap * 2);
|
||||||
|
|
||||||
|
// 세로 격자선
|
||||||
const verticalLines: number[] = [];
|
const verticalLines: number[] = [];
|
||||||
|
for (let i = 0; i <= columns; i++) {
|
||||||
// 좌측 경계선
|
const x = padding + i * cellWidth;
|
||||||
verticalLines.push(padding);
|
if (x <= containerWidth) {
|
||||||
|
verticalLines.push(x);
|
||||||
// 각 컬럼의 오른쪽 경계선들 (컬럼 사이의 격자선)
|
}
|
||||||
for (let i = 1; i < columns; i++) {
|
|
||||||
const x = padding + i * columnWidth + i * gap;
|
|
||||||
verticalLines.push(x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 우측 경계선
|
// 가로 격자선
|
||||||
verticalLines.push(containerWidth - padding);
|
|
||||||
|
|
||||||
// 가로 격자선 (동적 행 높이 단위)
|
|
||||||
const rowHeight = Math.max(20, gap);
|
|
||||||
const horizontalLines: number[] = [];
|
const horizontalLines: number[] = [];
|
||||||
for (let y = padding; y < containerHeight; y += rowHeight) {
|
for (let y = padding; y < containerHeight; y += cellHeight) {
|
||||||
horizontalLines.push(y);
|
horizontalLines.push(y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue