🔧 Fix: modo AR travando em loading — timeout de segurança, feedback visual no botão e auto-return retardado
- Viewer: loading state no botão 'Entrar em Modo XR' com Loader2 e texto 'Iniciando AR...' - Viewer: timeout de 15s que limpa o loading e mostra toast de erro se não entrar - Viewer: marca __setXrEntering(true) antes de navegar para bloquear auto-return prematuro - XRSession: delay do auto-return aumentado de 500ms para 2000ms - XRSession: novo flag isEnteringAR que previne navegação prematura para /viewer - XRSession: expõe __setXrEntering via window para Viewer setar o flag - Versão: v1.10 → v1.11 [31/05/2026 17:33:29]
This commit is contained in:
+1
-1
@@ -348,7 +348,7 @@ const Index = () => {
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<p className="mt-16 text-center font-mono text-xs text-muted-foreground/50">
|
<p className="mt-16 text-center font-mono text-xs text-muted-foreground/50">
|
||||||
SteelXR v1.10 — Q.C. Inspection
|
SteelXR v1.11 — Q.C. Inspection
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{showLogs && (
|
{showLogs && (
|
||||||
|
|||||||
+28
-9
@@ -218,17 +218,36 @@ const Viewer = () => {
|
|||||||
|
|
||||||
// Removido o bloqueio para permitir carregamento de modelo na sala
|
// Removido o bloqueio para permitir carregamento de modelo na sala
|
||||||
|
|
||||||
const handleEnterXR = async () => {
|
const [enteringXR, setEnteringXR] = useState(false);
|
||||||
|
|
||||||
|
const handleEnterXR = useCallback(async () => {
|
||||||
if (!xrSupported) {
|
if (!xrSupported) {
|
||||||
toast.error("WebXR não é suportado neste navegador. Use o navegador do Meta Quest 3.");
|
toast.error("WebXR não é suportado neste navegador. Use o navegador do Meta Quest 3.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Entra diretamente no AR para não perder o clique do usuário!
|
setEnteringXR(true);
|
||||||
import('@/stores/useXRStore').then(({ xrStore }) => {
|
|
||||||
xrStore.enterAR().catch(e => console.error('[Viewer] XR enterAR error:', e));
|
// 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");
|
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 = () => {
|
const handleEnterMeeting = () => {
|
||||||
// Coleta o eixo habilitado de seção no Viewer
|
// Coleta o eixo habilitado de seção no Viewer
|
||||||
@@ -297,9 +316,9 @@ const Viewer = () => {
|
|||||||
Reunião Virtual
|
Reunião Virtual
|
||||||
</Button>
|
</Button>
|
||||||
<ShareButton />
|
<ShareButton />
|
||||||
<Button className="gap-2 glow-primary h-9" disabled={!xrSupported} onClick={handleEnterXR}>
|
<Button className="gap-2 glow-primary h-9" disabled={!xrSupported || enteringXR} onClick={handleEnterXR}>
|
||||||
<Glasses className="h-4 w-4" />
|
{enteringXR ? <Loader2 className="h-4 w-4 animate-spin" /> : <Glasses className="h-4 w-4" />}
|
||||||
Entrar em Modo XR
|
{enteringXR ? 'Iniciando AR…' : 'Entrar em Modo XR'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
+21
-3
@@ -1048,17 +1048,21 @@ const XRSession = () => {
|
|||||||
const devkit = useDevKit();
|
const devkit = useDevKit();
|
||||||
const [simXR, setSimXR] = useState(false);
|
const [simXR, setSimXR] = useState(false);
|
||||||
const effectiveInXR = inXR || simXR;
|
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(() => {
|
useEffect(() => {
|
||||||
if (!model) navigate('/');
|
if (!model) navigate('/');
|
||||||
}, [model, navigate]);
|
}, [model, navigate]);
|
||||||
|
|
||||||
|
// Detecta quando a sessão XR de fato começa
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = store.subscribe((state) => {
|
const unsubscribe = store.subscribe((state) => {
|
||||||
const session = state.session;
|
const session = state.session;
|
||||||
if (session && !inXR) {
|
if (session && !inXR) {
|
||||||
console.log('[XR] ✅ Sessão AR ativa!');
|
console.log('[XR] ✅ Sessão AR ativa!');
|
||||||
setInXR(true);
|
setInXR(true);
|
||||||
|
setIsEnteringAR(false);
|
||||||
setAnchorMode('manual');
|
setAnchorMode('manual');
|
||||||
toast.success('Sessão AR iniciada!');
|
toast.success('Sessão AR iniciada!');
|
||||||
session.addEventListener('end', () => {
|
session.addEventListener('end', () => {
|
||||||
@@ -1070,6 +1074,20 @@ const XRSession = () => {
|
|||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [inXR, setAnchorMode]);
|
}, [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 handleDownloadMarker = useCallback(async () => {
|
||||||
const url = await generateMarkerDownloadURL();
|
const url = await generateMarkerDownloadURL();
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
@@ -1084,14 +1102,14 @@ const XRSession = () => {
|
|||||||
|
|
||||||
// Auto-return to viewer if user exits AR session
|
// Auto-return to viewer if user exits AR session
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// If we are mounted, and the session is completely absent/ended (inXR is false),
|
// Don't auto-return while we're waiting for the XR onboarding dialog to complete
|
||||||
// we wait a brief moment to avoid returning during initial transition, then return.
|
|
||||||
if (!inXR) {
|
if (!inXR) {
|
||||||
const timer = setTimeout(() => {
|
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) {
|
if (!store.getState().session) {
|
||||||
navigate('/viewer');
|
navigate('/viewer');
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 2000);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [inXR, navigate]);
|
}, [inXR, navigate]);
|
||||||
|
|||||||
Reference in New Issue
Block a user