From 71ee481c80e7905098d23b0d42422761c443597b 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, 27 Dec 2025 21:57:45 +0300 Subject: [PATCH 1/4] Added 2 column --- src/components/PreviewStep.tsx | 69 ++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/src/components/PreviewStep.tsx b/src/components/PreviewStep.tsx index b18b6d8..58c67a9 100644 --- a/src/components/PreviewStep.tsx +++ b/src/components/PreviewStep.tsx @@ -5,7 +5,7 @@ import * as THREE from 'three'; import JSZip from 'jszip'; import { AppConfig, GeneratedPart, LayoutSplits } from '../types'; import { createBinGeometry, generateSTL, exportSTL } from '../services/geometryGenerator'; -import { Download, Package, Info, Loader2, Share2, Check, Ruler } from 'lucide-react'; +import { Download, Package, Info, Loader2, Share2, Check, Ruler, MousePointer2 } from 'lucide-react'; import { generateShareUrl } from '../utils/share'; // --- DrawerFrame (Каркас) --- @@ -42,17 +42,15 @@ const BinMesh: React.FC = ({ part, thickness, cornerRadius, isSele return ( - {/* Модель */} { e.stopPropagation(); onClick(); }}> - {/* Белая обводка */} {isSelected && ( @@ -145,6 +143,7 @@ export const PreviewStep: React.FC = ({ parts, config, splits }) => { return (
+ {/* Верхняя панель */}
@@ -183,6 +182,7 @@ export const PreviewStep: React.FC = ({ parts, config, splits }) => {
+ {/* 3D Viewer */}
@@ -230,6 +230,7 @@ export const PreviewStep: React.FC = ({ parts, config, splits }) => {
+ {/* Sidebar List (Grid Layout) */}

@@ -245,27 +246,47 @@ export const PreviewStep: React.FC = ({ parts, config, splits }) => {

-
- {parts.map(part => ( -
{ itemRefs.current[part.id] = el }} - className={`p-4 rounded-lg border transition-all cursor-pointer group ${selectedId === part.id ? 'bg-slate-800 border-accent shadow-md shadow-accent/10 ring-1 ring-accent' : 'bg-slate-800/50 border-slate-700 hover:border-slate-500 hover:bg-slate-800'}`} - onClick={() => setSelectedId(part.id)} - > -
- {part.name} -
-
- - -
- ))} + {/* Цветной индикатор на фоне */} +
+ +
+ + {part.name} + +
+
+ + +
+ ))} +
From 0fcfc5820b3f77fcf3e02892310ccf45ed1ba2d8 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, 27 Dec 2025 22:09:49 +0300 Subject: [PATCH 2/4] Fix layout removed --- src/components/LayoutStep.tsx | 137 +++++++++++++++------------------- 1 file changed, 59 insertions(+), 78 deletions(-) diff --git a/src/components/LayoutStep.tsx b/src/components/LayoutStep.tsx index a8861eb..98ac76c 100644 --- a/src/components/LayoutStep.tsx +++ b/src/components/LayoutStep.tsx @@ -14,19 +14,21 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const svgRef = useRef(null); // State for interaction - const [phantomAxis, setPhantomAxis] = useState(null); // Proposed new line axis - const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); // Normalized 0-1 + const [phantomAxis, setPhantomAxis] = useState(null); + const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); const [hoveredSplit, setHoveredSplit] = useState<{ axis: Axis; index: number } | null>(null); const [dragging, setDragging] = useState<{ axis: Axis; index: number } | null>(null); + + // НОВОЕ: Состояние, чтобы кнопка не исчезала, пока мы на ней + const [isButtonHovered, setIsButtonHovered] = useState(false); // Constants - const SNAP_THRESHOLD = 0.02; // Reduced threshold slightly for better precision on large grid + // Увеличил порог с 0.02 до 0.03, чтобы линию было легче поймать + const SNAP_THRESHOLD = 0.03; const viewBoxW = 1000; const aspectRatio = config.drawer.depth / config.drawer.width; const viewBoxH = viewBoxW * aspectRatio; - // Helpers to calculate cell dimensions - // IMPORTANT: Dependencies must be correct. dragging updates splits, which updates sortedX/Y const sortedX = useMemo(() => [0, ...splits.x, 1].sort((a, b) => a - b), [splits.x]); const sortedY = useMemo(() => [0, ...splits.y, 1].sort((a, b) => a - b), [splits.y]); @@ -40,29 +42,29 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { setMousePos({ x: nx, y: ny }); if (dragging) { - // Move logic - // IMPORTANT: Create NEW array references so useMemo in this component and parent components detect changes const newSplits = { x: [...splits.x], y: [...splits.y] }; - const val = dragging.axis === 'x' ? nx : ny; newSplits[dragging.axis][dragging.index] = val; - onChange(newSplits); return; } - // Hover logic: Check if near existing lines + // ИСПРАВЛЕНИЕ: Если мышка на кнопке удаления, не пересчитываем hover, + // чтобы кнопка не исчезла под курсором. + if (isButtonHovered) return; + + // Hover logic let foundSplit = null; - // Check X splits (Vertical lines) + // Check X splits splits.x.forEach((val, idx) => { if (Math.abs(nx - val) < SNAP_THRESHOLD) foundSplit = { axis: 'x' as Axis, index: idx }; }); - // Check Y splits (Horizontal lines) + // Check Y splits if (!foundSplit) { splits.y.forEach((val, idx) => { if (Math.abs(ny - val) < SNAP_THRESHOLD) foundSplit = { axis: 'y' as Axis, index: idx }; @@ -71,69 +73,54 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { setHoveredSplit(foundSplit); - // Auto-detect axis for NEW lines if not hovering existing - // AND if not too close to other lines + // Phantom line logic if (!foundSplit) { - // Check proximity to ALL lines to prevent creating duplicates const closeToX = splits.x.some(val => Math.abs(nx - val) < SNAP_THRESHOLD); const closeToY = splits.y.some(val => Math.abs(ny - val) < SNAP_THRESHOLD); - - // Also check edges (0 and 1) const closeToEdgeX = nx < SNAP_THRESHOLD || nx > (1 - SNAP_THRESHOLD); const closeToEdgeY = ny < SNAP_THRESHOLD || ny > (1 - SNAP_THRESHOLD); - if (closeToX || closeToEdgeX) { - // Too close to X line or edge, don't allow vertical split here - // But might allow horizontal? - // Actually if we are close to an X line, we probably want to select it, which is handled by foundSplit. - // If foundSplit is null but closeToX is true, it means we are just outside threshold? - // Let's simplified: if too close to any parallel line, disable creation. + const distLeft = nx; + const distRight = 1 - nx; + const distTop = ny; + const distBottom = 1 - ny; + + const minXDist = Math.min(distLeft, distRight); + const minYDist = Math.min(distTop, distBottom); + + let potentialAxis: Axis = minXDist < minYDist ? 'y' : 'x'; + + let valid = true; + if (potentialAxis === 'x') { + if (closeToX || closeToEdgeX) valid = false; + } else { + if (closeToY || closeToEdgeY) valid = false; } - const distLeft = nx; - const distRight = 1 - nx; - const distTop = ny; - const distBottom = 1 - ny; - - const minXDist = Math.min(distLeft, distRight); - const minYDist = Math.min(distTop, distBottom); - - // Determine potential axis - let potentialAxis: Axis = minXDist < minYDist ? 'y' : 'x'; - - // Validate proximity for that axis - let valid = true; - if (potentialAxis === 'x') { - if (closeToX || closeToEdgeX) valid = false; - } else { - if (closeToY || closeToEdgeY) valid = false; - } - - if (valid) { - setPhantomAxis(potentialAxis); - } else { - setPhantomAxis(null); - } + if (valid) { + setPhantomAxis(potentialAxis); + } else { + setPhantomAxis(null); + } } else { setPhantomAxis(null); } }; const handleMouseDown = (e: React.MouseEvent) => { - if (hoveredSplit) { - // Start Dragging - if (e.button === 0) { // Left click + // Если нажали на кнопку, событие там обработается (stopPropagation) + // Здесь обрабатываем только клик по пустому месту или начало драга линии + if (hoveredSplit && !isButtonHovered) { + if (e.button === 0) { setDragging(hoveredSplit); - } else if (e.button === 2) { // Right click + } else if (e.button === 2) { removeSplit(hoveredSplit.axis, hoveredSplit.index); } - } else if (phantomAxis) { - // Create New Split + } else if (phantomAxis && !isButtonHovered) { const val = phantomAxis === 'x' ? mousePos.x : mousePos.y; const newSplits = { ...splits }; newSplits[phantomAxis] = [...newSplits[phantomAxis], val]; onChange(newSplits); - // Immediately start dragging the new line for fine-tuning setDragging({ axis: phantomAxis, index: newSplits[phantomAxis].length - 1 }); } }; @@ -148,6 +135,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { onChange(newSplits); setHoveredSplit(null); setDragging(null); + setIsButtonHovered(false); // Сбрасываем блокировку }; return ( @@ -216,10 +204,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { - - - - @@ -236,14 +220,9 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const cellWidthSVG = (x2 - x1) * viewBoxW; const cellHeightSVG = (y2 - y1) * viewBoxH; - // Dynamic Font Sizing Logic - // Max font size: 36px - // Must fit in height: 60% of height max - // Must fit in width: approx 25% of width max (assuming ~5 chars) let fontSize = Math.min(36, cellHeightSVG * 0.6); fontSize = Math.min(fontSize, cellWidthSVG * 0.25); - // Don't show text if calculated font size is too small to be readable if (fontSize < 10) return null; return ( @@ -274,23 +253,25 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { return ( removeSplit('x', i)}> - {/* Invisible wide hit area */} - {/* Visible Line */} - {/* Delete Button if hovered */} {(isHovered || isDragging) && ( - { e.stopPropagation(); removeSplit('x', i); }}> + { e.stopPropagation(); removeSplit('x', i); }} + onMouseEnter={() => setIsButtonHovered(true)} // Блокируем скрытие + onMouseLeave={() => setIsButtonHovered(false)} + > @@ -308,7 +289,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { return ( removeSplit('y', i)}> - {/* Invisible wide hit area */} = ({ config, splits, onChange }) => { className="transition-all duration-150" /> {(isHovered || isDragging) && ( - { e.stopPropagation(); removeSplit('y', i); }}> + { e.stopPropagation(); removeSplit('y', i); }} + onMouseEnter={() => setIsButtonHovered(true)} // Блокируем скрытие + onMouseLeave={() => setIsButtonHovered(false)} + > @@ -331,15 +316,13 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { ); })} - {/* --- Phantom Line (Preview) --- */} - {!hoveredSplit && !dragging && phantomAxis === 'x' && ( + {/* --- Phantom Lines --- */} + {!hoveredSplit && !dragging && !isButtonHovered && phantomAxis === 'x' && ( @@ -347,14 +330,12 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { )} - {!hoveredSplit && !dragging && phantomAxis === 'y' && ( + {!hoveredSplit && !dragging && !isButtonHovered && phantomAxis === 'y' && ( From 006cc784664031be774b236057fbdcfce7b22d5e 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, 27 Dec 2025 22:16:29 +0300 Subject: [PATCH 3/4] Fix select line --- src/components/LayoutStep.tsx | 174 ++++++++++++++++------------------ 1 file changed, 82 insertions(+), 92 deletions(-) diff --git a/src/components/LayoutStep.tsx b/src/components/LayoutStep.tsx index 98ac76c..2fab6ad 100644 --- a/src/components/LayoutStep.tsx +++ b/src/components/LayoutStep.tsx @@ -13,18 +13,12 @@ type Axis = 'x' | 'y'; export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const svgRef = useRef(null); - // State for interaction + // State const [phantomAxis, setPhantomAxis] = useState(null); const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); const [hoveredSplit, setHoveredSplit] = useState<{ axis: Axis; index: number } | null>(null); const [dragging, setDragging] = useState<{ axis: Axis; index: number } | null>(null); - - // НОВОЕ: Состояние, чтобы кнопка не исчезала, пока мы на ней - const [isButtonHovered, setIsButtonHovered] = useState(false); - // Constants - // Увеличил порог с 0.02 до 0.03, чтобы линию было легче поймать - const SNAP_THRESHOLD = 0.03; const viewBoxW = 1000; const aspectRatio = config.drawer.depth / config.drawer.width; const viewBoxH = viewBoxW * aspectRatio; @@ -32,8 +26,8 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const sortedX = useMemo(() => [0, ...splits.x, 1].sort((a, b) => a - b), [splits.x]); const sortedY = useMemo(() => [0, ...splits.y, 1].sort((a, b) => a - b), [splits.y]); - // Handlers - const handleMouseMove = (e: React.MouseEvent) => { + // --- Глобальный обработчик (для пустого места и перетаскивания) --- + const handleGlobalMouseMove = (e: React.MouseEvent) => { if (!svgRef.current) return; const rect = svgRef.current.getBoundingClientRect(); const nx = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); @@ -41,6 +35,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { setMousePos({ x: nx, y: ny }); + // 1. Если тащим - обновляем позицию if (dragging) { const newSplits = { x: [...splits.x], @@ -52,71 +47,69 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { return; } - // ИСПРАВЛЕНИЕ: Если мышка на кнопке удаления, не пересчитываем hover, - // чтобы кнопка не исчезла под курсором. - if (isButtonHovered) return; + // 2. Если мы здесь, значит мышка НЕ на существующей линии + // (иначе событие было бы перехвачено в handleSplitHover) + setHoveredSplit(null); - // Hover logic - let foundSplit = null; + // 3. Логика Фантомной линии (создание новой) + const SNAP_THRESHOLD = 0.02; - // Check X splits - splits.x.forEach((val, idx) => { - if (Math.abs(nx - val) < SNAP_THRESHOLD) foundSplit = { axis: 'x' as Axis, index: idx }; - }); + // Проверяем, не слишком ли мы близко к краям или другим линиям (чтобы не спамить) + const closeToX = splits.x.some(val => Math.abs(nx - val) < SNAP_THRESHOLD); + const closeToY = splits.y.some(val => Math.abs(ny - val) < SNAP_THRESHOLD); + const closeToEdgeX = nx < SNAP_THRESHOLD || nx > (1 - SNAP_THRESHOLD); + const closeToEdgeY = ny < SNAP_THRESHOLD || ny > (1 - SNAP_THRESHOLD); - // Check Y splits - if (!foundSplit) { - splits.y.forEach((val, idx) => { - if (Math.abs(ny - val) < SNAP_THRESHOLD) foundSplit = { axis: 'y' as Axis, index: idx }; - }); + const distLeft = nx; + const distRight = 1 - nx; + const distTop = ny; + const distBottom = 1 - ny; + + const minXDist = Math.min(distLeft, distRight); + const minYDist = Math.min(distTop, distBottom); + + // Определяем ось по близости к краю + let potentialAxis: Axis = minXDist < minYDist ? 'y' : 'x'; + + let valid = true; + if (potentialAxis === 'x') { + if (closeToX || closeToEdgeX) valid = false; + } else { + if (closeToY || closeToEdgeY) valid = false; } - setHoveredSplit(foundSplit); - - // Phantom line logic - if (!foundSplit) { - const closeToX = splits.x.some(val => Math.abs(nx - val) < SNAP_THRESHOLD); - const closeToY = splits.y.some(val => Math.abs(ny - val) < SNAP_THRESHOLD); - const closeToEdgeX = nx < SNAP_THRESHOLD || nx > (1 - SNAP_THRESHOLD); - const closeToEdgeY = ny < SNAP_THRESHOLD || ny > (1 - SNAP_THRESHOLD); - - const distLeft = nx; - const distRight = 1 - nx; - const distTop = ny; - const distBottom = 1 - ny; - - const minXDist = Math.min(distLeft, distRight); - const minYDist = Math.min(distTop, distBottom); - - let potentialAxis: Axis = minXDist < minYDist ? 'y' : 'x'; - - let valid = true; - if (potentialAxis === 'x') { - if (closeToX || closeToEdgeX) valid = false; - } else { - if (closeToY || closeToEdgeY) valid = false; - } - - if (valid) { - setPhantomAxis(potentialAxis); - } else { - setPhantomAxis(null); - } + if (valid) { + setPhantomAxis(potentialAxis); } else { setPhantomAxis(null); } }; + // --- Обработчик наведения на КОНКРЕТНУЮ линию --- + const handleSplitHover = (e: React.MouseEvent, axis: Axis, index: number) => { + // Если мы тащим линию, позволяем событию всплыть до глобального обработчика, + // чтобы он посчитал координаты. + if (dragging) return; + + // Если просто водим мышкой - блокируем всплытие, + // чтобы глобальный обработчик не думал, что мы в пустоте. + e.stopPropagation(); + + setHoveredSplit({ axis, index }); + setPhantomAxis(null); // Убираем фантом, раз мы на линии + }; + const handleMouseDown = (e: React.MouseEvent) => { - // Если нажали на кнопку, событие там обработается (stopPropagation) - // Здесь обрабатываем только клик по пустому месту или начало драга линии - if (hoveredSplit && !isButtonHovered) { + // Клик по существующей линии (hoveredSplit уже установлен через onMouseMove линии) + if (hoveredSplit) { if (e.button === 0) { setDragging(hoveredSplit); } else if (e.button === 2) { removeSplit(hoveredSplit.axis, hoveredSplit.index); } - } else if (phantomAxis && !isButtonHovered) { + } + // Клик по пустому месту (создание) + else if (phantomAxis) { const val = phantomAxis === 'x' ? mousePos.x : mousePos.y; const newSplits = { ...splits }; newSplits[phantomAxis] = [...newSplits[phantomAxis], val]; @@ -135,7 +128,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { onChange(newSplits); setHoveredSplit(null); setDragging(null); - setIsButtonHovered(false); // Сбрасываем блокировку }; return ( @@ -155,7 +147,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
- {/* Instruction Box */}
Инструкция @@ -167,7 +158,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => {
- {/* Rulers */}
0 {config.drawer.width} мм @@ -179,7 +169,6 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { {config.drawer.depth} мм
- {/* Main Interactive SVG */}
= ({ config, splits, onChange }) => { ref={svgRef} viewBox={`0 0 ${viewBoxW} ${viewBoxH}`} className="w-full h-full touch-none" - onMouseMove={handleMouseMove} + onMouseMove={handleGlobalMouseMove} onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} onMouseLeave={handleMouseUp} @@ -207,7 +196,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { - {/* --- Cell Dimensions Labels --- */} + {/* --- Labels --- */} {sortedX.slice(0, -1).map((x1, i) => { const x2 = sortedX[i + 1]; return sortedY.slice(0, -1).map((y1, j) => { @@ -244,7 +233,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { }); })} - {/* --- Existing X Lines (Vertical) --- */} + {/* --- X Lines (Vertical) --- */} {splits.x.map((x, i) => { const isHovered = hoveredSplit?.axis === 'x' && hoveredSplit.index === i; const isDragging = dragging?.axis === 'x' && dragging.index === i; @@ -252,26 +241,28 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const width = isHovered || isDragging ? 8 : 4; return ( - removeSplit('x', i)}> + removeSplit('x', i)} + // ВАЖНО: Событие вешаем на группу + onMouseMove={(e) => handleSplitHover(e, 'x', i)} + > + {/* Невидимая широкая зона захвата (80 единиц!) */} + {/* Видимая линия */} {(isHovered || isDragging) && ( - { e.stopPropagation(); removeSplit('x', i); }} - onMouseEnter={() => setIsButtonHovered(true)} // Блокируем скрытие - onMouseLeave={() => setIsButtonHovered(false)} - > + { e.stopPropagation(); removeSplit('x', i); }}> @@ -280,7 +271,7 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { ); })} - {/* --- Existing Y Lines (Horizontal) --- */} + {/* --- Y Lines (Horizontal) --- */} {splits.y.map((y, i) => { const isHovered = hoveredSplit?.axis === 'y' && hoveredSplit.index === i; const isDragging = dragging?.axis === 'y' && dragging.index === i; @@ -288,26 +279,25 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { const width = isHovered || isDragging ? 8 : 4; return ( - removeSplit('y', i)}> + removeSplit('y', i)} + onMouseMove={(e) => handleSplitHover(e, 'y', i)} + > {(isHovered || isDragging) && ( - { e.stopPropagation(); removeSplit('y', i); }} - onMouseEnter={() => setIsButtonHovered(true)} // Блокируем скрытие - onMouseLeave={() => setIsButtonHovered(false)} - > + { e.stopPropagation(); removeSplit('y', i); }}> @@ -317,26 +307,26 @@ export const LayoutStep: React.FC = ({ config, splits, onChange }) => { })} {/* --- Phantom Lines --- */} - {!hoveredSplit && !dragging && !isButtonHovered && phantomAxis === 'x' && ( - + {!hoveredSplit && !dragging && phantomAxis === 'x' && ( + )} - {!hoveredSplit && !dragging && !isButtonHovered && phantomAxis === 'y' && ( - + {!hoveredSplit && !dragging && phantomAxis === 'y' && ( + From 861dbef0e09b86fdd8c4e2aa09df3cb80913e8af 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, 27 Dec 2025 22:21:14 +0300 Subject: [PATCH 4/4] Fix model list --- src/components/PreviewStep.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/PreviewStep.tsx b/src/components/PreviewStep.tsx index 58c67a9..c9f9aca 100644 --- a/src/components/PreviewStep.tsx +++ b/src/components/PreviewStep.tsx @@ -5,7 +5,7 @@ import * as THREE from 'three'; import JSZip from 'jszip'; import { AppConfig, GeneratedPart, LayoutSplits } from '../types'; import { createBinGeometry, generateSTL, exportSTL } from '../services/geometryGenerator'; -import { Download, Package, Info, Loader2, Share2, Check, Ruler, MousePointer2 } from 'lucide-react'; +import { Download, Package, Info, Loader2, Share2, Check, Ruler } from 'lucide-react'; import { generateShareUrl } from '../utils/share'; // --- DrawerFrame (Каркас) --- @@ -247,14 +247,13 @@ export const PreviewStep: React.FC = ({ parts, config, splits }) => {
- {/* --- GRID LAYOUT --- */}
{parts.map(part => (
{ itemRefs.current[part.id] = el }} className={` - p-3 rounded-lg border transition-all cursor-pointer group flex flex-col gap-3 relative overflow-hidden + p-3 rounded-lg border transition-all cursor-pointer group flex flex-col gap-2 relative overflow-hidden ${selectedId === part.id ? 'bg-slate-800 border-accent shadow-md shadow-accent/10 ring-1 ring-accent' : 'bg-slate-800/50 border-slate-700 hover:border-slate-500 hover:bg-slate-800' @@ -262,12 +261,13 @@ export const PreviewStep: React.FC = ({ parts, config, splits }) => { `} onClick={() => setSelectedId(part.id)} > - {/* Цветной индикатор на фоне */} + {/* Фон-индикатор */}
+ {/* Заголовок */}
{part.name} @@ -277,10 +277,16 @@ export const PreviewStep: React.FC = ({ parts, config, splits }) => { style={{ backgroundColor: part.color }} />
+ + {/* --- РАЗМЕРЫ (НОВОЕ) --- */} +
+ {part.width.toFixed(0)} × {part.depth.toFixed(0)} × {part.height.toFixed(0)} +
+ {/* Кнопка */}