diff --git a/src/components/LayoutStep.tsx b/src/components/LayoutStep.tsx index 7b54400..fcb5a66 100644 --- a/src/components/LayoutStep.tsx +++ b/src/components/LayoutStep.tsx @@ -18,22 +18,20 @@ type DragTarget = export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const svgRef = useRef(null); - const [mode, setMode] = useState('lines'); + // States const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); const [isButtonHovered, setIsButtonHovered] = useState(false); - const [phantomMainAxis, setPhantomMainAxis] = useState(null); const [hoveredMainSplit, setHoveredMainSplit] = useState<{ axis: Axis; index: number } | null>(null); - const [hoveredCell, setHoveredCell] = useState<{ i: number; j: number } | null>(null); const [hoveredPartition, setHoveredPartition] = useState<{ id: string; cellKey: string } | null>(null); const [phantomPartition, setPhantomPartition] = useState<{ axis: Axis; offset: number } | null>(null); - const [selectedPartitionId, setSelectedPartitionId] = useState(null); const [dragging, setDragging] = useState(null); + // --- Safe Data (Защита от вылетов) --- const safeX = Array.isArray(splits?.x) ? splits.x : []; const safeY = Array.isArray(splits?.y) ? splits.y : []; const safePartitions = splits?.partitions || {}; @@ -48,6 +46,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const sortedX = useMemo(() => [0, ...safeX, 1].sort((a, b) => a - b), [safeX]); const sortedY = useMemo(() => [0, ...safeY, 1].sort((a, b) => a - b), [safeY]); + // --- Helper --- const getSelectedPartition = () => { if (!selectedPartitionId) return null; for (const key in safePartitions) { @@ -58,6 +57,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { }; const selectedData = getSelectedPartition(); + // --- ACTIONS --- const removeMainSplit = (axis: Axis, index: number) => { const newSplits = { ...splits, x: [...safeX], y: [...safeY] }; newSplits[axis] = newSplits[axis].filter((_, i) => i !== index); @@ -72,10 +72,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const current = safePartitions[key] || []; const newPart: Partition = { id: Math.random().toString(36).substr(2, 9), - axis, - offset, - height: config.drawer.height || 80, - rounded: false + axis, offset, height: config.drawer.height || 80, rounded: false }; onChange({ ...splits, partitions: { ...safePartitions, [key]: [...current, newPart] } }); setSelectedPartitionId(newPart.id); @@ -94,6 +91,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { setHoveredPartition(null); }; + // --- MOUSE HANDLER --- const handleMouseMove = (e: React.MouseEvent) => { if (!svgRef.current) return; const rect = svgRef.current.getBoundingClientRect(); @@ -117,13 +115,13 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const cellY1 = sortedY[j]; const cellY2 = sortedY[j+1]; let newOffset = 0; - if (pDrag.axis === 'x') { - newOffset = (nx - cellX1) / (cellX2 - cellX1); - } else { - newOffset = (ny - cellY1) / (cellY2 - cellY1); - } + if (pDrag.axis === 'x') { newOffset = (nx - cellX1) / (cellX2 - cellX1); } + else { newOffset = (ny - cellY1) / (cellY2 - cellY1); } + newOffset = Math.max(0.05, Math.min(0.95, newOffset)); - updatePartition(pDrag.cellKey, pDrag.id, { offset: newOffset }); + if (!isNaN(newOffset)) { + updatePartition(pDrag.cellKey, pDrag.id, { offset: newOffset }); + } } return; } @@ -234,10 +232,11 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { return (
+ {/* HEADER */}

- 2. Редактор макета + 2. Макет

@@ -253,12 +252,12 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { {mode === 'lines' ? (
- ЛКМ: Создать/Тянуть линию + ЛКМ: Линия ПКМ: Удалить
) : (
- ЛКМ в ячейке: Создать стенку + ЛКМ в ячейке: Стенка Драг: Двигать 2xЛКМ: Удалить
@@ -289,13 +288,17 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { - {/* CELLS & PARTITIONS */} + {/* --- CELLS & PARTITIONS --- */} {sortedX.slice(0, -1).map((x1, i) => { const x2 = sortedX[i + 1]; return sortedY.slice(0, -1).map((y1, j) => { const y2 = sortedY[j + 1]; + // SAFETY CHECK: Если y2 не определен (такого быть не должно, но вдруг) + if (y2 === undefined) return null; + const cellX = x1 * viewBoxW; const cellY = y1 * viewBoxH; const cellW = (x2 - x1) * viewBoxW; const cellH = (y2 - y1) * viewBoxH; + const key = `${i}-${j}`; const parts = safePartitions[key] || []; const isHovered = hoveredCell?.i === i && hoveredCell?.j === j && mode === 'cells'; @@ -331,7 +334,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { }); })} - {/* MAIN GRID */} + {/* --- MAIN GRID X --- */} {safeX.map((x, i) => { const isHovered = hoveredMainSplit?.axis === 'x' && hoveredMainSplit.index === i; return ( @@ -341,6 +344,8 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { ); })} + + {/* --- MAIN GRID Y --- */} {safeY.map((y, i) => { const isHovered = hoveredMainSplit?.axis === 'y' && hoveredMainSplit.index === i; return ( @@ -358,7 +363,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
- {/* SIDEBAR */} + {/* --- SIDEBAR --- */} {mode === 'cells' && selectedData && (
@@ -382,6 +387,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
)}
+
); }; \ No newline at end of file