1/** 2 * Converts base64-encoded data to a `Blob` object. 3 * @see https://stackoverflow.com/a/20151856 4 */ 5export function base64toBlob(base64Data: string, contentType: string): Blob { 6 contentType = contentType || ''; 7 const sliceSize = 1024; 8 const byteCharacters = atob(base64Data); 9 const bytesLength = byteCharacters.length; 10 const slicesCount = Math.ceil(bytesLength / sliceSize); 11 const byteArrays = new Array(slicesCount); 12 13 for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { 14 const begin = sliceIndex * sliceSize; 15 const end = Math.min(begin + sliceSize, bytesLength); 16 17 const bytes = new Array(end - begin); 18 for (let offset = begin, i = 0; offset < end; ++i, ++offset) { 19 bytes[i] = byteCharacters[offset].charCodeAt(0); 20 } 21 byteArrays[sliceIndex] = new Uint8Array(bytes); 22 } 23 // I cannot use `@ts-expect-error` here because some environments consider this correct: 24 // expo-module build - OK, 25 // expo-module test - error 26 // @ts-ignore `Blob` from `lib.dom.d.ts` and the one from `@types/react-native` differ somehow 27 return new Blob(byteArrays, { type: contentType }); 28} 29 30/** 31 * Converts blob to base64-encoded string with Data-URL prefix. 32 */ 33export function blobToBase64Async(blob: Blob): Promise<string> { 34 return new Promise((resolve, _) => { 35 const reader = new FileReader(); 36 reader.onloadend = () => resolve(reader.result as string); 37 reader.readAsDataURL(blob); 38 }); 39} 40 41export function htmlToPlainText(html: string) { 42 const tempDivElement = document.createElement('div'); 43 tempDivElement.innerHTML = html; 44 return tempDivElement.textContent || tempDivElement.innerText || ''; 45} 46 47export function getImageSizeFromBlobAsync(blob: Blob): Promise<{ width: number; height: number }> { 48 return new Promise((resolve, _) => { 49 const blobUrl = URL.createObjectURL(blob); 50 const img = document.createElement('img'); 51 img.src = blobUrl; 52 img.onload = function () { 53 resolve({ width: img.width, height: img.height }); 54 }; 55 }); 56} 57 58export async function findImageInClipboardAsync(items: ClipboardItems): Promise<Blob | null> { 59 for (const clipboardItem of items) { 60 // first look for png 61 if (clipboardItem.types.some((type) => type === 'image/png')) { 62 return await clipboardItem.getType('image/png'); 63 } 64 65 // alternatively, an image might be a jpeg 66 // NOTE: Currently, this is not supported by browsers yet. They only support PNG now 67 if (clipboardItem.types.some((type) => type === 'image/jpeg')) { 68 return await clipboardItem.getType('image/jpeg'); 69 } 70 } 71 return null; 72} 73 74export async function findHtmlInClipboardAsync(items: ClipboardItems): Promise<Blob | null> { 75 for (const clipboardItem of items) { 76 if (clipboardItem.types.some((type) => type === 'text/html')) { 77 return await clipboardItem.getType('text/html'); 78 } 79 } 80 return null; 81} 82 83export async function isClipboardPermissionDeniedAsync(): Promise<boolean> { 84 const queryOpts = { name: 'clipboard-read' as PermissionName }; 85 const permissionStatus = await navigator.permissions.query(queryOpts); 86 return permissionStatus.state === 'denied'; 87} 88