Skip to content

Commit

Permalink
Show progress bar when exporting emails
Browse files Browse the repository at this point in the history
When exporting mails no progress is informed to the user, just a
blocking dialog asking to wait.

Now it show a progress bar and informs the user how many emails have
been prepared and how many emails are going to be exported.

close #5546
  • Loading branch information
mup committed Nov 1, 2023
1 parent 28845c5 commit 4e7baa6
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 16 deletions.
47 changes: 35 additions & 12 deletions src/mail/export/Exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { EntityClient } from "../../api/common/EntityClient"
import { locator } from "../../api/main/MainLocator"
import { FileController, zipDataFiles } from "../../file/FileController"
import { MailFacade } from "../../api/worker/facades/lazy/MailFacade.js"
import { OperationId } from "../../api/main/OperationProgressTracker.js"
// .msg export is handled in DesktopFileExport because it uses APIs that can't be loaded web side
export type MailExportMode = "msg" | "eml"

Expand Down Expand Up @@ -58,19 +59,41 @@ export function generateExportFileName(subject: string, sentOn: Date, mode: Mail
* @returns {Promise<void>} resolved after the fileController
* was instructed to open the new zip File containing the exported files
*/
export function exportMails(mails: Array<Mail>, mailFacade: MailFacade, entityClient: EntityClient, fileController: FileController): Promise<void> {
const downloadPromise = promiseMap(mails, (mail) =>
import("../../misc/HtmlSanitizer").then(({ htmlSanitizer }) => makeMailBundle(mail, mailFacade, entityClient, fileController, htmlSanitizer)),
)
return Promise.all([getMailExportMode(), downloadPromise]).then(([mode, bundles]) => {
promiseMap(bundles, (bundle) => generateMailFile(bundle, generateExportFileName(bundle.subject, new Date(bundle.receivedOn), mode), mode)).then(
(files) => {
const zipName = `${sortableTimestamp()}-${mode}-mail-export.zip`
const maybeZipPromise = files.length === 1 ? Promise.resolve(files[0]) : zipDataFiles(files, zipName)
maybeZipPromise.then((outputFile) => fileController.saveDataFile(outputFile))
},
)
export async function exportMails(
mails: Array<Mail>,
mailFacade: MailFacade,
entityClient: EntityClient,
fileController: FileController,
operationId?: OperationId,
): Promise<void> {
// Considering that the effort for generating the bundle is higher
// than generating the files, we need to consider it twice, so the
// total effort would be (mailsToBundle * 2) + filesToGenerate
const totalMails = mails.length * 3
let doneMails = 0

const updateProgress = operationId !== undefined ? () => locator.operationProgressTracker.onProgress(operationId, (++doneMails / totalMails) * 100) : noOp

const downloadPromise = promiseMap(mails, async (mail) => {
const { htmlSanitizer } = await import("../../misc/HtmlSanitizer")
const bundle = await makeMailBundle(mail, mailFacade, entityClient, fileController, htmlSanitizer)
updateProgress()
updateProgress()
return bundle
})

const [mode, bundles] = await Promise.all([getMailExportMode(), downloadPromise])
const dataFiles: DataFile[] = []
for (const bundle of bundles) {
const mailFile = await generateMailFile(bundle, generateExportFileName(bundle.subject, new Date(bundle.receivedOn), mode), mode)
dataFiles.push(mailFile)
updateProgress()
}

const zipName = `${sortableTimestamp()}-${mode}-mail-export.zip`
const outputFile = await (dataFiles.length === 1 ? dataFiles[0] : zipDataFiles(dataFiles, zipName))

return fileController.saveDataFile(outputFile)
}

export function mailToEmlFile(mail: MailBundle, fileName: string): DataFile {
Expand Down
13 changes: 12 additions & 1 deletion src/mail/view/MailViewerToolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { isApp } from "../../api/common/Env.js"
import { locator } from "../../api/main/MainLocator.js"
import { showProgressDialog } from "../../gui/dialogs/ProgressDialog.js"
import { exportMails } from "../export/Exporter.js"
import { lang } from "../../misc/LanguageViewModel.js"

/*
note that mailViewerViewModel has a mailModel, so you do not need to pass both if you pass a mailViewerViewModel
Expand Down Expand Up @@ -128,9 +129,19 @@ export class MailViewerActions implements Component<MailViewerToolbarAttrs> {

private renderExportButton(attrs: MailViewerToolbarAttrs) {
if (!isApp() && attrs.mailModel.isExportingMailsAllowed()) {
const operation = locator.operationProgressTracker.startNewOperation()
return m(IconButton, {
title: "export_action",
click: () => showProgressDialog("pleaseWait_msg", exportMails(attrs.mails, locator.mailFacade, locator.entityClient, locator.fileController)),
click: () =>
showProgressDialog(
() =>
lang.get("mailExportProgress_msg", {
"{current}": Math.round((operation.progress() / 100) * attrs.mails.length).toFixed(0),
"{total}": attrs.mails.length,
}),
exportMails(attrs.mails, locator.mailFacade, locator.entityClient, locator.fileController, operation.id).finally(operation.done),
operation.progress,
),
icon: Icons.Export,
})
}
Expand Down
2 changes: 1 addition & 1 deletion src/misc/TranslationKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,6 @@ export type TranslationKeyType =
| "yourMessage_label"
| "you_label"
| "emptyString_msg"
| "changePaidPlan_msg"
| "paidEmailDomainLegacy_msg"
| "paidEmailDomainSignup_msg"
| "mailExportProgress_msg"
3 changes: 2 additions & 1 deletion src/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,7 @@ export default {
"yourMessage_label": "Deine Nachricht",
"you_label": "Du",
"paidEmailDomainSignup_msg": "Für die Nutzung der tuta.com-Domain ist ein kostenpflichtiges Abonnement erforderlich.",
"paidEmailDomainLegacy_msg": "Um die tuta.com-Domain zu nutzen, ist eines der neuen Abonnements erforderlich."
"paidEmailDomainLegacy_msg": "Um die tuta.com-Domain zu nutzen, ist eines der neuen Abonnements erforderlich.",
"mailExportProgress_msg": "Bereite {current} von {total} E-Mails für den Export vor"
}
}
3 changes: 2 additions & 1 deletion src/translations/de_sie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1601,6 +1601,7 @@ export default {
"yourMessage_label": "Ihre Nachricht",
"you_label": "Sie",
"paidEmailDomainSignup_msg": "Für die Nutzung der tuta.com-Domain ist ein kostenpflichtiges Abonnement erforderlich.",
"paidEmailDomainLegacy_msg": "Um die tuta.com-Domain zu nutzen, ist eines der neuen Abonnements erforderlich."
"paidEmailDomainLegacy_msg": "Um die tuta.com-Domain zu nutzen, ist eines der neuen Abonnements erforderlich.",
"mailExportProgress_msg": "Bereite {current} von {total} E-Mails für den Export vor"
}
}
1 change: 1 addition & 0 deletions src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1598,5 +1598,6 @@ export default {
"you_label": "You",
"paidEmailDomainSignup_msg": "In order to register an address with the tuta.com domain, a subscription is needed.",
"paidEmailDomainLegacy_msg": "In order to use the tuta.com domain, one of the new subscriptions is needed.",
"mailExportProgress_msg": "Prepared {current} of {total} emails for export"
}
}

0 comments on commit 4e7baa6

Please sign in to comment.