gemini-3d-drawing / components /LibraryPage.js
Trudy's picture
Initial commit with proper LFS tracking
5f07a23
import { useState, useEffect } from 'react';
import { X, ArrowLeft, Wand2 } from 'lucide-react';
import Masonry from 'react-masonry-css';
// Function to get last modified date from filename for sorting
const getDateFromFilename = (filename) => {
// Extract date if it's in the format "chrome-study - YYYY-MM-DDTHHMMSS"
const dateMatch = filename.match(/chrome-study - (\d{4}-\d{2}-\d{2}T\d{6})/);
if (dateMatch) {
return new Date(dateMatch[1].replace('T', 'T').slice(0, 19));
}
// Extract number if it's in the format "chrome-study (XX)" or "chrome-study-XX"
const numMatch = filename.match(/chrome-study[- ]\(?(\d+)/);
if (numMatch) {
return Number.parseInt(numMatch[1], 10);
}
return 0; // Default value for sorting
};
const LibraryPage = ({ onBack, onUseAsTemplate }) => {
const [images, setImages] = useState([]);
const [fullscreenImage, setFullscreenImage] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [hoveredImage, setHoveredImage] = useState(null);
// Breakpoint columns configuration for the masonry layout
const breakpointColumnsObj = {
default: 4,
1100: 3,
700: 2,
500: 1
};
useEffect(() => {
// Function to get all images from the library folder
const fetchImages = async () => {
try {
// Simulate fetching the list of images
// In a real app, you would fetch this from an API
const imageFiles = [
"chrome-study (17).png",
"chrome-study (19).png",
"chrome-study (27).png",
"chrome-study (43).png",
"chrome-study (47).png",
"chrome-study (48).png",
"chrome-study (55).png",
"chrome-study (56).png",
"chrome-study (58).png",
"chrome-study (62).png",
"chrome-study (64).png",
"chrome-study (72).png",
"chrome-study (76).png",
"chrome-study (77).png",
"chrome-study (78).png",
"chrome-study (79).png",
"chrome-study (81).png",
"chrome-study (83).png",
"chrome-study (84).png",
"chrome-study (86).png",
"chrome-study (87).png",
"chrome-study (92).png",
"chrome-study (94).png",
"chrome-study (95).png",
"chrome-study (98).png",
"chrome-study (99).png",
"chrome-study - 2025-03-29T231111.407.png",
"chrome-study - 2025-03-29T231628.676.png",
"chrome-study - 2025-03-29T231852.687.png",
"chrome-study - 2025-03-29T232157.263.png",
"chrome-study - 2025-03-29T232601.690.png",
"chrome-study - 2025-03-29T235802.886.png",
"chrome-study - 2025-03-30T000256.137.png",
"chrome-study - 2025-03-30T000847.148.png",
"chrome-study - 2025-03-30T001126.978.png",
"chrome-study - 2025-03-30T001518.410.png",
"chrome-study - 2025-03-30T002129.834.png",
"chrome-study - 2025-03-30T002928.187.png",
"chrome-study - 2025-03-30T003503.053.png",
"chrome-study - 2025-03-30T003713.255.png",
"chrome-study - 2025-03-30T003942.300.png",
"chrome-study - 2025-03-30T011127.402.png",
"chrome-study-11.png",
"chrome-study-6.png"
];
// Sort images by "newest" (using filename to guess date/order)
// In a real app, you would have actual metadata
const sortedImages = imageFiles.sort((a, b) => {
const dateA = getDateFromFilename(a);
const dateB = getDateFromFilename(b);
return dateB - dateA; // Descending order
});
setImages(sortedImages);
setIsLoading(false);
} catch (error) {
console.error("Error fetching library images:", error);
setIsLoading(false);
}
};
fetchImages();
}, []);
const handleImageClick = (imagePath) => {
setFullscreenImage(imagePath);
};
const handleKeyDown = (event, imagePath) => {
if (event.key === 'Enter' || event.key === ' ') {
setFullscreenImage(imagePath);
}
};
return (
<div className="flex min-h-screen flex-col items-center justify-start bg-gray-50 p-2 md:p-4 overflow-y-auto">
<div className="w-full max-w-[1800px] mx-auto pb-32">
{/* Fixed header section */}
<div className="fixed top-0 left-0 right-0 bg-gray-50 z-10 px-2 md:px-4 pt-2 md:pt-4 pb-3">
<div className="w-full max-w-[1800px] mx-auto">
{/* Simple Header */}
<div className="flex items-center justify-between mt-4 mx-1">
<button
type="button"
onClick={onBack}
className="flex items-center text-gray-800 hover:text-gray-600 hover:cursor-pointer transition-colors text-lg font-medium"
aria-label="Go back to gallery"
>
<ArrowLeft className="w-5 h-5 mr-1" />
Gallery
</button>
<div>
<span className="inline-flex items-center rounded-full border px-5 py-2 border-gray-200 bg-gray-100 text-base text-gray-500">
Submit by replying to this{" "}<a href="https://x.com/dev_valladares/status/1799888888888888888" target="_blank" rel="noreferrer" className="underline ml-1">tweet</a>
</span>
</div>
</div>
</div>
</div>
{/* Content with padding to account for fixed header */}
<div className="space-y-4 mt-28">
{/* Loading state */}
{isLoading && (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-gray-400" />
</div>
)}
{/* Masonry grid of images */}
<Masonry
breakpointCols={breakpointColumnsObj}
className="flex w-auto -ml-4"
columnClassName="pl-4 bg-clip-padding"
>
{images.map((image, index) => (
<button
key={image}
className="mb-4 cursor-pointer transform transition-transform hover:scale-[1.01] text-left block w-full p-0 border-0 bg-transparent"
onClick={() => handleImageClick(`/library/${image}`)}
onMouseEnter={() => setHoveredImage(image)}
onMouseLeave={() => setHoveredImage(null)}
type="button"
aria-label={`Screenshot ${index + 1}`}
>
<div className="relative rounded-xl overflow-hidden border border-gray-200 shadow-sm bg-white">
<img
src={`/library/${image}`}
alt={`Screenshot ${index + 1}`}
className="w-full h-auto object-cover"
loading="lazy"
/>
{/* Use as template button */}
{hoveredImage === image && onUseAsTemplate && (
<div className="absolute bottom-2 right-2 z-10">
<button
onClick={(e) => {
e.stopPropagation(); // Prevent opening the fullscreen view
onUseAsTemplate(`/library/${image}`);
}}
className="flex items-center gap-1 bg-white/90 hover:bg-white text-gray-800 px-3 py-1.5 rounded-full text-xs font-medium shadow-md transition-all"
type="button"
>
<Wand2 className="w-3 h-3" />
Use as template
</button>
</div>
)}
</div>
</button>
))}
</Masonry>
{/* No images state */}
{!isLoading && images.length === 0 && (
<div className="flex flex-col items-center justify-center h-64 text-gray-500">
<p className="text-lg mb-2">No images in library</p>
<p className="text-sm">Create some images to see them here</p>
</div>
)}
</div>
</div>
{/* Fullscreen image modal */}
{fullscreenImage && (
<div className="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4">
<div className="relative max-w-4xl w-full max-h-[90vh]">
<button
type="button"
onClick={() => setFullscreenImage(null)}
className="absolute -top-12 right-0 p-2 text-white hover:text-gray-300 transition-colors"
aria-label="Close fullscreen view"
>
<X className="w-6 h-6" />
</button>
<img
src={fullscreenImage}
alt="Fullscreen view"
className="w-full h-auto object-contain max-h-[90vh] rounded-lg"
/>
</div>
</div>
)}
</div>
);
};
export default LibraryPage;