import React, { Suspense, useEffect, useRef, useState } from 'react'; import { Canvas } from '@react-three/fiber'; import { OrbitControls, Center, Environment } from '@react-three/drei'; 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 { generateShareUrl } from '../utils/share'; // --- DrawerFrame (Каркас) --- const DrawerFrame = ({ config }: { config: AppConfig }) => { const { width, depth, height } = config.drawer; const offset = 0.5; return ( ) } // --- BinMesh (Ячейка) --- interface BinMeshProps { part: GeneratedPart; thickness: number; isSelected: boolean; onClick: () => void; } const BinMesh: React.FC = ({ part, thickness, isSelected, onClick }) => { const geometry = React.useMemo(() => { return createBinGeometry(part.width, part.depth, part.height, thickness); }, [part, thickness]); return ( { e.stopPropagation(); onClick(); }}> {isSelected && ( )} ); }; // --- PreviewStep (Основной) --- interface Props { parts: GeneratedPart[]; config: AppConfig; splits: LayoutSplits; } export const PreviewStep: React.FC = ({ parts, config, splits }) => { const [selectedId, setSelectedId] = useState(null); const [isZipping, setIsZipping] = useState(false); const [shareUrlCopied, setShareUrlCopied] = useState(false); const itemRefs = useRef<{ [key: string]: HTMLDivElement | null }>({}); useEffect(() => { if (selectedId && itemRefs.current[selectedId]) { itemRefs.current[selectedId]?.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, [selectedId]); const handleDownload = (part: GeneratedPart) => { const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness); const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial()); exportSTL(mesh, `${part.name.replace(/\s+/g, '_')}.stl`); }; const handleDownloadAll = async () => { if (isZipping) return; setIsZipping(true); try { const zip = new JSZip(); parts.forEach(part => { const geometry = createBinGeometry(part.width, part.depth, part.height, config.wallThickness); const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial()); const stlData = generateSTL(mesh); zip.file(`${part.name.replace(/\s+/g, '_')}.stl`, stlData); }); const content = await zip.generateAsync({ type: "blob" }); const link = document.createElement('a'); link.href = URL.createObjectURL(content); link.download = "PrintFit_Project.zip"; document.body.appendChild(link); link.click(); document.body.removeChild(link); } catch (e: any) { alert(`Ошибка архивации: ${e.message}`); } finally { setIsZipping(false); } }; // --- Функция копирования --- const handleShare = async () => { const url = generateShareUrl(config, splits); let success = false; try { if (navigator.clipboard && navigator.clipboard.writeText) { await navigator.clipboard.writeText(url); success = true; } else { throw new Error('Clipboard API unavailable'); } } catch (err) { try { const textArea = document.createElement("textarea"); textArea.value = url; textArea.style.position = "fixed"; textArea.style.left = "-9999px"; textArea.style.top = "0"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); const result = document.execCommand('copy'); document.body.removeChild(textArea); if (result) success = true; } catch (e) { console.error("Copy failed", e); } } if (success) { setShareUrlCopied(true); setTimeout(() => setShareUrlCopied(false), 3000); } else { prompt("Скопируйте ссылку вручную:", url); } }; return (
{/* Верхняя панель: Размеры + Поделиться */}
Размеры ящика:
{/* --- ОБНОВЛЕННЫЙ БЛОК РАЗМЕРОВ --- */}
Ширина: {config.drawer.width}
Глубина: {config.drawer.depth}
Высота: {config.drawer.height}
мм
{/* 3D Viewer */}
Управление
  • • ЛКМ: Вращение
  • • ПКМ: Перемещение
  • • Скролл: Масштаб
{parts.map(part => ( setSelectedId(part.id)} /> ))}
{/* Sidebar List */}

Детали ({parts.length})

{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}
))}
); };