diff --git a/.svelte-kit/generated/server/internal.js b/.svelte-kit/generated/server/internal.js index d1b56de8..d835717f 100644 --- a/.svelte-kit/generated/server/internal.js +++ b/.svelte-kit/generated/server/internal.js @@ -24,7 +24,7 @@ export const options = { app: ({ head, body, assets, nonce, env }) => "\n\n \n \n S\" />\n \n \n \n \n \n \n \n \n " + head + "\n \n \n
" + body + "
\n \n\n", error: ({ status, message }) => "\n\n\t\n\t\t\n\t\t" + message + "\n\n\t\t\n\t\n\t\n\t\t
\n\t\t\t" + status + "\n\t\t\t
\n\t\t\t\t

" + message + "

\n\t\t\t
\n\t\t
\n\t\n\n" }, - version_hash: "qnx61n" + version_hash: "co0587" }; export async function get_hooks() { diff --git a/src/lib/convert-image-to-webp.ts b/src/lib/convert-image-to-webp.ts index fcf9480e..cdaf9623 100644 --- a/src/lib/convert-image-to-webp.ts +++ b/src/lib/convert-image-to-webp.ts @@ -11,6 +11,13 @@ function isHeicFile(file: File): boolean { ); } +/** True if the file is an image we can process (image/* or HEIC by type/extension). */ +export function isAcceptableImageFile(file: File | null): boolean { + if (!file) return false; + const type = (file.type ?? "").toLowerCase(); + return type.startsWith("image/") || isHeicFile(file); +} + function makeOutputFile(blob: Blob, baseName: string, ext: string): File { return new File([blob], `${baseName}.${ext}`, { type: blob.type, @@ -18,78 +25,93 @@ function makeOutputFile(blob: Blob, baseName: string, ext: string): File { }); } -/** - * Converts an image File to WebP (or JPEG on Safari where WebP isn't supported). - * HEIC from iPhone is handled: if the image fails to load, we decode with heic2any first. - */ -export function convertImageToWebP(file: File, quality = 0.85): Promise { +function isSafari(): boolean { + if (typeof navigator === "undefined") return false; + const ua = navigator.userAgent; + return ua.includes("Safari") && !ua.includes("Chrome"); +} + +/** Load blob in Image, draw to canvas, encode as WebP or JPEG. Rejects if image fails to load or encode. */ +function tryCanvas(sourceBlob: Blob, baseName: string, quality: number): Promise { return new Promise((resolve, reject) => { - const baseName = file.name.replace(/\.[^.]+$/i, "") || "image"; - - const tryCanvas = (sourceBlob: Blob) => { - const url = URL.createObjectURL(sourceBlob); - const img = new Image(); - img.onload = () => { - URL.revokeObjectURL(url); - const canvas = document.createElement("canvas"); - canvas.width = img.naturalWidth; - canvas.height = img.naturalHeight; - const ctx = canvas.getContext("2d"); - if (!ctx) { - reject(new Error("Could not get canvas context")); - return; - } - ctx.drawImage(img, 0, 0); - // Safari doesn't support WebP in toBlob; try WebP first, fall back to JPEG - canvas.toBlob( - (blob) => { - if (blob && blob.type === "image/webp") { - resolve(makeOutputFile(blob, baseName, "webp")); - return; - } - canvas.toBlob( - (jpegBlob) => { - if (!jpegBlob) { - reject(new Error("Failed to encode image")); - return; - } - resolve(makeOutputFile(jpegBlob, baseName, "jpg")); - }, - "image/jpeg", - quality - ); - }, - "image/webp", - quality - ); - }; - img.onerror = () => { - URL.revokeObjectURL(url); - reject(new Error("Failed to load image")); - }; - img.src = url; - }; - - const url = URL.createObjectURL(file); + const url = URL.createObjectURL(sourceBlob); const img = new Image(); img.onload = () => { URL.revokeObjectURL(url); - tryCanvas(file); - }; - img.onerror = async () => { - URL.revokeObjectURL(url); - // HEIC often fails to load in Image() on iOS; decode with heic2any then run canvas - if (isHeicFile(file)) { - try { - const jpegBlob = await convertHeicBlobToJpeg(file); - tryCanvas(jpegBlob); - } catch (e) { - reject(e instanceof Error ? e : new Error("Failed to convert HEIC")); - } - } else { - reject(new Error("Failed to load image")); + const canvas = document.createElement("canvas"); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + const ctx = canvas.getContext("2d"); + if (!ctx) { + reject(new Error("Could not get canvas context")); + return; } + ctx.drawImage(img, 0, 0); + // Safari doesn't support WebP in toBlob; try WebP first, fall back to JPEG + canvas.toBlob( + (blob) => { + if (blob && blob.type === "image/webp") { + resolve(makeOutputFile(blob, baseName, "webp")); + return; + } + canvas.toBlob( + (jpegBlob) => { + if (!jpegBlob) { + reject(new Error("Failed to encode image")); + return; + } + resolve(makeOutputFile(jpegBlob, baseName, "jpg")); + }, + "image/jpeg", + quality + ); + }, + "image/webp", + quality + ); + }; + img.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error("Failed to load image")); }; img.src = url; }); } + +/** + * Converts an image File to WebP (or JPEG on Safari where WebP isn't supported). + * HEIC: Safari uses native Image() first, then heic2any fallback; other browsers use heic2any first, then Image() fallback. + */ +export async function convertImageToWebP(file: File, quality = 0.85): Promise { + const baseName = file.name.replace(/\.[^.]+$/i, "") || "image"; + + if (isHeicFile(file)) { + const safari = isSafari(); + if (safari) { + // Safari can decode HEIC in Image(); heic2any is unreliable on Safari (e.g. transparent output) + try { + return await tryCanvas(file, baseName, quality); + } catch { + try { + const jpegBlob = await convertHeicBlobToJpeg(file); + return await tryCanvas(jpegBlob, baseName, quality); + } catch (e) { + throw e instanceof Error ? e : new Error("Failed to convert HEIC"); + } + } + } + // Non-Safari: Image() usually can't decode HEIC; use heic2any first + try { + const jpegBlob = await convertHeicBlobToJpeg(file); + return await tryCanvas(jpegBlob, baseName, quality); + } catch { + try { + return await tryCanvas(file, baseName, quality); + } catch (e) { + throw e instanceof Error ? e : new Error("Failed to convert HEIC"); + } + } + } + + return await tryCanvas(file, baseName, quality); +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5a745672..422a6805 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -17,27 +17,39 @@

Stackq

Go to
{/if} diff --git a/src/routes/HomeWithDialog.svelte b/src/routes/HomeWithDialog.svelte index cb1eeda1..16808db8 100644 --- a/src/routes/HomeWithDialog.svelte +++ b/src/routes/HomeWithDialog.svelte @@ -42,31 +42,41 @@

Stackq

- - - - +
+ Scan + Coming soon +
+
+ New transaction + Coming soon +
- +
+ New Item + Coming soon +
- - - - - - +
+ Items + Coming soon +
+
+ Transactions + Coming soon +
- - - +
+ Accounts + Coming soon +
diff --git a/src/routes/receipts/ReceiptForm.svelte b/src/routes/receipts/ReceiptForm.svelte index b19901a0..6f6e2ac0 100644 --- a/src/routes/receipts/ReceiptForm.svelte +++ b/src/routes/receipts/ReceiptForm.svelte @@ -1,7 +1,7 @@