File Preview Architecture
Modular, plugin-driven file preview engine with reactive menubar registration!
Viewing files directly in the browser shouldn't mean loading a monolith that tries to handle every file format under the sun. Whether it's a PDF, a high-res image, an MP4 video, or a raw text file, Platrium uses a modular plugin architecture to render rich file previews with dedicated toolbar controls and context menus!
Plugin & Menu Architecture
Platrium separates file previewing into two distinct systems: Plugin Registration (which plugin renders which MIME type) and Menu Registration (how plugins contribute commands to the top Menubar and Context Menu).
Plugin Registration (PluginRegistry)
Every preview plugin implements the PluginDefinition interface, declaring its supported MIME types (exact strings like application/pdf or wildcard patterns like image/*):
export interface PluginDefinition {
id: string;
name: string;
supportedMimeTypes: string[]; // e.g. ["image/*", "application/png"]
maxSizeBytes?: number; // max size in bytes (undefined = 32MB default limit, 0 = unlimited)
component: ComponentType<FilePreviewPluginProps>;
}Plugins register with the global previewRegistry instance (previewRegistry.register(ImagePlugin)). When a file is loaded, previewRegistry.getPluginForMimeType(mimeType) finds the matching handler and caches the lookup for O(1) evaluation. If no plugin matches, it returns FallbackPlugin.
Preview File Size Limits (maxSizeBytes)
To prevent non-range plugins from crashing browser memory with huge files (like a 500MB PNG image), Platrium enforces file size limits during the initial HEAD /rawcontent/{id} call:
- Default Limit (
32 MB): If a plugin omitsmaxSizeBytes(leaves itundefined),FilePreviewCoreappliesDEFAULT_MAX_PREVIEW_SIZE_BYTES(32 MB). - Custom Plugin Limit: Plugins can specify custom limits (e.g.
ImagePreviewPluginspecifies25 * 1024 * 1024= 25 MB). - Unlimited (
0): SettingmaxSizeBytes: 0specifies that the plugin supports progressive/range-based streaming (e.g.VideoPlugin) and has no size cap. - Fallback Trigger: If
sizeBytes > maxSizeBytes,FilePreviewCorerendersFallbackPlugindisplaying a "File Too Large to Preview" warning with a manual download button.
Menu Registration (FilePreviewMenuContext)
Instead of hardcoding toolbar buttons, menu contributions are fully reactive:
FilePreviewCorewraps the previewer with<FilePreviewMenuProvider>.FilePreviewMenuBarregisters core defaults (Download,File Info) under theFilecategory usinguseRegisterMenu("File", [...]).- The active plugin calls
useRegisterMenu("Tools", [...])oruseRegisterMenu("View", [...])directly inside its component body. FilePreviewMenuBargroups registered items by category and renders the top Menubar and right-clickContextMenu.- On plugin unmount,
useRegisterMenuautomatically cleans up the plugin's items.
Loop Safety: FilePreviewMenuProvider uses deep-equality checks on item properties and memoizes the context value. Inline array definitions in plugin renders will not cause re-render loops!
Metadata Resolution & Direct Streaming
Platrium fetches file metadata first via HTTP HEAD before initializing plugins or streaming content.
Metadata Resolution via HTTP HEAD
When navigating to /file/:id, FilePreviewCore executes a lightweight HTTP HEAD request to /rawcontent/{fileId}.
The Service Worker responds instantly with response headers containing:
Content-Type(e.g.image/png,application/pdf)Content-Length(file size in bytes)Content-Disposition(original filename)
This allows FilePreviewCore to pick the right plugin without downloading any file body payload upfront!
Direct Streaming via /rawcontent/
Plugins render media elements by pointing directly to /rawcontent/{fileId}:
- Images & Video: Rendered using standard
<img src="/rawcontent/{fileId}" />or<video src="/rawcontent/{fileId}" />elements. The Downloads Service Worker intercepts these requests and streams the bytes seamlessly. - Documents & Range Requests: PDF or audio plugins issue HTTP
Rangerequests directly to/rawcontent/{fileId}to stream byte chunks on-demand. - Fallback View: If no plugin supports the MIME type,
FallbackPlugindisplays a warning screen with a manual download action.
Building a Custom Preview Plugin
Here is a complete example showing how to build an ImagePreviewPlugin that streams an image directly via /rawcontent/ and registers custom menu items:
import React, { useState } from "react";
import type { FilePreviewPluginProps, PluginDefinition } from "../PluginDefinition";
import { useRegisterMenu } from "../FilePreviewMenuContext";
import { previewRegistry } from "../PluginRegistry";
import { RotateCw, ZoomIn, ZoomOut } from "lucide-react";
export const ImagePluginComponent: React.FC<FilePreviewPluginProps> = ({ info }) => {
const [rotation, setRotation] = useState(0);
// 1. Register custom menu items for Tools and View categories
useRegisterMenu("Tools", [
{
id: "img-rotate",
label: "Rotate Clockwise",
category: "Tools",
icon: RotateCw,
shortcut: "⌘R",
onClick: () => setRotation((r) => (r + 90) % 360),
},
]);
useRegisterMenu("View", [
{
id: "img-zoom-in",
label: "Zoom In",
category: "View",
icon: ZoomIn,
shortcut: "⌘+",
onClick: () => {
/* Custom zoom logic */
},
},
]);
// 2. Stream image directly via /rawcontent/{fileId}
const rawContentUrl = `/rawcontent/${info.fileId}`;
return (
<div className="flex items-center justify-center h-full w-full p-4 overflow-hidden">
<img
src={rawContentUrl}
alt={info.fileName}
style={{ transform: `rotate(${rotation}deg)` }}
className="max-h-full max-w-full object-contain transition-transform duration-200"
/>
</div>
);
};
// 3. Define Plugin supporting image wildcard MIME types
export const ImagePlugin: PluginDefinition = {
id: "image-previewer",
name: "Image Previewer",
supportedMimeTypes: ["image/*"],
maxSizeBytes: 25 * 1024 * 1024, // 25 MB max limit (undefined = 32MB default, 0 = unlimited)
component: ImagePluginComponent,
};
// 4. Register with global PluginRegistry
previewRegistry.register(ImagePlugin);