import { useEffect, useRef, useState, type ReactNode } from "react"; import { Download, Printer } from "lucide-react"; import { Dialog } from "./dialog"; import { formatSize } from "@/lib/format"; import { previewKind, TEXT_PREVIEW_CHARS, TEXT_PREVIEW_MAX } from "@/lib/preview"; import { t } from "@/lib/i18n"; /** * One blob, described the way both callers can describe it. The URLs are built * by the caller so this stays a presentational component: nothing in `ui/` * reaches for the JMAP client, and this is not the file to break that with. */ export interface PreviewFile { name: string; type: string; size?: number | null; /** Plain download -- the server sends it as an attachment. */ url: string; /** The same blob asked for inline. Only the allowlisted types come back that way. */ inlineUrl: string; } /** * Shows a file without downloading it: pictures, PDFs, and anything text. * * Grown out of the attachment preview in MessageView, which is where it still * has one of its two callers -- the other is Files, which until now could only * hand you the bytes. */ export function FilePreviewDialog({ file, onClose, caption }: { file: PreviewFile | null; onClose: () => void; caption?: ReactNode }) { const kind = file ? previewKind(file.type, file.name) : null; const tooBig = kind === "text" && typeof file?.size === "number" && file.size > TEXT_PREVIEW_MAX; const pdfRef = useRef(null); /* * Print what is on screen, not the mail or the file list behind it. * * A PDF is its own document inside an iframe, and the page around it cannot * paginate it -- printing the page yields the first screenful of the viewer * and nothing else. Same origin, so we can ask the iframe to print itself, * which is the browser's own PDF print. Chrome sometimes refuses while the * viewer is still loading; opening it in a tab leaves the reader somewhere * they can print from, which is better than a silent no-op. * * Pictures and text are ours to lay out, so those go through the page with * the dialog marked and everything else dropped -- see `printing-preview` in * the print block of app.css. */ const print = () => { if (kind === "pdf") { const frame = pdfRef.current; try { if (!frame?.contentWindow) throw new Error("no frame"); frame.contentWindow.focus(); frame.contentWindow.print(); } catch { if (file) window.open(file.inlineUrl, "_blank", "noopener"); } return; } const root = document.documentElement; const clear = () => { root.classList.remove("printing-preview"); window.removeEventListener("afterprint", clear); }; window.addEventListener("afterprint", clear); root.classList.add("printing-preview"); try { window.print(); } finally { clear(); } }; return ( {kind && !tooBig && } {t("Download")} )} > {file && ( <> {tooBig ? (

{t("This file is too big to show here ({size}) — download it to read it.", { size: formatSize(file.size ?? 0) })}

) : kind === "image" ? ( {file.name} ) : kind === "pdf" ? (