diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 50089d2..4aad18a 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -348,7 +348,7 @@ const Index = () => { {/* Footer */}

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

{showLogs && ( diff --git a/src/pages/Viewer.tsx b/src/pages/Viewer.tsx index bef4145..07835c1 100644 --- a/src/pages/Viewer.tsx +++ b/src/pages/Viewer.tsx @@ -218,17 +218,36 @@ const Viewer = () => { // Removido o bloqueio para permitir carregamento de modelo na sala - const handleEnterXR = async () => { + const [enteringXR, setEnteringXR] = useState(false); + + const handleEnterXR = useCallback(async () => { if (!xrSupported) { toast.error("WebXR não é suportado neste navegador. Use o navegador do Meta Quest 3."); return; } - // Entra diretamente no AR para não perder o clique do usuário! - import('@/stores/useXRStore').then(({ xrStore }) => { - xrStore.enterAR().catch(e => console.error('[Viewer] XR enterAR error:', e)); + 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 { + const { xrStore } = await import('@/stores/useXRStore'); + // Marca que está entrando no AR antes de navegar — bloqueia auto-return prematuro + (window as unknown as { __setXrEntering?: (v: boolean) => void }).__setXrEntering?.(true); + await xrStore.enterAR(); + clearTimeout(timeoutId); navigate("/xr"); - }); - }; + } 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."); + } + }, [xrSupported, navigate]); const handleEnterMeeting = () => { // Coleta o eixo habilitado de seção no Viewer @@ -297,9 +316,9 @@ const Viewer = () => { Reunião Virtual - diff --git a/src/pages/XRSession.tsx b/src/pages/XRSession.tsx index 0d54ccf..323ba88 100644 --- a/src/pages/XRSession.tsx +++ b/src/pages/XRSession.tsx @@ -1048,17 +1048,21 @@ const XRSession = () => { const devkit = useDevKit(); const [simXR, setSimXR] = useState(false); 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); useEffect(() => { if (!model) navigate('/'); }, [model, navigate]); + // Detecta quando a sessão XR de fato começa useEffect(() => { const unsubscribe = store.subscribe((state) => { const session = state.session; if (session && !inXR) { console.log('[XR] ✅ Sessão AR ativa!'); setInXR(true); + setIsEnteringAR(false); setAnchorMode('manual'); toast.success('Sessão AR iniciada!'); session.addEventListener('end', () => { @@ -1070,6 +1074,20 @@ const XRSession = () => { return unsubscribe; }, [inXR, setAnchorMode]); + // Flag que indica que o usuário pediu para entrar no AR (enterAR chamado). + // Usada para bloquear o auto-return durante o onboarding do WebXR. + useEffect(() => { + // Se inXR virou true mas isEnteringAR é true, a sessão começou — limpa o flag + if (inXR && isEnteringAR) { + setIsEnteringAR(false); + } + }, [inXR, isEnteringAR]); + + // Expõe setIsEnteringAR para o Viewer via window (permite marcar "entrando" antes de navegar) + useEffect(() => { + (window as unknown as { __setXrEntering?: (v: boolean) => void }).__setXrEntering = setIsEnteringAR; + }, []); + const handleDownloadMarker = useCallback(async () => { const url = await generateMarkerDownloadURL(); const a = document.createElement('a'); @@ -1084,14 +1102,14 @@ const XRSession = () => { // Auto-return to viewer if user exits AR session useEffect(() => { - // If we are mounted, and the session is completely absent/ended (inXR is false), - // we wait a brief moment to avoid returning during initial transition, then return. + // Don't auto-return while we're waiting for the XR onboarding dialog to complete if (!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) { navigate('/viewer'); } - }, 500); + }, 2000); return () => clearTimeout(timer); } }, [inXR, navigate]);