diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 39facad..5b1a841 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -365,7 +365,7 @@ const Index = () => { {/* Footer */}

- SteelXR v1.11 — Q.C. Inspection + SteelXR v1.12 — Q.C. Inspection

{showLogs && ( diff --git a/src/pages/Viewer.tsx b/src/pages/Viewer.tsx index 612c863..fd542fd 100644 --- a/src/pages/Viewer.tsx +++ b/src/pages/Viewer.tsx @@ -259,37 +259,12 @@ const Viewer = () => { const [enteringXR, setEnteringXR] = useState(false); - const handleEnterXR = useCallback(async () => { + const handleEnterXR = useCallback(() => { if (!xrSupported) { toast.error("WebXR não é suportado neste navegador. Use o navegador do Meta Quest 3."); return; } - setEnteringXR(true); - - // Timeout de segurança — se não entrar em 15s, cancela - const timeoutId = setTimeout(() => { - setEnteringXR(false); - toast.error("Tempo limite ao iniciar modo AR. Verifique se o Meta Quest Browser está conectado."); - }, 15000); - - try { - // Marca que está entrando no AR antes de navegar — bloqueia auto-return prematuro - (window as unknown as { __setXrEntering?: (v: boolean) => void }).__setXrEntering?.(true); - - // Chamada síncrona de enterAR para preservar o User Gesture exigido pelo WebXR - const enterPromise = xrStore.enterAR(); - - clearTimeout(timeoutId); - navigate("/xr"); - - await enterPromise; - } catch (e) { - clearTimeout(timeoutId); - setEnteringXR(false); - (window as unknown as { __setXrEntering?: (v: boolean) => void }).__setXrEntering?.(false); - console.error('[Viewer] XR enterAR error:', e); - toast.error("Falha ao iniciar modo AR. Verifique se o Meta Quest Browser está conectado."); - } + navigate("/xr"); }, [xrSupported, navigate]); const handleEnterMeeting = () => { diff --git a/src/pages/XRSession.tsx b/src/pages/XRSession.tsx index 323ba88..34f7042 100644 --- a/src/pages/XRSession.tsx +++ b/src/pages/XRSession.tsx @@ -7,7 +7,7 @@ import { xrStore as store } from '@/stores/useXRStore'; import * as THREE from 'three'; import { useModelStore } from '@/stores/useModelStore'; import { toast } from 'sonner'; -import { ArrowLeft, Download, QrCode, Crosshair, Home } from 'lucide-react'; +import { ArrowLeft, Download, QrCode, Crosshair, Home, Glasses } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { generateMarkerDownloadURL } from '@/lib/trackingMarker'; import { XRHud } from '@/components/XRHud'; @@ -1050,6 +1050,7 @@ const XRSession = () => { const effectiveInXR = inXR || simXR; // Indica que enterAR() foi chamado mas a sessão ainda não começou — previne auto-return prematuro const [isEnteringAR, setIsEnteringAR] = useState(false); + const [hasClickedEnter, setHasClickedEnter] = useState(false); useEffect(() => { if (!model) navigate('/'); @@ -1098,12 +1099,10 @@ const XRSession = () => { toast.success('Marcador baixado — Imprima em 15×15cm'); }, []); - if (!model) return null; - // Auto-return to viewer if user exits AR session useEffect(() => { // Don't auto-return while we're waiting for the XR onboarding dialog to complete - if (!inXR) { + if (hasClickedEnter && !inXR) { const timer = setTimeout(() => { // Only return if the session is truly absent AND we're not in the middle of entering AR if (!store.getState().session) { @@ -1112,7 +1111,9 @@ const XRSession = () => { }, 2000); return () => clearTimeout(timer); } - }, [inXR, navigate]); + }, [inXR, hasClickedEnter, navigate]); + + if (!model) return null; return (
@@ -1262,6 +1263,82 @@ const XRSession = () => { + {/* Onboarding do AR */} + {!effectiveInXR && ( +
+
+
+
+ +
+
+ +
+

+ Pronto para entrar no Modo AR? +

+

+ Você está prestes a carregar o modelo em escala real 1:1 no seu ambiente físico. +

+
+ + {/* Informações da Peça */} +
+
+ Modelo Ativo: + {model.fileName} +
+
+ Tamanho: + {(model.fileSize / (1024 * 1024)).toFixed(2)} MB +
+
+ + {/* Dicas Rápidas */} +
+

+ 💡 Guia de Controles Rápidos: +

+
    +
  • Teletransporte: Thumbstick para frente. Solte para teleportar.
  • +
  • Escala Real (1:1): Aponte o feixe para a peça e teletransporte nela.
  • +
  • Mover Peça: Segure o Grip de qualquer controle para arrastar.
  • +
  • Girar/Zoom: Ambas as miras na peça, segure ambos os Grips e mova as mãos.
  • +
  • Menu do App: Botões A/B/X/Y abrem/fecham o menu flutuante.
  • +
+
+ + {/* Ações */} +
+ + + +
+
+
+ )} + {/* Floating DOM HUD overlay — only visible OUTSIDE passthrough. Inside AR, the DOM is occluded by the headset compositor; the in-world XRHudInWorld replaces it. */}