diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 7019617714..27fc2e1396 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -24,6 +24,7 @@ import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx"; import type { InfoProps } from "../widgets/dialogs/info.jsx"; import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx"; import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx"; +import type { PrintPreviewData } from "../widgets/dialogs/print_preview.jsx"; import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; import type NoteTreeWidget from "../widgets/note_tree.js"; import Component from "./component.js"; @@ -330,6 +331,7 @@ export type CommandMappings = { toggleRightPane: CommandData; printActiveNote: CommandData; exportAsPdf: CommandData; + showPrintPreview: PrintPreviewData; openNoteExternally: CommandData; openNoteCustom: CommandData; openNoteOnServer: CommandData; diff --git a/apps/client/src/layouts/layout_commons.tsx b/apps/client/src/layouts/layout_commons.tsx index 50550ea4b5..52f232eaa7 100644 --- a/apps/client/src/layouts/layout_commons.tsx +++ b/apps/client/src/layouts/layout_commons.tsx @@ -24,6 +24,7 @@ import InfoDialog from "../widgets/dialogs/info.js"; import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js"; import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx"; import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx"; +import PrintPreviewDialog from "../widgets/dialogs/print_preview.jsx"; import ToastContainer from "../widgets/Toast.jsx"; export function applyModals(rootContainer: RootContainer) { @@ -51,6 +52,7 @@ export function applyModals(rootContainer: RootContainer) { .child() .child() .child() + .child() .child() .child(); } diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index 0e335e4da0..9fb2b1ec0f 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -2305,6 +2305,11 @@ "toggle": "Toggle right panel", "custom_widget_go_to_source": "Go to source code" }, + "print_preview": { + "title": "Print preview", + "close": "Close", + "save": "Save as PDF" + }, "pdf": { "attachments_one": "{{count}} attachment", "attachments_other": "{{count}} attachments", diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx index 40ae3e49e9..26765d46d5 100644 --- a/apps/client/src/widgets/NoteDetail.tsx +++ b/apps/client/src/widgets/NoteDetail.tsx @@ -4,6 +4,7 @@ import clsx from "clsx"; import { isValidElement, VNode } from "preact"; import { useEffect, useRef, useState } from "preact/hooks"; +import appContext from "../components/app_context"; import NoteContext from "../components/note_context"; import FNote from "../entities/fnote"; import type { PrintReport } from "../print"; @@ -146,11 +147,17 @@ export default function NoteDetail() { toast.closePersistent("printing"); handlePrintReport(printReport); }; + const onPreviewResult = (_e: any, { buffer, title }: { buffer: Uint8Array; title: string }) => { + toast.closePersistent("printing"); + appContext.triggerCommand("showPrintPreview", { pdfBuffer: buffer, title }); + }; ipcRenderer.on("print-progress", onPrintProgress); ipcRenderer.on("print-done", onPrintDone); + ipcRenderer.on("export-as-pdf-preview-result", onPreviewResult); return () => { ipcRenderer.off("print-progress", onPrintProgress); ipcRenderer.off("print-done", onPrintDone); + ipcRenderer.off("export-as-pdf-preview-result", onPreviewResult); }; }, []); @@ -215,7 +222,7 @@ export default function NoteDetail() { showToast("exporting_pdf"); const { ipcRenderer } = dynamicRequire("electron"); - ipcRenderer.send("export-as-pdf", { + ipcRenderer.send("export-as-pdf-preview", { title: note.title, notePath: noteContext.notePath, pageSize: note.getAttributeValue("label", "printPageSize") ?? "Letter", diff --git a/apps/client/src/widgets/dialogs/print_preview.tsx b/apps/client/src/widgets/dialogs/print_preview.tsx new file mode 100644 index 0000000000..ca8cfdfd46 --- /dev/null +++ b/apps/client/src/widgets/dialogs/print_preview.tsx @@ -0,0 +1,68 @@ +import { useRef, useState } from "preact/hooks"; +import Modal from "../react/Modal"; +import PdfViewer from "../type_widgets/file/PdfViewer"; +import Button from "../react/Button"; +import { useTriliumEvent } from "../react/hooks"; +import { t } from "../../services/i18n"; +import { dynamicRequire } from "../../services/utils"; + +export interface PrintPreviewData { + pdfBuffer: Uint8Array; + title: string; +} + +export default function PrintPreviewDialog() { + const [shown, setShown] = useState(false); + const [pdfUrl, setPdfUrl] = useState(); + const bufferRef = useRef(); + const titleRef = useRef(""); + + useTriliumEvent("showPrintPreview", (data: PrintPreviewData) => { + bufferRef.current = data.pdfBuffer; + titleRef.current = data.title; + + const blob = new Blob([data.pdfBuffer as BlobPart], { type: "application/pdf" }); + const url = URL.createObjectURL(blob); + setPdfUrl(url); + setShown(true); + }); + + function handleClose() { + setShown(false); + if (pdfUrl) { + URL.revokeObjectURL(pdfUrl); + setPdfUrl(undefined); + } + bufferRef.current = undefined; + } + + function handleSave() { + if (!bufferRef.current) return; + + const { ipcRenderer } = dynamicRequire("electron"); + ipcRenderer.send("save-pdf", { + title: titleRef.current, + buffer: bufferRef.current + }); + handleClose(); + } + + return ( + +