From f0fdc8d70cd6d4428905dc55fa37524bee02460b Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Tue, 13 May 2025 23:16:18 +0545 Subject: [PATCH] Implements: export presentation as pdf using puppeteer --- app/ipc/export_handlers.ts | 62 +++++++++++-------- app/preloads/index.ts | 3 +- app/utils/dialog.ts | 26 ++++++++ .../presentation/components/Header.tsx | 26 ++++---- 4 files changed, 75 insertions(+), 42 deletions(-) create mode 100644 app/utils/dialog.ts diff --git a/app/ipc/export_handlers.ts b/app/ipc/export_handlers.ts index fc015baa..9866921e 100644 --- a/app/ipc/export_handlers.ts +++ b/app/ipc/export_handlers.ts @@ -1,34 +1,44 @@ -import { ipcMain, shell, dialog } from "electron"; -import { downloadsDir } from "../utils/constants"; +import { ipcMain, } from "electron"; +import { baseDir, downloadsDir, isDev } from "../utils/constants"; import fs from "fs"; import path from "path"; +import puppeteer from "puppeteer"; +import { showFileDownloadedDialog } from "../utils/dialog"; export function setupExportHandlers() { ipcMain.handle("file-downloaded", async (_, filePath: string): Promise => { - try { - const fileName = path.basename(filePath); - const destinationPath = path.join(downloadsDir, fileName); + const fileName = path.basename(filePath); + const destinationPath = path.join(downloadsDir, fileName); - await fs.promises.rename(filePath, destinationPath); - - const { response } = await dialog.showMessageBox({ - type: 'question', - buttons: ['Open File', 'Open Folder', 'Cancel'], - defaultId: 0, - title: 'File Downloaded', - message: 'What would you like to do?' - }); - - if (response === 0) { - await shell.openPath(destinationPath); - } else if (response === 1) { - await shell.openPath(downloadsDir); - } - - return { success: true }; - } catch (error: any) { - console.error('Error handling downloaded file:', error); - return { success: false }; - } + await fs.promises.rename(filePath, destinationPath); + const success = await showFileDownloadedDialog(destinationPath); + return { success }; }); + + ipcMain.handle("export-as-pdf", async (_, id: string, title: string) => { + const ppt_url = `${process.env.NEXT_PUBLIC_FAST_API}/presentation/${id}`; + + const browser = await puppeteer.launch({ + headless: true, + executablePath: isDev ? undefined : path.join(baseDir, "dependencies/chrome-headless-shell/linux_build/chrome-headless-shell"), + args: ['--no-sandbox', '--disable-setuid-sandbox'] + }); + + const page = await browser.newPage(); + await page.setViewport({ width: 1280, height: 720 }); + + await page.goto(ppt_url, { waitUntil: "networkidle0", timeout: 30000 }); + + const pdfBuffer = await page.pdf({ + height: 720, + }); + + const destinationPath = path.join(downloadsDir, `${title}.pdf`); + await fs.promises.writeFile(destinationPath, pdfBuffer); + + const success = await showFileDownloadedDialog(destinationPath); + + return { success }; + }) + } \ No newline at end of file diff --git a/app/preloads/index.ts b/app/preloads/index.ts index 91ad8fe4..fa589931 100644 --- a/app/preloads/index.ts +++ b/app/preloads/index.ts @@ -10,10 +10,11 @@ contextBridge.exposeInMainWorld('env', { contextBridge.exposeInMainWorld('electron', { fileDownloaded: (filePath: string) => ipcRenderer.invoke("file-downloaded", filePath), + exportAsPDF: (id: string, title: string) => ipcRenderer.invoke("export-as-pdf", id, title), getUserConfig: () => ipcRenderer.invoke("get-user-config"), setUserConfig: (userConfig: UserConfig) => ipcRenderer.invoke("set-user-config", userConfig), readFile: (filePath: string) => ipcRenderer.invoke("read-file", filePath), - getSlideMetadata: (url: string, theme: string, customColors?: any, tempDirectory?: string) => + getSlideMetadata: (url: string, theme: string, customColors?: any, tempDirectory?: string) => ipcRenderer.invoke("get-slide-metadata", url, theme, customColors, tempDirectory), getFooter: (userId: string) => ipcRenderer.invoke("get-footer", userId), setFooter: (userId: string, properties: any) => ipcRenderer.invoke("set-footer", userId, properties), diff --git a/app/utils/dialog.ts b/app/utils/dialog.ts new file mode 100644 index 00000000..fec26bb3 --- /dev/null +++ b/app/utils/dialog.ts @@ -0,0 +1,26 @@ +import { shell } from "electron"; +import { dialog } from "electron"; +import path from "path"; + +export async function showFileDownloadedDialog(filePath: string): Promise { + try { + const { response } = await dialog.showMessageBox({ + type: 'question', + buttons: ['Open File', 'Open Folder', 'Cancel'], + defaultId: 0, + title: 'File Downloaded', + message: 'What would you like to do?' + }); + + if (response === 0) { + await shell.openPath(filePath); + } else if (response === 1) { + await shell.openPath(path.dirname(filePath)); + } + + return true; + } catch (error: any) { + console.error('Error handling downloaded file:', error); + return false; + } +} \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index 2b66f697..34471bfe 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -215,26 +215,22 @@ const Header = ({ const handleExportPdf = async () => { if (isStreaming) return; + setOpen(false); try { - setOpen(false); - setShowLoader(true); - const apiBody = await metaData(); + toast({ + title: "Exporting presentation...", + description: "Please wait while we export your presentation.", + variant: "default", + }); - const data = await PresentationGenerationApi.exportAsPDF(apiBody); - - if (data.path) { - setShowLoader(false); - // @ts-ignore - const ipcResponse = await window.electron.fileDownloaded(data.path); - - if (!ipcResponse.success) { - throw new Error("Failed to download file"); - } + // @ts-ignore + const ipcResponse = await window.electron.exportAsPDF(presentation_id, presentationData!.presentation!.title); + if (!ipcResponse.success) { + throw new Error("Failed to export as PDF"); } - setShowLoader(false); + } catch (err) { console.error(err); - setShowLoader(false); toast({ title: "Having trouble exporting!", description: