mirror of https://github.com/buster-so/buster.git
export to pdf hook update
This commit is contained in:
parent
078ea21086
commit
1317190edf
|
@ -22,7 +22,7 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
|
||||||
const editor = useEditorRef();
|
const editor = useEditorRef();
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
|
||||||
const { exportToHtml, exportToPdf, exportToImage, exportToMarkdown } = useExportReport(editor);
|
const { exportToHtml, exportToPdf, exportToImage, exportToMarkdown } = useExportReport();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
|
<DropdownMenu open={open} onOpenChange={setOpen} modal={false}>
|
||||||
|
@ -34,16 +34,16 @@ export function ExportToolbarButton({ children, ...props }: DropdownMenuProps) {
|
||||||
|
|
||||||
<DropdownMenuContent align="start">
|
<DropdownMenuContent align="start">
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuItem onSelect={exportToHtml}>
|
<DropdownMenuItem onSelect={() => exportToHtml(editor)}>
|
||||||
{NodeTypeLabels.exportAsHtml.label}
|
{NodeTypeLabels.exportAsHtml.label}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onSelect={exportToPdf}>
|
<DropdownMenuItem onSelect={() => exportToPdf(editor)}>
|
||||||
{NodeTypeLabels.exportAsPdf.label}
|
{NodeTypeLabels.exportAsPdf.label}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onSelect={exportToImage}>
|
<DropdownMenuItem onSelect={() => exportToImage(editor)}>
|
||||||
{NodeTypeLabels.exportAsImage.label}
|
{NodeTypeLabels.exportAsImage.label}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onSelect={exportToMarkdown}>
|
<DropdownMenuItem onSelect={() => exportToMarkdown(editor)}>
|
||||||
{NodeTypeLabels.exportAsMarkdown.label}
|
{NodeTypeLabels.exportAsMarkdown.label}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './useExportReport';
|
|
@ -4,11 +4,12 @@ import type { PlateEditor } from 'platejs/react';
|
||||||
import { NodeTypeLabels } from '../config/labels';
|
import { NodeTypeLabels } from '../config/labels';
|
||||||
import { createSlateEditor, serializeHtml } from 'platejs';
|
import { createSlateEditor, serializeHtml } from 'platejs';
|
||||||
import { MarkdownPlugin } from '@platejs/markdown';
|
import { MarkdownPlugin } from '@platejs/markdown';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export const useExportReport = (editor: PlateEditor) => {
|
export const useExportReport = () => {
|
||||||
const { openErrorMessage, openInfoMessage } = useBusterNotifications();
|
const { openErrorMessage, openInfoMessage } = useBusterNotifications();
|
||||||
|
|
||||||
const getCanvas = async () => {
|
const getCanvas = async (editor: PlateEditor) => {
|
||||||
const { default: html2canvas } = await import('html2canvas-pro');
|
const { default: html2canvas } = await import('html2canvas-pro');
|
||||||
|
|
||||||
const style = document.createElement('style');
|
const style = document.createElement('style');
|
||||||
|
@ -69,9 +70,9 @@ export const useExportReport = (editor: PlateEditor) => {
|
||||||
window.URL.revokeObjectURL(blobUrl);
|
window.URL.revokeObjectURL(blobUrl);
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportToPdf = async () => {
|
const exportToPdf = async (editor: PlateEditor) => {
|
||||||
try {
|
try {
|
||||||
const canvas = await getCanvas();
|
const canvas = await getCanvas(editor);
|
||||||
const PDFLib = await import('pdf-lib');
|
const PDFLib = await import('pdf-lib');
|
||||||
const pdfDoc = await PDFLib.PDFDocument.create();
|
const pdfDoc = await PDFLib.PDFDocument.create();
|
||||||
const page = pdfDoc.addPage([canvas.width, canvas.height]);
|
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 {
|
try {
|
||||||
const canvas = await getCanvas();
|
const canvas = await getCanvas(editor);
|
||||||
await downloadFile(canvas.toDataURL('image/png'), 'plate.png');
|
await downloadFile(canvas.toDataURL('image/png'), 'plate.png');
|
||||||
openInfoMessage(NodeTypeLabels.imageExportedSuccessfully.label);
|
openInfoMessage(NodeTypeLabels.imageExportedSuccessfully.label);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -102,7 +103,7 @@ export const useExportReport = (editor: PlateEditor) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportToHtml = async () => {
|
const exportToHtml = async (editor: PlateEditor) => {
|
||||||
try {
|
try {
|
||||||
const BaseEditorKit = await import('../editor-base-kit').then(
|
const BaseEditorKit = await import('../editor-base-kit').then(
|
||||||
(module) => module.BaseEditorKit
|
(module) => module.BaseEditorKit
|
||||||
|
@ -157,7 +158,7 @@ export const useExportReport = (editor: PlateEditor) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportToMarkdown = async () => {
|
const exportToMarkdown = async (editor: PlateEditor) => {
|
||||||
try {
|
try {
|
||||||
const md = editor.getApi(MarkdownPlugin).markdown.serialize();
|
const md = editor.getApi(MarkdownPlugin).markdown.serialize();
|
||||||
const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;
|
const url = `data:text/markdown;charset=utf-8,${encodeURIComponent(md)}`;
|
||||||
|
@ -168,10 +169,13 @@ export const useExportReport = (editor: PlateEditor) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return useMemo(
|
||||||
exportToPdf,
|
() => ({
|
||||||
exportToImage,
|
exportToPdf,
|
||||||
exportToHtml,
|
exportToImage,
|
||||||
exportToMarkdown
|
exportToHtml,
|
||||||
};
|
exportToMarkdown
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,7 @@ import type { VerificationStatus } from '@buster/server-shared/share';
|
||||||
import { useSaveToCollectionsDropdownContent } from '@/components/features/dropdowns/SaveToCollectionsDropdown';
|
import { useSaveToCollectionsDropdownContent } from '@/components/features/dropdowns/SaveToCollectionsDropdown';
|
||||||
import { getReportEditor } from '@/components/ui/report/editorRegistry';
|
import { getReportEditor } from '@/components/ui/report/editorRegistry';
|
||||||
import { NodeTypeLabels } from '@/components/ui/report/config/labels';
|
import { NodeTypeLabels } from '@/components/ui/report/config/labels';
|
||||||
|
import { useExportReport } from '@/components/ui/report/hooks';
|
||||||
|
|
||||||
export const ReportThreeDotMenu = React.memo(
|
export const ReportThreeDotMenu = React.memo(
|
||||||
({
|
({
|
||||||
|
@ -313,69 +314,18 @@ const useDuplicateReportSelectMenu = (): DropdownItem => {
|
||||||
// Download as PDF
|
// Download as PDF
|
||||||
const useDownloadPdfSelectMenu = ({ reportId }: { reportId: string }): DropdownItem => {
|
const useDownloadPdfSelectMenu = ({ reportId }: { reportId: string }): DropdownItem => {
|
||||||
const { openErrorMessage, openInfoMessage } = useBusterNotifications();
|
const { openErrorMessage, openInfoMessage } = useBusterNotifications();
|
||||||
|
const editor = getReportEditor(reportId);
|
||||||
|
const { exportToPdf } = useExportReport();
|
||||||
|
|
||||||
const onClick = useMemoizedFn(async () => {
|
const onClick = useMemoizedFn(async () => {
|
||||||
const editor = getReportEditor(reportId);
|
|
||||||
if (!editor) {
|
if (!editor) {
|
||||||
openErrorMessage(NodeTypeLabels.failedToExportPdf.label);
|
openErrorMessage(NodeTypeLabels.failedToExportPdf.label);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
await exportToPdf(editor);
|
||||||
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');
|
|
||||||
|
|
||||||
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(
|
return useMemo(
|
||||||
|
|
Loading…
Reference in New Issue