From b840acfe0ee0e7b16223f4b22a30dc20cf46a2e7 Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sat, 1 Feb 2025 11:57:39 +0700 Subject: [PATCH 1/3] Implement remembrance into the import webdav dialog --- src/core/webdavconnection.cpp | 64 ++++++++++++++++- src/core/webdavconnection.h | 2 + src/qml/QFieldLocalDataPickerScreen.qml | 93 ++++++++++++++++++++----- 3 files changed, 138 insertions(+), 21 deletions(-) diff --git a/src/core/webdavconnection.cpp b/src/core/webdavconnection.cpp index 9310b15d39..4c95c148b8 100644 --- a/src/core/webdavconnection.cpp +++ b/src/core/webdavconnection.cpp @@ -224,11 +224,10 @@ void WebdavConnection::processDirParserFinished() } } } + mAvailablePaths.sort(); mIsFetchingAvailablePaths = false; emit isFetchingAvailablePathsChanged(); - - mAvailablePaths.sort(); emit availablePathsChanged(); } else if ( mIsImportingPath || mIsDownloadingPath ) @@ -419,6 +418,12 @@ void WebdavConnection::getWebdavItems() jsonFile.write( jsonDocument.toJson() ); jsonFile.close(); + QSettings settings; + settings.beginGroup( QStringLiteral( "/qfield/webdavImports/%1/users/%2" ).arg( QUrl::toPercentEncoding( mUrl ), QUrl::toPercentEncoding( mUsername ) ) ); + settings.setValue( QStringLiteral( "lastImportPath" ), mProcessRemotePath ); + settings.setValue( QStringLiteral( "lastImportTime" ), QDateTime::currentDateTime() ); + settings.endGroup(); + mIsImportingPath = false; emit isImportingPathChanged(); emit importSuccessful( mProcessLocalPath ); @@ -431,6 +436,61 @@ void WebdavConnection::getWebdavItems() } } +QVariantMap WebdavConnection::importHistory() +{ + QVariantMap history; + + QSettings settings; + settings.beginGroup( QStringLiteral( "/qfield/webdavImports" ) ); + const QStringList urls = settings.childGroups(); + settings.endGroup(); + + QDateTime lastUrlImportTime( QDate( 1900, 0, 0 ), QTime( 0, 0, 0, 0 ) ); + QString lastUrl; + QVariantMap urlsDetails; + for ( const QString &url : urls ) + { + const QString decodedUrl = QUrl::fromPercentEncoding( url.toLatin1() ); + settings.beginGroup( QStringLiteral( "/qfield/webdavImports/%1/users" ).arg( url ) ); + const QStringList users = settings.childGroups(); + settings.endGroup(); + + QDateTime lastUserImportTime( QDate( 1900, 0, 0 ), QTime( 0, 0, 0, 0 ) ); + QString lastUser; + QVariantMap usersDetails; + for ( const QString &user : users ) + { + const QString decodedUser = QUrl::fromPercentEncoding( user.toLatin1() ); + settings.beginGroup( QStringLiteral( "/qfield/webdavImports/%1/users/%2" ).arg( url, user ) ); + + QVariantMap details; + details["lastImportPath"] = settings.value( "lastImportPath" ).toString(); + usersDetails[decodedUser] = details; + + if ( lastUserImportTime < settings.value( "lastImportTime" ).toDateTime() ) + { + lastUserImportTime = settings.value( "lastImportTime" ).toDateTime(); + lastUser = decodedUser; + } + if ( lastUrlImportTime < settings.value( "lastImportTime" ).toDateTime() ) + { + lastUrlImportTime = settings.value( "lastImportTime" ).toDateTime(); + lastUrl = decodedUrl; + } + } + + QVariantMap details; + details["users"] = usersDetails; + details["lastUser"] = lastUser; + urlsDetails[decodedUrl] = details; + } + + history["urls"] = urlsDetails; + history["lastUrl"] = lastUrl; + + return history; +} + void WebdavConnection::putLocalItems() { if ( !mWebdavMkDirs.isEmpty() ) diff --git a/src/core/webdavconnection.h b/src/core/webdavconnection.h index 601aa10aae..b4ba899088 100644 --- a/src/core/webdavconnection.h +++ b/src/core/webdavconnection.h @@ -94,6 +94,8 @@ class WebdavConnection : public QObject Q_INVOKABLE static bool hasWebdavConfiguration( const QString &path ); + Q_INVOKABLE static QVariantMap importHistory(); + signals: void urlChanged(); void usernameChanged(); diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 2932f08ec4..e6f48434e3 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -901,6 +901,18 @@ Page { onImportSuccessful: path => { table.model.currentPath = path; } + + onIsFetchingAvailablePathsChanged: { + if (!isFetchingAvailablePaths && importWebdavDialog.visible) { + importWebdavPathInput.model = [""].concat(availablePaths); + if (importWebdavDialog.importHistory["urls"][url] !== undefined && importWebdavDialog.importHistory["urls"][url]["users"][username] !== undefined) { + const index = importWebdavPathInput.find(importWebdavDialog.importHistory["urls"][url]["users"][username]["lastImportPath"]); + if (index >= 0) { + importWebdavPathInput.currentIndex = index; + } + } + } + } } } } @@ -1009,10 +1021,21 @@ Page { focus: visible parent: mainWindow.contentItem + property var importHistory: undefined + onAboutToShow: { if (webdavConnectionLoader.item) { - webdavConnectionLoader.item.url = importWebdavUrlInput.text; - webdavConnectionLoader.item.username = importWebdavUserInput.text; + importHistory = webdavConnectionLoader.item.importHistory(); + importWebdavUrlInput.model = [""].concat(Object.keys(importHistory["urls"])); + if (importHistory["lastUrl"] !== "") { + importWebdavUrlInput.editText = importHistory["lastUrl"]; + importWebdavUserInput.model = [""].concat(Object.keys(importHistory["urls"][importHistory["lastUrl"]]["users"])); + importWebdavUserInput.editText = importHistory["urls"][importHistory["lastUrl"]]["lastUser"]; + } else { + importWebdavUserInput.model = []; + } + webdavConnectionLoader.item.url = importWebdavUrlInput.editText; + webdavConnectionLoader.item.username = importWebdavUserInput.editText; webdavConnectionLoader.item.password = importWebdavPasswordInput.text; webdavConnectionLoader.item.storePassword = importWebdavStorePasswordCheck.checked; } @@ -1038,28 +1061,60 @@ Page { color: Theme.mainTextColor } - TextField { + Label { + width: importWebdavUrlLabel.width + text: qsTr("WebDAV server URL") + wrapMode: Text.WordWrap + font: Theme.defaultFont + color: Theme.secondaryTextColor + } + + ComboBox { id: importWebdavUrlInput enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths width: importWebdavUrlLabel.width - placeholderText: qsTr("WebDAV server URL") - - onDisplayTextChanged: { - if (webdavConnectionLoader.item) { - webdavConnectionLoader.item.url = displayText; + editable: true + + Connections { + target: importWebdavUrlInput.contentItem + ignoreUnknownSignals: true + + function onDisplayTextChanged() { + if (webdavConnectionLoader.item && webdavConnectionLoader.item.url !== importWebdavUrlInput.editText) { + webdavConnectionLoader.item.url = importWebdavUrlInput.editText; + if (importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText] !== undefined) { + importWebdavUserInput.model = [""].concat(Object.keys(importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText]["users"])); + importWebdavUserInput.editText = importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText]["lastUser"]; + } else { + importWebdavUserInput.model = []; + } + } } } } - TextField { + Label { + width: importWebdavUrlLabel.width + text: qsTr("User and password") + wrapMode: Text.WordWrap + font: Theme.defaultFont + color: Theme.secondaryTextColor + } + + ComboBox { id: importWebdavUserInput enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths width: importWebdavUrlLabel.width - placeholderText: qsTr("User") + editable: true - onDisplayTextChanged: { - if (webdavConnectionLoader.item) { - webdavConnectionLoader.item.username = displayText; + Connections { + target: importWebdavUserInput.contentItem + ignoreUnknownSignals: true + + function onDisplayTextChanged() { + if (webdavConnectionLoader.item) { + webdavConnectionLoader.item.username = importWebdavUserInput.editText; + } } } } @@ -1069,7 +1124,7 @@ Page { enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths width: importWebdavUrlLabel.width rightPadding: leftPadding + (importWebdavShowPasswordInput.width - leftPadding) - placeholderText: text === "" && webdavConnectionLoader.item && webdavConnectionLoader.item.isPasswordStored ? qsTr("Password (leave empty to use remembered)") : qsTr("Password") + placeholderText: text === "" && webdavConnectionLoader.item && webdavConnectionLoader.item.isPasswordStored ? qsTr("leave empty to use remembered") : "" echoMode: TextInput.Password onDisplayTextChanged: { @@ -1126,7 +1181,7 @@ Page { QfButton { id: importWebdavFetchFoldersButton anchors.verticalCenter: importWebdavPathInput.verticalCenter - visible: !webdavConnectionLoader.item || webdavConnectionLoader.item.availablePaths.length === 0 + visible: importWebdavPathInput.count <= 1 enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths width: importWebdavUrlLabel.width - (importWebdavFetchFoldersIndicator.visible ? importWebdavFetchFoldersIndicator.width + 5 : 0) text: !enabled ? qsTr("Fetching remote folders") : qsTr("Fetch remote folders") @@ -1139,9 +1194,9 @@ Page { ComboBox { id: importWebdavPathInput width: importWebdavUrlLabel.width - (importWebdavRefetchFoldersButton.width + 5) - (importWebdavFetchFoldersIndicator.visible ? importWebdavFetchFoldersIndicator.width + 5 : 0) - visible: webdavConnectionLoader.item && webdavConnectionLoader.item.availablePaths.length > 0 + visible: importWebdavPathInput.count > 1 enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - model: [''].concat(webdavConnectionLoader.item ? webdavConnectionLoader.item.availablePaths : []) + model: [''] } QfToolButton { @@ -1171,8 +1226,8 @@ Page { onAccepted: { if (importWebdavPathInput.displayText !== '' && webdavConnectionLoader.item) { - webdavConnectionLoader.item.url = importWebdavUrlInput.text; - webdavConnectionLoader.item.username = importWebdavUserInput.text; + webdavConnectionLoader.item.url = importWebdavUrlInput.editText; + webdavConnectionLoader.item.username = importWebdavUserInput.editText; webdavConnectionLoader.item.password = importWebdavPasswordInput.text; webdavConnectionLoader.item.storePassword = importWebdavStorePasswordCheck.checked; webdavConnectionLoader.item.importPath(importWebdavPathInput.displayText, platformUtilities.applicationDirectory() + "Imported Projects/"); From 55bb36297620b1f8dee89d28f20f1e3ce0beb02c Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Tue, 4 Feb 2025 10:10:38 +0700 Subject: [PATCH 2/3] Fix imported webdav folders get lost in a "bermuda triangle" area --- src/qml/QFieldLocalDataPickerScreen.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index e6f48434e3..4ca88e33f1 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -1230,7 +1230,7 @@ Page { webdavConnectionLoader.item.username = importWebdavUserInput.editText; webdavConnectionLoader.item.password = importWebdavPasswordInput.text; webdavConnectionLoader.item.storePassword = importWebdavStorePasswordCheck.checked; - webdavConnectionLoader.item.importPath(importWebdavPathInput.displayText, platformUtilities.applicationDirectory() + "Imported Projects/"); + webdavConnectionLoader.item.importPath(importWebdavPathInput.displayText, platformUtilities.applicationDirectory() + "/Imported Projects/"); } } } From 944c6e4e95e629802be02aabc2a97019336624fe Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Tue, 4 Feb 2025 18:29:46 +0700 Subject: [PATCH 3/3] Implement tree navigation when importing remote webdav folder --- src/qml/QFieldLocalDataPickerScreen.qml | 433 ++++++++++++++++-------- 1 file changed, 287 insertions(+), 146 deletions(-) diff --git a/src/qml/QFieldLocalDataPickerScreen.qml b/src/qml/QFieldLocalDataPickerScreen.qml index 4ca88e33f1..e4bc28e827 100644 --- a/src/qml/QFieldLocalDataPickerScreen.qml +++ b/src/qml/QFieldLocalDataPickerScreen.qml @@ -904,9 +904,10 @@ Page { onIsFetchingAvailablePathsChanged: { if (!isFetchingAvailablePaths && importWebdavDialog.visible) { - importWebdavPathInput.model = [""].concat(availablePaths); + swipeDialog.currentIndex = 1; + importWebdavPathInput.model = availablePaths; if (importWebdavDialog.importHistory["urls"][url] !== undefined && importWebdavDialog.importHistory["urls"][url]["users"][username] !== undefined) { - const index = importWebdavPathInput.find(importWebdavDialog.importHistory["urls"][url]["users"][username]["lastImportPath"]); + const index = importWebdavPathInput.model.indexOf(importWebdavDialog.importHistory["urls"][url]["users"][username]["lastImportPath"]); if (index >= 0) { importWebdavPathInput.currentIndex = index; } @@ -1024,6 +1025,7 @@ Page { property var importHistory: undefined onAboutToShow: { + swipeDialog.currentIndex = 0; if (webdavConnectionLoader.item) { importHistory = webdavConnectionLoader.item.importHistory(); importWebdavUrlInput.model = [""].concat(Object.keys(importHistory["urls"])); @@ -1041,196 +1043,335 @@ Page { } } - Column { - width: childrenRect.width - height: childrenRect.height - spacing: 10 - - TextMetrics { - id: importWebdavUrlLabelMetrics - font: importWebdavUrlLabel.font - text: importWebdavUrlLabel.text - } + SwipeView { + id: swipeDialog + width: mainWindow.width - 60 < importWebdavUrlLabelMetrics.width ? mainWindow.width - 60 : importWebdavUrlLabelMetrics.width + clip: true + + Column { + id: firstPage + width: childrenRect.width + height: childrenRect.height + spacing: 10 + + TextMetrics { + id: importWebdavUrlLabelMetrics + font: importWebdavUrlLabel.font + text: importWebdavUrlLabel.text + } - Label { - id: importWebdavUrlLabel - width: mainWindow.width - 60 < importWebdavUrlLabelMetrics.width ? mainWindow.width - 60 : importWebdavUrlLabelMetrics.width - text: qsTr("Type the WebDAV details below to import a remote folder:") - wrapMode: Text.WordWrap - font: Theme.defaultFont - color: Theme.mainTextColor - } + Label { + id: importWebdavUrlLabel + width: mainWindow.width - 60 < importWebdavUrlLabelMetrics.width ? mainWindow.width - 60 : importWebdavUrlLabelMetrics.width + text: qsTr("Type the WebDAV details below to import a remote folder:") + wrapMode: Text.WordWrap + font: Theme.defaultFont + color: Theme.mainTextColor + } - Label { - width: importWebdavUrlLabel.width - text: qsTr("WebDAV server URL") - wrapMode: Text.WordWrap - font: Theme.defaultFont - color: Theme.secondaryTextColor - } + Label { + width: importWebdavUrlLabel.width + text: qsTr("WebDAV server URL") + wrapMode: Text.WordWrap + font: Theme.defaultFont + color: Theme.secondaryTextColor + } - ComboBox { - id: importWebdavUrlInput - enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - width: importWebdavUrlLabel.width - editable: true - - Connections { - target: importWebdavUrlInput.contentItem - ignoreUnknownSignals: true - - function onDisplayTextChanged() { - if (webdavConnectionLoader.item && webdavConnectionLoader.item.url !== importWebdavUrlInput.editText) { - webdavConnectionLoader.item.url = importWebdavUrlInput.editText; - if (importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText] !== undefined) { - importWebdavUserInput.model = [""].concat(Object.keys(importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText]["users"])); - importWebdavUserInput.editText = importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText]["lastUser"]; - } else { - importWebdavUserInput.model = []; + ComboBox { + id: importWebdavUrlInput + enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths + width: importWebdavUrlLabel.width + editable: true + + Connections { + target: importWebdavUrlInput.contentItem + ignoreUnknownSignals: true + + function onDisplayTextChanged() { + if (webdavConnectionLoader.item && webdavConnectionLoader.item.url !== importWebdavUrlInput.editText) { + webdavConnectionLoader.item.url = importWebdavUrlInput.editText; + if (importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText] !== undefined) { + importWebdavUserInput.model = [""].concat(Object.keys(importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText]["users"])); + importWebdavUserInput.editText = importWebdavDialog.importHistory["urls"][importWebdavUrlInput.editText]["lastUser"]; + } else { + importWebdavUserInput.model = []; + } } } } } - } - Label { - width: importWebdavUrlLabel.width - text: qsTr("User and password") - wrapMode: Text.WordWrap - font: Theme.defaultFont - color: Theme.secondaryTextColor - } + Label { + width: importWebdavUrlLabel.width + text: qsTr("User and password") + wrapMode: Text.WordWrap + font: Theme.defaultFont + color: Theme.secondaryTextColor + } - ComboBox { - id: importWebdavUserInput - enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - width: importWebdavUrlLabel.width - editable: true + ComboBox { + id: importWebdavUserInput + enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths + width: importWebdavUrlLabel.width + editable: true - Connections { - target: importWebdavUserInput.contentItem - ignoreUnknownSignals: true + Connections { + target: importWebdavUserInput.contentItem + ignoreUnknownSignals: true - function onDisplayTextChanged() { - if (webdavConnectionLoader.item) { - webdavConnectionLoader.item.username = importWebdavUserInput.editText; + function onDisplayTextChanged() { + if (webdavConnectionLoader.item) { + webdavConnectionLoader.item.username = importWebdavUserInput.editText; + } } } } - } - TextField { - id: importWebdavPasswordInput - enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - width: importWebdavUrlLabel.width - rightPadding: leftPadding + (importWebdavShowPasswordInput.width - leftPadding) - placeholderText: text === "" && webdavConnectionLoader.item && webdavConnectionLoader.item.isPasswordStored ? qsTr("leave empty to use remembered") : "" - echoMode: TextInput.Password + TextField { + id: importWebdavPasswordInput + enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths + width: importWebdavUrlLabel.width + rightPadding: leftPadding + (importWebdavShowPasswordInput.width - leftPadding) + placeholderText: text === "" && webdavConnectionLoader.item && webdavConnectionLoader.item.isPasswordStored ? qsTr("leave empty to use remembered") : "" + echoMode: TextInput.Password - onDisplayTextChanged: { - if (webdavConnectionLoader.item) { - webdavConnectionLoader.item.password = text; + onDisplayTextChanged: { + if (webdavConnectionLoader.item) { + webdavConnectionLoader.item.password = text; + } } - } - QfToolButton { - id: importWebdavShowPasswordInput + QfToolButton { + id: importWebdavShowPasswordInput - property int originalEchoMode: TextInput.Normal + property int originalEchoMode: TextInput.Normal - visible: (!!parent.echoMode && parent.echoMode !== TextInput.Normal) || originalEchoMode !== TextInput.Normal - iconSource: parent.echoMode === TextInput.Normal ? Theme.getThemeVectorIcon('ic_hide_green_48dp') : Theme.getThemeVectorIcon('ic_show_green_48dp') - iconColor: Theme.mainColor - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - opacity: parent.text.length > 0 ? 1 : 0.25 - z: 1 + visible: (!!parent.echoMode && parent.echoMode !== TextInput.Normal) || originalEchoMode !== TextInput.Normal + iconSource: parent.echoMode === TextInput.Normal ? Theme.getThemeVectorIcon('ic_hide_green_48dp') : Theme.getThemeVectorIcon('ic_show_green_48dp') + iconColor: Theme.mainColor + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + opacity: parent.text.length > 0 ? 1 : 0.25 + z: 1 - onClicked: { - if (parent.echoMode !== TextInput.Normal) { - originalEchoMode = parent.echoMode; - parent.echoMode = TextInput.Normal; - } else { - parent.echoMode = originalEchoMode; + onClicked: { + if (parent.echoMode !== TextInput.Normal) { + originalEchoMode = parent.echoMode; + parent.echoMode = TextInput.Normal; + } else { + parent.echoMode = originalEchoMode; + } } } } - } - CheckBox { - id: importWebdavStorePasswordCheck - width: importWebdavUrlLabel.width - enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - text: qsTr('Remember password') - font: Theme.defaultFont - checked: true - } - - Label { - width: importWebdavUrlLabel.width - visible: importWebdavPathInput.visible - text: qsTr("Select the remote folder to import:") - wrapMode: Text.WordWrap - font: Theme.defaultFont - color: Theme.mainTextColor - } + CheckBox { + id: importWebdavStorePasswordCheck + width: importWebdavUrlLabel.width + enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths + text: qsTr('Remember password') + font: Theme.defaultFont + checked: true + } - Row { - spacing: 5 + Row { + QfButton { + id: importWebdavFetchFoldersButton + anchors.verticalCenter: importWebdavFetchFoldersIndicator.verticalCenter + enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths + width: importWebdavUrlLabel.width - (importWebdavFetchFoldersIndicator.visible ? importWebdavFetchFoldersIndicator.width : 0) + text: !enabled ? qsTr("Fetching remote folders") : qsTr("Fetch remote folders") - QfButton { - id: importWebdavFetchFoldersButton - anchors.verticalCenter: importWebdavPathInput.verticalCenter - visible: importWebdavPathInput.count <= 1 - enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - width: importWebdavUrlLabel.width - (importWebdavFetchFoldersIndicator.visible ? importWebdavFetchFoldersIndicator.width + 5 : 0) - text: !enabled ? qsTr("Fetching remote folders") : qsTr("Fetch remote folders") + onClicked: { + webdavConnectionLoader.item.fetchAvailablePaths(); + } + } - onClicked: { - webdavConnectionLoader.item.fetchAvailablePaths(); + BusyIndicator { + id: importWebdavFetchFoldersIndicator + anchors.verticalCenter: importWebdavFetchFoldersButton.verticalCenter + width: 48 + height: 48 + visible: webdavConnectionLoader.item && webdavConnectionLoader.item.isFetchingAvailablePaths + running: visible } } + } - ComboBox { - id: importWebdavPathInput - width: importWebdavUrlLabel.width - (importWebdavRefetchFoldersButton.width + 5) - (importWebdavFetchFoldersIndicator.visible ? importWebdavFetchFoldersIndicator.width + 5 : 0) - visible: importWebdavPathInput.count > 1 - enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - model: [''] + Column { + Label { + width: importWebdavUrlLabel.width + visible: importWebdavPathInput.visible + text: qsTr("Select the remote folder to import:") + wrapMode: Text.WordWrap + font: Theme.defaultFont + color: Theme.mainTextColor } - QfToolButton { - id: importWebdavRefetchFoldersButton - anchors.verticalCenter: importWebdavPathInput.verticalCenter - visible: importWebdavPathInput.visible - enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths - bgcolor: "transparent" - iconSource: Theme.getThemeVectorIcon("refresh_24dp") - iconColor: enabled ? Theme.mainTextColor : Theme.mainTextDisabledColor + Rectangle { + id: importWebdavPathContainer + width: importWebdavUrlLabel.width + height: 340 + color: Theme.controlBackgroundColor + border.color: Theme.controlBorderColor + border.width: 1 + + ListView { + id: importWebdavPathInput + anchors.fill: parent + anchors.margins: 1 + enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths + clip: true + model: [] + + property var expandedPaths: [] + property int expandedPathsClicks: 0 + + delegate: Rectangle { + id: rectangleDialog + + anchors.margins: 10 + width: parent ? parent.width : undefined + height: lineDialog.isVisible ? lineDialog.height + 20 : 0 + color: importWebdavPathInput.currentIndex == index ? Theme.mainColor : Theme.mainBackgroundColor + clip: true + + Row { + id: lineDialog + anchors.verticalCenter: parent.verticalCenter + spacing: 5 + + property string label: { + let parts = modelData.split('/'); + if (parts.length > 1) { + return parts[parts.length - 2]; + } + return ""; + } + property int level: Math.max(0, modelData.split('/').length - 2) + property bool isVisible: { + let parts = modelData.split('/').slice(1, -2); + while (parts.length > 0) { + if (importWebdavPathInput.expandedPaths.indexOf("/" + parts.join("/") + "/") == -1) { + return false; + } + parts = parts.slice(0, -1); + } + return true; + } + property bool hasChildren: { + for (const availablePath of importWebdavPathInput.model) { + if (availablePath.indexOf(modelData) === 0 && availablePath !== modelData) { + return true; + } + } + return false; + } - onClicked: { - webdavConnectionLoader.item.fetchAvailablePaths(); + Item { + id: expandSpacing + height: 35 + width: 20 * Math.max(1, lineDialog.level) - 1 + } + + QfToolButton { + id: epxandButton + height: 35 + width: height + anchors.verticalCenter: parent.verticalCenter + iconSource: Theme.getThemeVectorIcon('ic_legend_collapsed_state_24dp') + iconColor: Theme.mainTextColor + bgcolor: "transparent" + enabled: false + opacity: lineDialog.level > 0 && lineDialog.hasChildren ? 1 : 0 + rotation: importWebdavPathInput.expandedPaths.indexOf(modelData) > -1 ? 90 : 0 + + Behavior on rotation { + NumberAnimation { + duration: 100 + } + } + } + + Text { + id: contentTextDialog + width: rectangleDialog.width - epxandButton.width - expandSpacing.width - 10 + anchors.verticalCenter: parent.verticalCenter + leftPadding: 5 + font.pointSize: Theme.defaultFont.pointSize + font.weight: model.checked ? Font.DemiBold : Font.Normal + elide: Text.ElideRight + wrapMode: Text.WordWrap + color: lineDialog.label !== "" ? Theme.mainTextColor : importWebdavPathInput.currentIndex == index ? "white" : Theme.secondaryTextColor + textFormat: Text.RichText + text: lineDialog.label !== "" ? lineDialog.label : qsTr("(root folder)") + } + } + + /* bottom border */ + Rectangle { + anchors.bottom: parent.bottom + height: 1 + color: Theme.controlBorderColor + width: parent.width + visible: lineDialog.isVisible + } + + MouseArea { + anchors.fill: parent + anchors.rightMargin: 48 + onClicked: mouse => { + importWebdavPathInput.currentIndex = index; + } + onDoubleClicked: mouse => { + const index = importWebdavPathInput.expandedPaths.indexOf(modelData); + if (importWebdavPathInput.expandedPaths.indexOf(modelData) == -1) { + importWebdavPathInput.expandedPaths.push(modelData); + } else { + importWebdavPathInput.expandedPaths.splice(index, 1); + } + importWebdavPathInput.expandedPathsChanged(); + } + } + } } } - BusyIndicator { - id: importWebdavFetchFoldersIndicator - anchors.verticalCenter: importWebdavPathInput.verticalCenter - width: 48 - height: 48 - visible: webdavConnectionLoader.item && webdavConnectionLoader.item.isFetchingAvailablePaths - running: visible + Row { + spacing: 5 + + QfButton { + id: importWebdavRefetchFoldersButton + width: importWebdavUrlLabel.width - (importWebdavRefreshFoldersIndicator.visible ? importWebdavRefreshFoldersIndicator.width : 0) + enabled: !webdavConnectionLoader.item || !webdavConnectionLoader.item.isFetchingAvailablePaths + bgcolor: "transparent" + text: !enabled ? qsTr("Refreshing remote folders") : qsTr("Refresh remote folders") + + onClicked: { + importWebdavPathInput.currentIndex = -1; + webdavConnectionLoader.item.fetchAvailablePaths(); + } + } + + BusyIndicator { + id: importWebdavRefreshFoldersIndicator + anchors.verticalCenter: importWebdavRefetchFoldersButton.verticalCenter + width: 48 + height: 48 + visible: webdavConnectionLoader.item && webdavConnectionLoader.item.isFetchingAvailablePaths + running: visible + } } } } onAccepted: { - if (importWebdavPathInput.displayText !== '' && webdavConnectionLoader.item) { + if (importWebdavPathInput.currentIndex > -1 && webdavConnectionLoader.item) { webdavConnectionLoader.item.url = importWebdavUrlInput.editText; webdavConnectionLoader.item.username = importWebdavUserInput.editText; webdavConnectionLoader.item.password = importWebdavPasswordInput.text; webdavConnectionLoader.item.storePassword = importWebdavStorePasswordCheck.checked; - webdavConnectionLoader.item.importPath(importWebdavPathInput.displayText, platformUtilities.applicationDirectory() + "/Imported Projects/"); + webdavConnectionLoader.item.importPath(importWebdavPathInput.model[importWebdavPathInput.currentIndex], platformUtilities.applicationDirectory() + "/Imported Projects/"); } } }