Spaces:
Running
Running
import React, { useEffect, useState, useRef } from "react"; | |
import { useParams, useNavigate } from "react-router-dom"; | |
import Layout from "@/components/layout/Layout"; | |
import Header from "../components/Header"; | |
import { useUrdf } from "@/hooks/useUrdfData"; | |
import { toast } from "sonner"; | |
import { getImageUrl } from "@/api/urdfApi"; | |
import { useUrdf as useUrdfContext } from "@/hooks/useUrdf"; | |
import { downloadAndExtractZip } from "@/lib/UrdfDragAndDrop"; | |
import { supabase } from "@/integrations/supabase/client"; | |
import { Loader2 } from "lucide-react"; | |
// This is needed to make TypeScript recognize webkitdirectory as a valid attribute | |
declare module "react" { | |
interface InputHTMLAttributes<T> extends React.HTMLAttributes<T> { | |
directory?: string; | |
webkitdirectory?: string; | |
} | |
} | |
const ContentDetail: React.FC = () => { | |
const { id } = useParams<{ id: string }>(); | |
const navigate = useNavigate(); | |
const { data: content, isLoading, error } = useUrdf(id || ""); | |
const [imageUrl, setImageUrl] = useState<string>("/placeholder.svg"); | |
const { urdfProcessor, processUrdfFiles, currentRobotData } = | |
useUrdfContext(); | |
const [isLoadingUrdf, setIsLoadingUrdf] = useState(false); | |
// Reference to track if we've already loaded this model | |
const hasModelBeenLoaded = useRef(false); | |
// State to track when the model is fully loaded and ready to display | |
const [isModelReady, setIsModelReady] = useState(false); | |
// If error occurred during fetch, show error and redirect | |
useEffect(() => { | |
if (error) { | |
toast.error("Error loading robot details"); | |
navigate("/"); | |
} | |
}, [error, navigate]); | |
// If content not found and not loading, redirect to home | |
useEffect(() => { | |
if (!content && !isLoading && !error) { | |
toast.error("Robot not found"); | |
navigate("/"); | |
} | |
}, [content, isLoading, error, navigate]); | |
// Fetch the image when content is loaded | |
useEffect(() => { | |
if (content?.imageUrl) { | |
const fetchImage = async () => { | |
const url = await getImageUrl(content.imageUrl); | |
setImageUrl(url); | |
}; | |
fetchImage(); | |
} | |
}, [content]); | |
// Reset the model states when the ID changes | |
useEffect(() => { | |
hasModelBeenLoaded.current = false; | |
setIsModelReady(false); | |
}, [id]); | |
// Check if we already have this model loaded | |
useEffect(() => { | |
if (content && currentRobotData?.name === content.title) { | |
// The model is already loaded | |
hasModelBeenLoaded.current = true; | |
setIsModelReady(true); | |
console.log("Model already loaded and ready to display"); | |
} | |
}, [content, currentRobotData]); | |
// Load the URDF model automatically when the content details are loaded | |
useEffect(() => { | |
const loadUrdfModel = async () => { | |
// Only load if we have content, a processor, not currently loading, | |
// and haven't already loaded this model | |
if ( | |
content?.urdfPath && | |
urdfProcessor && | |
!isLoadingUrdf && | |
!hasModelBeenLoaded.current | |
) { | |
// Mark that we've attempted to load this model | |
hasModelBeenLoaded.current = true; | |
setIsLoadingUrdf(true); | |
setIsModelReady(false); // Reset model ready state while loading | |
console.log(`Auto-loading URDF model for ${content.title}...`); | |
const loadingToast = toast.loading(`Loading ${content.title}...`, { | |
description: "Downloading and extracting URDF model", | |
}); | |
try { | |
// Get the actual URL for the zip file | |
let zipUrl = content.urdfPath; | |
// If it's a storage path and not a full URL, get a signed URL | |
if (!zipUrl.startsWith("http")) { | |
const { data, error } = await supabase.storage | |
.from("robotbucket") | |
.createSignedUrl(zipUrl, 3600); // 1 hour expiry | |
if (error) { | |
throw new Error( | |
`Failed to get URL for zip file: ${error.message}` | |
); | |
} | |
zipUrl = data.signedUrl; | |
} | |
// Download and extract the zip file | |
const { files, availableModels } = await downloadAndExtractZip( | |
zipUrl, | |
urdfProcessor | |
); | |
// Dismiss loading toast | |
toast.dismiss(loadingToast); | |
// If successful, process the extracted files similar to drag and drop | |
if (files && availableModels.length > 0) { | |
await processUrdfFiles(files, availableModels); | |
toast.success(`Loaded model: ${content.title}`, { | |
description: "URDF model ready for viewing", | |
}); | |
// Set a short timeout to ensure the URDF context is fully updated | |
setTimeout(() => { | |
setIsModelReady(true); | |
}, 1000); | |
} else { | |
toast.error("No URDF models found in the zip file"); | |
} | |
} catch (error) { | |
// Dismiss loading toast and show error | |
toast.dismiss(loadingToast); | |
console.error("Error processing URDF zip:", error); | |
toast.error("Failed to load URDF model", { | |
description: | |
error instanceof Error ? error.message : "Unknown error", | |
}); | |
} finally { | |
setIsLoadingUrdf(false); | |
} | |
} | |
}; | |
loadUrdfModel(); | |
}, [content, urdfProcessor, processUrdfFiles, isLoadingUrdf]); | |
// If still loading content data, show initial loading state | |
if (isLoading || !content) { | |
return ( | |
<div className="min-h-screen bg-netflix-background flex items-center justify-center"> | |
<div className="text-white text-xl">Loading robot details...</div> | |
</div> | |
); | |
} | |
// // If content is loaded but URDF model is still loading or processing, show URDF loading state | |
// if (!isModelReady || isLoadingUrdf) { | |
// return ( | |
// <div className="min-h-screen bg-netflix-background flex flex-col items-center justify-center"> | |
// <Loader2 className="h-12 w-12 text-white animate-spin mb-4" /> | |
// <div className="text-white text-xl"> | |
// Loading {content.title} model... | |
// </div> | |
// <p className="text-gray-400 mt-2"> | |
// Please wait while we prepare the 3D model | |
// </p> | |
// </div> | |
// ); | |
// } | |
// Only render the full layout once the model is ready | |
return ( | |
<div className="flex flex-col h-screen bg-netflix-background text-white overflow-hidden"> | |
{/* Layout taking full height */} | |
<div className="w-full h-full"> | |
<Layout /> | |
</div> | |
{/* Header absolutely positioned at the top */} | |
<div className="absolute top-0 left-0 right-0 z-50"> | |
<Header /> | |
</div> | |
</div> | |
); | |
}; | |
export default ContentDetail; | |