proton-bridge/internal/frontend/qml/ImportExportUI/DialogExport.qml

460 lines
16 KiB
QML

// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
// Export dialog
import QtQuick 2.8
import QtQuick.Controls 2.2
import ProtonUI 1.0
import ImportExportUI 1.0
// TODO
// - make ErrorDialog module
// - map decision to error code : ask (default), skip ()
// - what happens when import fails ? heuristic to find mail where to start from
Dialog {
id: root
enum Page {
LoadingStructure = 0, Options, Progress
}
title : set_title()
property string account
property string address
property alias finish: finish
property string msgClearUnfished: qsTr ("Remove already exported files.")
isDialogBusy : true // currentIndex == 0 || currentIndex == 3
signal cancel()
signal okay()
Rectangle { // 0
id: dialogLoading
width: root.width
height: root.height
color: Style.transparent
Text {
anchors.centerIn : dialogLoading
font.pointSize: Style.dialog.titleSize * Style.pt
color: Style.dialog.text
horizontalAlignment: Text.AlignHCenter
text: qsTr("Loading folders and labels for", "todo") +"\n" + address
}
}
Rectangle { // 1
id: dialogInput
width: root.width
height: root.height
color: Style.transparent
Row {
id: inputRow
anchors {
topMargin : root.titleHeight
top : parent.top
horizontalCenter : parent.horizontalCenter
}
spacing: 3*Style.main.leftMargin
property real columnWidth : (root.width - Style.main.leftMargin - inputRow.spacing - Style.main.rightMargin) / 2
property real columnHeight : root.height - root.titleHeight - Style.main.leftMargin
ExportStructure {
id: sourceFoldersInput
width : inputRow.columnWidth
height : inputRow.columnHeight
title : qsTr("From: %1", "todo").arg(address)
}
Column {
spacing: (inputRow.columnHeight - dateRangeInput.height - outputFormatInput.height - outputPathInput.height - buttonRow.height - infotipEncrypted.height) / 4
DateRange{
id: dateRangeInput
}
OutputFormat {
id: outputFormatInput
}
Row {
spacing: Style.dialog.spacing
CheckBoxLabel {
id: exportEncrypted
text: qsTr("Export emails that cannot be decrypted as ciphertext")
anchors {
bottom: parent.bottom
bottomMargin: Style.dialog.fontSize/1.8
}
}
InfoToolTip {
id: infotipEncrypted
anchors {
verticalCenter: exportEncrypted.verticalCenter
}
info: qsTr("Checking this option will export all emails that cannot be decrypted in ciphertext. If this option is not checked, these emails will not be exported", "todo")
}
}
FileAndFolderSelect {
id: outputPathInput
title: qsTr("Select location of export:", "todo")
width : inputRow.columnWidth // stretch folder input
}
Row {
id: buttonRow
anchors.right : parent.right
spacing : Style.dialog.rightMargin
ButtonRounded {
id:buttonCancel
fa_icon: Style.fa.times
text: qsTr("Cancel")
color_main: Style.main.textBlue
onClicked : root.cancel()
}
ButtonRounded {
id: buttonNext
fa_icon: Style.fa.check
text: qsTr("Export","todo")
enabled: transferRules != 0
color_main: Style.dialog.background
color_minor: enabled ? Style.dialog.textBlue : Style.main.textDisabled
isOpaque: true
onClicked : root.okay()
}
}
}
}
}
Rectangle { // 2
id: progressStatus
width: root.width
height: root.height
color: "transparent"
Row {
anchors {
bottom: progressbarExport.top
bottomMargin: Style.dialog.heightSeparator
left: progressbarExport.left
}
spacing: Style.main.rightMargin
AccessibleText {
id: statusLabel
text : qsTr("Status:")
font.pointSize: Style.main.iconSize * Style.pt
color : Style.main.text
}
AccessibleText {
anchors.baseline: statusLabel.baseline
text : {
if (progressbarExport.isFinished) return qsTr("finished")
if (go.progressDescription == "") return qsTr("exporting")
return go.progressDescription
}
elide: Text.ElideMiddle
width: progressbarExport.width - parent.spacing - statusLabel.width
font.pointSize: Style.dialog.textSize * Style.pt
color : Style.main.textDisabled
}
}
ProgressBar {
id: progressbarExport
implicitWidth : 2*progressStatus.width/3
implicitHeight : Style.exporting.rowHeight
value: go.progress
property int current: go.total * go.progress
property bool isFinished: finishedPartBar.width == progressbarExport.width
anchors {
centerIn: parent
}
background: Rectangle {
radius : Style.exporting.boxRadius
color : Style.exporting.progressBackground
}
contentItem: Item {
Rectangle {
id: finishedPartBar
width : parent.width * progressbarExport.visualPosition
height : parent.height
radius : Style.exporting.boxRadius
gradient : Gradient {
GradientStop { position: 0.00; color: Qt.lighter(Style.exporting.progressStatus,1.1) }
GradientStop { position: 0.66; color: Style.exporting.progressStatus }
GradientStop { position: 1.00; color: Qt.darker(Style.exporting.progressStatus,1.1) }
}
Behavior on width {
NumberAnimation { duration:800; easing.type: Easing.InOutQuad }
}
}
Text {
anchors.centerIn: parent
text: {
if (progressbarExport.isFinished) {
if (go.progressDescription=="") return qsTr("Export finished","todo")
else return qsTr("Export failed: %1").arg(go.progressDescription)
}
if (
go.progressDescription == gui.enums.progressInit ||
(go.progress==0 && go.description=="")
) {
if (go.total>1) return qsTr("Estimating the total number of messages (%1)","todo").arg(go.total)
else return qsTr("Estimating the total number of messages","todo")
}
var msg = qsTr("Exporting message %1 of %2 (%3%)","todo")
if (pauseButton.paused) msg = qsTr("Exporting paused at message %1 of %2 (%3%)","todo")
return msg.arg(progressbarExport.current).arg(go.total).arg(Math.floor(go.progress*100))
}
color: Style.main.background
font {
pointSize: Style.dialog.fontSize * Style.pt
}
}
}
}
Row {
anchors {
top: progressbarExport.bottom
topMargin : Style.dialog.heightSeparator
horizontalCenter: parent.horizontalCenter
}
spacing: Style.dialog.rightMargin
ButtonRounded {
id: pauseButton
property bool paused : false
fa_icon : paused ? Style.fa.play : Style.fa.pause
text : paused ? qsTr("Resume") : qsTr("Pause")
color_main : Style.dialog.textBlue
onClicked : {
if (paused) {
if (winMain.updateState == gui.enums.statusNoInternet) {
go.notifyError(gui.enums.errNoInternet)
return
}
go.resumeProcess()
} else {
go.pauseProcess()
}
paused = !paused
pauseButton.focus=false
}
visible : !progressbarExport.isFinished
}
ButtonRounded {
fa_icon : Style.fa.times
text : qsTr("Cancel")
color_main : Style.dialog.textBlue
visible : !progressbarExport.isFinished
onClicked : root.ask_cancel_progress()
}
ButtonRounded {
id: finish
fa_icon : Style.fa.check
text : qsTr("Okay","todo")
color_main : Style.dialog.background
color_minor : Style.dialog.textBlue
isOpaque : true
visible : progressbarExport.isFinished
onClicked : root.okay()
}
}
ClickIconText {
id: buttonHelp
anchors {
right : parent.right
bottom : parent.bottom
rightMargin : Style.main.rightMargin
bottomMargin : Style.main.rightMargin
}
textColor : Style.main.textDisabled
iconText : Style.fa.question_circle
text : qsTr("Help", "directs the user to the online user guide")
textBold : true
onClicked : Qt.openUrlExternally("https://protonmail.com/support/categories/import-export/")
}
}
PopupMessage {
id: errorPopup
width: root.width
height: root.height
}
function check_inputs() {
if (currentIndex == 1) {
// at least one email to export
if (transferRules.rowCount() == 0){
errorPopup.show(qsTr("No emails found to export. Please try another address.", "todo"))
return false
}
// at least one source selected
/*
if (!transferRules.atLeastOneSelected) {
errorPopup.show(qsTr("Please select at least one item to export.", "todo"))
return false
}
*/
// check path
var folderCheck = go.checkPathStatus(outputPathInput.path)
switch (folderCheck) {
case gui.enums.pathEmptyPath:
errorPopup.show(qsTr("Missing export path. Please select an output folder."))
break;
case gui.enums.pathWrongPath:
errorPopup.show(qsTr("Folder '%1' not found. Please select an output folder.").arg(outputPathInput.path))
break;
case gui.enums.pathOK | gui.enums.pathNotADir:
errorPopup.show(qsTr("File '%1' is not a folder. Please select an output folder.").arg(outputPathInput.path))
break;
case gui.enums.pathWrongPermissions:
errorPopup.show(qsTr("Cannot access folder '%1'. Please check folder permissions.").arg(outputPathInput.path))
break;
}
if (
(folderCheck&gui.enums.pathOK)==0 ||
(folderCheck&gui.enums.pathNotADir)==gui.enums.pathNotADir
) return false
if (winMain.updateState == gui.enums.statusNoInternet) {
errorPopup.show(qsTr("Please check your internet connection."))
return false
}
}
return true
}
function set_title() {
switch(root.currentIndex){
case 1 : return qsTr("Select what you'd like to export:")
default: return ""
}
}
function clear_status() {
go.progress=0.0
go.total=0.0
go.progressDescription=gui.enums.progressInit
}
function ask_cancel_progress(){
errorPopup.buttonYes.visible = true
errorPopup.buttonNo.visible = true
errorPopup.buttonOkay.visible = false
errorPopup.show ("Are you sure you want to cancel this export?")
}
onCancel : {
switch (root.currentIndex) {
case 0 :
case 1 : root.hide(); break;
case 2 : // progress bar
go.cancelProcess();
// no break
default:
root.clear_status()
root.currentIndex=1
}
}
onOkay : {
var isOK = check_inputs()
if (!isOK) return
timer.interval= currentIndex==1 ? 1 : 300
switch (root.currentIndex) {
case 2: // progress
root.clear_status()
root.hide()
break
case 0: // loading structure
dateRangeInput.getRange()
//no break
default:
incrementCurrentIndex()
timer.start()
}
}
onShow: {
if (winMain.updateState==gui.enums.statusNoInternet) {
if (winMain.updateState==gui.enums.statusNoInternet) {
go.notifyError(gui.enums.errNoInternet)
root.hide()
return
}
}
root.clear_status()
root.currentIndex=0
timer.interval = 300
timer.start()
dateRangeInput.allDates = true
}
Connections {
target: timer
onTriggered : {
switch (currentIndex) {
case 0:
go.loadStructureForExport(root.account, root.address)
sourceFoldersInput.hasItems = (transferRules.rowCount() > 0)
break
case 2:
dateRangeInput.applyRange()
go.startExport(
outputPathInput.path,
root.address,
outputFormatInput.checkedText,
exportEncrypted.checked
)
break
}
}
}
Connections {
target: errorPopup
onClickedOkay : errorPopup.hide()
onClickedYes : {
root.cancel()
errorPopup.hide()
}
onClickedNo : {
errorPopup.hide()
}
}
}