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