From b5cbc7347a7b4ea5b423b8fdf0e0c08661e7b48a Mon Sep 17 00:00:00 2001 From: Ben Bolte Date: Mon, 4 Nov 2024 14:04:22 -0800 Subject: [PATCH 01/10] refactor to improve organization (#524) * refactor to improve organization * extensive refactoring * components mostly working well * formatting * fixes --- frontend/public/examples/main.js | 6 - frontend/src/App.tsx | 9 +- .../src/components/DownloadKernelImage.tsx | 4 - frontend/src/components/SprigInitializer.tsx | 5 - .../listing/ListingArtifactRenderer.tsx | 43 + .../src/components/listing/ListingBody.tsx | 228 ---- .../components/listing/ListingDescription.tsx | 146 +-- .../components/listing/ListingFileUpload.tsx | 8 +- .../listing/ListingImageFlipper.tsx | 72 ++ .../listing/ListingImageGallery.tsx | 174 +++ .../ListingLoadingSkeleton.tsx} | 6 +- .../components/listing/ListingMetadata.tsx | 96 ++ .../src/components/listing/ListingName.tsx | 105 ++ .../components/listing/ListingRenderer.tsx | 107 ++ .../components/listing/ListingVoteButtons.tsx | 109 -- .../src/components/listing/UploadContent.tsx | 2 - .../listing/onshape/ListingOnshape.tsx | 125 +- .../listing/onshape/ListingOnshapeUpdate.tsx | 35 +- .../components/listing/pics/placeholder.jpg | Bin 0 -> 50230 bytes frontend/src/components/listing/types.ts | 6 + .../components/listings/ListingGridCard.tsx | 5 +- frontend/src/components/pages/FileBrowser.tsx | 68 +- .../pages/{ListingDetails.tsx => Listing.tsx} | 69 +- frontend/src/components/pages/Login.tsx | 2 +- frontend/src/components/pages/Signup.tsx | 2 +- frontend/src/components/pages/StompyMini.tsx | 46 - frontend/src/components/pages/StompyPro.tsx | 46 - .../src/components/products/ProductPage.tsx | 1125 ----------------- frontend/src/components/ui/Card.tsx | 2 +- frontend/src/components/ui/PageHeader.tsx | 54 +- frontend/src/gen/api.ts | 88 +- frontend/src/hooks/useGetListing.tsx | 47 - store/app/crud/listings.py | 12 +- store/app/routers/artifacts.py | 17 +- store/app/routers/listings.py | 140 +- 35 files changed, 958 insertions(+), 2051 deletions(-) create mode 100644 frontend/src/components/listing/ListingArtifactRenderer.tsx delete mode 100644 frontend/src/components/listing/ListingBody.tsx create mode 100644 frontend/src/components/listing/ListingImageFlipper.tsx create mode 100644 frontend/src/components/listing/ListingImageGallery.tsx rename frontend/src/components/{products/ProductPageSkeleton.tsx => listing/ListingLoadingSkeleton.tsx} (95%) create mode 100644 frontend/src/components/listing/ListingMetadata.tsx create mode 100644 frontend/src/components/listing/ListingName.tsx create mode 100644 frontend/src/components/listing/ListingRenderer.tsx delete mode 100644 frontend/src/components/listing/ListingVoteButtons.tsx create mode 100644 frontend/src/components/listing/pics/placeholder.jpg create mode 100644 frontend/src/components/listing/types.ts rename frontend/src/components/pages/{ListingDetails.tsx => Listing.tsx} (51%) delete mode 100644 frontend/src/components/pages/StompyMini.tsx delete mode 100644 frontend/src/components/pages/StompyPro.tsx delete mode 100644 frontend/src/components/products/ProductPage.tsx delete mode 100644 frontend/src/hooks/useGetListing.tsx diff --git a/frontend/public/examples/main.js b/frontend/public/examples/main.js index 9b4b3596..df46016a 100644 --- a/frontend/public/examples/main.js +++ b/frontend/public/examples/main.js @@ -299,12 +299,6 @@ export class MuJoCoDemo { cvel_slice !== -1 ? this.simulation.cvel.slice(cvel_slice) : []; const qfrc_actuator = this.simulation.qfrc_actuator; - // console.log('qpos length:', qpos.length); - // console.log('qvel length:', qvel.length); - // console.log('cinert length:', cinert.length); - // console.log('cvel length:', cvel.length); - // console.log('qfrc_actuator length:', qfrc_actuator.length); - const obsComponents = [ ...qpos, ...qvel, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0205c5cc..96063146 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -17,7 +17,7 @@ import Create from "@/components/pages/Create"; import EmailSignup from "@/components/pages/EmailSignup"; import FileBrowser from "@/components/pages/FileBrowser"; import Home from "@/components/pages/Home"; -import ListingDetails from "@/components/pages/ListingDetails"; +import Listing from "@/components/pages/Listing"; import Login from "@/components/pages/Login"; import Logout from "@/components/pages/Logout"; import NotFound from "@/components/pages/NotFound"; @@ -33,8 +33,6 @@ import OrderSuccess from "./components/pages/OrderSuccess"; import OrdersPage from "./components/pages/Orders"; import PrivacyPolicy from "./components/pages/PrivacyPolicy"; import ResearchPage from "./components/pages/ResearchPage"; -import StompyMini from "./components/pages/StompyMini"; -import StompyPro from "./components/pages/StompyPro"; import TerminalPage from "./components/pages/Terminal"; import TermsOfService from "./components/pages/TermsOfService"; @@ -74,7 +72,7 @@ const App = () => { } /> } + element={} /> } /> } /> @@ -82,9 +80,6 @@ const App = () => { } /> } /> - } /> - } /> - } /> } /> diff --git a/frontend/src/components/DownloadKernelImage.tsx b/frontend/src/components/DownloadKernelImage.tsx index 9846587b..5073739f 100644 --- a/frontend/src/components/DownloadKernelImage.tsx +++ b/frontend/src/components/DownloadKernelImage.tsx @@ -49,15 +49,11 @@ const DownloadKernelImage = ({ kernelImage, onEdit, onDelete }: Props) => { setIsDownloading(true); try { - console.log( - `Requesting download URL for kernel image ID: ${kernelImage.id}`, - ); const response = await axios.get( `/api/kernel-images/download/${kernelImage.id}`, ); const presignedUrl = response.data; - console.log(`Received presigned URL: ${presignedUrl}`); // Open the URL in a new tab window.open(presignedUrl, "_blank"); diff --git a/frontend/src/components/SprigInitializer.tsx b/frontend/src/components/SprigInitializer.tsx index 6a7d3d37..05baaebc 100644 --- a/frontend/src/components/SprigInitializer.tsx +++ b/frontend/src/components/SprigInitializer.tsx @@ -13,8 +13,6 @@ const SprigInitializer = () => { if (sprigConsent) { const initializeSprig = () => { if (window.Sprig && typeof window.Sprig === "function") { - console.log("Sprig initialized"); - // Set user ID and email in Sprig if user is authenticated if (isAuthenticated && currentUser) { window.Sprig("setUserId", currentUser.id); @@ -24,14 +22,11 @@ const SprigInitializer = () => { // Track page view window.Sprig("track", "page_view", { path: location.pathname }); } else { - console.log("Sprig is not ready yet. Retrying..."); setTimeout(initializeSprig, 500); } }; initializeSprig(); - } else { - console.log("Sprig tracking is disabled due to user preferences."); } }, [location.pathname, isAuthenticated, currentUser]); diff --git a/frontend/src/components/listing/ListingArtifactRenderer.tsx b/frontend/src/components/listing/ListingArtifactRenderer.tsx new file mode 100644 index 00000000..3442e87f --- /dev/null +++ b/frontend/src/components/listing/ListingArtifactRenderer.tsx @@ -0,0 +1,43 @@ +import { FaFileArchive } from "react-icons/fa"; +import { Link } from "react-router-dom"; + +import placeholder from "@/components/listing/pics/placeholder.jpg"; +import { Artifact } from "@/components/listing/types"; + +interface Props { + artifact: Artifact; +} + +const ListingArtifactRenderer = ({ artifact }: Props) => { + switch (artifact.artifact_type) { + case "image": + return ( + {artifact.name} + ); + case "tgz": + return ( +
+ + + +
+ ); + default: + return ( + {artifact.name} + ); + } +}; + +export default ListingArtifactRenderer; diff --git a/frontend/src/components/listing/ListingBody.tsx b/frontend/src/components/listing/ListingBody.tsx deleted file mode 100644 index 25d9cf55..00000000 --- a/frontend/src/components/listing/ListingBody.tsx +++ /dev/null @@ -1,228 +0,0 @@ -import React, { useEffect, useState } from "react"; -import Masonry from "react-masonry-css"; - -import ListingOnshape from "@/components/listing/onshape/ListingOnshape"; -import ProductPage from "@/components/products/ProductPage"; -import { Card, CardContent } from "@/components/ui/Card"; -import { useAlertQueue } from "@/hooks/useAlertQueue"; -import { useAuthentication } from "@/hooks/useAuth"; - -import ArtifactCard from "./artifacts/ArtifactCard"; -import LoadingArtifactCard from "./artifacts/LoadingArtifactCard"; - -// Update the ListingResponse type to match the actual structure -type ListingResponse = { - id: string; - name: string; - username: string | null; - slug: string | null; - description: string | null; - child_ids: string[]; - tags: string[]; - onshape_url: string | null; - can_edit: boolean; - created_at: number; - creator_name: string | null; - uploaded_files?: { url: string }[]; - price: number | null; - images?: string[]; - artifacts?: - | { - artifact_id: string; - listing_id: string; - name: string; - artifact_type: - | "image" - | "urdf" - | "mjcf" - | "stl" - | "obj" - | "dae" - | "ply" - | "tgz" - | "zip"; - description: string | null; - timestamp: number; - urls: { large: string }; - is_main: boolean; - }[] - | undefined; - main_image_url?: string; -}; - -interface ListingBodyProps { - listing: ListingResponse; - newTitle?: string; -} - -const ListingBody: React.FC = ({ listing, newTitle }) => { - const [isModalOpen, setIsModalOpen] = useState(false); - const [selectedImage, setSelectedImage] = useState(null); - const [images, setImages] = useState(listing.images || []); - const [artifacts, setArtifacts] = useState< - ListingResponse["artifacts"] | null - >(null); - const auth = useAuthentication(); - const { addErrorAlert } = useAlertQueue(); - - const breakpointColumnsObj = { - default: 3, - 1024: 2, - 640: 1, - }; - - const handleDeleteArtifact = (artifactId: string) => { - setArtifacts((prevArtifacts) => - prevArtifacts - ? prevArtifacts.filter( - (artifact) => artifact.artifact_id !== artifactId, - ) - : null, - ); - }; - - useEffect(() => { - const fetchArtifacts = async () => { - try { - const { data, error } = await auth.client.GET( - "/artifacts/list/{listing_id}", - { - params: { path: { listing_id: listing.id } }, - }, - ); - - if (error) { - addErrorAlert(error); - } else { - setArtifacts(data.artifacts); - const artifactImages = data.artifacts - .filter( - (artifact: { artifact_type: string }) => - artifact.artifact_type === "image", - ) - .map( - (artifact: { urls: { large: string } }) => artifact.urls.large, - ); - - const uploadedImages = - listing.uploaded_files?.map((file: { url: string }) => file.url) || - []; - - const allImages = [...uploadedImages, ...artifactImages]; - if (listing.main_image_url) { - const mainImageIndex = allImages.findIndex( - (img) => img === listing.main_image_url, - ); - if (mainImageIndex !== -1) { - const [mainImage] = allImages.splice(mainImageIndex, 1); - allImages.unshift(mainImage); - } - } - - setImages(allImages); - } - } catch (err) { - addErrorAlert( - `Error fetching artifacts: ${err instanceof Error ? err.message : String(err)}`, - ); - } - }; - - fetchArtifacts(); - }, [listing.id, listing.main_image_url, auth.client, addErrorAlert]); - - const productInfo = { - name: newTitle || listing.name, - description: listing.description || "Product Description", - price: listing.price ?? 0, - productId: listing.id, - }; - - const openModal = (image: string) => { - setSelectedImage(image); - setIsModalOpen(true); - }; - - const closeModal = () => { - setSelectedImage(null); - setIsModalOpen(false); - }; - - return ( -
- { - setImages(newImages); - }} - /> -
- {}} - edit={listing.can_edit} - /> -
- -
- - {artifacts === null ? ( - - ) : artifacts ? ( - artifacts - .slice() - .reverse() - .filter((artifact) => artifact.artifact_type !== "image") - .map((artifact) => ( - - - - handleDeleteArtifact(artifact.artifact_id) - } - canEdit={listing.can_edit} - /> - - - )) - ) : null} - -
- - {isModalOpen && selectedImage && ( -
-
- Selected - -
-
- )} -
- ); -}; - -export default ListingBody; diff --git a/frontend/src/components/listing/ListingDescription.tsx b/frontend/src/components/listing/ListingDescription.tsx index 8c9bc6f9..50f8848a 100644 --- a/frontend/src/components/listing/ListingDescription.tsx +++ b/frontend/src/components/listing/ListingDescription.tsx @@ -1,5 +1,5 @@ -import { useEffect, useState } from "react"; -import { FaFile, FaPen } from "react-icons/fa"; +import { useState } from "react"; +import { FaCheck, FaPen, FaTimes } from "react-icons/fa"; import Markdown from "react-markdown"; import { TextArea } from "@/components/ui/Input/Input"; @@ -11,7 +11,6 @@ import remarkGfm from "remark-gfm"; interface RenderDescriptionProps { description: string; - onImageClick?: (src: string, alt: string) => void; } const transformUrl = (url: string) => { @@ -21,10 +20,7 @@ const transformUrl = (url: string) => { return `https://${url}`; }; -export const RenderDescription = ({ - description, - onImageClick, -}: RenderDescriptionProps) => { +export const RenderDescription = ({ description }: RenderDescriptionProps) => { return (
(

{children}

), - img: ({ src, alt }) => ( - src && onImageClick?.(src, alt ?? "")} - > - {alt} - {alt && {alt}} - - ), }} > {description} @@ -110,21 +97,6 @@ const ListingDescription = (props: Props) => { ); const [hasChanged, setHasChanged] = useState(false); const [submitting, setSubmitting] = useState(false); - const [debouncedDescription, setDebouncedDescription] = useState( - initialDescription ?? "", - ); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [imageModal, setImageModal] = useState<[string, string] | null>(null); - - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedDescription(newDescription); - }, 300); - - return () => { - clearTimeout(handler); - }; - }, [newDescription]); const handleSave = async () => { if (!hasChanged) { @@ -149,61 +121,83 @@ const ListingDescription = (props: Props) => { } else { addAlert("Listing updated successfully", "success"); setIsEditing(false); + setNewDescription(newDescription); + setHasChanged(false); } setSubmitting(false); }; + const handleCancel = () => { + setIsEditing(false); + setNewDescription(initialDescription ?? ""); + setHasChanged(false); + }; + return ( -
+
{submitting ? ( ) : ( <> - {isEditing && ( -