Spaces:
Running
Running
File size: 9,111 Bytes
5f07a23 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
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; |