add camera take picture
Deploy to Cloudflare Pages / deploy (push) Successful in 1m1s

This commit is contained in:
2026-08-24 10:12:41 +07:00 Unverified
parent 889f684b99
commit ffa9b10cd3
7 changed files with 93 additions and 14 deletions
+1
View File
@@ -16,6 +16,7 @@ declare global {
// interface PageData {}
interface PageState {
item?: ItemId;
imageDataUrl?: string;
}
interface Platform {
env: {
+24
View File
@@ -0,0 +1,24 @@
import { createContext } from "svelte";
export type CameraCapture = () => string | undefined;
const emptyCapture: CameraCapture = () => undefined;
export class CameraController {
#capture: CameraCapture = emptyCapture;
registerCapture(capture: CameraCapture) {
this.#capture = capture;
return () => {
if (this.#capture === capture) this.#capture = emptyCapture;
};
}
capture() {
return this.#capture();
}
}
export const [getCameraController, setCameraController] =
createContext<CameraController>();
+13 -4
View File
@@ -55,11 +55,20 @@
}
});
export function takePicture() {
if (!canvas) return console.error("No canvas");
export function takePicture(): string | undefined {
if (!canvas) {
console.error("No canvas");
return;
}
const context = canvas.getContext("2d");
if (!context) return console.error("No context");
if (!video) return console.error("No video");
if (!context) {
console.error("No context");
return;
}
if (!video) {
console.error("No video");
return;
}
context.drawImage(video, 0, 0, width, height);
const imageDataUrl = canvas.toDataURL("image/png");
+14
View File
@@ -1,9 +1,22 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { page } from "$app/state";
import { getCameraController } from "$lib/camera-context";
import { cn } from "$lib/utils";
import ScanIcon from "@lucide/svelte/icons/scan-search";
const cameraController = getCameraController();
const selected = $derived(page.url.pathname === "/app/scan");
async function handleClick(event: MouseEvent) {
if (!selected) return;
event.preventDefault();
const imageDataUrl = cameraController.capture();
await goto("/app/list/manual", {
state: imageDataUrl ? { imageDataUrl } : {},
});
}
</script>
<a
@@ -12,6 +25,7 @@
selected && "dark:bg-white bg-black dark:text-blue-500",
)}
href="/app/scan"
onclick={handleClick}
>
<ScanIcon />
</a>
+6
View File
@@ -1,6 +1,12 @@
<script lang="ts">
import {
CameraController,
setCameraController,
} from "$lib/camera-context";
import NavbarWrapper from "./navbar.svelte";
setCameraController(new CameraController());
let { children } = $props();
</script>
+22 -10
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { goto } from "$app/navigation";
import { page } from "$app/state";
import Image from "$lib/components/image.svelte";
import Badge from "$lib/components/ui/badge/badge.svelte";
import Button from "$lib/components/ui/button/button.svelte";
@@ -73,17 +74,16 @@
return { dataUrl, mimeType, bytes };
}
async function onFileChange(e: Event) {
const file = (e.currentTarget as HTMLInputElement).files?.[0];
if (!file) return;
preview = await readAsDataUrl(file);
errorMessage = "";
status = "analyzing";
async function analyzeImage(input: File | string) {
try {
const suggestions = await analyzeFoodImage(preview);
const source = await loadImageElement(preview);
const dataUrl =
typeof input === "string" ? input : await readAsDataUrl(input);
preview = dataUrl;
errorMessage = "";
status = "analyzing";
const suggestions = await analyzeFoodImage(dataUrl);
const source = await loadImageElement(dataUrl);
drafts = await Promise.all(
suggestions.map(async (s) => {
const icon = await cropToBox(source, s.box2d);
@@ -107,6 +107,18 @@
}
}
async function onFileChange(e: Event) {
const file = (e.currentTarget as HTMLInputElement).files?.[0];
if (!file) return;
await analyzeImage(file);
}
$effect(() => {
const imageDataUrl = page.state.imageDataUrl;
if (imageDataUrl) void analyzeImage(imageDataUrl);
});
async function confirm() {
const included = drafts.filter((d) => d.include);
if (included.length === 0) return;
+13
View File
@@ -1,7 +1,20 @@
<script lang="ts">
import Camera from "$lib/components/camera.svelte";
import { getCameraController } from "$lib/camera-context";
import SwitchCamera from "@lucide/svelte/icons/switch-camera";
const cameraController = getCameraController();
let camera = $state<Camera>();
$effect(() => {
const currentCamera = camera;
if (!currentCamera) return;
return cameraController.registerCapture(() => {
const imageDataUrl = currentCamera.takePicture();
return typeof imageDataUrl === "string" ? imageDataUrl : undefined;
});
});
</script>
<Camera bind:this={camera} />