Pi-Web File Viewer Supported File Types: How Preview Rendering Works
The Pi-Web file viewer supports images (PNG, JPG, GIF, SVG), text/source code files (JavaScript, TypeScript, Python, Go, and 15+ languages), Markdown files, and binary files, with each type triggering a distinct rendering pipeline using React components like HighlightedCode and MarkdownBody.
The FileViewer component in the agegr/pi-web repository handles file preview functionality through a type-safe discrimination system. Understanding what file types are supported and how the preview mechanism processes each category is essential for developers extending the codebase or debugging rendering issues.
Supported File Types in the Viewer
The file viewer categorizes all content into four distinct types based on file extension and MIME type detection. The viewFile function in lib/file-access.ts implements this classification logic at lines 82–105.
Image Files
Supported extensions: .png, .jpg, .jpeg, .gif, .svg
When viewFile detects an image MIME type through the mimeFromExtension mapping, it returns an ImageFileInfo object containing a URL pointer to /api/files/{fileId} rather than the file content itself. In components/FileViewer.tsx (lines 68–70), the component renders a native <img> element:
} else if (fileInfo.type === "image") {
return <img src={fileInfo.url} alt={fileInfo.name} className="max-w-full h-auto" />;
}
This approach offloads image decoding to the browser while avoiding memory overhead from reading binary data into the React state.
Text and Source Code Files
Supported extensions: .js, .ts, .tsx, .jsx, .json, .py, .go, .java, .c, .cpp, .rs, .html, .css, .txt
Text files trigger the TextFileInfo type path. The languageFromExtension function (lines 59–76 in lib/file-access.ts) maps file extensions to Prism.js language identifiers. The viewFile function reads the file contents using fs.readFile with UTF-8 encoding and returns both the content string and the detected language.
In FileViewer.tsx (lines 64–66), text content flows into the HighlightedCode component:
if (fileInfo.type === "text") {
return <HighlightedCode code={content} language={fileInfo.language} />;
}
The HighlightedCode component (located in components/HighlightedCode.tsx) leverages react-syntax-highlighter with the a11yLight theme, line numbers enabled, and automatic line wrapping to provide syntax highlighting for the 15+ supported programming languages.
Markdown Files
Supported extension: .md
Although Markdown files technically match text/markdown MIME types, Pi-Web treats them as a distinct MarkdownFileInfo type to enable rich rendering. When viewFile encounters a .md extension (lines 97–101 in lib/file-access.ts), it reads the full text content and returns type: "markdown" instead of the generic text classification.
The viewer passes this content to MarkdownBody (lines 66–68 in FileViewer.tsx), which uses react-markdown with the remark-gfm plugin for GitHub Flavored Markdown support. This enables tables, strikethrough, and task lists beyond standard CommonMark.
Binary and Unsupported Files
Fallback behavior: Any file without a recognized image or text MIME type
When mimeFromExtension returns null or the extension does not match known text patterns, viewFile returns a BinaryFileInfo object containing only the filename (lines 103–104). The viewer displays a placeholder message: [Binary file cannot be displayed]. In the render logic (lines 70–73), binary files appear within a <pre> block, though the architecture prevents the actual binary buffer from entering client-side memory.
How the Preview Pipeline Works
Understanding the technical flow reveals why certain file types render differently and where to intercept the process for customization.
File Type Detection Logic
The detection pipeline runs entirely within the viewFile async function in lib/file-access.ts:
- Extension extraction:
path.extname(filePath)isolates the file extension - MIME mapping:
mimeFromExtensionchecks against a hardcoded Record object mapping extensions to MIME types (lines 40–53) - Branching logic:
- If
mime?.startsWith("image/")→ return ImageFileInfo - Else if
mime?.startsWith("text/")→ read UTF-8 content, detect language, return TextFileInfo - Else if
ext === ".md"→ read UTF-8 content, return MarkdownFileInfo - Else → return BinaryFileInfo
- If
This discrimination happens server-side (or in the main process) before any content reaches the React component tree.
Rendering Components
The FileViewer component orchestrates rendering through a useMemo hook (lines 62–76) that switches on fileInfo.type:
const rendered = useMemo(() => {
if (!fileInfo) return null;
if (fileInfo.type === "text") {
return <HighlightedCode code={content} language={fileInfo.language} />;
} else if (fileInfo.type === "markdown") {
return <MarkdownBody markdown={content} />;
} else if (fileInfo.type === "image") {
return <img src={fileInfo.url} alt={fileInfo.name} className="max-w-full h-auto" />;
} else if (fileInfo.type === "binary") {
return <pre>{content}</pre>;
} else {
return <div>Unsupported preview.</div>;
}
}, [fileInfo, content]);
State management follows a strict loading pattern: useEffect triggers the async viewFile call, sets loading states, handles cancellation via a cancelled flag (lines 30–53), and persists view state (scroll position) through fileViewState on unmount (lines 56–60).
Working with the File Viewer Programmatically
To integrate the viewer into a custom page or extend its supported types, use the fileId encoding pattern demonstrated in the source:
import { FileViewer } from "../components/FileViewer";
// Render a TypeScript file with syntax highlighting
export const CodePreviewPage = () => {
const fileId = encodeURIComponent("/home/user/project/src/utils.ts");
return <FileViewer fileId={fileId} />;
};
// Render a Markdown README
export const DocsPage = () => {
const fileId = encodeURIComponent("./docs/README.md");
return <FileViewer fileId={fileId} />;
};
Extending supported languages: Add new entries to the languageFromExtension Record in lib/file-access.ts (line 60) and ensure the corresponding Prism.js language is imported in HighlightedCode.tsx.
Adding new image formats: Extend the mimeFromExtension map (lines 41–52) with entries like .webp: "image/webp"; the viewer will automatically route them through the image rendering path.
Summary
- Image files (PNG, JPG, GIF, SVG) preview via direct URL references to the
/api/files/endpoint, rendered with native<img>tags - Text files receive automatic language detection and highlight via
react-syntax-highlighterbased on thelanguageFromExtensionmapping inlib/file-access.ts - Markdown files bypass standard text highlighting to render through
react-markdownwith GitHub Flavored Markdown support - Binary files display placeholder text without loading content into memory, improving security and performance
- The preview pipeline uses a discriminated union pattern (
FileInfotypes) determined server-side byviewFilebefore React rendering decisions occur inFileViewer.tsx
Frequently Asked Questions
How does Pi-Web determine which syntax highlighting language to use?
Pi-Web uses the languageFromExtension function in lib/file-access.ts (lines 59–76) to map file extensions to Prism.js language identifiers. When viewFile processes a text file, it extracts the extension via path.extname, looks up the language in a hardcoded Record object, and passes that string to the HighlightedCode component. If no mapping exists, the language parameter becomes null and react-syntax-highlighter renders the code without specific highlighting.
Why do binary files show [Binary file cannot be displayed] instead of downloading?
The viewFile function intentionally limits memory usage and security exposure by never reading binary files into JavaScript strings. At lines 40–46 in FileViewer.tsx, the component checks info.type and sets a placeholder string when detecting binary content during the effect hook. To implement downloads, you would modify the binary case to return a download URL similar to the image file approach, then render an anchor tag or trigger a programmatic download in the UI.
Can I add support for PDF files or other document types?
Currently, PDFs map to application/pdf in mimeFromExtension but fall through to the binary case since no specific renderer exists. To add PDF support, extend the FileInfo union type in lib/file-access.ts with a new PdfFileInfo variant, add a conditional branch in viewFile to return this type, and implement a corresponding renderer in FileViewer.tsx (such as an <iframe> or PDF.js component) within the useMemo rendering logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →