From 1f034d72a61b9050901903e3c973cf98af931836 Mon Sep 17 00:00:00 2001 From: Samantha Date: Tue, 9 Jun 2026 20:53:40 +0900 Subject: [PATCH] face detection & firestore & adding images to the messages --- package-lock.json | 7 ++ package.json | 1 + src/lib/Firebase/storage.js | 10 ++ src/lib/components/BottomSheet.svelte | 11 ++ src/lib/components/ComposeSheet.svelte | 154 ++++++++++++++++++++++++- src/lib/components/MapView.Svelte | 37 +++--- src/lib/components/SidePanel.svelte | 11 ++ src/lib/utils/faceDetection.js | 44 +++++++ 8 files changed, 249 insertions(+), 26 deletions(-) create mode 100644 src/lib/Firebase/storage.js create mode 100644 src/lib/utils/faceDetection.js diff --git a/package-lock.json b/package-lock.json index 253aecd..2db436b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "@googlemaps/js-api-loader": "^2.1.0", + "@mediapipe/tasks-vision": "^0.10.35", "firebase": "^12.14.0", "ngeohash": "^0.6.3" }, @@ -755,6 +756,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.35", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.35.tgz", + "integrity": "sha512-HOvadwVRE6JC+45nyYhmnywnr5h/J8KZvOeUNVOG9q/0875pZgItznFB9bRTvLc264YSJqiZ1NsIpCStJw/egg==", + "license": "Apache-2.0" + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", diff --git a/package.json b/package.json index 2adeda2..9e83d5f 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "@googlemaps/js-api-loader": "^2.1.0", + "@mediapipe/tasks-vision": "^0.10.35", "firebase": "^12.14.0", "ngeohash": "^0.6.3" } diff --git a/src/lib/Firebase/storage.js b/src/lib/Firebase/storage.js new file mode 100644 index 0000000..47d2a48 --- /dev/null +++ b/src/lib/Firebase/storage.js @@ -0,0 +1,10 @@ +import { ref, uploadBytes, getDownloadURL } from 'firebase/storage' +import { storage } from './config.js'; + +export async function uploadImage(file) { + //create unique file name and stores them in one folder in firebase + const filename = `messages/${Date.now()}_${file.name}`; + const storageRef = ref(storage, filename); + await uploadBytes(storageRef, file); + return getDownloadURL(storageRef); +} \ No newline at end of file diff --git a/src/lib/components/BottomSheet.svelte b/src/lib/components/BottomSheet.svelte index 9b4e001..3a2de26 100644 --- a/src/lib/components/BottomSheet.svelte +++ b/src/lib/components/BottomSheet.svelte @@ -22,6 +22,9 @@ {#if message}
+ {#if message.imageUrl} + message attachment + {/if}

{message.text}

{#if decay}

left {decay.daysAgo} days ago. fading in {decay.daysLeft} days.

@@ -114,6 +117,14 @@ animation: pulsate 1.5s ease-in-out 3; } + .message-img{ + width: 100%; + max-height: 220px; + object-fit: cover; + border-radius: 12px; + margin-bottom: 0.75rem; + } + @keyframes pulsate { 0%, 100% {box-shadow: 0 0 0 0 rgba(78,205,196,0.4); } 50% {box-shadow: 0 0 0 12px rgba(78, 205, 196, 0);} diff --git a/src/lib/components/ComposeSheet.svelte b/src/lib/components/ComposeSheet.svelte index 50c662b..f43a644 100644 --- a/src/lib/components/ComposeSheet.svelte +++ b/src/lib/components/ComposeSheet.svelte @@ -2,6 +2,9 @@ import { mapStore } from '$lib/stores/mapStore.js'; import { messagesStore } from '$lib/stores/messagesStore.js'; import { addMessage } from '$lib/firebase/messages.js' + import { hasFace } from '$lib/utils/faceDetection.js'; + import { uploadImage } from '$lib/Firebase/storage.js'; + let {lat, lng} = $props(); @@ -9,23 +12,78 @@ let submitting = $state(false); let remaining = $derived(240-text.length); + let selectedFile = $state(null); + let imagePreview = $state(null); // what will read + let imageError = $state(null); + let checkingFace = $state(false); // this will show the checking image... message + + async function handleImageSelect(event) { + const file = event.target.files[0]; // only one file will be allowed so always index 0 + + if (!file) return; // no file do nothing + checkingFace = true; // show loading state + imageError = null; // clear errors + + try { + // run face detection + const faceFound = await hasFace(file); + + if (faceFound) { + // no upload & error + imageError = 'Images may not contain faces. Please choose another photo.'; + + //reset file input for other attempt + event.target.value = ''; + selectedFile= null; + imagePreview = null; + } else { + // no face we move on + selectedFile = file; + // create preview + imagePreview = URL.createObjectURL(file); + } + + } catch (err) { + // fixed: previously allowed the upload on any detection error — wrong for a + // face-blocking feature. Now we surface the error and block the upload instead. + console.error('[faceDetection] error:', err); + imageError = 'Could not verify the image. Please try a different photo.'; + event.target.value = ''; + selectedFile = null; + imagePreview = null; + } finally { + // no matter what turn the loading state off + checkingFace = false; + } + } + async function handleSubmit() { + // no submit with no text or text over limit if (!text.trim() || remaining < 0) return; + submitting = true; + let imageUrl = ''; // stays empty if no image - await addMessage(lat, lng, text.trim()); + if (selectedFile) { + imageUrl = await uploadImage(selectedFile); + } - //refresh the messages store so pin is there - const { getNearbyMessages } = await import('$lib/firebase/messages.js'); + await addMessage(lat, lng, text.trim(), imageUrl); + + // reload pins so that they show up with new message + const { getNearbyMessages } = await import ('$lib/firebase/messages.js'); const updated = await getNearbyMessages(lat, lng); messagesStore.set(updated); - //reset and close + // reset compose state text = ''; + selectedFile = null; + imagePreview = null; submitting = false; - mapStore.set({selectedMessage: null, composing: false}); - } + // close the sheet + mapStore.set({selectedMessage: null, composing:false}); + }
@@ -45,6 +103,35 @@ rows="5" > +
+ {#if checkingFace} +

Checking image...

+ + {:else if imagePreview} +
+ preview + + +
+ + {:else} + {#if imageError} +

{imageError}

+ {/if} + + {/if} +
+