import { useEffect, useRef, useCallback, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Upload, Glasses, CheckCircle, XCircle, Loader2, Box, Package } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { useModelStore } from '@/stores/useModelStore'; import { toast } from 'sonner'; import { getSupportedExtension, convertToGLB, ACCEPTED_EXTENSIONS } from '@/lib/convertToGLB'; import { convertIFCtoGLB } from '@/lib/convertIFC'; import { validateModelFile } from '@/lib/validateModelFile'; import { CloudLoader } from '@/components/CloudLoader'; import { SceneModelList } from '@/components/SceneModelList'; import { RecentFilesList } from '@/components/RecentFilesList'; import { addRecentFile } from '@/lib/recentFiles'; const Index = () => { const navigate = useNavigate(); const fileInputRef = useRef(null); const [loadingDemoIFC, setLoadingDemoIFC] = useState(false); const [loadingDemoCotovelo, setLoadingDemoCotovelo] = useState(false); const [converting, setConverting] = useState(false); const { model, addModel, models, maxModels, xrSupported, setXrSupported } = useModelStore(); useEffect(() => { if (navigator.xr) { navigator.xr.isSessionSupported('immersive-ar').then(setXrSupported).catch(() => setXrSupported(false)); } else { setXrSupported(false); } }, [setXrSupported]); const handleFileUpload = useCallback(async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; const ext = getSupportedExtension(file.name); if (!ext) { toast.error('Formato inválido. Selecione um arquivo .GLB, .OBJ, .STL ou .IFC'); return; } // Pré-validação rápida (tamanho) antes de ler o arquivo inteiro na memória const sizeCheck = validateModelFile(file.name, file.size, ext); if (!sizeCheck.ok) { toast.error(sizeCheck.reason!); return; } if (ext === 'glb') { // Para GLB, valida assinatura "glTF" antes de criar o objectURL try { const head = await file.slice(0, 16).arrayBuffer(); const sigCheck = validateModelFile(file.name, file.size, ext, head); if (!sigCheck.ok) { toast.error(sigCheck.reason!); if (sigCheck.detail) console.warn('[validateModelFile]', sigCheck.detail); return; } } catch (err) { console.error('[upload] header read failed', err); } const url = URL.createObjectURL(file); addModel({ fileName: file.name, fileSize: file.size, url }); addRecentFile(file.name, file).catch(() => {}); toast.success(`Modelo "${file.name}" carregado com sucesso!`); return; } // Convert OBJ/STL/IFC to GLB setConverting(true); try { const buffer = await file.arrayBuffer(); const sigCheck = validateModelFile(file.name, file.size, ext, buffer); if (!sigCheck.ok) { toast.error(sigCheck.reason!); if (sigCheck.detail) console.warn('[validateModelFile]', sigCheck.detail); return; } let result; if (ext === 'ifc') { result = await convertIFCtoGLB(buffer, file.name); } else { result = await convertToGLB(buffer, ext, file.name); } const url = URL.createObjectURL(result.blob); addModel({ fileName: result.fileName, fileSize: result.fileSize, url }); addRecentFile(result.fileName, result.blob).catch(() => {}); toast.success(`"${file.name}" convertido para GLB e carregado!`); } catch (err) { console.error('[upload] conversion failed', err); const msg = err instanceof Error ? err.message : 'erro desconhecido'; toast.error(`Falha ao converter "${file.name}": ${msg}`); } finally { setConverting(false); } }, [addModel]); // Carregar da Nuvem agora é encapsulado em const handleLoadDemoIFC = useCallback(async () => { setLoadingDemoIFC(true); try { const response = await fetch('/models/demo250hp.ifc'); const buffer = await response.arrayBuffer(); const result = await convertIFCtoGLB(buffer, 'demo250hp.ifc'); const url = URL.createObjectURL(result.blob); addModel({ fileName: result.fileName, fileSize: result.fileSize, url }); addRecentFile(result.fileName, result.blob).catch(() => {}); toast.success('Modelo demo "250 HP" (IFC) carregado!'); } catch (err) { console.error(err); toast.error('Falha ao converter modelo demo IFC'); } finally { setLoadingDemoIFC(false); } }, [addModel]); const handleLoadDemoCotovelo = useCallback(async () => { setLoadingDemoCotovelo(true); try { const response = await fetch('/models/B126-COTOVELO.ifc'); const buffer = await response.arrayBuffer(); const result = await convertIFCtoGLB(buffer, 'B126-COTOVELO.ifc'); const url = URL.createObjectURL(result.blob); addModel({ fileName: result.fileName, fileSize: result.fileSize, url }); toast.success('Modelo demo "B126 Cotovelo" (IFC) carregado!'); } catch (err) { console.error(err); toast.error('Falha ao converter modelo demo Cotovelo'); } finally { setLoadingDemoCotovelo(false); } }, [addModel]); const handleEnterViewer = () => { if (!model) { toast.error('Importe um modelo GLB primeiro'); return; } navigate('/viewer'); }; return (
{/* Logo area */}

TrackSteelXR

Inspeção de Qualidade Industrial

{/* Main card */}
{/* Import button */} {/* Demo buttons */}
{models.length > 0 && (

{models.length < maxModels ? `Você pode adicionar mais ${maxModels - models.length} peça(s) à cena` : `Limite de ${maxModels} peças simultâneas atingido`}

)} {/* Enter viewer */} {/* XR Status */}
{xrSupported === null ? <> Verificando suporte WebXR… : xrSupported ? <> WebXR Compatível — Passthrough disponível : <> WebXR não disponível neste navegador }
{/* Footer */}

TrackSteelXR v1.0 — Q.C. Inspection

); }; export default Index;