|
import { useState, useEffect } from 'react'; |
|
import { X, ArrowLeft, Wand2 } from 'lucide-react'; |
|
import Masonry from 'react-masonry-css'; |
|
|
|
|
|
const getDateFromFilename = (filename) => { |
|
|
|
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)); |
|
} |
|
|
|
|
|
const numMatch = filename.match(/chrome-study[- ]\(?(\d+)/); |
|
if (numMatch) { |
|
return Number.parseInt(numMatch[1], 10); |
|
} |
|
|
|
return 0; |
|
}; |
|
|
|
const LibraryPage = ({ onBack, onUseAsTemplate }) => { |
|
const [images, setImages] = useState([]); |
|
const [fullscreenImage, setFullscreenImage] = useState(null); |
|
const [isLoading, setIsLoading] = useState(true); |
|
const [hoveredImage, setHoveredImage] = useState(null); |
|
|
|
|
|
const breakpointColumnsObj = { |
|
default: 4, |
|
1100: 3, |
|
700: 2, |
|
500: 1 |
|
}; |
|
|
|
useEffect(() => { |
|
|
|
const fetchImages = async () => { |
|
try { |
|
|
|
|
|
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" |
|
]; |
|
|
|
|
|
|
|
const sortedImages = imageFiles.sort((a, b) => { |
|
const dateA = getDateFromFilename(a); |
|
const dateB = getDateFromFilename(b); |
|
return dateB - dateA; |
|
}); |
|
|
|
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; |