From 1317190edfdf5e66fc0f093c4b0d8aadc2dde8db Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Fri, 8 Aug 2025 10:33:28 -0600 Subject: [PATCH] export to pdf hook update --- .../report/elements/ExportToolbarButton.tsx | 10 ++-- .../src/components/ui/report/hooks/index.ts | 1 + .../ui/report/hooks/useExportReport.ts | 32 +++++----- .../ReportThreeDotMenu.tsx | 60 ++----------------- 4 files changed, 29 insertions(+), 74 deletions(-) create mode 100644 apps/web/src/components/ui/report/hooks/index.ts diff --git a/apps/web/src/components/ui/report/elements/ExportToolbarButton.tsx b/apps/web/src/components/ui/report/elements/ExportToolbarButton.tsx index 30a53471f..84b5c2b0c 100644 --- a/apps/web/src/components/ui/report/elements/ExportToolbarButton.tsx +++ b/apps/web/src/components/ui/report/elements/ExportToolbarButton.tsx @@ -22,7 +22,7 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) { const editor = useEditorRef(); const [open, setOpen] = React.useState(false); - const { exportToHtml, exportToPdf, exportToImage, exportToMarkdown } = useExportReport(editor); + const { exportToHtml, exportToPdf, exportToImage, exportToMarkdown } = useExportReport(); return ( @@ -34,16 +34,16 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) { - + exportToHtml(editor)}> {NodeTypeLabels.exportAsHtml.label} - + exportToPdf(editor)}> {NodeTypeLabels.exportAsPdf.label} - + exportToImage(editor)}> {NodeTypeLabels.exportAsImage.label} - + exportToMarkdown(editor)}> {NodeTypeLabels.exportAsMarkdown.label} diff --git a/apps/web/src/components/ui/report/hooks/index.ts b/apps/web/src/components/ui/report/hooks/index.ts new file mode 100644 index 000000000..d21893b56 --- /dev/null +++ b/apps/web/src/components/ui/report/hooks/index.ts @@ -0,0 +1 @@ +export * from './useExportReport'; diff --git a/apps/web/src/components/ui/report/hooks/useExportReport.ts b/apps/web/src/components/ui/report/hooks/useExportReport.ts index baa99718b..b4f2bd1ea 100644 --- a/apps/web/src/components/ui/report/hooks/useExportReport.ts +++ b/apps/web/src/components/ui/report/hooks/useExportReport.ts @@ -4,11 +4,12 @@ import type { PlateEditor } from 'platejs/react'; import { NodeTypeLabels } from '../config/labels'; import { createSlateEditor, serializeHtml } from 'platejs'; import { MarkdownPlugin } from '@platejs/markdown'; +import { useMemo } from 'react'; -export const useExportReport = (editor: PlateEditor) => { +export const useExportReport = () => { const { openErrorMessage, openInfoMessage } = useBusterNotifications(); - const getCanvas = async () => { + const getCanvas = async (editor: PlateEditor) => { const { default: html2canvas } = await import('html2canvas-pro'); const style = document.createElement('style'); @@ -69,9 +70,9 @@ export const useExportReport = (editor: PlateEditor) => { window.URL.revokeObjectURL(blobUrl); }; - const exportToPdf = async () => { + const exportToPdf = async (editor: PlateEditor) => { try { - const canvas = await getCanvas(); + const canvas = await getCanvas(editor); const PDFLib = await import('pdf-lib'); const pdfDoc = await PDFLib.PDFDocument.create(); const page = pdfDoc.addPage([canvas.width, canvas.height]); @@ -92,9 +93,9 @@ export const useExportReport = (editor: PlateEditor) => { } }; - const exportToImage = async () => { + const exportToImage = async (editor: PlateEditor) => { try { - const canvas = await getCanvas(); + const canvas = await getCanvas(editor); await downloadFile(canvas.toDataURL('image/png'), 'plate.png'); openInfoMessage(NodeTypeLabels.imageExportedSuccessfully.label); } catch (error) { @@ -102,7 +103,7 @@ export const useExportReport = (editor: PlateEditor) => { } }; - const exportToHtml = async () => { + const exportToHtml = async (editor: PlateEditor) => { try { const BaseEditorKit = await import('../editor-base-kit').then( (module) => module.BaseEditorKit @@ -157,7 +158,7 @@ export const useExportReport = (editor: PlateEditor) => { } }; - const exportToMarkdown = async () => { + const exportToMarkdown = async (editor: PlateEditor) => { try { const md = editor.getApi(MarkdownPlugin).markdown.serialize(); const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`; @@ -168,10 +169,13 @@ export const useExportReport = (editor: PlateEditor) => { } }; - return { - exportToPdf, - exportToImage, - exportToHtml, - exportToMarkdown - }; + return useMemo( + () => ({ + exportToPdf, + exportToImage, + exportToHtml, + exportToMarkdown + }), + [] + ); }; diff --git a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/ReportContainerHeaderButtons/ReportThreeDotMenu.tsx b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/ReportContainerHeaderButtons/ReportThreeDotMenu.tsx index 6f32e5905..8f4df0542 100644 --- a/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/ReportContainerHeaderButtons/ReportThreeDotMenu.tsx +++ b/apps/web/src/layouts/ChatLayout/FileContainer/FileContainerHeader/ReportContainerHeaderButtons/ReportThreeDotMenu.tsx @@ -28,6 +28,7 @@ import type { VerificationStatus } from '@buster/server-shared/share'; import { useSaveToCollectionsDropdownContent } from '@/components/features/dropdowns/SaveToCollectionsDropdown'; import { getReportEditor } from '@/components/ui/report/editorRegistry'; import { NodeTypeLabels } from '@/components/ui/report/config/labels'; +import { useExportReport } from '@/components/ui/report/hooks'; export const ReportThreeDotMenu = React.memo( ({ @@ -313,69 +314,18 @@ const useDuplicateReportSelectMenu = (): DropdownItem => { // Download as PDF const useDownloadPdfSelectMenu = ({ reportId }: { reportId: string }): DropdownItem => { const { openErrorMessage, openInfoMessage } = useBusterNotifications(); + const editor = getReportEditor(reportId); + const { exportToPdf } = useExportReport(); const onClick = useMemoizedFn(async () => { - const editor = getReportEditor(reportId); if (!editor) { openErrorMessage(NodeTypeLabels.failedToExportPdf.label); return; } - try { - const html2canvas = await import('html2canvas-pro').then((m) => m.default); - const standardWidth = '850px'; - const node = editor.api.toDOMNode(editor); - if (!node) throw new Error('Editor not found'); + await exportToPdf(editor); - const style = document.createElement('style'); - document.head.append(style); - const canvas = await html2canvas(node, { - onclone: (document: Document) => { - const editorElement = document.querySelector('[contenteditable="true"]'); - if (editorElement) { - const existingStyle = editorElement.getAttribute('style') || ''; - editorElement.setAttribute( - 'style', - `${existingStyle}; width: ${standardWidth} !important; max-width: ${standardWidth} !important; min-width: ${standardWidth} !important;` - ); - Array.from(editorElement.querySelectorAll('*')).forEach((element) => { - const elementStyle = element.getAttribute('style') || ''; - element.setAttribute( - 'style', - `${elementStyle}; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important` - ); - }); - } else { - throw new Error('Editor element not found'); - } - } - }); - style.remove(); - - const PDFLib = await import('pdf-lib'); - const pdfDoc = await PDFLib.PDFDocument.create(); - const page = pdfDoc.addPage([canvas.width, canvas.height]); - const imageEmbed = await pdfDoc.embedPng(canvas.toDataURL('PNG')); - const { height, width } = imageEmbed.scale(1); - page.drawImage(imageEmbed, { height, width, x: 0, y: 0 }); - const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true }); - - // download - const response = await fetch(pdfBase64); - const blob = await response.blob(); - const blobUrl = window.URL.createObjectURL(blob); - const link = document.createElement('a'); - link.href = blobUrl; - link.download = 'report.pdf'; - document.body.append(link); - link.click(); - link.remove(); - window.URL.revokeObjectURL(blobUrl); - - openInfoMessage(NodeTypeLabels.pdfExportedSuccessfully.label); - } catch (error) { - openErrorMessage(NodeTypeLabels.failedToExportPdf.label); - } + // }); return useMemo(