Open bot files in a preview modal instead of a new tab

Markdown rendered, CSV/TSV as a table, images, PDFs, audio, video and
sandboxed HTML in place, other text as code, with Download and Open in a
new tab. Used by file chips, message attachments, the files modal, task
files and the files browser.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-28 19:25:49 +01:00
co-authored by Claude Opus 5.5
parent 521c2bbac0
commit faa554d5e3
26 changed files with 960 additions and 42 deletions
@@ -0,0 +1,50 @@
import Loading from "@moduletrace/twui/elements/Loading";
import Stack from "@moduletrace/twui/layout/Stack";
import useFileText from "@/src/hooks/use-file-text";
import formatBytes from "@/src/utils/format-bytes";
import { AppData } from "@/src/data/app-data";
import type { FilePreviewKind } from "@/src/utils/file-preview-kind";
import FilePreviewCode from "./file-preview-code";
import FilePreviewMarkdown from "./file-preview-markdown";
import FilePreviewNotice from "./file-preview-notice";
import FilePreviewTable from "./file-preview-table";
type Props = {
kind: FilePreviewKind;
url: string;
size?: number;
};
/**
* A text file read into the preview: markdown rendered, CSV or TSV as a
* table, anything else as code.
*/
export default function FilePreviewText({ kind, url, size }: Props) {
const { text, loading, error, too_large } = useFileText({ url, size });
if (loading) {
return (
<Stack className="grow w-full items-center justify-center">
<Loading />
</Stack>
);
}
if (too_large) {
return (
<FilePreviewNotice
message={`This file is bigger than ${formatBytes({ bytes: AppData.FilePreviewMaxTextBytes })}, too big to preview. Download it to open it.`}
/>
);
}
if (error || text == null) {
return <FilePreviewNotice message={error || "The file couldn't be read."} />;
}
if (kind == "markdown") return <FilePreviewMarkdown text={text} />;
if (kind == "csv") return <FilePreviewTable text={text} delimiter="," />;
if (kind == "tsv") return <FilePreviewTable text={text} delimiter={"\t"} />;
return <FilePreviewCode text={text} />;
}