urdf-visualizer / viewer /src /pages /ContentDetail.tsx
jurmy24's picture
feat: add viewer code
72f0edb
raw
history blame
6.77 kB
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;