From 2079a6cf36c1a1a2f4d11b0bc750599ab6188966 Mon Sep 17 00:00:00 2001 From: John Fortner <108406653+fortnerj@users.noreply.github.com> Date: Tue, 15 Nov 2022 15:03:29 -0600 Subject: [PATCH] upload image --- package-lock.json | 68 +++++++++++++-- package.json | 4 + src/components.d.ts | 28 ++++++ src/components/camera/camera.css | 0 src/components/camera/camera.tsx | 145 +++++++++++++++++++++++++++++++ src/global/app.ts | 1 + 6 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 src/components/camera/camera.css create mode 100644 src/components/camera/camera.tsx diff --git a/package-lock.json b/package-lock.json index cb45f71..4ed8c12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,17 @@ "license": "MIT", "dependencies": { "@capacitor/android": "^3.4.1", + "@capacitor/camera": "^4.1.3", "@capacitor/core": "^3.4.1", + "@capacitor/filesystem": "^4.1.3", "@capacitor/ios": "^3.4.3", "@capacitor/keyboard": "^1.2.2", "@capacitor/project": "^2.0.8", "@capacitor/share": "^1.1.2", "@capacitor/splash-screen": "^1.2.2", + "@capacitor/storage": "^1.2.5", "@ionic-native/screenshot": "^5.36.0", + "@ionic/pwa-elements": "^3.1.1", "@stencil/store": "^1.5.0", "classnames": "^2.3.1", "cordova-plugin-screenshot": "https://github.com/gitawego/cordova-screenshot.git", @@ -27,7 +31,7 @@ "@capacitor/configure": "^1.0.28", "@ionic-native/core": "^5.36.0", "@ionic/core": "^5.3.2", - "@stencil/core": "^2.13.0", + "@stencil/core": "^2.19.2", "@types/jest": "^27.0.3", "jest": "^27.4.5", "jest-cli": "^27.4.5", @@ -605,6 +609,14 @@ "@capacitor/core": "^3.6.0" } }, + "node_modules/@capacitor/camera": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-4.1.3.tgz", + "integrity": "sha512-XhOMsdrRelKns/+X9W8uY2Qy0o2NPrY13gzLujxGuMZIuIzIZD4JvRUq373cIq1e2gfX/8r1Vux5QCamWM/e0A==", + "peerDependencies": { + "@capacitor/core": "^4.0.0" + } + }, "node_modules/@capacitor/cli": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-3.6.0.tgz", @@ -695,6 +707,14 @@ "tslib": "^2.1.0" } }, + "node_modules/@capacitor/filesystem": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-4.1.3.tgz", + "integrity": "sha512-+k6jcOuYPDfEYuqA1PrsNe/XhgCdiHEH1l6v1Ecih5OYFOoaqqwB0Ksi1OprxNpBcMQQkEW8VBbHoIc32DS6eQ==", + "peerDependencies": { + "@capacitor/core": "^4.0.0" + } + }, "node_modules/@capacitor/gradle-parse": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@capacitor/gradle-parse/-/gradle-parse-2.0.10.tgz", @@ -773,6 +793,15 @@ "@capacitor/core": "^3.0.0" } }, + "node_modules/@capacitor/storage": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@capacitor/storage/-/storage-1.2.5.tgz", + "integrity": "sha512-pWhnw4U7wN/zFV4lA4BLMypSufTktCbk548Yk4whSb0KAq3R0mLfSubRIHkGdcPqQWCsM6g/zUuszurhz6ncWQ==", + "deprecated": "package has been renamed to @capacitor/preferences", + "peerDependencies": { + "@capacitor/core": "^3.0.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -882,6 +911,11 @@ "npm": ">=6.0.0" } }, + "node_modules/@ionic/pwa-elements": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@ionic/pwa-elements/-/pwa-elements-3.1.1.tgz", + "integrity": "sha512-BoEX1pSrKn5dP4VQwaf8DweAmT4ynmpW+9f6oj/W1xLe54bmuXcUY07ziuZX1DLu50xHSYcO5qJ/dbYC/w6wgA==" + }, "node_modules/@ionic/utils-array": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.5.tgz", @@ -1363,9 +1397,9 @@ } }, "node_modules/@stencil/core": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.17.0.tgz", - "integrity": "sha512-KJJH097K2TJlJWj2LANWO0yXCidxOpv1L2MmtYqFxE443t75fxOhtKBZq6zebDaZj2DFGlUtV4pcM/uapfd8TQ==", + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.19.2.tgz", + "integrity": "sha512-TK3sHqyQAACwcac4fWntypFtN9c/y+y9ioZojeCnNDY3dxF33Ax70lL9ZLDyQnWWR+aTh1WFoqB5sYOnZIUKMA==", "dev": true, "bin": { "stencil": "bin/stencil" @@ -8221,6 +8255,11 @@ "resolved": "https://registry.npmjs.org/@capacitor/android/-/android-3.6.0.tgz", "integrity": "sha512-X6n0OLy7BE3c6qfVuL7UYyq/aIwEsqIAqtyDOwMdj5k+P1rLQVsGaWERXUtC0BGeoKBD5YgbWiyKwAwg5Spjdg==" }, + "@capacitor/camera": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@capacitor/camera/-/camera-4.1.3.tgz", + "integrity": "sha512-XhOMsdrRelKns/+X9W8uY2Qy0o2NPrY13gzLujxGuMZIuIzIZD4JvRUq373cIq1e2gfX/8r1Vux5QCamWM/e0A==" + }, "@capacitor/cli": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@capacitor/cli/-/cli-3.6.0.tgz", @@ -8294,6 +8333,11 @@ "tslib": "^2.1.0" } }, + "@capacitor/filesystem": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@capacitor/filesystem/-/filesystem-4.1.3.tgz", + "integrity": "sha512-+k6jcOuYPDfEYuqA1PrsNe/XhgCdiHEH1l6v1Ecih5OYFOoaqqwB0Ksi1OprxNpBcMQQkEW8VBbHoIc32DS6eQ==" + }, "@capacitor/gradle-parse": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@capacitor/gradle-parse/-/gradle-parse-2.0.10.tgz", @@ -8356,6 +8400,11 @@ "resolved": "https://registry.npmjs.org/@capacitor/splash-screen/-/splash-screen-1.2.2.tgz", "integrity": "sha512-iGh9gc0rdS3R+Wr+bD/+tJl3qbmJZ5xVQAP5UsD6U6Y3ydCBo9KpA8DEqxaBZVsCDaLt67009hK5nxKBwjRn7Q==" }, + "@capacitor/storage": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@capacitor/storage/-/storage-1.2.5.tgz", + "integrity": "sha512-pWhnw4U7wN/zFV4lA4BLMypSufTktCbk548Yk4whSb0KAq3R0mLfSubRIHkGdcPqQWCsM6g/zUuszurhz6ncWQ==" + }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -8452,6 +8501,11 @@ } } }, + "@ionic/pwa-elements": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@ionic/pwa-elements/-/pwa-elements-3.1.1.tgz", + "integrity": "sha512-BoEX1pSrKn5dP4VQwaf8DweAmT4ynmpW+9f6oj/W1xLe54bmuXcUY07ziuZX1DLu50xHSYcO5qJ/dbYC/w6wgA==" + }, "@ionic/utils-array": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@ionic/utils-array/-/utils-array-2.1.5.tgz", @@ -8836,9 +8890,9 @@ } }, "@stencil/core": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.17.0.tgz", - "integrity": "sha512-KJJH097K2TJlJWj2LANWO0yXCidxOpv1L2MmtYqFxE443t75fxOhtKBZq6zebDaZj2DFGlUtV4pcM/uapfd8TQ==", + "version": "2.19.2", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.19.2.tgz", + "integrity": "sha512-TK3sHqyQAACwcac4fWntypFtN9c/y+y9ioZojeCnNDY3dxF33Ax70lL9ZLDyQnWWR+aTh1WFoqB5sYOnZIUKMA==", "dev": true }, "@stencil/store": { diff --git a/package.json b/package.json index f2ec7af..e080ba8 100644 --- a/package.json +++ b/package.json @@ -32,13 +32,17 @@ }, "dependencies": { "@capacitor/android": "^3.4.1", + "@capacitor/camera": "^4.1.3", "@capacitor/core": "^3.4.1", + "@capacitor/filesystem": "^4.1.3", "@capacitor/ios": "^3.4.3", "@capacitor/keyboard": "^1.2.2", "@capacitor/project": "^2.0.8", "@capacitor/share": "^1.1.2", "@capacitor/splash-screen": "^1.2.2", + "@capacitor/storage": "^1.2.5", "@ionic-native/screenshot": "^5.36.0", + "@ionic/pwa-elements": "^3.1.1", "@stencil/store": "^1.5.0", "classnames": "^2.3.1", "cordova-plugin-screenshot": "https://github.com/gitawego/cordova-screenshot.git", diff --git a/src/components.d.ts b/src/components.d.ts index da65a11..ccfcbcb 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -16,6 +16,9 @@ export namespace Components { "to"?: string; "value": string | number; } + interface AppCamera { + "expand"?: 'block' | 'full'; + } interface AppLanding { } interface AppLayout { @@ -28,6 +31,8 @@ export namespace Components { } interface AppSlider { } + interface AppUpload { + } } export interface AppLayoutCustomEvent extends CustomEvent { detail: T; @@ -44,6 +49,12 @@ declare global { prototype: HTMLAppButtonElement; new (): HTMLAppButtonElement; }; + interface HTMLAppCameraElement extends Components.AppCamera, HTMLStencilElement { + } + var HTMLAppCameraElement: { + prototype: HTMLAppCameraElement; + new (): HTMLAppCameraElement; + }; interface HTMLAppLandingElement extends Components.AppLanding, HTMLStencilElement { } var HTMLAppLandingElement: { @@ -74,13 +85,21 @@ declare global { prototype: HTMLAppSliderElement; new (): HTMLAppSliderElement; }; + interface HTMLAppUploadElement extends Components.AppUpload, HTMLStencilElement { + } + var HTMLAppUploadElement: { + prototype: HTMLAppUploadElement; + new (): HTMLAppUploadElement; + }; interface HTMLElementTagNameMap { "app-button": HTMLAppButtonElement; + "app-camera": HTMLAppCameraElement; "app-landing": HTMLAppLandingElement; "app-layout": HTMLAppLayoutElement; "app-result": HTMLAppResultElement; "app-root": HTMLAppRootElement; "app-slider": HTMLAppSliderElement; + "app-upload": HTMLAppUploadElement; } } declare namespace LocalJSX { @@ -94,6 +113,9 @@ declare namespace LocalJSX { "to"?: string; "value"?: string | number; } + interface AppCamera { + "expand"?: 'block' | 'full'; + } interface AppLanding { } interface AppLayout { @@ -108,13 +130,17 @@ declare namespace LocalJSX { interface AppSlider { "onCurrentSlideIndex"?: (event: AppSliderCustomEvent) => void; } + interface AppUpload { + } interface IntrinsicElements { "app-button": AppButton; + "app-camera": AppCamera; "app-landing": AppLanding; "app-layout": AppLayout; "app-result": AppResult; "app-root": AppRoot; "app-slider": AppSlider; + "app-upload": AppUpload; } } export { LocalJSX as JSX }; @@ -122,11 +148,13 @@ declare module "@stencil/core" { export namespace JSX { interface IntrinsicElements { "app-button": LocalJSX.AppButton & JSXBase.HTMLAttributes; + "app-camera": LocalJSX.AppCamera & JSXBase.HTMLAttributes; "app-landing": LocalJSX.AppLanding & JSXBase.HTMLAttributes; "app-layout": LocalJSX.AppLayout & JSXBase.HTMLAttributes; "app-result": LocalJSX.AppResult & JSXBase.HTMLAttributes; "app-root": LocalJSX.AppRoot & JSXBase.HTMLAttributes; "app-slider": LocalJSX.AppSlider & JSXBase.HTMLAttributes; + "app-upload": LocalJSX.AppUpload & JSXBase.HTMLAttributes; } } } diff --git a/src/components/camera/camera.css b/src/components/camera/camera.css new file mode 100644 index 0000000..e69de29 diff --git a/src/components/camera/camera.tsx b/src/components/camera/camera.tsx new file mode 100644 index 0000000..af0c7be --- /dev/null +++ b/src/components/camera/camera.tsx @@ -0,0 +1,145 @@ +// create arrow function to capture an image from capacitor camera plugin and save it to the file system + + +// Imports were manually added. +import { Component, Prop, h } from '@stencil/core'; + +// Generated code depecrated code here. +// import { Camera, CameraResultType } from '@capacitor/core'; + +//Manually added the correct imports +import { Camera, ImageOptions, CameraResultType, Photo } from '@capacitor/camera'; +import { Capacitor } from '@capacitor/core'; +import { Directory, Filesystem } from '@capacitor/filesystem'; +import { Storage } from '@capacitor/storage'; + +// This code block was generated inside the captureImage function. I moved it here +const options: ImageOptions = { + quality: 100, + allowEditing: false, + resultType: CameraResultType.Uri, + }; + + +// Generated by typing "@Component" and then pressing tab. +@Component({ + tag: 'app-camera', + styleUrl: 'camera.css', + shadow: true, +}) + + +// Generated from the following statment +// use stenciljs to create arrow function to capture an image from capacitor camera plugin and save it to the file system +// had to minor adjustments to the generated code. Such as removing 'const' infront of some functions. + +export class AppCamera { + private PHOTO_STORAGE: string = 'photos'; + @Prop() expand?: 'block' | 'full'; + + +captureImage = async () => { + try { + const image = await Camera.getPhoto(options); + + // Save photo + const imageFile = await this.saveImage(image); + + // Write information to local storage + Storage.set({ + key: this.PHOTO_STORAGE, + value: JSON.stringify(imageFile) + }); + } catch (error) { + + } + }; + + +// Here I had to be more specific and help CoPilot get started to generated code closer to what I was looking for. +// CoPilot began relevant suggestions after starting the function with 'async saveImage(cameraPhoto: Photo) { const base64Data = await this.readAsBase64(cameraPhoto);' +async saveImage(cameraPhoto: Photo) { + const base64Data = await this.readAsBase64(cameraPhoto); + const fileName = new Date().getTime() + '.jpeg'; + + const savedFile = await Filesystem.writeFile({ + path: fileName, + data: base64Data, + directory: Directory.Data + }); + console.log('saved: ', savedFile); + console.log('Converted: ', Capacitor.convertFileSrc(savedFile.uri)); + + + if (this.getPlatform() === 'mobile') { + // Display the new image by rewriting the 'file://' path to HTTP + // Details: https://ionicframework.com/docs/building/webview#file-protocol + return { + filepath: savedFile.uri, + webviewPath: Capacitor.convertFileSrc(savedFile.uri), + }; + } else { + // Use webPath to display the new image instead of base64 since it's + // already loaded into memory + return { + filepath: fileName, + webviewPath: cameraPhoto.webPath + }; + } +} + +// CoPilot generated this function after starting the it with 'async readAsBase64(cameraPhoto: Photo) { ' +async readAsBase64(cameraPhoto: Photo) { + if (this.getPlatform() === 'mobile') { + // Read the file into base64 format + const file = await Filesystem.readFile({ + path: cameraPhoto.path + }); + return file.data; + } else { + // Fetch the photo, read as a blob, then convert to base64 format + const response = await fetch(cameraPhoto.webPath!); + const blob = await response.blob(); + return await this.convertBlobToBase64(blob) as string; + } +} + +// Manually added code, I was not able to get CoPiolot to generate a function with Camera.pickImages. +async uploadImage() { + try { + const image = await Camera.pickImages({limit: 1}); + if(image.photos.length === 1) { + // alert('UPLOADING...'); + // console.log('image: ', image); + } + else { + alert('Please select 1 image'); + } + } catch (error) { + throw new Error(error.message); + } +} + + + convertBlobToBase64 = (blob: Blob) => new Promise((resolve, reject) => { + const reader = new FileReader; + reader.onerror = reject; + reader.onload = () => { + resolve(reader.result); + }; + reader.readAsDataURL(blob); + }); + + // Manually added code, CoPiolot only showed deperacted code + getPlatform() { + return Capacitor.isNativePlatform() ? 'mobile' : 'web' + } + + + //CoPilot generated after all other functions were created and I started with "render()" + render() { + return ( + Capture Image + ); + } +} diff --git a/src/global/app.ts b/src/global/app.ts index 4b8af8a..e511184 100644 --- a/src/global/app.ts +++ b/src/global/app.ts @@ -1,5 +1,6 @@ import '@ionic/core'; // import { setupConfig } from '@ionic/core'; +import '@ionic/pwa-elements'; export default () => { // setupConfig({