feat(files): a file that is not media opens in a viewer — an image, a PDF, text as text
- GET /api/files/{id}/preview: an allow-list on the stored extension (downloads/preview.py) picks
image / PDF / text; served inline under `CSP: sandbox; frame-ancestors 'self'`, the type from the
table; HTML and SVG arrive as plain text; a text is its first 1 MiB (X-Preview-Truncated), sent
as UTF-8 whatever it was written in (BOM, UTF-8, else cp1250); a NUL means binary: 415
- the list says which preview each file gets (asset.preview); FE/BE contract on kinds and header
- the viewer (shared dialog shell): ← / → through the list's such files in its order, Save, open in
a tab, the file's menu; one stage height for every kind; a no-preview card offers the file
- the name, the card's picture and the menu's Preview open it; an image up to 8 MB is its own
thumbnail
This commit is contained in:
@@ -105,3 +105,33 @@ describe("JobThumb · no picture", () => {
|
||||
expect(container.querySelector("svg.lucide-film")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
// A file that is not media: an image is its own picture (its sandboxed preview), unless it is too big
|
||||
// for a thumbnail or fails; anything else draws the file glyph.
|
||||
describe("JobThumb · a file", () => {
|
||||
const image = (over: Partial<NonNullable<DownloadJob["asset"]>> = {}) =>
|
||||
withAsset("png", { media_kind: "file", preview: "image", size_bytes: 200_000, ...over });
|
||||
|
||||
it("shows an image through its preview", () => {
|
||||
const { container } = render(<JobThumb job={image()} />);
|
||||
expect(new URL(container.querySelector("img")!.src).pathname).toBe("/api/files/1/preview");
|
||||
});
|
||||
|
||||
it("keeps the glyph for an image too big to be a thumbnail, and for other files", () => {
|
||||
const { container, unmount } = render(<JobThumb job={image({ size_bytes: 50_000_000 })} />);
|
||||
expect(container.querySelector("img")).toBeNull();
|
||||
expect(container.querySelector("svg.lucide-file")).not.toBeNull();
|
||||
unmount();
|
||||
const { container: pdf } = render(
|
||||
<JobThumb job={withAsset("pdf", { media_kind: "file", preview: "pdf" })} />,
|
||||
);
|
||||
expect(pdf.querySelector("img")).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to the glyph when the image fails", () => {
|
||||
const { container } = render(<JobThumb job={image()} />);
|
||||
fireEvent.error(container.querySelector("img")!);
|
||||
expect(container.querySelector("img")).toBeNull();
|
||||
expect(container.querySelector("svg.lucide-file")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user