This commit is contained in:
Vendored
+1
@@ -16,6 +16,7 @@ declare global {
|
||||
// interface PageData {}
|
||||
interface PageState {
|
||||
item?: ItemId;
|
||||
imageDataUrl?: string;
|
||||
}
|
||||
interface Platform {
|
||||
env: {
|
||||
|
||||
@@ -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>();
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user