Files
Verso/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
T
claude 200bff4ecb
Build and Deploy Verso / deploy (push) Successful in 12m51s
feat(typst): browser-side live preview via typst.ts WASM
Adds a dual-mode Typst preview: a new "Live (browser)" mode compiles and
renders Typst documents entirely in-browser using typst.ts WASM (28 MB
compiler + 1 MB renderer). The existing server-side PDF mode is preserved
and selectable via a new "Preview mode" section in the recompile dropdown,
visible only for Typst projects.

Architecture:
- Web Worker (typst-preview-worker.ts) runs the WASM compiler; queues
  compile requests so only the latest compile runs after each keypress
- TypstWasmPreview component initialises the renderer on the main thread,
  listens to changedAt from the compile context, debounces at 400 ms, and
  renders SVG into a container div via renderToSvg
- typstPreviewMode ('wasm'|'pdf') is persisted per-project in localStorage
- isTypstProject, changedAt, typstPreviewMode, setTypstPreviewMode are
  exposed through both LocalCompileContext and DetachCompileContext
- Fonts loaded from jsDelivr CDN (text subset only) on first use
- Phase 1: single-file Typst only (no #include, no images)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 13:42:39 +00:00

77 lines
2.6 KiB
TypeScript

import { ElementType, lazy, memo, Suspense } from 'react'
import classNames from 'classnames'
import PdfViewer from './pdf-viewer'
import { FullSizeLoadingSpinner } from '../../../shared/components/loading-spinner'
import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
import { PdfPreviewMessages } from './pdf-preview-messages'
import CompileTimeWarningUpgradePrompt from './compile-time-warning-upgrade-prompt'
import { PdfPreviewProvider } from './pdf-preview-provider'
import PdfPreviewHybridToolbar from '@/features/pdf-preview/components/pdf-preview-hybrid-toolbar'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import PdfCodeCheckFailedBanner from '@/features/pdf-preview/components/pdf-code-check-failed-banner'
import getMeta from '@/utils/meta'
import PdfLogsViewer from '@/features/pdf-preview/components/pdf-logs-viewer'
const TypstWasmPreview = lazy(
() =>
import(
/* webpackChunkName: "typst-wasm-preview" */
'@/features/typst-preview/components/typst-wasm-preview'
)
)
function PdfPreviewPane() {
const {
pdfUrl,
pdfViewer,
darkModePdf: darkModeSetting,
activeOverallTheme,
isTypstProject,
typstPreviewMode,
} = useCompileContext()
const { compileTimeout } = getMeta('ol-compileSettings')
const isHtmlOutput = pdfUrl?.includes('output.html')
const darkModePdf =
(pdfViewer === 'pdfjs' || isHtmlOutput) &&
activeOverallTheme === 'dark' &&
darkModeSetting
const isWasmMode = isTypstProject && typstPreviewMode === 'wasm'
const classes = classNames('pdf', 'full-size', {
'pdf-empty': !pdfUrl && !isWasmMode,
'pdf-dark-mode': darkModePdf,
})
const pdfPromotions = importOverleafModules('pdfPreviewPromotions') as {
import: { default: ElementType }
path: string
}[]
return (
<div className={classes}>
<PdfPreviewProvider>
<PdfPreviewHybridToolbar />
<PdfCodeCheckFailedBanner />
<PdfPreviewMessages>
{compileTimeout < 60 && <CompileTimeWarningUpgradePrompt />}
</PdfPreviewMessages>
<Suspense fallback={<FullSizeLoadingSpinner delay={500} />}>
<div className="pdf-viewer" data-testid="pdf-viewer">
{isWasmMode ? (
<TypstWasmPreview />
) : (
<PdfViewer />
)}
</div>
</Suspense>
<PdfLogsViewer />
{pdfPromotions.map(({ import: { default: Component }, path }) => (
<Component key={path} />
))}
</PdfPreviewProvider>
</div>
)
}
export default memo(PdfPreviewPane)