diff --git a/enjoy/src/main/downloader.ts b/enjoy/src/main/downloader.ts index 584217ef..f9fbea30 100644 --- a/enjoy/src/main/downloader.ts +++ b/enjoy/src/main/downloader.ts @@ -1,4 +1,4 @@ -import { ipcMain, app } from "electron"; +import { ipcMain, app, BrowserWindow } from "electron"; import path from "path"; import fs from "fs"; import mainWin from "@main/window"; @@ -77,6 +77,44 @@ class Downloader { }); } + prinfAsPDF(content: string, savePath: string) { + let pdfWin: BrowserWindow | null = null; + + return new Promise((resolve, reject) => { + pdfWin = new BrowserWindow({ + webPreferences: { + nodeIntegration: true, + webSecurity: false, + }, + show: false, + width: 800, + height: 600, + fullscreenable: false, + minimizable: false, + }); + + pdfWin.loadURL(`data:text/html;charset=utf-8,${encodeURI(content)}`); + + pdfWin.webContents.on("did-finish-load", () => { + pdfWin.webContents + .printToPDF({ printBackground: true }) + .then((data) => { + fs.writeFile(savePath, data, (error) => { + if (error) throw error; + + resolve(savePath); + + pdfWin.close(); + pdfWin = null; + }); + }) + .catch((error) => { + reject(error); + }); + }); + }); + } + cancel(filename: string) { logger.debug("dashboard", this.dashboard()); this.tasks @@ -125,6 +163,9 @@ class Downloader { ipcMain.handle("download-dashboard", () => { return this.dashboard(); }); + ipcMain.handle("print-as-pdf", (_event, content, savePath) => { + return this.prinfAsPDF(content, savePath); + }); } } diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts index 3e685b9e..f616440a 100644 --- a/enjoy/src/preload.ts +++ b/enjoy/src/preload.ts @@ -497,6 +497,9 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", { start: (url: string, savePath?: string) => { return ipcRenderer.invoke("download-start", url, savePath); }, + printAsPdf: (content: string, savePath: string) => { + return ipcRenderer.invoke("print-as-pdf", content, savePath); + }, cancel: (filename: string) => { ipcRenderer.invoke("download-cancel", filename); }, diff --git a/enjoy/src/renderer/components/medias/index.ts b/enjoy/src/renderer/components/medias/index.ts index 3f2a514f..a99b6b46 100644 --- a/enjoy/src/renderer/components/medias/index.ts +++ b/enjoy/src/renderer/components/medias/index.ts @@ -12,3 +12,4 @@ export * from "./media-provider"; export * from "./media-tabs"; export * from "./media-loading-modal"; export * from "./add-media-button"; +export * from "./media-transcription-download"; diff --git a/enjoy/src/renderer/components/medias/media-transcription-download.tsx b/enjoy/src/renderer/components/medias/media-transcription-download.tsx new file mode 100644 index 00000000..970ab998 --- /dev/null +++ b/enjoy/src/renderer/components/medias/media-transcription-download.tsx @@ -0,0 +1,81 @@ +import { useContext } from "react"; +import { Button, toast } from "@renderer/components/ui"; +import { t } from "i18next"; +import { + MediaPlayerProviderContext, + AppSettingsProviderContext, +} from "@/renderer/context"; +import { AlignmentResult } from "echogarden/dist/api/API.d.js"; +import { convertWordIpaToNormal } from "@/utils"; +import template from "./transcription.template.html?raw"; + +export const MediaTranscriptionDownload = () => { + const { media, transcription } = useContext(MediaPlayerProviderContext); + const { EnjoyApp, learningLanguage, ipaMappings } = useContext( + AppSettingsProviderContext + ); + + function generateContent() { + const language = transcription.language || learningLanguage; + const sentences = transcription.result as AlignmentResult; + + const contents = sentences.timeline.map((sentence) => { + let words = sentence.text.split(" "); + + const ipas = sentence.timeline.map((w) => + w.timeline.map((t) => + language.startsWith("en") + ? convertWordIpaToNormal( + t.timeline.map((s) => s.text), + { mappings: ipaMappings } + ).join("") + : t.text + ) + ); + + if (words.length !== sentence.timeline.length) { + words = sentence.timeline.map((w) => w.text); + } + + return ` +
+ ${words + .map( + (word, index) => ` +
+
${word}
+
${ipas[index]}
+
` + ) + .join("")} +
`; + }); + + return template + .replace("$title", media.name) + .replace("$content", contents.join("")); + } + + async function download() { + try { + const savePath = await EnjoyApp.dialog.showSaveDialog({ + title: t("download"), + defaultPath: `${media.name}.pdf`, + }); + + if (!savePath) return; + + await EnjoyApp.download.printAsPdf(generateContent(), savePath); + + toast.success(t("downloadedSuccessfully")); + } catch (err) { + toast.error(`${t("downloadFailed")}: ${err.message}`); + } + } + + return ( + + ); +}; diff --git a/enjoy/src/renderer/components/medias/media-transcription.tsx b/enjoy/src/renderer/components/medias/media-transcription.tsx index f9e92f93..3076420a 100644 --- a/enjoy/src/renderer/components/medias/media-transcription.tsx +++ b/enjoy/src/renderer/components/medias/media-transcription.tsx @@ -26,6 +26,7 @@ import { formatDuration } from "@renderer/lib/utils"; import { MediaTranscriptionReadButton, MediaTranscriptionGenerateButton, + MediaTranscriptionDownload, TranscriptionEditButton, } from "@renderer/components"; @@ -163,6 +164,9 @@ export const MediaTranscription = (props: { display?: boolean }) => { + + + diff --git a/enjoy/src/renderer/components/medias/transcription.template.html b/enjoy/src/renderer/components/medias/transcription.template.html new file mode 100644 index 00000000..d2350f11 --- /dev/null +++ b/enjoy/src/renderer/components/medias/transcription.template.html @@ -0,0 +1,46 @@ + + + + + + Transcription + + + +
$title
+
$content
+ + diff --git a/enjoy/src/types/enjoy-app.d.ts b/enjoy/src/types/enjoy-app.d.ts index 867fe7ae..75cb9345 100644 --- a/enjoy/src/types/enjoy-app.d.ts +++ b/enjoy/src/types/enjoy-app.d.ts @@ -291,6 +291,7 @@ type EnjoyAppType = { cancelAll: () => void; dashboard: () => Promise; removeAllListeners: () => void; + printAsPdf: (content: string, savePath?: string) => Promise; }; cacheObjects: { get: (key: string) => Promise; diff --git a/enjoy/tsconfig.json b/enjoy/tsconfig.json index 138d3867..a2699394 100644 --- a/enjoy/tsconfig.json +++ b/enjoy/tsconfig.json @@ -18,6 +18,7 @@ "@main/*": ["./src/main/*"], "@commands": ["./src/commands"] }, + "types": ["vite/client"], "emitDecoratorMetadata": true, "experimentalDecorators": true, "useDefineForClassFields": false