diff --git a/apps/web/src/components/common/file-library-modal.tsx b/apps/web/src/components/common/file-library-modal.tsx index e68fa680..a951cdd6 100644 --- a/apps/web/src/components/common/file-library-modal.tsx +++ b/apps/web/src/components/common/file-library-modal.tsx @@ -1,4 +1,4 @@ -import { Check, FolderOpen, Loader2, Search, X } from "lucide-react"; +import { Check, FolderOpen, ImageIcon, Loader2, Search, X } from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { apiListFiles, @@ -9,6 +9,52 @@ import { } from "@/lib/api"; import { cn } from "@/lib/utils"; +function AuthImage({ src, alt, className }: { src: string; alt: string; className?: string }) { + const [blobUrl, setBlobUrl] = useState(null); + const [failed, setFailed] = useState(false); + + useEffect(() => { + let revoked = false; + fetch(src, { headers: formatHeaders() }) + .then((res) => { + if (!res.ok) throw new Error(); + return res.blob(); + }) + .then((blob) => { + if (revoked) return; + setBlobUrl(URL.createObjectURL(blob)); + }) + .catch(() => { + if (!revoked) setFailed(true); + }); + return () => { + revoked = true; + setBlobUrl((prev) => { + if (prev) URL.revokeObjectURL(prev); + return null; + }); + }; + }, [src]); + + if (failed) { + return ( +
+ +
+ ); + } + + if (!blobUrl) { + return ( +
+
+
+ ); + } + + return {alt}; +} + interface FileLibraryModalProps { open: boolean; onClose: () => void; @@ -170,11 +216,10 @@ export function FileLibraryModal({ open, onClose, onImport }: FileLibraryModalPr : "border-border hover:border-primary/50", )} > - {file.originalName} {checked && (
diff --git a/apps/web/src/components/files/file-details.tsx b/apps/web/src/components/files/file-details.tsx index cd4d6dd6..0cd50fd3 100644 --- a/apps/web/src/components/files/file-details.tsx +++ b/apps/web/src/components/files/file-details.tsx @@ -1,5 +1,5 @@ import { TOOLS } from "@snapotter/shared"; -import { FileImage, Workflow } from "lucide-react"; +import { FileImage, ImageIcon, Workflow } from "lucide-react"; import { useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { @@ -13,6 +13,52 @@ import { cn } from "@/lib/utils"; import { useFileStore } from "@/stores/file-store"; import { useFilesPageStore } from "@/stores/files-page-store"; +function AuthImage({ src, alt, className }: { src: string; alt: string; className?: string }) { + const [blobUrl, setBlobUrl] = useState(null); + const [failed, setFailed] = useState(false); + + useEffect(() => { + let revoked = false; + fetch(src, { headers: formatHeaders() }) + .then((res) => { + if (!res.ok) throw new Error(); + return res.blob(); + }) + .then((blob) => { + if (revoked) return; + setBlobUrl(URL.createObjectURL(blob)); + }) + .catch(() => { + if (!revoked) setFailed(true); + }); + return () => { + revoked = true; + setBlobUrl((prev) => { + if (prev) URL.revokeObjectURL(prev); + return null; + }); + }; + }, [src]); + + if (failed) { + return ( +
+ +
+ ); + } + + if (!blobUrl) { + return ( +
+
+
+ ); + } + + return {alt}; +} + function toolName(toolId: string): string { return TOOLS.find((t) => t.id === toolId)?.name ?? toolId; } @@ -130,7 +176,7 @@ export function FileDetails({ mobile = false }: FileDetailsProps) { > {/* Thumbnail */}
-