diff --git a/frontend/components/screen/ScreenDesigner.tsx b/frontend/components/screen/ScreenDesigner.tsx index 2a82ff33..bf54e7b8 100644 --- a/frontend/components/screen/ScreenDesigner.tsx +++ b/frontend/components/screen/ScreenDesigner.tsx @@ -1811,8 +1811,9 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const rect = canvasRef.current?.getBoundingClientRect(); if (!rect) return; - const dropX = e.clientX - rect.left; - const dropY = e.clientY - rect.top; + // ๐ฅ ์ค์: ์ค ๋ ๋ฒจ์ ๊ณ ๋ คํ ๋ง์ฐ์ค ์์น ๊ณ์ฐ + const dropX = (e.clientX - rect.left) / zoomLevel; + const dropY = (e.clientY - rect.top) / zoomLevel; // ํ์ฌ ํด์๋์ ๋ง๋ ๊ฒฉ์ ์ ๋ณด ๊ณ์ฐ const currentGridInfo = layout.gridSettings @@ -1830,9 +1831,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD ? snapToGrid({ x: dropX, y: dropY, z: 1 }, currentGridInfo, layout.gridSettings as GridUtilSettings) : { x: dropX, y: dropY, z: 1 }; - console.log("๐๏ธ ๋ ์ด์์ ๋๋กญ:", { + console.log("๐๏ธ ๋ ์ด์์ ๋๋กญ (์ค ๋ณด์ ):", { + zoomLevel, layoutType: layoutData.layoutType, zonesCount: layoutData.zones.length, + mouseRaw: { x: e.clientX - rect.left, y: e.clientY - rect.top }, dropPosition: { x: dropX, y: dropY }, snappedPosition, }); @@ -1869,7 +1872,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD toast.success(`${layoutData.label} ๋ ์ด์์์ด ์ถ๊ฐ๋์์ต๋๋ค.`); }, - [layout, gridInfo, screenResolution, snapToGrid, saveToHistory], + [layout, gridInfo, screenResolution, snapToGrid, saveToHistory, zoomLevel], ); // handleZoneComponentDrop์ handleComponentDrop์ผ๋ก ๋์ฒด๋จ @@ -1954,32 +1957,47 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const componentWidth = component.defaultSize?.width || 120; const componentHeight = component.defaultSize?.height || 36; - // ๋ฐฉ๋ฒ 1: ๋ง์ฐ์ค ํฌ์ธํฐ๋ฅผ ์ปดํฌ๋ํธ ์ค์ฌ์ผ๋ก (ํ์ฌ ๋ฐฉ์) - const dropX_centered = e.clientX - rect.left - componentWidth / 2; - const dropY_centered = e.clientY - rect.top - componentHeight / 2; + // ๐ฅ ์ค์: ์ค ๋ ๋ฒจ๊ณผ transform-origin์ ๊ณ ๋ คํ ๋ง์ฐ์ค ์์น ๊ณ์ฐ + // 1. ์บ๋ฒ์ค๊ฐ scale() ๋ณํ๋์ด ์์ (transform-origin: top center) + // 2. ์บ๋ฒ์ค๊ฐ justify-center๋ก ์ค์ ์ ๋ ฌ๋์ด ์์ + + // ์ค์ ์บ๋ฒ์ค ๋ ผ๋ฆฌ์ ํฌ๊ธฐ + const canvasLogicalWidth = screenResolution.width; + + // ํ๋ฉด์ ์บ๋ฒ์ค ์ค์ ํฌ๊ธฐ (์ค์ผ์ผ ์ ์ฉ ํ) + const canvasVisualWidth = canvasLogicalWidth * zoomLevel; + + // ์ค์ ์ ๋ ฌ๋ก ์ธํ ์ผ์ชฝ ์คํ์ ๊ณ์ฐ + // rect.left๋ ์ด๋ฏธ ์ค์ ์ ๋ ฌ๋ ์์น๋ฅผ ๋ฐ์ํ๊ณ ์์ + + // ๋ง์ฐ์ค์ ์บ๋ฒ์ค ๋ด ์๋ ์์น (์ค์ผ์ผ ๋ณด์ ) + const mouseXInCanvas = (e.clientX - rect.left) / zoomLevel; + const mouseYInCanvas = (e.clientY - rect.top) / zoomLevel; - // ๋ฐฉ๋ฒ 2: ๋ง์ฐ์ค ํฌ์ธํฐ๋ฅผ ์ปดํฌ๋ํธ ์ข์๋จ์ผ๋ก (์ฌ์ฉ์๊ฐ ์ํ ์๋ ์๋ ๋ฐฉ์) - const dropX_topleft = e.clientX - rect.left; - const dropY_topleft = e.clientY - rect.top; + // ๋ฐฉ๋ฒ 1: ๋ง์ฐ์ค ํฌ์ธํฐ๋ฅผ ์ปดํฌ๋ํธ ์ค์ฌ์ผ๋ก + const dropX_centered = mouseXInCanvas - componentWidth / 2; + const dropY_centered = mouseYInCanvas - componentHeight / 2; + + // ๋ฐฉ๋ฒ 2: ๋ง์ฐ์ค ํฌ์ธํฐ๋ฅผ ์ปดํฌ๋ํธ ์ข์๋จ์ผ๋ก + const dropX_topleft = mouseXInCanvas; + const dropY_topleft = mouseYInCanvas; // ์ฌ์ฉ์๊ฐ ์ํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ: ๋ง์ฐ์ค ํฌ์ธํฐ๊ฐ ์ข์๋จ์ ์ค๋๋ก const dropX = dropX_topleft; const dropY = dropY_topleft; - console.log("๐ฏ ์์น ๊ณ์ฐ ๋๋ฒ๊น :", { - "1. ๋ง์ฐ์ค ์์น": { clientX: e.clientX, clientY: e.clientY }, - "2. ์บ๋ฒ์ค ์์น": { left: rect.left, top: rect.top, width: rect.width, height: rect.height }, - "3. ์บ๋ฒ์ค ๋ด ์๋ ์์น": { x: e.clientX - rect.left, y: e.clientY - rect.top }, - "4. ์ปดํฌ๋ํธ ํฌ๊ธฐ": { width: componentWidth, height: componentHeight }, - "5a. ์ค์ฌ ๋ฐฉ์ ์ข์๋จ": { x: dropX_centered, y: dropY_centered }, - "5b. ์ข์๋จ ๋ฐฉ์": { x: dropX_topleft, y: dropY_topleft }, - "6. ์ ํ๋ ๋ฐฉ์": { dropX, dropY }, - "7. ์์ ์ปดํฌ๋ํธ ์ค์ฌ": { x: dropX + componentWidth / 2, y: dropY + componentHeight / 2 }, - "8. ๋ง์ฐ์ค์ ์ค์ฌ ์ผ์น ํ์ธ": { - match: - Math.abs(dropX + componentWidth / 2 - (e.clientX - rect.left)) < 1 && - Math.abs(dropY + componentHeight / 2 - (e.clientY - rect.top)) < 1, - }, + console.log("๐ฏ ์์น ๊ณ์ฐ ๋๋ฒ๊น (์ค ๋ ๋ฒจ + ์ค์์ ๋ ฌ ๋ฐ์):", { + "1. ์ค ๋ ๋ฒจ": zoomLevel, + "2. ๋ง์ฐ์ค ์์น (ํ๋ฉด)": { clientX: e.clientX, clientY: e.clientY }, + "3. ์บ๋ฒ์ค ์์น (rect)": { left: rect.left, top: rect.top, width: rect.width, height: rect.height }, + "4. ์บ๋ฒ์ค ๋ ผ๋ฆฌ์ ํฌ๊ธฐ": { width: canvasLogicalWidth, height: screenResolution.height }, + "5. ์บ๋ฒ์ค ์๊ฐ์ ํฌ๊ธฐ": { width: canvasVisualWidth, height: screenResolution.height * zoomLevel }, + "6. ๋ง์ฐ์ค ์บ๋ฒ์ค ๋ด ์๋์์น (์ค ์ )": { x: e.clientX - rect.left, y: e.clientY - rect.top }, + "7. ๋ง์ฐ์ค ์บ๋ฒ์ค ๋ด ์๋์์น (์ค ๋ณด์ )": { x: mouseXInCanvas, y: mouseYInCanvas }, + "8. ์ปดํฌ๋ํธ ํฌ๊ธฐ": { width: componentWidth, height: componentHeight }, + "9a. ์ค์ฌ ๋ฐฉ์": { x: dropX_centered, y: dropY_centered }, + "9b. ์ข์๋จ ๋ฐฉ์": { x: dropX_topleft, y: dropY_topleft }, + "10. ์ต์ข ์ ํ": { dropX, dropY }, }); // ํ์ฌ ํด์๋์ ๋ง๋ ๊ฒฉ์ ์ ๋ณด ๊ณ์ฐ @@ -2826,7 +2844,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ์ปดํฌ๋ํธ ๋๋๊ทธ ์์ const startComponentDrag = useCallback( - (component: ComponentData, event: React.MouseEvent) => { + (component: ComponentData, event: React.MouseEvent | React.DragEvent) => { event.preventDefault(); const rect = canvasRef.current?.getBoundingClientRect(); if (!rect) return; @@ -2839,9 +2857,10 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD })); } - // ์บ๋ฒ์ค ๋ด๋ถ์ ์๋ ์ขํ ๊ณ์ฐ (์คํฌ๋กค ์๋ ๊ณ ์ ์บ๋ฒ์ค) - const relativeMouseX = event.clientX - rect.left; - const relativeMouseY = event.clientY - rect.top; + // ๐ฅ ์ค์: ์ค ๋ ๋ฒจ์ ๊ณ ๋ คํ ๋ง์ฐ์ค ์์น ๊ณ์ฐ + // ์บ๋ฒ์ค๊ฐ scale() ๋ณํ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋ง์ฐ์ค ์์น๋ ์ญ๋ณํ ํ์ + const relativeMouseX = (event.clientX - rect.left) / zoomLevel; + const relativeMouseY = (event.clientY - rect.top) / zoomLevel; // ๋ค์ค ์ ํ๋ ์ปดํฌ๋ํธ๋ค ํ์ธ const isDraggedComponentSelected = groupState.selectedComponents.includes(component.id); @@ -2866,13 +2885,14 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD } // console.log("๋๋๊ทธ ์์:", component.id, "์ด๋ํ ์ปดํฌ๋ํธ ์:", componentsToMove.length); - console.log("๋ง์ฐ์ค ์์น:", { + console.log("๋ง์ฐ์ค ์์น (์ค ๋ณด์ ):", { + zoomLevel, clientX: event.clientX, clientY: event.clientY, rectLeft: rect.left, rectTop: rect.top, - relativeX: relativeMouseX, - relativeY: relativeMouseY, + mouseRaw: { x: event.clientX - rect.left, y: event.clientY - rect.top }, + mouseZoomCorrected: { x: relativeMouseX, y: relativeMouseY }, componentX: component.position.x, componentY: component.position.y, grabOffsetX: relativeMouseX - component.position.x, @@ -2906,7 +2926,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD justFinishedDrag: false, }); }, - [groupState.selectedComponents, layout.components, dragState.justFinishedDrag], + [groupState.selectedComponents, layout.components, dragState.justFinishedDrag, zoomLevel], ); // ๋๋๊ทธ ์ค ์์น ์ ๋ฐ์ดํธ (์ฑ๋ฅ ์ต์ ํ + ์ค์๊ฐ ์ ๋ฐ์ดํธ) @@ -2916,9 +2936,10 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD const rect = canvasRef.current.getBoundingClientRect(); - // ์บ๋ฒ์ค ๋ด๋ถ์ ์๋ ์ขํ ๊ณ์ฐ (์คํฌ๋กค ์๋ ๊ณ ์ ์บ๋ฒ์ค) - const relativeMouseX = event.clientX - rect.left; - const relativeMouseY = event.clientY - rect.top; + // ๐ฅ ์ค์: ์ค ๋ ๋ฒจ์ ๊ณ ๋ คํ ๋ง์ฐ์ค ์์น ๊ณ์ฐ + // ์บ๋ฒ์ค๊ฐ scale() ๋ณํ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋ง์ฐ์ค ์์น๋ ์ญ๋ณํ ํ์ + const relativeMouseX = (event.clientX - rect.left) / zoomLevel; + const relativeMouseY = (event.clientY - rect.top) / zoomLevel; // ์ปดํฌ๋ํธ ํฌ๊ธฐ ๊ฐ์ ธ์ค๊ธฐ const draggedComp = layout.components.find((c) => c.id === dragState.draggedComponent.id); @@ -2936,8 +2957,11 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD }; // ๋๋๊ทธ ์ํ ์ ๋ฐ์ดํธ - console.log("๐ฅ ScreenDesigner updateDragPosition:", { + console.log("๐ฅ ScreenDesigner updateDragPosition (์ค ๋ณด์ ):", { + zoomLevel, draggedComponentId: dragState.draggedComponent.id, + mouseRaw: { x: event.clientX - rect.left, y: event.clientY - rect.top }, + mouseZoomCorrected: { x: relativeMouseX, y: relativeMouseY }, oldPosition: dragState.currentPosition, newPosition: newPosition, }); @@ -2961,7 +2985,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD // ์ค์ ๋ ์ด์์ ์ ๋ฐ์ดํธ๋ endDrag์์ ์ฒ๋ฆฌ // ์์ฑ ํจ๋์์๋ dragState.currentPosition์ ์ฐธ์กฐํ์ฌ ์ค์๊ฐ ํ์ }, - [dragState.isDragging, dragState.draggedComponent, dragState.grabOffset], + [dragState.isDragging, dragState.draggedComponent, dragState.grabOffset, zoomLevel], ); // ๋๋๊ทธ ์ข ๋ฃ @@ -4416,7 +4440,7 @@ export default function ScreenDesigner({ selectedScreen, onBackToList }: ScreenD ); })()} - {/* ๐ฅ ์ค ์ ์ฉ ์ ์คํฌ๋กค ์์ญ ํ๋ณด๋ฅผ ์ํ ๋ํผ */} + {/* ๐ฅ ์ค ์ ์ฉ ์ ์คํฌ๋กค ์์ญ ํ๋ณด๋ฅผ ์ํ ๋ํผ - ์ค์ ์ ๋ ฌ๋ก ๋ณ๊ฒฝ */}
+ โ ๏ธ ์ปฌ๋ผ ๋๋น๊ฐ ๋๋ฌด ์์ต๋๋ค (์ต์ {MIN_COLUMN_WIDTH}px ๊ถ์ฅ) +
+ )}- 1 ์ด์์ ์ซ์๋ฅผ ์ ๋ ฅํ์ธ์ + ์ต๋ {safeMaxColumns}๊ฐ๊น์ง ์ค์ ๊ฐ๋ฅ (์ต์ ์ปฌ๋ผ ๋๋น {MIN_COLUMN_WIDTH}px)