From 5b79bfb19d9811e3d038b848a9da684833a932ae Mon Sep 17 00:00:00 2001 From: kjs Date: Fri, 7 Nov 2025 11:36:58 +0900 Subject: [PATCH] =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=9C=84=EC=B9=98=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/components/screen/ScreenDesigner.tsx | 102 +++++++++++------- .../components/screen/panels/GridPanel.tsx | 24 +++-- .../screen/panels/UnifiedPropertiesPanel.tsx | 14 ++- frontend/lib/utils/gridUtils.ts | 17 ++- 4 files changed, 106 insertions(+), 51 deletions(-) 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 ); })()} - {/* ๐Ÿ”ฅ ์คŒ ์ ์šฉ ์‹œ ์Šคํฌ๋กค ์˜์—ญ ํ™•๋ณด๋ฅผ ์œ„ํ•œ ๋ž˜ํผ */} + {/* ๐Ÿ”ฅ ์คŒ ์ ์šฉ ์‹œ ์Šคํฌ๋กค ์˜์—ญ ํ™•๋ณด๋ฅผ ์œ„ํ•œ ๋ž˜ํผ - ์ค‘์•™ ์ •๋ ฌ๋กœ ๋ณ€๊ฒฝ */}
= ({ }); }; + // ์ตœ๋Œ€ ์ปฌ๋Ÿผ ์ˆ˜ ๊ณ„์‚ฐ (์ตœ์†Œ ์ปฌ๋Ÿผ ๋„ˆ๋น„ 30px ๊ธฐ์ค€) + const MIN_COLUMN_WIDTH = 30; + const maxColumns = screenResolution + ? Math.floor((screenResolution.width - gridSettings.padding * 2 + gridSettings.gap) / (MIN_COLUMN_WIDTH + gridSettings.gap)) + : 24; + const safeMaxColumns = Math.max(1, Math.min(maxColumns, 100)); // ์ตœ๋Œ€ 100๊ฐœ๋กœ ์ œํ•œ + // ์‹ค์ œ ๊ฒฉ์ž ์ •๋ณด ๊ณ„์‚ฐ const actualGridInfo = screenResolution ? calculateGridInfo(screenResolution.width, screenResolution.height, { @@ -49,7 +56,7 @@ export const GridPanel: React.FC = ({ // ์ปฌ๋Ÿผ์ด ๋„ˆ๋ฌด ์ž‘์€์ง€ ํ™•์ธ const isColumnsTooSmall = screenResolution && actualGridInfo - ? actualGridInfo.columnWidth < 30 // 30px ๋ฏธ๋งŒ์ด๋ฉด ๋„ˆ๋ฌด ์ž‘๋‹ค๊ณ  ํŒ๋‹จ + ? actualGridInfo.columnWidth < MIN_COLUMN_WIDTH : false; return ( @@ -134,22 +141,22 @@ export const GridPanel: React.FC = ({ id="columns" type="number" min={1} - max={24} + max={safeMaxColumns} value={gridSettings.columns} onChange={(e) => { const value = parseInt(e.target.value, 10); - if (!isNaN(value) && value >= 1 && value <= 24) { + if (!isNaN(value) && value >= 1 && value <= safeMaxColumns) { updateSetting("columns", value); } }} className="h-8 text-xs" /> - / 24 + / {safeMaxColumns}
updateSetting("columns", value)} @@ -157,8 +164,13 @@ export const GridPanel: React.FC = ({ />
1์—ด - 24์—ด + {safeMaxColumns}์—ด
+ {isColumnsTooSmall && ( +

+ โš ๏ธ ์ปฌ๋Ÿผ ๋„ˆ๋น„๊ฐ€ ๋„ˆ๋ฌด ์ž‘์Šต๋‹ˆ๋‹ค (์ตœ์†Œ {MIN_COLUMN_WIDTH}px ๊ถŒ์žฅ) +

+ )}
diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index 8d7cd091..6d063640 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -139,6 +139,13 @@ export const UnifiedPropertiesPanel: React.FC = ({ const renderGridSettings = () => { if (!gridSettings || !onGridSettingsChange) return null; + // ์ตœ๋Œ€ ์ปฌ๋Ÿผ ์ˆ˜ ๊ณ„์‚ฐ + const MIN_COLUMN_WIDTH = 30; + const maxColumns = currentResolution + ? Math.floor((currentResolution.width - gridSettings.padding * 2 + gridSettings.gap) / (MIN_COLUMN_WIDTH + gridSettings.gap)) + : 24; + const safeMaxColumns = Math.max(1, Math.min(maxColumns, 100)); // ์ตœ๋Œ€ 100๊ฐœ๋กœ ์ œํ•œ + return (
@@ -190,21 +197,22 @@ export const UnifiedPropertiesPanel: React.FC = ({ id="columns" type="number" min={1} + max={safeMaxColumns} step="1" value={gridSettings.columns} onChange={(e) => { const value = parseInt(e.target.value, 10); - if (!isNaN(value) && value >= 1) { + if (!isNaN(value) && value >= 1 && value <= safeMaxColumns) { updateGridSetting("columns", value); } }} className="h-6 px-2 py-0 text-xs" style={{ fontSize: "12px" }} - placeholder="1 ์ด์ƒ์˜ ์ˆซ์ž" + placeholder={`1~${safeMaxColumns}`} />

- 1 ์ด์ƒ์˜ ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š” + ์ตœ๋Œ€ {safeMaxColumns}๊ฐœ๊นŒ์ง€ ์„ค์ • ๊ฐ€๋Šฅ (์ตœ์†Œ ์ปฌ๋Ÿผ ๋„ˆ๋น„ {MIN_COLUMN_WIDTH}px)

diff --git a/frontend/lib/utils/gridUtils.ts b/frontend/lib/utils/gridUtils.ts index 419937f0..7ea3f6b4 100644 --- a/frontend/lib/utils/gridUtils.ts +++ b/frontend/lib/utils/gridUtils.ts @@ -15,17 +15,28 @@ export function calculateGridInfo( containerHeight: number, gridSettings: GridSettings, ): GridInfo { - const { columns, gap, padding } = gridSettings; + const { gap, padding } = gridSettings; + let { columns } = gridSettings; - // ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋„ˆ๋น„ ๊ณ„์‚ฐ (ํŒจ๋”ฉ ์ œ์™ธ) + // ๐Ÿ”ฅ ์ตœ์†Œ ์ปฌ๋Ÿผ ๋„ˆ๋น„๋ฅผ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ตœ๋Œ€ ์ปฌ๋Ÿผ ์ˆ˜ ๊ณ„์‚ฐ + const MIN_COLUMN_WIDTH = 30; // ์ตœ์†Œ ์ปฌ๋Ÿผ ๋„ˆ๋น„ 30px const availableWidth = containerWidth - padding * 2; + const maxPossibleColumns = Math.floor((availableWidth + gap) / (MIN_COLUMN_WIDTH + gap)); + + // ์„ค์ •๋œ ์ปฌ๋Ÿผ ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์œผ๋ฉด ์ž๋™์œผ๋กœ ์ œํ•œ + if (columns > maxPossibleColumns) { + console.warn( + `โš ๏ธ ๊ฒฉ์ž ์ปฌ๋Ÿผ ์ˆ˜๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์Šต๋‹ˆ๋‹ค. ${columns}๊ฐœ โ†’ ${maxPossibleColumns}๊ฐœ๋กœ ์ž๋™ ์กฐ์ •๋จ (์ตœ์†Œ ์ปฌ๋Ÿผ ๋„ˆ๋น„: ${MIN_COLUMN_WIDTH}px)`, + ); + columns = Math.max(1, maxPossibleColumns); + } // ๊ฒฉ์ž ๊ฐ„๊ฒฉ์„ ๊ณ ๋ คํ•œ ์ปฌ๋Ÿผ ๋„ˆ๋น„ ๊ณ„์‚ฐ const totalGaps = (columns - 1) * gap; const columnWidth = (availableWidth - totalGaps) / columns; return { - columnWidth: Math.max(columnWidth, 20), // ์ตœ์†Œ 20px๋กœ ์ค„์—ฌ์„œ ๋” ๋งŽ์€ ์ปฌ๋Ÿผ ํ‘œ์‹œ + columnWidth: Math.max(columnWidth, MIN_COLUMN_WIDTH), totalWidth: containerWidth, totalHeight: containerHeight, };