Created
April 7, 2023 20:22
-
-
Save john/a52e62e8bcc1d1cf460f9af1b317877d to your computer and use it in GitHub Desktop.
Mobile web page to take photos and upload to an API
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en" data-bs-theme="auto"> | |
<head> | |
<script src="https://getbootstrap.com/docs/5.3/assets/js/color-modes.js"></script> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<meta name="description" content=""> | |
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> | |
<meta name="generator" content="Hugo 0.111.3"> | |
<title>WasteMAP Capture : Upload waste emissions images</title> | |
<script src="https://unpkg.com/[email protected]/dist/vue.js"></script> | |
<script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script> | |
<link rel="canonical" href="/"> | |
<link href="https://getbootstrap.com//docs/5.3/dist/css/bootstrap.min.css" rel="stylesheet" | |
integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp" crossorigin="anonymous"> | |
<!-- Favicons --> | |
<link rel="apple-touch-icon" href="https://getbootstrap.com/docs/5.3/assets/img/favicons/apple-touch-icon.png" sizes="180x180"> | |
<link rel="icon" href="https://getbootstrap.com/docs/5.3/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png"> | |
<link rel="icon" href="https://getbootstrap.com/docs/5.3/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png"> | |
<link rel="manifest" href="https://getbootstrap.com/docs/5.3/assets/img/favicons/manifest.json"> | |
<link rel="mask-icon" href="https://getbootstrap.com/docs/5.3/assets/img/favicons/safari-pinned-tab.svg" color="#712cf9"> | |
<link rel="icon" href="https://getbootstrap.com/docs/5.3/assets/img/favicons/favicon.ico"> | |
<meta name="theme-color" content="#712cf9"> | |
<style> | |
.bd-placeholder-img { | |
font-size: 1.125rem; | |
text-anchor: middle; | |
-webkit-user-select: none; | |
-moz-user-select: none; | |
user-select: none; | |
} | |
@media (min-width: 768px) { | |
.bd-placeholder-img-lg { | |
font-size: 3.5rem; | |
} | |
} | |
.b-example-divider { | |
width: 100%; | |
height: 3rem; | |
background-color: rgba(0, 0, 0, .1); | |
border: solid rgba(0, 0, 0, .15); | |
border-width: 1px 0; | |
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15); | |
} | |
.b-example-vr { | |
flex-shrink: 0; | |
width: 1.5rem; | |
height: 100vh; | |
} | |
.bi { | |
vertical-align: -.125em; | |
fill: currentColor; | |
} | |
.nav-scroller { | |
position: relative; | |
z-index: 2; | |
height: 2.75rem; | |
overflow-y: hidden; | |
} | |
.nav-scroller .nav { | |
display: flex; | |
flex-wrap: nowrap; | |
padding-bottom: 1rem; | |
margin-top: -1px; | |
overflow-x: auto; | |
text-align: center; | |
white-space: nowrap; | |
-webkit-overflow-scrolling: touch; | |
} | |
.btn-bd-primary { | |
--bd-violet-bg: #712cf9; | |
--bd-violet-rgb: 112.520718, 44.062154, 249.437846; | |
--bs-btn-font-weight: 600; | |
--bs-btn-color: var(--bs-white); | |
--bs-btn-bg: var(--bd-violet-bg); | |
--bs-btn-border-color: var(--bd-violet-bg); | |
--bs-btn-hover-color: var(--bs-white); | |
--bs-btn-hover-bg: #6528e0; | |
--bs-btn-hover-border-color: #6528e0; | |
--bs-btn-focus-shadow-rgb: var(--bd-violet-rgb); | |
--bs-btn-active-color: var(--bs-btn-hover-color); | |
--bs-btn-active-bg: #5a23c8; | |
--bs-btn-active-border-color: #5a23c8; | |
} | |
.bd-mode-toggle { | |
z-index: 1500; | |
} | |
</style> | |
</head> | |
<body> | |
<main class="container"> | |
<div class="bg-body-tertiary p-5 rounded mt-3"> | |
<h1>WasteMap Capture</h1> | |
<p class="lead">Upload photos of potential waste methane sites for inclusion in WasteMAP.</p> | |
<br /> | |
<div id="app"> | |
<div v-if="!image"> | |
<div>Click 'Chose file' to either</div> | |
<h3>Take or upload a photo</h3> | |
<input type="file" @change="onFileChange" accept="image/*" capture="environment"> | |
</div> | |
<div v-else> | |
<h2 v-if="uploadURL">Success! Image uploaded to WasteMAP.</h2> | |
<div v-if="uploadURL">This image is waiting to be evaluated and processed. Thank you for contributing to WasteMAP!</div> | |
<div v-if="!uploadURL" id="uploading" style="display: none; margin: 0 0 25px 0;"><i>Uploading...</i></div> | |
<button v-if="!uploadURL" @click="removeImage" class="btn btn-warning">Remove image</button> | |
<button v-if="!uploadURL" @click="uploadImage" class="btn btn-primary" onclick="document.getElementById('uploading').style.display = 'block';">Upload image</button> | |
<br /> | |
<br /> | |
<img :src="image" /> | |
<br /> | |
<br /> | |
</div> | |
</div> | |
<script> | |
const API_ENDPOINT = '' | |
new Vue({ | |
el: "#app", | |
data: { | |
image: '', | |
uploadURL: '' | |
}, | |
methods: { | |
onFileChange (e) { | |
let files = e.target.files || e.dataTransfer.files | |
if (!files.length) return | |
this.createImage(files[0]) | |
}, | |
createImage (file) { | |
// var image = new Image() | |
let reader = new FileReader() | |
reader.onload = (e) => { | |
console.log('length: ', e.target.result.includes('data:image/jpeg')) | |
if (!e.target.result.includes('data:image/jpeg')) { | |
return alert('Wrong file type - JPG only.') | |
} | |
// if (e.target.result.length > MAX_IMAGE_SIZE) { | |
// return alert('Image is loo large.') | |
// } | |
this.image = e.target.result | |
} | |
reader.readAsDataURL(file) | |
}, | |
removeImage: function (e) { | |
console.log('Remove clicked') | |
this.image = '' | |
}, | |
uploadImage: async function (e) { | |
console.log('Upload clicked') | |
// Get the presigned URL | |
const response = await axios({ | |
method: 'GET', | |
url: API_ENDPOINT | |
}) | |
console.log('Response: ', response) | |
console.log('Uploading: ', this.image) | |
let binary = atob(this.image.split(',')[1]) | |
let array = [] | |
for (var i = 0; i < binary.length; i++) { | |
array.push(binary.charCodeAt(i)) | |
} | |
let blobData = new Blob([new Uint8Array(array)], {type: 'image/jpeg'}) | |
console.log('Uploading to: ', response.uploadURL) | |
const result = await fetch(response.uploadURL, { | |
method: 'PUT', | |
body: blobData | |
}) | |
console.log('Result: ', result) | |
// Final URL for the user doesn't need the query string params | |
this.uploadURL = response.uploadURL.split('?')[0] | |
} | |
} | |
}) | |
</script> | |
</div> | |
</main> | |
<nav class="navbar fixed-bottom navbar-expand-sm navbar-dark bg-dark"> | |
<div class="container-fluid"> | |
<a class="navbar-brand" href="#">WasteMAP</a> | |
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" | |
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation"> | |
<span class="navbar-toggler-icon"></span> | |
</button> | |
</div> | |
</div> | |
</nav> | |
<script src="https://getbootstrap.com/docs/5.3/dist/js/bootstrap.bundle.min.js" | |
integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N" | |
crossorigin="anonymous"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.min.js" | |
integrity="sha512-xsoiisGNT6Dw2Le1Cocn5305Uje1pOYeSzrpO3RD9K+JTpVH9KqSXksXqur8cobTEKJcFz0COYq4723mzo88/Q==" | |
crossorigin="anonymous" | |
referrerpolicy="no-referrer"></script> | |
</body> | |
<!-- | |
Notes: | |
Uploader based on this: | |
https://aws.amazon.com/blogs/compute/uploading-to-amazon-s3-directly-from-a-web-or-mobile-application/ | |
For which you need to install the SAM cli: | |
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html | |
Sending the notification: | |
https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-s3-object-created-tutorial.html | |
Lambda to get EXIF data: | |
Which was based on this, but I had to update the Python version to 3.9 (3.6 is no longer supported), | |
and monkey with the bucket name to get it to use the one created for the app: | |
https://serverlessrepo.aws.amazon.com/applications/us-east-1/174570359254/exif-metadata-extractor | |
--> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment