import { useState, useCallback } from 'react'; import { Cloud, Loader2, Link2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogTrigger, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { useModelStore } from '@/stores/useModelStore'; import { toast } from 'sonner'; import { getSupportedExtension, convertToGLB } from '@/lib/convertToGLB'; import { convertIFCtoGLB } from '@/lib/convertIFC'; const DEFAULT_URL = 'https://store.tracksteel.com.br/demo.ifc'; const PRESETS: { label: string; url: string }[] = [ { label: 'Demo IFC (TrackSteel)', url: DEFAULT_URL }, ]; /** Normaliza URLs de Dropbox / OneDrive para apontar ao binário direto. */ function normalizeCloudUrl(input: string): string { try { const u = new URL(input); if (/(^|\.)dropbox\.com$/.test(u.hostname)) { u.searchParams.set('dl', '1'); return u.toString(); } if (/(^|\.)1drv\.ms$|onedrive\.live\.com$/.test(u.hostname)) { u.searchParams.set('download', '1'); return u.toString(); } return input; } catch { return input; } } /** Tenta obter um token JWT de uma instância FileBrowser NoAuth (POST /api/login vazio). */ async function tryFileBrowserToken(origin: string): Promise { try { const r = await fetch(`${origin}/api/login`, { method: 'POST', mode: 'cors' }); if (!r.ok) return null; const txt = (await r.text()).trim(); // FileBrowser devolve o JWT como texto puro if (txt && txt.split('.').length === 3) return txt; return null; } catch { return null; } } /** Faz fetch tentando vários endpoints típicos de hosts que servem SPA (FileBrowser etc.). */ async function smartFetch(rawUrl: string): Promise { const url = normalizeCloudUrl(rawUrl.trim()); let res = await fetch(url, { mode: 'cors' }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const ct = res.headers.get('content-type') || ''; if (!ct.includes('text/html')) return res; // Servidor devolveu HTML — tentamos endpoints binários do FileBrowser const u = new URL(url); const path = u.pathname.startsWith('/api/raw/') ? u.pathname : `/api/raw${u.pathname}`; // 1ª tentativa: /api/raw/... cru try { const r = await fetch(`${u.origin}${path}${u.search}`, { mode: 'cors' }); if (r.ok && !(r.headers.get('content-type') || '').includes('text/html')) return r; if (r.status !== 401) { // pode ser outro erro — segue para fluxo de token } } catch { /* continua */ } // 2ª tentativa: pega token JWT NoAuth e usa ?auth= const token = await tryFileBrowserToken(u.origin); if (token) { const sep = u.search ? '&' : '?'; const r = await fetch(`${u.origin}${path}${u.search}${sep}auth=${encodeURIComponent(token)}`, { mode: 'cors' }); if (r.ok && !(r.headers.get('content-type') || '').includes('text/html')) return r; } throw new Error( 'O servidor devolveu HTML. Use um link DIRETO do arquivo (ex.: Dropbox ?dl=1, OneDrive ?download=1, ' + 'ou habilite endpoint público no FileBrowser).' ); } interface CloudLoaderProps { /** Renderiza como botão compacto (para HUD do AR) */ compact?: boolean; /** Variante visual do botão */ variant?: 'default' | 'outline'; className?: string; } export function CloudLoader({ compact = false, variant = 'outline', className }: CloudLoaderProps) { const setModel = useModelStore((s) => s.setModel); const [open, setOpen] = useState(false); const [url, setUrl] = useState(DEFAULT_URL); const [loading, setLoading] = useState(false); const handleLoad = useCallback(async (rawUrl: string) => { if (!rawUrl) return; setLoading(true); try { const fileName = rawUrl.split('/').pop()?.split('?')[0] || 'modelo-cloud'; const ext = getSupportedExtension(fileName); if (!ext) { toast.error('URL deve apontar para .GLB, .OBJ, .STL ou .IFC'); return; } const response = await smartFetch(rawUrl); const buffer = await response.arrayBuffer(); let blob: Blob, outName = fileName, outSize = buffer.byteLength; if (ext === 'glb') { blob = new Blob([buffer], { type: 'model/gltf-binary' }); } else if (ext === 'ifc') { const r = await convertIFCtoGLB(buffer, fileName); blob = r.blob; outName = r.fileName; outSize = r.fileSize; } else { const r = await convertToGLB(buffer, ext, fileName); blob = r.blob; outName = r.fileName; outSize = r.fileSize; } const blobUrl = URL.createObjectURL(blob); setModel({ fileName: outName, fileSize: outSize, url: blobUrl }); toast.success(`"${outName}" carregado da nuvem`); setOpen(false); } catch (err) { console.error(err); toast.error(err instanceof Error ? err.message : 'Falha ao carregar da nuvem (verifique URL/CORS)'); } finally { setLoading(false); } }, [setModel]); return ( {compact ? ( ) : ( )} Carregar modelo da nuvem URL direta para .GLB, .OBJ, .STL ou .IFC. Dropbox use ?dl=1, OneDrive ?download=1. FileBrowser é detectado automaticamente.
setUrl(e.target.value)} placeholder="https://exemplo.com/modelo.glb" disabled={loading} className="font-mono text-xs" />
{PRESETS.length > 0 && (
{PRESETS.map((p) => ( ))}
)}
); }