Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at 6bfe758d2a9ea376552fb45e5e589bccd0cf4df5 180 lines 4.3 kB view raw
1/// <reference lib="dom" /> 2 3import {type PickerImage} from './picker.shared' 4import {type Dimensions} from './types' 5import {blobToDataUri, getDataUriSize} from './util' 6 7export async function compressIfNeeded( 8 img: PickerImage, 9 maxSize: number, 10): Promise<PickerImage> { 11 if (img.size < maxSize) { 12 return img 13 } 14 return await doResize(img.path, { 15 width: img.width, 16 height: img.height, 17 mode: 'stretch', 18 maxSize, 19 }) 20} 21 22export interface DownloadAndResizeOpts { 23 uri: string 24 width: number 25 height: number 26 mode: 'contain' | 'cover' | 'stretch' 27 maxSize: number 28 timeout: number 29} 30 31export async function downloadAndResize(opts: DownloadAndResizeOpts) { 32 const controller = new AbortController() 33 const to = setTimeout(() => controller.abort(), opts.timeout || 5e3) 34 const res = await fetch(opts.uri) 35 const resBody = await res.blob() 36 clearTimeout(to) 37 38 const dataUri = await blobToDataUri(resBody) 39 return await doResize(dataUri, opts) 40} 41 42export async function shareImageModal(_opts: {uri: string}) { 43 // TODO 44 throw new Error('TODO') 45} 46 47export async function saveImageToMediaLibrary(_opts: {uri: string}) { 48 // TODO 49 throw new Error('TODO') 50} 51 52export async function getImageDim(path: string): Promise<Dimensions> { 53 var img = document.createElement('img') 54 const promise = new Promise((resolve, reject) => { 55 img.onload = resolve 56 img.onerror = reject 57 }) 58 img.src = path 59 await promise 60 return {width: img.width, height: img.height} 61} 62 63// internal methods 64// = 65 66interface DoResizeOpts { 67 width: number 68 height: number 69 mode: 'contain' | 'cover' | 'stretch' 70 maxSize: number 71} 72 73async function doResize( 74 dataUri: string, 75 opts: DoResizeOpts, 76): Promise<PickerImage> { 77 let newDataUri 78 79 let minQualityPercentage = 0 80 let maxQualityPercentage = 101 //exclusive 81 82 while (maxQualityPercentage - minQualityPercentage > 1) { 83 const qualityPercentage = Math.round( 84 (maxQualityPercentage + minQualityPercentage) / 2, 85 ) 86 const tempDataUri = await createResizedImage(dataUri, { 87 width: opts.width, 88 height: opts.height, 89 quality: qualityPercentage / 100, 90 mode: opts.mode, 91 }) 92 93 if (getDataUriSize(tempDataUri) < opts.maxSize) { 94 minQualityPercentage = qualityPercentage 95 newDataUri = tempDataUri 96 } else { 97 maxQualityPercentage = qualityPercentage 98 } 99 } 100 101 if (!newDataUri) { 102 throw new Error('Failed to compress image') 103 } 104 return { 105 path: newDataUri, 106 mime: 'image/jpeg', 107 size: getDataUriSize(newDataUri), 108 width: opts.width, 109 height: opts.height, 110 } 111} 112 113function createResizedImage( 114 dataUri: string, 115 { 116 width, 117 height, 118 quality, 119 mode, 120 }: { 121 width: number 122 height: number 123 quality: number 124 mode: 'contain' | 'cover' | 'stretch' 125 }, 126): Promise<string> { 127 return new Promise((resolve, reject) => { 128 const img = document.createElement('img') 129 img.addEventListener('load', () => { 130 const canvas = document.createElement('canvas') 131 const ctx = canvas.getContext('2d') 132 if (!ctx) { 133 return reject(new Error('Failed to resize image')) 134 } 135 136 let scale = 1 137 if (mode === 'cover') { 138 scale = img.width < img.height ? width / img.width : height / img.height 139 } else if (mode === 'contain') { 140 scale = img.width > img.height ? width / img.width : height / img.height 141 } 142 let w = img.width * scale 143 let h = img.height * scale 144 145 canvas.width = w 146 canvas.height = h 147 148 ctx.drawImage(img, 0, 0, w, h) 149 resolve(canvas.toDataURL('image/jpeg', quality)) 150 }) 151 img.addEventListener('error', ev => { 152 reject(ev.error) 153 }) 154 img.src = dataUri 155 }) 156} 157 158export async function saveBytesToDisk( 159 filename: string, 160 bytes: Uint8Array<ArrayBuffer>, 161 type: string, 162) { 163 const blob = new Blob([bytes], {type}) 164 const url = URL.createObjectURL(blob) 165 await downloadUrl(url, filename) 166 // Firefox requires a small delay 167 setTimeout(() => URL.revokeObjectURL(url), 100) 168 return true 169} 170 171async function downloadUrl(href: string, filename: string) { 172 const a = document.createElement('a') 173 a.href = href 174 a.download = filename 175 a.click() 176} 177 178export async function safeDeleteAsync() { 179 // no-op 180}