From cde48b08432653f1a769a4b8dbc3b1f053f052cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A5=D0=B0=D0=BB=D0=B8=D0=BC=D0=BE=D0=B2=20=D0=A0=D1=83?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=BC?= Date: Sat, 10 Jan 2026 20:50:09 +0300 Subject: [PATCH] 4 --- src/components/LayoutStep.tsx | 122 ++++++---------------------------- 1 file changed, 19 insertions(+), 103 deletions(-) diff --git a/src/components/LayoutStep.tsx b/src/components/LayoutStep.tsx index dcf746c..7b54400 100644 --- a/src/components/LayoutStep.tsx +++ b/src/components/LayoutStep.tsx @@ -1,6 +1,5 @@ import React, { useRef, useState, useMemo } from 'react'; import { AppConfig, LayoutSplits, Partition } from '../types'; -// Импортируем только то, что реально используем import { Grid, MousePointer2, Trash2, RotateCcw, X, Plus } from 'lucide-react'; interface Props { @@ -22,29 +21,23 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const [mode, setMode] = useState('lines'); - // Мышь и состояния const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); const [isButtonHovered, setIsButtonHovered] = useState(false); - // Main Grid States const [phantomMainAxis, setPhantomMainAxis] = useState(null); const [hoveredMainSplit, setHoveredMainSplit] = useState<{ axis: Axis; index: number } | null>(null); - // Partition States 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); - // Selection & Dragging const [selectedPartitionId, setSelectedPartitionId] = useState(null); const [dragging, setDragging] = useState(null); - // --- Safe Data Access --- const safeX = Array.isArray(splits?.x) ? splits.x : []; const safeY = Array.isArray(splits?.y) ? splits.y : []; const safePartitions = splits?.partitions || {}; - // --- Dimensions & Aspect Ratio --- const drawerW = Math.max(1, config.drawer.width || 300); const drawerD = Math.max(1, config.drawer.depth || 400); const aspectRatio = drawerD / drawerW; @@ -55,7 +48,6 @@ 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: Get Partition Data --- const getSelectedPartition = () => { if (!selectedPartitionId) return null; for (const key in safePartitions) { @@ -66,7 +58,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { }; const selectedData = getSelectedPartition(); - // --- ACTIONS: MAIN GRID --- const removeMainSplit = (axis: Axis, index: number) => { const newSplits = { ...splits, x: [...safeX], y: [...safeY] }; newSplits[axis] = newSplits[axis].filter((_, i) => i !== index); @@ -76,7 +67,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { setIsButtonHovered(false); }; - // --- ACTIONS: PARTITIONS --- const createPartition = (i: number, j: number, axis: Axis, offset: number) => { const key = `${i}-${j}`; const current = safePartitions[key] || []; @@ -104,18 +94,15 @@ 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(); if (rect.width === 0) return; - // Normalize coordinates (0 to 1) const nx = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); const ny = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height)); setMousePos({ x: nx, y: ny }); - // --- DRAGGING LOGIC --- if (dragging) { if (dragging.type === 'main') { const newSplits = { ...splits, x: [...safeX], y: [...safeY] }; @@ -123,11 +110,9 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { newSplits[dragging.axis][dragging.index] = val; onChange(newSplits); } else { - // Dragging Partition const pDrag = dragging as { type: 'partition'; cellKey: string; id: string; axis: Axis }; const [iStr, jStr] = pDrag.cellKey.split('-'); const i = parseInt(iStr); const j = parseInt(jStr); - const cellX1 = sortedX[i]; const cellX2 = sortedX[i+1]; const cellY1 = sortedY[j]; const cellY2 = sortedY[j+1]; @@ -145,12 +130,9 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { if (isButtonHovered) return; - // --- HOVER LOGIC: MAIN LINES --- if (mode === 'lines') { setHoveredMainSplit(null); const SNAP = 0.015; - - // Phantom Line if (nx > SNAP && nx < 1-SNAP && ny > SNAP && ny < 1-SNAP) { const closeToX = safeX.some(val => Math.abs(nx - val) < SNAP); const closeToY = safeY.some(val => Math.abs(ny - val) < SNAP); @@ -159,15 +141,11 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { setPhantomMainAxis(Math.min(nx, distRight) < Math.min(ny, distBottom) ? 'y' : 'x'); } else { setPhantomMainAxis(null); } } - } - - // --- HOVER LOGIC: PARTITIONS --- - else if (mode === 'cells') { + } else if (mode === 'cells') { setHoveredPartition(null); setPhantomPartition(null); setHoveredCell(null); - // 1. Find which cell we are in let cellIndex = null; for(let i=0; i= sortedX[i] && nx <= sortedX[i+1]) { @@ -188,15 +166,11 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const cx1 = sortedX[cellIndex.i]; const cx2 = sortedX[cellIndex.i+1]; const cy1 = sortedY[cellIndex.j]; const cy2 = sortedY[cellIndex.j+1]; const cw = cx2 - cx1; const ch = cy2 - cy1; - - // Local coords in cell (0..1) const lx = (nx - cx1) / cw; const ly = (ny - cy1) / ch; - // Check existing partitions let foundPart = null; const PART_SNAP = 0.05; - for (const p of parts) { if (p.axis === 'x') { if (Math.abs(lx - p.offset) < PART_SNAP * (aspectRatio > 1 ? 1 : aspectRatio)) foundPart = p; @@ -208,14 +182,11 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { if (foundPart) { setHoveredPartition({ id: foundPart.id, cellKey: key }); } else { - // Show Phantom Partition const distLeft = lx; const distRight = 1 - lx; const distTop = ly; const distBottom = 1 - ly; const minX = Math.min(distLeft, distRight); const minY = Math.min(distTop, distBottom); - const axis = minX < minY ? 'y' : 'x'; - if (lx > 0.05 && lx < 0.95 && ly > 0.05 && ly < 0.95) { setPhantomPartition({ axis, offset: axis === 'x' ? lx : ly }); } @@ -238,8 +209,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { onChange(newSplits); setDragging({ type: 'main', axis: phantomMainAxis, index: newSplits[phantomMainAxis].length - 1 }); } - } - else if (mode === 'cells') { + } else if (mode === 'cells') { if (hoveredPartition) { const key = hoveredPartition.cellKey; const parts = safePartitions[key] || []; @@ -262,39 +232,23 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { } }; - const handleMainSplitHover = (e: React.MouseEvent, axis: Axis, index: number) => { - if (mode !== 'lines' || dragging) return; - e.stopPropagation(); - setHoveredMainSplit({ axis, index }); - setPhantomMainAxis(null); - }; - return (
- {/* HEADER */}

2. Редактор макета

-
- - + +
-
- {/* --- INSTRUCTIONS --- */} + {/* INSTRUCTIONS */}
{mode === 'lines' ? ( @@ -311,11 +265,10 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { )}
- {/* --- CANVAS --- */} + {/* CANVAS */}
-
1 ? 'auto' : '100%', height: aspectRatio > 1 ? '100%' : 'auto', @@ -325,16 +278,9 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { : (dragging ? 'grabbing' : hoveredPartition ? 'grab' : hoveredCell ? 'crosshair' : 'default'), }} > - setDragging(null)} - onMouseLeave={() => setDragging(null)} - onContextMenu={(e) => e.preventDefault()} + onMouseMove={handleGlobalMouseMove} onMouseDown={handleMouseDown} onMouseUp={() => setDragging(null)} onMouseLeave={() => setDragging(null)} onContextMenu={(e) => e.preventDefault()} > @@ -343,34 +289,28 @@ 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]; 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'; const isAnyPartSelected = parts.some(p => p.id === selectedPartitionId); return ( - {/* Highlight Cell */} {isHovered && } {isAnyPartSelected && mode === 'cells' && } - - {/* Existing Partitions */} {parts.map(p => { const isSelected = selectedPartitionId === p.id; const isHoveredPart = hoveredPartition?.id === p.id; let lx1, ly1, lx2, ly2; if (p.axis === 'x') { const px = cellX + (cellW * p.offset); lx1 = px; ly1 = cellY; lx2 = px; ly2 = cellY + cellH; } else { const py = cellY + (cellH * p.offset); lx1 = cellX; ly1 = py; lx2 = cellX + cellW; ly2 = py; } - return ( { e.stopPropagation(); removePartition(key, p.id); }}> @@ -378,8 +318,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { ); })} - - {/* Phantom Partition */} {isHovered && phantomPartition && !hoveredPartition && !dragging && ( {phantomPartition.axis === 'x' ? @@ -393,28 +331,20 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { }); })} - {/* --- MAIN GRID X --- */} + {/* MAIN GRID */} {safeX.map((x, i) => { const isHovered = hoveredMainSplit?.axis === 'x' && hoveredMainSplit.index === i; return ( - { if(mode === 'lines' && !dragging) { e.stopPropagation(); setHoveredMainSplit({axis: 'x', index: i}); setPhantomMainAxis(null); }}} - onDoubleClick={(e) => { e.stopPropagation(); removeMainSplit('x', i); }} - > + { if(mode === 'lines' && !dragging) { e.stopPropagation(); setHoveredMainSplit({axis: 'x', index: i}); setPhantomMainAxis(null); }}} onDoubleClick={(e) => { e.stopPropagation(); removeMainSplit('x', i); }}> ); })} - - {/* --- MAIN GRID Y --- */} {safeY.map((y, i) => { const isHovered = hoveredMainSplit?.axis === 'y' && hoveredMainSplit.index === i; return ( - { if(mode === 'lines' && !dragging) { e.stopPropagation(); setHoveredMainSplit({axis: 'y', index: i}); setPhantomMainAxis(null); }}} - onDoubleClick={(e) => { e.stopPropagation(); removeMainSplit('y', i); }} - > + { if(mode === 'lines' && !dragging) { e.stopPropagation(); setHoveredMainSplit({axis: 'y', index: i}); setPhantomMainAxis(null); }}} onDoubleClick={(e) => { e.stopPropagation(); removeMainSplit('y', i); }}> @@ -428,44 +358,30 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
- {/* --- SIDEBAR --- */} + {/* SIDEBAR */} {mode === 'cells' && selectedData && (
-

- Настройки стенки -

+

Настройки стенки

-
-
- Высота {selectedData.part.height} мм -
- updatePartition(selectedData.key, selectedData.part.id, { height: parseFloat(e.target.value) })} - className="w-full h-1 bg-slate-600 rounded-lg appearance-none cursor-pointer accent-purple-500" - /> +
Высота {selectedData.part.height} мм
+ updatePartition(selectedData.key, selectedData.part.id, { height: parseFloat(e.target.value) })} className="w-full h-1 bg-slate-600 rounded-lg appearance-none cursor-pointer accent-purple-500"/>
- updatePartition(selectedData.key, selectedData.part.id, { rounded: e.target.checked })} - className="w-4 h-4 rounded bg-slate-700 border-slate-600 text-purple-500 focus:ring-0 cursor-pointer" - /> + updatePartition(selectedData.key, selectedData.part.id, { rounded: e.target.checked })} className="w-4 h-4 rounded bg-slate-700 border-slate-600 text-purple-500 focus:ring-0 cursor-pointer"/>
-
- Выделите стенку для настройки.
Двойной клик удаляет её. -
+
Выделите стенку для настройки.
Двойной клик удаляет её.
)}
-
); }; \ No newline at end of file