your personal website on atproto - mirror blento.app
25
fork

Configure Feed

Select the types of activity you want to include in your feed.

at fix/layout-stuff 144 lines 3.8 kB view raw
1import { getCDNImageBlobUrl, uploadBlob } from './methods'; 2 3export function compressImage( 4 file: File | Blob, 5 maxSize: number = 900 * 1024, 6 maxDimension: number = 2048 7): Promise<{ 8 blob: Blob; 9 aspectRatio: { 10 width: number; 11 height: number; 12 }; 13}> { 14 return new Promise((resolve, reject) => { 15 const img = new Image(); 16 const reader = new FileReader(); 17 18 reader.onload = (e) => { 19 if (!e.target?.result) { 20 return reject(new Error('Failed to read file.')); 21 } 22 img.src = e.target.result as string; 23 }; 24 25 reader.onerror = (err) => reject(err); 26 reader.readAsDataURL(file); 27 28 img.onload = () => { 29 let width = img.width; 30 let height = img.height; 31 32 const isSmallEnough = file.size <= maxSize; 33 34 if (width > maxDimension || height > maxDimension) { 35 if (width > height) { 36 height = Math.round((maxDimension / width) * height); 37 width = maxDimension; 38 } else { 39 width = Math.round((maxDimension / height) * width); 40 height = maxDimension; 41 } 42 } 43 44 // Create a canvas to draw the image 45 const canvas = document.createElement('canvas'); 46 canvas.width = width; 47 canvas.height = height; 48 const ctx = canvas.getContext('2d'); 49 if (!ctx) return reject(new Error('Failed to get canvas context.')); 50 ctx.drawImage(img, 0, 0, width, height); 51 52 // Use WebP if supported, fall back to JPEG (Safari doesn't support WebP encoding) 53 const supportsWebP = canvas.toDataURL('image/webp').startsWith('data:image/webp'); 54 const mimeType = supportsWebP ? 'image/webp' : 'image/jpeg'; 55 let quality = 0.9; 56 57 function attemptCompression() { 58 canvas.toBlob( 59 (blob) => { 60 if (!blob) { 61 return reject(new Error('Compression failed.')); 62 } 63 if (isSmallEnough || blob.size <= maxSize || quality < 0.3) { 64 resolve({ 65 blob, 66 aspectRatio: { 67 width, 68 height 69 } 70 }); 71 } else { 72 quality -= 0.1; 73 attemptCompression(); 74 } 75 }, 76 mimeType, 77 quality 78 ); 79 } 80 81 attemptCompression(); 82 }; 83 84 img.onerror = (err) => reject(err); 85 }); 86} 87 88export async function checkAndUploadImage( 89 recordWithImage: Record<string, any>, 90 key: string = 'image', 91 // e.g. /api/image-proxy?url= 92 imageProxy?: string 93) { 94 if (!recordWithImage[key]) return; 95 96 // Already uploaded as blob 97 if (typeof recordWithImage[key] === 'object' && recordWithImage[key].$type === 'blob') { 98 return; 99 } 100 101 if (typeof recordWithImage[key] === 'string' && imageProxy) { 102 const proxyUrl = imageProxy + encodeURIComponent(recordWithImage[key]); 103 const response = await fetch(proxyUrl); 104 if (!response.ok) { 105 throw Error('failed to get image from image proxy'); 106 } 107 108 const blob = await response.blob(); 109 const compressedBlob = await compressImage(blob); 110 111 recordWithImage[key] = await uploadBlob({ blob: compressedBlob.blob }); 112 113 return; 114 } 115 116 if (recordWithImage[key]?.blob) { 117 if (recordWithImage[key].objectUrl) { 118 URL.revokeObjectURL(recordWithImage[key].objectUrl); 119 } 120 const compressedBlob = await compressImage(recordWithImage[key].blob); 121 recordWithImage[key] = await uploadBlob({ blob: compressedBlob.blob }); 122 } 123} 124 125export function getImageFromRecord( 126 recordWithImage: Record<string, any> | undefined, 127 did: string, 128 key: string = 'image' 129): string | undefined { 130 if (!recordWithImage?.[key]) return; 131 132 if (typeof recordWithImage[key] === 'object' && recordWithImage[key].$type === 'blob') { 133 return getCDNImageBlobUrl({ did, blob: recordWithImage[key] }); 134 } 135 136 if (recordWithImage[key].objectUrl) return recordWithImage[key].objectUrl; 137 138 if (recordWithImage[key].blob) { 139 recordWithImage[key].objectUrl = URL.createObjectURL(recordWithImage[key].blob); 140 return recordWithImage[key].objectUrl; 141 } 142 143 return recordWithImage[key]; 144}