diff --git a/apps/web/src/components/tools/collage-preview.tsx b/apps/web/src/components/tools/collage-preview.tsx index 5283974a..20d90b76 100644 --- a/apps/web/src/components/tools/collage-preview.tsx +++ b/apps/web/src/components/tools/collage-preview.tsx @@ -61,12 +61,7 @@ export function CollagePreview() { } if (phase === "processing") { - return ( -
- -

Creating your collage...

-
- ); + return ; } if (phase === "result" && resultUrl) { @@ -150,6 +145,29 @@ function UploadArea() { ); } +function ProcessingView() { + const progress = useCollageStore((s) => s.progress); + const label = progress < 80 ? "Uploading images..." : "Processing collage..."; + + return ( +
+ +
+
+ {label} + {progress}% +
+
+
+
+
+
+ ); +} + /** The live CSS Grid collage canvas. */ function CollageCanvas({ template }: { template: CollageTemplate }) { const store = useCollageStore(); diff --git a/apps/web/src/components/tools/collage-settings.tsx b/apps/web/src/components/tools/collage-settings.tsx index 65955640..cf2b6174 100644 --- a/apps/web/src/components/tools/collage-settings.tsx +++ b/apps/web/src/components/tools/collage-settings.tsx @@ -63,11 +63,11 @@ export function CollageSettings() { if (!hasImages || !template) return; store.setPhase("processing"); + store.setProgress(0); store.setError(null); try { const formData = new FormData(); - // Send images in cell-assignment order for (let i = 0; i < template.cells.length; i++) { const imgIdx = cellAssignments[i] ?? -1; if (imgIdx >= 0 && images[imgIdx]) { @@ -94,18 +94,50 @@ export function CollageSettings() { }), ); - const res = await fetch("/api/v1/tools/collage", { - method: "POST", - headers: formatHeaders(), - body: formData, + const result = await new Promise<{ + downloadUrl: string; + processedSize: number; + originalSize: number; + jobId: string; + }>((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open("POST", "/api/v1/tools/collage"); + + const headers = formatHeaders(); + headers.forEach((value, key) => { + xhr.setRequestHeader(key, value); + }); + + xhr.upload.onprogress = (e) => { + if (e.lengthComputable) { + store.setProgress(Math.round((e.loaded / e.total) * 80)); + } + }; + + xhr.onload = () => { + if (xhr.status >= 200 && xhr.status < 300) { + store.setProgress(100); + try { + resolve(JSON.parse(xhr.responseText)); + } catch { + reject(new Error("Invalid response")); + } + } else { + try { + const body = JSON.parse(xhr.responseText); + reject(new Error(body.error || `Failed: ${xhr.status}`)); + } catch { + reject(new Error(`Failed: ${xhr.status}`)); + } + } + }; + + xhr.onerror = () => reject(new Error("Network error")); + xhr.send(formData); + + store.setProgress(5); }); - if (!res.ok) { - const body = await res.json().catch(() => ({})); - throw new Error(body.error || `Failed: ${res.status}`); - } - - const result = await res.json(); store.setResult(result.downloadUrl, result.processedSize, result.originalSize, result.jobId); } catch (err) { store.setError(err instanceof Error ? err.message : "Collage failed"); diff --git a/apps/web/src/stores/collage-store.ts b/apps/web/src/stores/collage-store.ts index fbc2abb8..62aa1e1a 100644 --- a/apps/web/src/stores/collage-store.ts +++ b/apps/web/src/stores/collage-store.ts @@ -50,6 +50,7 @@ interface CollageState { // UI state selectedCell: number | null; phase: Phase; + progress: number; resultUrl: string | null; resultSize: number | null; originalSize: number | null; @@ -74,6 +75,7 @@ interface CollageState { setQuality: (v: number) => void; setSelectedCell: (v: number | null) => void; setPhase: (v: Phase) => void; + setProgress: (v: number) => void; setResult: (url: string, size: number, originalSize: number, jobId: string) => void; setError: (e: string | null) => void; reset: () => void; @@ -105,6 +107,7 @@ export const useCollageStore = create((set, get) => ({ quality: 90, selectedCell: null, phase: "upload", + progress: 0, resultUrl: null, resultSize: null, originalSize: null, @@ -263,6 +266,7 @@ export const useCollageStore = create((set, get) => ({ setQuality: (v) => set({ quality: v, resultUrl: null }), setSelectedCell: (v) => set({ selectedCell: v }), setPhase: (v) => set({ phase: v }), + setProgress: (v) => set({ progress: v }), setResult: (url, size, originalSize, jobId) => set({ resultUrl: url, resultSize: size, originalSize, jobId, phase: "result", error: null }), setError: (e) => set({ error: e, phase: "editing" }), @@ -287,6 +291,7 @@ export const useCollageStore = create((set, get) => ({ quality: 90, selectedCell: null, phase: "upload", + progress: 0, resultUrl: null, resultSize: null, originalSize: null,