Files
supabase/apps/studio/components/interfaces/Explorer/NotebookPdf/NotebookPdfDocument.tsx
T
Joshen Lim 38fa40e851 Joshenlim/fe 4445 explorer export as pdf option 2 (#50786)
## Context

Another take on [this
PR](https://github.com/supabase/supabase/pull/50733), decided that it's
better UX + DX to use a PDF library (`@react-pdf/renderer`) instead for
handling exporting Notebooks to PDF. The previous method using `print`
involved a lot of sparse patching in various places + had a lot of
limitations, not to mention its awkward UX to "Print".

There's a lot of code changes in this PR but it's because we're
constructing the PDF document based on the notebook's contents using
primitives from `@react-pdf/renderer` which gives us a lot more control
over the PDF's output.

PDF output will differ slightly from the Notebook UI in the browser
- Added a custom header in the report which covers the notebook name +
project name, and when its exported
- Empty markdown cells are skipped
- Query cells will show both SQL content + results (either table or
chart)
- Opting to skip syntax highlighting for SQL content (Can consider
separately)
- Results will only be included in the PDF if the notebook's been run
(Aligns with Copy as Markdown CTA)
- For table results, _all_ rows will be rendered (Aligns with Copy as
Markdown CTA)

Feel free to give it a spin! 🙂 🙏 

<img width="248" height="207" alt="image"
src="https://github.com/user-attachments/assets/e38c14ba-7ee8-40ce-99b1-fa126453bf0f"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
  - Added an “Export as PDF” option for notebooks.
- PDFs include notebook details, Markdown content, SQL queries, query
results, tables, and charts.
- Query exports show errors, empty results, row counts, and applicable
row limits.
- Export progress is displayed while the PDF is generated, and failures
are reported via notifications.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-24 13:23:38 +08:00

73 lines
2.4 KiB
TypeScript

import { Document, Page, Path, Svg, Text, View } from '@react-pdf/renderer'
import dayjs from 'dayjs'
import type { Snapshot } from 'valtio'
import { type QueryResult } from '../types'
import { NotebookPdfMarkdown } from './NotebookPdfMarkdown'
import { NotebookPdfQueryCell } from './NotebookPdfQueryCell'
import { pdfStyles } from './theme'
import { isQueryCell, type Cell } from '@/data/content/notebooks/notebook-schema'
interface NotebookPdfDocumentProps {
name: string
projectName?: string
cells: readonly Snapshot<Cell>[]
results: ReadonlyMap<string, QueryResult>
}
function SupabaseLogoMark() {
return (
<Svg viewBox="0 0 109 113" style={pdfStyles.headerLogoMark}>
<Path
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z"
fill="#3ECF8E"
/>
<Path
d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z"
fill="#3ECF8E"
/>
</Svg>
)
}
export function NotebookPdfDocument({
name,
projectName,
cells,
results,
}: NotebookPdfDocumentProps) {
const exportedDate = dayjs().format('MMM D, YYYY')
return (
<Document title={name}>
<Page size="A4" style={pdfStyles.page}>
<View style={pdfStyles.headerBanner}>
<View style={pdfStyles.headerLeft}>
<View style={pdfStyles.headerLogoRow}>
<SupabaseLogoMark />
<Text style={pdfStyles.headerLogoText}>supabase</Text>
</View>
<Text style={pdfStyles.headerLabel}>EXPLORER NOTEBOOK</Text>
</View>
<View style={pdfStyles.headerRight}>
<Text style={pdfStyles.headerTitle}>{name}</Text>
<Text style={pdfStyles.headerMeta}>Exported {exportedDate}</Text>
{projectName !== undefined && (
<Text style={pdfStyles.headerMeta}>Project: {projectName}</Text>
)}
</View>
</View>
<View style={pdfStyles.headerRule} />
{cells.map((cell) =>
isQueryCell(cell) ? (
<NotebookPdfQueryCell key={cell._id} cell={cell} result={results.get(cell._id)} />
) : (
<NotebookPdfMarkdown key={cell._id} markdown={cell.text} />
)
)}
</Page>
</Document>
)
}