Skip to content

File viewer: per-filetype rendering

At a glance

  • FileView dispatches to one of 6 organisms by SchemaVariant — they're not individually exported, this page is their only documentation.
  • RawView is separate: exported on its own, plain props, no store access, not part of the FileView switch.
  • Pdf/Table/Text/Email use free-text selection (useTextSelect); Xml/Json use click-driven reference selection instead.
  • useJsonFile has no error handling around JSON.parse — a malformed file throws uncaught.

FileView is the one rendering component file-viewer exports from its package root. It reads the current file's SchemaVariant from the store and switches (via ts-pattern's exhaustive match) to the matching organism — PdfView, TableView, XmlView, TextView, EmailView, or JsonView. Those six organisms are not individually exported from the package; FileView is the only supported entry point into them, so this page is where their per-filetype behavior is documented directly.

RawView is the exception — it is separately exported, and it's a different kind of component: a plain controlled contentEditable text view taking rawText/setRawText as props, with no store access of its own. It's not part of the FileView switch at all — something outside this package decides when to show a raw/editable view instead of the type-specific one.

The six organisms

Each organism pairs a presentational component with a use-* hook that loads and shapes that file type's content from the store's current File object (useStore(({ file: f }) => f.getFile())):

  • PdfusePdf drives react-pdf: tracks pageNumber, extracts the current page's text (page.getTextContent()) whenever it changes, and exposes previousPage/nextPage. PdfView layers highlight <mark> spans onto react-pdf's customTextRenderer per text chunk — matching by counting which occurrence of a highlighted string a given chunk represents, since the same text can appear more than once on a page.

  • TableuseTableFile reads the file with the xlsx library and converts the first worksheet into rows/columns for react-data-grid (ws_to_rdg), including a heuristic that treats numeric cells under column names like date/time/nr/amount as dates or plain numbers rather than raw Excel serial numbers (isDateTimeColumn/ isNumberColumn, matched by substring against the column letter — note this checks the column letter like "A", not a header row, so it only fires on the nr/id/etc. substrings appearing in the letter itself; see the TODO in Selection & paths about how cell selection turns a click into a path).

  • XmluseXmlFile just reads the file as text; XmlView renders it with react-interactive-xml-viewer and, in "Reference" selection mode, wires its onClickTag/onClickAttribute/onClickValue callbacks to useXmlPath (see Selection & paths).

  • TextuseText paginates the file's text content into fixed-size pages (linesPerPage = 50) and tracks each page's character offset into the full text (getPageOffset, reconstructed by summing prior lines' lengths) — that offset is what lets highlight ranges, which are computed against the full text, be mapped onto the currently-displayed page.

  • EmailuseEmail parses .eml (via eml-parse-js) or .msg (via @kenjiuno/msgreader, decompressing RTF bodies with @kenjiuno/decompressrtf + rtf-stream-parser when present) into { text, html }, sanitizes the HTML (strips <script>, on* handlers, and non-http src/href), and rewrites cid: image references to inline base64 data URIs. EmailView's highlight logic for the HTML case is the most involved in the package: it walks the DOM to a flat text offset space, finds the target occurrence there, then re-locates and wraps it back across the original (possibly multiple) text nodes, because a single highlighted string in the rendered text can span more than one DOM text node.

  • JsonuseJsonFile simply JSON.parses the store's text content. JsonView renders it with @uiw/react-json-view, overriding its KeyName/Row renderers to call useJsonPath on click in "Reference" mode.

    Uncaught parse errors

    useJsonFile doesn't wrap its JSON.parse call — a malformed JSON file throws uncaught rather than showing an error state in the viewer.

Pdf, Table, Text, and Email all call useTextSelect unconditionally on mount, wiring up text-selection anchoring for that view; Xml and Json don't — they use their tag/value-click model instead (see Selection & paths for both mechanisms).

Shared chrome

Most organisms end with the same <Footer /> (from molecules), and the paginated ones (Pdf, Text) share the same <Move into={false} /> / page-counter / <Move /> header pattern from @morph-mapper/ui's Move action.