From 18cc8f23db1bdc2ca380ee9605e5c92bb54d9454 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 21:40:13 +0300 Subject: [PATCH] 6 --- src/components/LayoutStep.tsx | 82 +++++++++++++++++++++++------------ 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/src/components/LayoutStep.tsx b/src/components/LayoutStep.tsx index fcb5a66..23dd414 100644 --- a/src/components/LayoutStep.tsx +++ b/src/components/LayoutStep.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState, useMemo } from 'react'; import { AppConfig, LayoutSplits, Partition } from '../types'; -import { Grid, MousePointer2, Trash2, RotateCcw, X, Plus } from 'lucide-react'; +import { Grid, MousePointer2, Trash2, RotateCcw, X, Move } from 'lucide-react'; interface Props { config: AppConfig; @@ -11,31 +11,39 @@ interface Props { type EditMode = 'lines' | 'cells'; type Axis = 'x' | 'y'; -// Тип для перетаскивания +// Типы для перетаскивания type DragTarget = | { type: 'main'; axis: Axis; index: number } | { type: 'partition'; cellKey: string; id: string; axis: Axis }; 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); + + // 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 (Защита от вылетов) --- + // --- Safe Data --- const safeX = Array.isArray(splits?.x) ? splits.x : []; const safeY = Array.isArray(splits?.y) ? splits.y : []; const safePartitions = splits?.partitions || {}; + // Размеры const drawerW = Math.max(1, config.drawer.width || 300); const drawerD = Math.max(1, config.drawer.depth || 400); const aspectRatio = drawerD / drawerW; @@ -72,7 +80,10 @@ 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); @@ -115,13 +126,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); } - - newOffset = Math.max(0.05, Math.min(0.95, newOffset)); - if (!isNaN(newOffset)) { - updatePartition(pDrag.cellKey, pDrag.id, { offset: newOffset }); + 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 }); } return; } @@ -230,18 +241,33 @@ 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. Макет + 2. Редактор макета

+
- - + +
+ @@ -252,12 +278,12 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { {mode === 'lines' ? (
- ЛКМ: Линия + ЛКМ: Создать/Тянуть линию ПКМ: Удалить
) : (
- ЛКМ в ячейке: Стенка + ЛКМ в ячейке: Создать стенку Драг: Двигать 2xЛКМ: Удалить
@@ -267,7 +293,8 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { {/* CANVAS */}
-
1 ? 'auto' : '100%', height: aspectRatio > 1 ? '100%' : 'auto', @@ -288,14 +315,12 @@ 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 y2 = sortedY[j + 1]; // ВОТ ЭТО ИСПРАВЛЕНО + const cellX = x1 * viewBoxW; const cellY = y1 * viewBoxH; const cellW = (x2 - x1) * viewBoxW; const cellH = (y2 - y1) * viewBoxH; @@ -306,14 +331,18 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { 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); }}> @@ -321,6 +350,8 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { ); })} + + {/* Phantom Partition */} {isHovered && phantomPartition && !hoveredPartition && !dragging && ( {phantomPartition.axis === 'x' ? @@ -334,7 +365,7 @@ 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 ( @@ -344,8 +375,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { ); })} - - {/* --- MAIN GRID Y --- */} {safeY.map((y, i) => { const isHovered = hoveredMainSplit?.axis === 'y' && hoveredMainSplit.index === i; return ( @@ -363,7 +392,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
- {/* --- SIDEBAR --- */} + {/* SIDEBAR */} {mode === 'cells' && selectedData && (
@@ -387,7 +416,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
)}
-
); }; \ No newline at end of file