diff --git a/core/src/core-plugin-definitions.ts b/core/src/core-plugin-definitions.ts index 2089ed009c..24fc68b192 100644 --- a/core/src/core-plugin-definitions.ts +++ b/core/src/core-plugin-definitions.ts @@ -334,7 +334,7 @@ export interface CameraOptions { */ source?: CameraSource; /** - * iOS only: The default camera direction. By default the rear camera. + * iOS and Web only: The camera direction. * Default: CameraDirection.Rear */ direction?: CameraDirection; @@ -344,6 +344,15 @@ export interface CameraOptions { */ presentationStyle?: 'fullscreen' | 'popover'; + /** + * Web only: Whether to use the PWA Element experience or file input. The + * default is to use PWA Elements if installed and fall back to file input. + * To always use file input, set this to `true`. + * + * Learn more about PWA Elements: https://capacitorjs.com/docs/pwa-elements + */ + webUseInput?: boolean; + /** * If use CameraSource.Prompt only, can change Prompt label. * default: @@ -393,7 +402,10 @@ export interface CameraPhoto { */ exif?: any; /** - * The format of the image. Currently, only "jpeg" is supported. + * The format of the image, ex: jpeg, png, gif. + * + * iOS and Android only support jpeg. + * Web supports jpeg and png. gif is only supported if using file input. */ format: string; } diff --git a/core/src/web/camera.ts b/core/src/web/camera.ts index 7c51b5c1af..9412c64850 100644 --- a/core/src/web/camera.ts +++ b/core/src/web/camera.ts @@ -4,7 +4,9 @@ import { CameraPlugin, CameraPhoto, CameraOptions, - CameraResultType + CameraResultType, + CameraDirection, + CameraSource } from '../core-plugin-definitions'; export class CameraPluginWeb extends WebPlugin implements CameraPlugin { @@ -16,29 +18,108 @@ export class CameraPluginWeb extends WebPlugin implements CameraPlugin { } async getPhoto(options: CameraOptions): Promise { - options; - return new Promise(async (resolve, reject) => { - const cameraModal: any = document.createElement('pwa-camera-modal'); - document.body.appendChild(cameraModal); - await cameraModal.componentOnReady(); - cameraModal.addEventListener('onPhoto', async (e: any) => { - const photo = e.detail; - - if (photo === null) { - reject('User cancelled photos app'); - } else if (photo instanceof Error) { - reject(photo.message); + if (options.webUseInput) { + this.fileInputExperience(options, resolve); + } else { + if (customElements.get('pwa-camera-modal')) { + const cameraModal: any = document.createElement('pwa-camera-modal'); + document.body.appendChild(cameraModal); + try { + await cameraModal.componentOnReady(); + cameraModal.addEventListener('onPhoto', async (e: any) => { + const photo = e.detail; + + if (photo === null) { + reject('User cancelled photos app'); + } else if (photo instanceof Error) { + reject(photo.message); + } else { + resolve(await this._getCameraPhoto(photo, options)); + } + + cameraModal.dismiss(); + document.body.removeChild(cameraModal); + }); + + cameraModal.present(); + } catch (e) { + this.fileInputExperience(options, resolve); + } } else { - resolve(await this._getCameraPhoto(photo, options)); + console.error(`Unable to load PWA Element 'pwa-camera-modal'. See the docs: https://capacitorjs.com/docs/pwa-elements.`); + this.fileInputExperience(options, resolve); } + } + }); + } + + private fileInputExperience(options: CameraOptions, resolve: any) { + let input = document.querySelector('#_capacitor-camera-input') as HTMLInputElement; + + const cleanup = () => { + input.parentNode && input.parentNode.removeChild(input); + }; - cameraModal.dismiss(); - document.body.removeChild(cameraModal); - }); + if (!input) { + input = document.createElement('input') as HTMLInputElement; + input.id = '_capacitor-camera-input'; + input.type = 'file'; + document.body.appendChild(input); + } - cameraModal.present(); + input.accept = 'image/*'; + (input as any).capture = true; + + if (options.source === CameraSource.Photos || options.source === CameraSource.Prompt) { + input.removeAttribute('capture'); + } else if (options.direction === CameraDirection.Front) { + (input as any).capture = 'user'; + } else if (options.direction === CameraDirection.Rear) { + (input as any).capture = 'environment'; + } + + input.addEventListener('change', (_e: any) => { + const file = input.files[0]; + let format = 'jpeg'; + + if (file.type === 'image/png') { + format = 'png'; + } else if (file.type === 'image/gif') { + format = 'gif'; + } + + if (options.resultType === CameraResultType.DataUrl || options.resultType === CameraResultType.Base64) { + const reader = new FileReader(); + + reader.addEventListener('load', () => { + if (options.resultType === CameraResultType.DataUrl) { + resolve({ + dataUrl: reader.result, + format + } as CameraPhoto); + } else if (options.resultType === CameraResultType.Base64) { + const b64 = (reader.result as string).split(',')[1]; + resolve({ + base64String: b64, + format + } as CameraPhoto); + } + + cleanup(); + }); + + reader.readAsDataURL(file); + } else { + resolve({ + webPath: URL.createObjectURL(file), + format: format + }); + cleanup(); + } }); + + input.click(); } private _getCameraPhoto(photo: Blob, options: CameraOptions) { diff --git a/example/src/pages/camera/camera.html b/example/src/pages/camera/camera.html index e5f84a3847..d55138ad4b 100644 --- a/example/src/pages/camera/camera.html +++ b/example/src/pages/camera/camera.html @@ -15,9 +15,12 @@ - + + + + diff --git a/example/src/pages/camera/camera.ts b/example/src/pages/camera/camera.ts index 904eafa587..cb9b8c99eb 100644 --- a/example/src/pages/camera/camera.ts +++ b/example/src/pages/camera/camera.ts @@ -3,6 +3,7 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { Plugins, + CameraDirection, CameraResultType, CameraSource, FilesystemDirectory @@ -35,11 +36,44 @@ export class CameraPage { console.log('ionViewDidLoad CameraPage'); } - async getPhoto() { + async getFileInput() { const image = await Plugins.Camera.getPhoto({ quality: 90, allowEditing: true, resultType: CameraResultType.DataUrl, + webUseInput: true + }) + console.log('Got image back', image.path, image.webPath, image.format, image.exif); + this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (image.dataUrl)); + } + + async getPhotoFront() { + const image = await Plugins.Camera.getPhoto({ + quality: 90, + allowEditing: true, + direction: CameraDirection.Front, + resultType: CameraResultType.DataUrl + }) + console.log('Got image back', image.path, image.webPath, image.format, image.exif); + this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (image.dataUrl)); + } + + async getBase64() { + const image = await Plugins.Camera.getPhoto({ + quality: 90, + allowEditing: true, + resultType: CameraResultType.Base64 + }) + console.log('Got image back', image.base64String, image.format); + this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (`data:${image.format};base64,${image.base64String}`)); + } + + async getPhotoPWAElements() { + const image = await Plugins.Camera.getPhoto({ + quality: 90, + allowEditing: true, + resultType: CameraResultType.DataUrl, + source: CameraSource.Camera }) console.log('Got image back', image.path, image.webPath, image.format, image.exif); this.image = this.sanitizer.bypassSecurityTrustResourceUrl(image && (image.dataUrl));