UNPKG

react-native-image-picker

Version:

A React Native module that allows you to use native UI to select media from the device library or directly from the camera

334 lines (282 loc) 8.76 kB
import { CameraOptions, ImageLibraryOptions, Callback, ImagePickerResponse, ErrorCode, Asset, MediaType, } from '../types'; const DEFAULT_OPTIONS: Pick< ImageLibraryOptions & CameraOptions, 'mediaType' | 'includeBase64' | 'selectionLimit' > = { mediaType: 'photo', includeBase64: false, selectionLimit: 1, }; export function camera( options: CameraOptions = DEFAULT_OPTIONS, callback?: Callback, ): Promise<ImagePickerResponse> { if (options.mediaType !== 'photo') { const result = { errorCode: 'others' as ErrorCode, errorMessage: 'For now, only photo mediaType is supported for web', } if (callback) callback(result); return Promise.resolve(result); } const container = document.createElement('div'); const wrapper = document.createElement('div'); const content = document.createElement('div'); const buttons = document.createElement('div'); const btnCapture = document.createElement('button'); const btnBack = document.createElement('button'); const btnSave = document.createElement('button'); const btnCancel = document.createElement('button'); const video = document.createElement('video'); const canvas = document.createElement('canvas'); // init video navigator.mediaDevices.getUserMedia({ audio: false, video: true }) .then(stream => { video.srcObject = stream; video.play(); }).catch(err => { console.log(err); }) const isAlreadyUsingFontAwesome = !!document.getElementsByClassName('fa').length; if (!isAlreadyUsingFontAwesome) { const isAlreadyInjectedFontAwesome = !!document.getElementById('injected-font-awesome'); if (!isAlreadyInjectedFontAwesome) { // inject font-awesome const head = document.getElementsByTagName('HEAD')[0]; const link = document.createElement('link'); link.id = 'injected-font-awesome'; link.rel = 'stylesheet'; link.type = 'text/css'; link.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'; head.appendChild(link); } } container.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.9); display: flex; align-items: center; justify-content: center; `; wrapper.style.cssText = ` position: relative; min-height: min(480px, 100vh); min-width: min(640px, 100vw); border-radius: 8px 8px 0 0; background-color: #333333; `; video.style.cssText = canvas.style.cssText = ` position: absolute; top: 0; left: 0; border-radius: 8px 8px 0 0; `; content.style.cssText = ` display: flex; flex-direction: column; margin: auto; `; buttons.style.cssText = ` display: flex; align-items: center; justify-content: space-evenly; min-height: 60px; background-color: #333333; border-radius: 0 0 8px 8px; `; btnCapture.innerHTML = '<i class="fa fa-2x fa-camera"></i>'; // btnCapture.title = 'Capture'; btnBack.innerHTML = '<i class="fa fa-2x fa-undo"></i>'; // btnBack.title = 'Back'; btnSave.innerHTML = '<i class="fa fa-2x fa-check"></i>'; // btnSave.title = 'Apply'; btnCancel.innerHTML = '<i class="fa fa-2x fa-close"></i>'; // btnCancel.title = 'Cancel'; btnCapture.style.cssText = btnBack.style.cssText = btnSave.style.cssText = btnCancel.style.cssText = ` padding: 10px; color: #f2f2f2; border: 0; background: transparent; `; wrapper.append(video); wrapper.append(canvas); content.append(wrapper); content.append(buttons); container.append(content); document.body.appendChild(container); let hasPhoto = false; const handleButtons = () => { buttons.innerHTML = ''; if (hasPhoto) { buttons.append(btnBack); buttons.append(btnSave); } else { buttons.append(btnCapture); } buttons.append(btnCancel); } handleButtons(); return new Promise((resolve) => { btnCapture.addEventListener('click', async () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight; canvas.getContext('2d')?.drawImage(video, 0, 0, canvas.width, canvas.height); hasPhoto = true; handleButtons(); }) btnBack.addEventListener('click', () => { canvas.getContext('2d')?.clearRect(0, 0, canvas.width, canvas.height); hasPhoto = false; handleButtons(); }) btnSave.addEventListener('click', async () => { const uri = canvas.toDataURL('image/png'); const asset: Asset = { uri }; const result = {assets: [asset]}; if (callback) callback(result); resolve(result); document.body.removeChild(container); }) btnCancel.addEventListener('click', async () => { const result = { assets: [], didCancel: true, } if (callback) callback(result); resolve(result); document.body.removeChild(container); }) }) } export function imageLibrary( options: ImageLibraryOptions = DEFAULT_OPTIONS, callback?: Callback, ): Promise<ImagePickerResponse> { // Only supporting 'photo' mediaType for now. if (options.mediaType !== 'photo') { const result = { errorCode: 'others' as ErrorCode, errorMessage: 'For now, only photo mediaType is supported for web', }; if (callback) callback(result); return Promise.resolve(result); } const input = document.createElement('input'); input.style.display = 'none'; input.setAttribute('type', 'file'); input.setAttribute('accept', getWebMediaType(options.mediaType)); if (options.selectionLimit! > 1) { input.setAttribute('multiple', 'multiple'); } document.body.appendChild(input); return new Promise((resolve) => { const inputChangeHandler = async () => { if (input.files) { if (options.selectionLimit! <= 1) { const img = await readFile(input.files[0], { includeBase64: options.includeBase64, }); const result = {assets: [img]}; if (callback) callback(result); resolve(result); } else { const imgs = await Promise.all( Array.from(input.files).map((file) => readFile(file, {includeBase64: options.includeBase64}), ), ); const result = { didCancel: false, assets: imgs, }; if (callback) callback(result); resolve(result); } } cleanup(); }; const inputCancelHandler = async () => { resolve({didCancel: true}); cleanup(); }; const cleanup = () => { input.removeEventListener('change', inputChangeHandler); input.removeEventListener('cancel', inputCancelHandler); document.body.removeChild(input); }; input.addEventListener('change', inputChangeHandler); input.addEventListener('cancel', inputCancelHandler); const event = new MouseEvent('click'); input.dispatchEvent(event); }); } function readFile( targetFile: Blob, options: Partial<ImageLibraryOptions>, ): Promise<Asset> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onerror = () => { reject( new Error( `Failed to read the selected media because the operation failed.`, ), ); }; reader.onload = ({target}) => { const uri = target?.result; const returnRaw = () => resolve({ uri: uri as string, width: 0, height: 0, }); if (typeof uri === 'string') { const image = new Image(); image.src = uri; image.onload = () => resolve({ uri, width: image.naturalWidth ?? image.width, height: image.naturalHeight ?? image.height, // The blob's result cannot be directly decoded as Base64 without // first removing the Data-URL declaration preceding the // Base64-encoded data. To retrieve only the Base64 encoded string, // first remove data:*/*;base64, from the result. // https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsDataURL ...(options.includeBase64 && { base64: uri.substr(uri.indexOf(',') + 1), }), }); image.onerror = () => returnRaw(); } else { returnRaw(); } }; reader.readAsDataURL(targetFile); }); } function getWebMediaType(mediaType: MediaType) { const webMediaTypes = { photo: 'image/*', video: 'video/*', mixed: 'image/*,video/*', }; return webMediaTypes[mediaType] ?? webMediaTypes.photo; }