4
5function Popup({ isOpen, onClose, content }) {
6 const [convertedImages, setConvertedImages] = useState({});
7 const [converting, setConverting] = useState({});
8
12 const response = await fetch(url);
13 const blob = await response.blob();
14 const img = await createImageBitmap(blob);
15
16 const canvas = document.createElement('canvas');
18 canvas.height = img.height;
19 const ctx = canvas.getContext('2d');
20 ctx.drawImage(img, 0, 0);
21
22 const webpBlob = await new Promise(resolve => canvas.toBlob(resolve, 'image/webp'));
23 const webpUrl = URL.createObjectURL(webpBlob);
24
25 setConvertedImages(prev => ({ ...prev, [index]: { url: webpUrl, size: webpBlob.size } }));
26 } catch (error) {
27 console.error("Error converting image:", error);
28 }
29 setConverting(prev => ({ ...prev, [index]: false }));
45 <button className="close-btn" onClick={onClose}>×</button>
46 <div className="popup-content">
47 {content.type === 'images' && (
48 <>
49 <h3>Images</h3>
50 <ul>
51 {content.items.map((item, index) => (
53 {item.url.split('/').pop()} - {formatSize(item.transferSize)}
54 <a href={item.url} download className="download-btn">Download</a>
55 {!convertedImages[index] && (
56 <button
57 onClick={() => convertToWebP(item.url, index)}
62 </button>
63 )}
64 {convertedImages[index] && (
65 <div>
66 <p>WebP Size: {formatSize(convertedImages[index].size)}</p>
67 <a href={convertedImages[index].url} download="converted.webp" className="download-btn">Download WebP</a>
68 </div>
69 )}
170
171 const resourceSummary = audits['resource-summary']?.details?.items || [];
172 const images = resourceSummary.find(item => item.resourceType === 'image') || { requestCount: 0, transferSize: 0 };
173 const videos = resourceSummary.find(item => item.resourceType === 'media') || { requestCount: 0, transferSize: 0 };
174 const totalBytes = resourceSummary.reduce((sum, item) => sum + (item.transferSize || 0), 0);
175
176 const networkRequests = audits['network-requests']?.details?.items || [];
177 const imageItems = networkRequests.filter(item => item.resourceType === 'Image' || item.resourceType === 'image');
178 const videoItems = networkRequests.filter(item => item.resourceType === 'Media' || item.resourceType === 'media');
179
202 cls: audits['cumulative-layout-shift']?.displayValue || 'N/A',
203 performance: categories.performance ? `${Math.round(categories.performance.score * 100)}%` : 'N/A',
204 images: {
205 count: images.requestCount || 0,
206 size: ((images.transferSize || 0) / 1024 / 1024).toFixed(2),
207 items: imageItems
208 },
209 videos: {
276 <li>Cumulative Layout Shift (CLS): {performanceMetrics.cls}</li>
277 <li>Performance Score: {performanceMetrics.performance}</li>
278 <li>Images: <span className="clickable" onClick={() => handleResourceClick('images')}>{performanceMetrics.images.count}</span> ({performanceMetrics.images.size} MB)</li>
279 <li>Videos: <span className="clickable" onClick={() => handleResourceClick('videos')}>{performanceMetrics.videos.count}</span> ({performanceMetrics.videos.size} MB)</li>
280 <li>Total Estimated Size: {performanceMetrics.totalSize}</li>