Skip to content

Commit

Permalink
UI for Export mailbox in settings
Browse files Browse the repository at this point in the history
close: #7952

Co-authored-by: ivk <ivk@tutao.de>
  • Loading branch information
2 people authored and wrdhub committed Jan 23, 2025
1 parent 3277049 commit 154392e
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 0 deletions.
56 changes: 56 additions & 0 deletions src/mail-app/mail/model/MailExportController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { MailboxDetail } from "../../../common/mailFunctionality/MailboxModel"
import Stream from "mithril/stream"
import stream from "mithril/stream"

export type MailExportState =
| { type: "idle"; lastExport: Date | null }
| { type: "exporting"; mailbox: MailboxDetail; progress: number; exportedMails: number }
| {
type: "finished"
mailbox: MailboxDetail
exportedMails: number
}

/**
* Controller to keep the state of mail exporting with the details.
*/
export class MailExportController {
private _state: Stream<MailExportState> = stream({ type: "idle", lastExport: new Date() })
private progressTimeout: number | null = null

get state(): Stream<MailExportState> {
return this._state
}

/**
* Start exporting the mailbox for the user
* @param mailbox
*/
startExport(mailbox: MailboxDetail) {
this._state({ type: "exporting", mailbox: mailbox, progress: 0, exportedMails: 0 })
this.progressTimeout = window.setInterval(() => {
const oldState = this._state()
if (oldState.type === "exporting") {
if (oldState.progress >= 0.9) {
this._state({ type: "finished", mailbox: mailbox, exportedMails: oldState.exportedMails })
} else {
this._state({
...oldState,
progress: oldState.progress + 0.1,
exportedMails: oldState.exportedMails + 100,
})
}
}
}, 1000)
}

/**
* When the user wants to cancel the exporting
*/
cancelExport() {
if (this.progressTimeout) {
clearInterval(this.progressTimeout)
this.progressTimeout = null
}
}
}
5 changes: 5 additions & 0 deletions src/mail-app/mailLocator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ import type { CalendarContactPreviewViewModel } from "../calendar-app/calendar/g
import { KeyLoaderFacade } from "../common/api/worker/facades/KeyLoaderFacade.js"
import { ContactSuggestion } from "../common/native/common/generatedipc/ContactSuggestion"
import { MailImporter } from "./mail/import/MailImporter.js"
import { MailExportController } from "./mail/model/MailExportController"

assertMainOrNode()

Expand Down Expand Up @@ -1170,6 +1171,10 @@ class MailLocator {
return this.loginFacade.migrateKdfType(KdfType.Bcrypt, passphrase, currentUser)
}

readonly mailExportController: () => Promise<MailExportController> = lazyMemoized(async () => {
return new MailExportController()
})

/**
* Factory method for credentials provider that will return an instance injected with the implementations appropriate for the platform.
*/
Expand Down
124 changes: 124 additions & 0 deletions src/mail-app/settings/MailExportSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import m, { Children, Component, Vnode } from "mithril"
import { lang } from "../../common/misc/LanguageViewModel"
import { theme } from "../../common/gui/theme"
import { px } from "../../common/gui/size"
import { ProgressBar } from "../../common/gui/base/ProgressBar"
import { DropDownSelector, type DropDownSelectorAttrs } from "../../common/gui/base/DropDownSelector"
import { MailboxDetail } from "../../common/mailFunctionality/MailboxModel"
import { getMailboxName } from "../../common/mailFunctionality/SharedMailUtils"
import { mailLocator } from "../mailLocator"
import { first } from "@tutao/tutanota-utils"
import { LoginController } from "../../common/api/main/LoginController"
import { Button, ButtonType } from "../../common/gui/base/Button"
import { MailExportController } from "../mail/model/MailExportController"
import { formatDate } from "../../common/misc/Formatter"
import Stream from "mithril/stream"

interface MailExportSettingsAttrs {
mailboxDetails: MailboxDetail[]
logins: LoginController
mailExportController: MailExportController
}

export class MailExportSettings implements Component<MailExportSettingsAttrs> {
private selectedMailbox: MailboxDetail | null = null
private controllerSubscription: Stream<void> | null = null

oncreate(vnode: Vnode<MailExportSettingsAttrs>) {
this.controllerSubscription = vnode.attrs.mailExportController.state.map(m.redraw)
}

onremove() {
if (this.controllerSubscription) {
this.controllerSubscription.end()
this.controllerSubscription = null
}
}

view(vnode: Vnode<MailExportSettingsAttrs>): Children {
const { mailboxDetails } = vnode.attrs
this.selectedMailbox = this.selectedMailbox ?? first(mailboxDetails)
const state = vnode.attrs.mailExportController.state()
return [
m(DropDownSelector, {
label: "mailboxToExport_label",
items: mailboxDetails.map((mailboxDetail) => {
return { name: getMailboxName(mailLocator.logins, mailboxDetail), value: mailboxDetail }
}),
selectedValue: this.selectedMailbox,
selectionChangedHandler: (selectedMailbox) => {
this.selectedMailbox = selectedMailbox
},
dropdownWidth: 300,
disabled: state.type === "exporting",
} satisfies DropDownSelectorAttrs<MailboxDetail>),
state.type === "exporting"
? [
m(".flex-space-between.items-center.mt.mb-s", [
m(".flex-grow.mr", [
m(
"small.noselect",
lang.get("exportingEmails_label", {
"{count}": state.exportedMails,
}),
),
m(
".rel.full-width.mt-s",
{
style: {
"background-color": theme.content_border,
height: px(2),
},
},
m(ProgressBar, { progress: state.progress }),
),
]),
m(Button, {
label: "cancel_action",
type: ButtonType.Secondary,
click: () => {
vnode.attrs.mailExportController.cancelExport()
},
}),
]),
]
: state.type === "idle"
? [
m(".flex-space-between.items-center.mt.mb-s", [
m(
"small.noselect",
state.lastExport
? lang.get("lastExportTime_Label", {
"{date}": formatDate(state.lastExport),
})
: null,
),
m(Button, {
label: "export_action",
click: () => {
if (this.selectedMailbox) {
vnode.attrs.mailExportController.startExport(this.selectedMailbox)
}
},
type: ButtonType.Secondary,
}),
]),
]
: [
m(".flex-space-between.items-center.mt.mb-s", [
m(
"small.noselect",
lang.get("mailsExported_label", {
"{numbers}": state.exportedMails,
}),
),
m(Button, {
label: "open_action",
click: () => {},
type: ButtonType.Secondary,
}),
]),
],
]
}
}
15 changes: 15 additions & 0 deletions src/mail-app/settings/MailSettingsViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import { UpdatableSettingsViewer } from "../../common/settings/Interfaces.js"
import { mailLocator } from "../mailLocator.js"
import { getDefaultSenderFromUser, getFolderName } from "../mail/model/MailUtils.js"
import { elementIdPart } from "../../common/api/common/utils/EntityUtils.js"
import { MailExportSettings } from "./MailExportSettings"
import { MailExportController } from "../mail/model/MailExportController"

assertMainOrNode()

Expand All @@ -70,6 +72,7 @@ export class MailSettingsViewer implements UpdatableSettingsViewer {
private customerInfo: CustomerInfo | null
private mailAddressTableModel: MailAddressTableModel | null = null
private mailAddressTableExpanded: boolean
private mailExportController: MailExportController | null = null

private offlineStorageSettings = new OfflineStorageSettingsModel(mailLocator.logins.getUserController(), deviceConfig)

Expand All @@ -87,6 +90,10 @@ export class MailSettingsViewer implements UpdatableSettingsViewer {
this._inboxRulesTableLines = stream<Array<TableLineAttrs>>([])
this._outOfOfficeStatus = stream(lang.get("deactivated_label"))
this._indexStateWatch = null
mailLocator.mailExportController().then((controller) => {
this.mailExportController = controller
m.redraw()
})
// normally we would maybe like to get it as an argument but these viewers are created in an odd way
mailLocator.mailAddressTableModelForOwnMailbox().then((model) => {
this.mailAddressTableModel = model
Expand Down Expand Up @@ -382,6 +389,14 @@ export class MailSettingsViewer implements UpdatableSettingsViewer {
}),
),
],
m(".h4.mt-l", lang.get("exportMailbox_label")),
this.mailExportController
? m(MailExportSettings, {
mailboxDetails: mailLocator.mailboxModel.mailboxDetails(),
logins: mailLocator.logins,
mailExportController: this.mailExportController,
})
: null,
],
),
]
Expand Down

0 comments on commit 154392e

Please sign in to comment.