Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6a4887b
feat(socketapi): add file actions option to list of options for files.
camilasan Jun 23, 2025
0857e65
feat(declarativeui): add basic class to retrieve json from server.
camilasan Aug 17, 2025
749acb5
feat(declarativeui): fetch delcarativeui endpoints in capabilities.
camilasan Sep 2, 2025
fcbdc77
feat(declarativeui): display application window with declarative ui.
camilasan Sep 2, 2025
d9aa794
feat(declarativeui): list declarative ui and file actions in context …
camilasan Sep 2, 2025
61d9a00
feat(declarativeui): list declarative ui and file actions in the tray…
camilasan Sep 2, 2025
cdfdec4
fix(declarativeui): remove log messages.
camilasan Sep 2, 2025
9254b31
feat(declarativeui): add icon and filter to endpoint model and UI.
camilasan Sep 2, 2025
61a0255
feat: style file actions window.
camilasan Sep 3, 2025
84182cd
feat: display response from request from file actions.
camilasan Sep 3, 2025
b0144ae
feat(declarativeui): improve the looks of the file actions Window.
camilasan Sep 3, 2025
ef47072
fix: context menu.
camilasan Sep 4, 2025
a412e37
fix: update datat parsing to new API format.
camilasan Sep 16, 2025
2fcd450
feat: add helper function to match string to SimpleApiJob::Verb.
camilasan Sep 16, 2025
f9ba223
fix: handle file action response.
camilasan Sep 17, 2025
68fdd15
refactor: create function to set file id and mime type.
camilasan Sep 17, 2025
89617e4
feat: add logging category to FileActionsModel.
camilasan Sep 17, 2025
c34e6b5
refactor: improve error handling.
camilasan Sep 17, 2025
a51c1b3
feat: return default icon when server doesn't have one.
camilasan Sep 18, 2025
cda47d5
fix: parse response with declarative UI elements like url.
camilasan Sep 30, 2025
7bb54ab
refactor: remove unused DeclarativeUi class.
camilasan Oct 1, 2025
a998866
fix: files license.
camilasan Oct 1, 2025
d35122d
refactor: remove Declarative UI menu item.
camilasan Oct 1, 2025
aa96145
fix: use reference type in for loop.
camilasan Oct 1, 2025
4a95c2c
fix: spaces and sizes in the file actions window.
camilasan Oct 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ SPDX-FileCopyrightText = "2016 Nextcloud GmbH"
SPDX-License-Identifier = "LicenseRef-NextcloudTrademarks"

[[annotations]]
path = ["theme/white/error.svg", "theme/black/error.svg","theme/white/warning.svg", "theme/black/warning.svg", "theme/white/info.svg", "theme/black/info.svg", "theme/info.svg", "theme/white/nextcloud/*.svg", "theme/black/nextcloud/*.svg", "theme/colored/nextcloud/*.svg", "theme/black/label.svg", "theme/white/label.svg", "theme/colored/user-status-*.svg", "theme/account.svg","theme/add.svg","theme/change.svg","theme/chevron-double-up.svg","theme/close.svg","theme/confirm.svg","theme/copy.svg","theme/delete.svg","theme/external.svg","theme/files.svg","theme/lock-broken.svg","theme/lock-http.svg","theme/lock-https.svg","theme/lock.svg","theme/magnifying-glass.svg","theme/more.svg","theme/network.svg","theme/public.svg","theme/reply.svg","theme/send.svg","theme/settings.svg","theme/share.svg","theme/sync-arrow.svg", "theme/black/account-group.svg","theme/black/clear.svg","theme/black/expand-less-black.svg","theme/black/folder-group.svg","theme/black/search.svg", "theme/colored/add-bordered.svg", "theme/colored/change-bordered.svg", "theme/colored/delete-bordered.svg", "theme/colored/delete.svg", "theme/*/activity.svg", "theme/*/add.svg","theme/*/bell.svg","theme/*/calendar.svg","theme/*/caret-down.svg","theme/*/change.svg","theme/*/close.svg","theme/*/comment.svg","theme/*/confirm.svg","theme/*/control-next.svg","theme/*/control-prev.svg","theme/*/edit.svg","theme/*/email.svg","theme/*/external.png","theme/*/external.svg","theme/*/[email protected]","theme/*/folder.png","theme/*/folder.svg","theme/*/[email protected]","theme/*/more-apps.svg","theme/*/nc-assistant-app.svg","theme/*/settings.svg","theme/*/user.svg","theme/*/wizard-files.png","theme/*/wizard-files.svg","theme/*/[email protected]","theme/*/wizard-groupware.png","theme/*/wizard-groupware.svg","theme/*/[email protected]", "theme/cfapishellext_custom_states/0-locked.svg","theme/cfapishellext_custom_states/1-shared.svg","theme/cfapishellext_custom_states/1024-0-locked.png","theme/cfapishellext_custom_states/1024-1-shared.png","theme/cfapishellext_custom_states/128-0-locked.png","theme/cfapishellext_custom_states/128-1-shared.png","theme/cfapishellext_custom_states/24-0-locked.png","theme/cfapishellext_custom_states/24-1-shared.png","theme/cfapishellext_custom_states/256-0-locked.png","theme/cfapishellext_custom_states/256-1-shared.png","theme/cfapishellext_custom_states/32-0-locked.png","theme/cfapishellext_custom_states/32-1-shared.png","theme/cfapishellext_custom_states/40-0-locked.png","theme/cfapishellext_custom_states/40-1-shared.png","theme/cfapishellext_custom_states/48-0-locked.png","theme/cfapishellext_custom_states/48-1-shared.png","theme/cfapishellext_custom_states/512-0-locked.png","theme/cfapishellext_custom_states/512-1-shared.png","theme/cfapishellext_custom_states/64-0-locked.png","theme/cfapishellext_custom_states/64-1-shared.png"]
path = ["theme/white/error.svg", "theme/black/error.svg","theme/white/warning.svg", "theme/black/warning.svg", "theme/white/info.svg", "theme/black/info.svg", "theme/info.svg", "theme/white/nextcloud/*.svg", "theme/black/nextcloud/*.svg", "theme/colored/nextcloud/*.svg", "theme/black/label.svg", "theme/white/label.svg", "theme/colored/user-status-*.svg", "theme/account.svg","theme/add.svg","theme/change.svg","theme/chevron-double-up.svg","theme/close.svg","theme/confirm.svg","theme/copy.svg","theme/delete.svg","theme/external.svg","theme/files.svg","theme/lock-broken.svg","theme/lock-http.svg","theme/lock-https.svg","theme/lock.svg","theme/magnifying-glass.svg","theme/more.svg","theme/network.svg","theme/public.svg","theme/reply.svg","theme/send.svg","theme/settings.svg","theme/share.svg","theme/sync-arrow.svg", "theme/black/account-group.svg","theme/black/clear.svg","theme/black/expand-less-black.svg","theme/black/folder-group.svg","theme/black/search.svg", "theme/colored/add-bordered.svg", "theme/colored/change-bordered.svg", "theme/colored/delete-bordered.svg", "theme/colored/delete.svg", "theme/*/activity.svg", "theme/*/add.svg","theme/*/bell.svg","theme/*/calendar.svg","theme/*/caret-down.svg","theme/*/change.svg","theme/*/close.svg","theme/*/comment.svg","theme/*/confirm.svg","theme/*/control-next.svg","theme/*/control-prev.svg","theme/*/edit.svg","theme/*/email.svg","theme/*/external.png","theme/*/external.svg","theme/*/[email protected]","theme/*/folder.png","theme/*/folder.svg","theme/*/[email protected]","theme/*/more-apps.svg","theme/*/nc-assistant-app.svg","theme/*/settings.svg","theme/*/user.svg","theme/*/wizard-files.png","theme/*/wizard-files.svg","theme/*/[email protected]","theme/*/wizard-groupware.png","theme/*/wizard-groupware.svg","theme/*/[email protected]", "theme/cfapishellext_custom_states/0-locked.svg","theme/cfapishellext_custom_states/1-shared.svg","theme/cfapishellext_custom_states/1024-0-locked.png","theme/cfapishellext_custom_states/1024-1-shared.png","theme/cfapishellext_custom_states/128-0-locked.png","theme/cfapishellext_custom_states/128-1-shared.png","theme/cfapishellext_custom_states/24-0-locked.png","theme/cfapishellext_custom_states/24-1-shared.png","theme/cfapishellext_custom_states/256-0-locked.png","theme/cfapishellext_custom_states/256-1-shared.png","theme/cfapishellext_custom_states/32-0-locked.png","theme/cfapishellext_custom_states/32-1-shared.png","theme/cfapishellext_custom_states/40-0-locked.png","theme/cfapishellext_custom_states/40-1-shared.png","theme/cfapishellext_custom_states/48-0-locked.png","theme/cfapishellext_custom_states/48-1-shared.png","theme/cfapishellext_custom_states/512-0-locked.png","theme/cfapishellext_custom_states/512-1-shared.png","theme/cfapishellext_custom_states/64-0-locked.png","theme/cfapishellext_custom_states/64-1-shared.png", "theme/backup.svg", "theme/convert_to_text.svg", "theme/file-open.svg"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2018-2025 Google LLC"
SPDX-License-Identifier = "Apache-2.0"
1 change: 1 addition & 0 deletions resources.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@
<file>src/gui/macOS/ui/FileProviderEvictionDialog.qml</file>
<file>src/gui/macOS/ui/FileProviderSyncStatus.qml</file>
<file>src/gui/macOS/ui/FileProviderStorageInfo.qml</file>
<file>src/gui/declarativeui/FileActionsWindow.qml</file>
</qresource>
</RCC>
6 changes: 6 additions & 0 deletions src/gui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ set(client_SRCS
wizard/linklabel.cpp
wizard/wizardproxysettingsdialog.h
wizard/wizardproxysettingsdialog.cpp
declarativeui/declarativeuimodel.h
declarativeui/declarativeuimodel.cpp
declarativeui/declarativeui.h
declarativeui/declarativeui.cpp
declarativeui/fileactionsmodel.h
declarativeui/fileactionsmodel.cpp
)

if (NOT DISABLE_ACCOUNT_MIGRATION)
Expand Down
3 changes: 3 additions & 0 deletions src/gui/application.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/*
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014 ownCloud GmbH
Expand Down Expand Up @@ -425,6 +425,9 @@
connect(FolderMan::instance()->socketApi(), &SocketApi::fileActivityCommandReceived,
_gui.data(), &ownCloudGui::slotShowFileActivityDialog);

connect(FolderMan::instance()->socketApi(), &SocketApi::fileActionsCommandReceived,
_gui.data(), &ownCloudGui::slotShowFileActionsDialog);

// startup procedure.
connect(&_checkConnectionTimer, &QTimer::timeout, this, &Application::slotCheckConnection);
_checkConnectionTimer.setInterval(ConnectionValidator::DefaultCallingIntervalMsec); // check for connection every 32 seconds.
Expand Down
263 changes: 263 additions & 0 deletions src/gui/declarativeui/FileActionsWindow.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
/*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

pragma ComponentBehavior: Bound

import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import com.nextcloud.desktopclient
import Style

ApplicationWindow {
id: root
height: Style.filesActionsHeight
width: Style.filesActionsWidth
flags: Systray.useNormalWindow ? Qt.Window : Qt.Dialog | Qt.FramelessWindowHint
visible: true
color: "transparent"

property var accountState: ({})
property string localPath: ""
property string shortLocalPath: ""
property var response: ({})

readonly property int windowRadius: Systray.useNormalWindow ? 0.0 : Style.trayWindowRadius

title: qsTr("File actions for %1").arg(root.shortLocalPath)

FileActionsModel {
id: fileActionModel
accountState: root.accountState
localPath: root.localPath
}

background: Rectangle {
id: maskSource
radius: root.windowRadius
border.width: Style.trayWindowBorderWidth
border.color: palette.dark
color: palette.window
}

OpacityMask {
anchors.fill: parent
anchors.margins: Style.trayWindowBorderWidth
source: maskSourceItem
maskSource: maskSource
}

Rectangle {
id: maskSourceItem
anchors.fill: parent
anchors.margins: Style.standardSpacing
radius: root.windowRadius
clip: true
color: Style.colorWithoutTransparency(palette.base)

ColumnLayout {
id: windowContent
anchors.fill: parent
anchors.margins: Style.standardSpacing

RowLayout {
id: windowHeader
Layout.fillWidth: true
spacing: Style.standardSpacing

Image {
source: "image://svgimage-custom-color/file-open.svg/" + palette.windowText
Layout.maximumWidth: Style.minimumActivityItemHeight
Layout.maximumHeight: Style.minimumActivityItemHeight
Layout.alignment: Qt.AlignVCenter
Layout.margins: Style.extraSmallSpacing
}

Label {
id: headerLocalPath
text: root.shortLocalPath
elide: Text.ElideRight
font.bold: true
font.pixelSize: Style.pixelSize
color: palette.text
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
}

Button {
id: closeButton
flat: true
padding: Style.extraSmallSpacing
spacing: 0
icon.source: "image://svgimage-custom-color/close.svg/" + palette.windowText
icon.width: Style.extraSmallIconSize
icon.height: Style.extraSmallIconSize
Layout.alignment: Qt.AlignTop | Qt.AlignRight
Layout.rightMargin: Style.extraSmallSpacing
Layout.topMargin: Style.extraSmallSpacing
onClicked: root.close()
background: Rectangle {
color: "transparent"
radius: root.windowRadius
border.width: closeButton.hovered ? Style.trayWindowBorderWidth : 0
border.color: palette.dark
anchors.fill: parent
Layout.margins: Style.extraSmallSpacing
}
}
}

Rectangle {
id: lineTop
Layout.fillWidth: true
Layout.minimumHeight: Style.extraExtraSmallSpacing
color: palette.dark
}

ListView {
id: fileActionsView
model: fileActionModel
clip: true
spacing: Style.trayHorizontalMargin
Layout.fillWidth: true
Layout.fillHeight: true
delegate: fileActionsDelegate
}

Button {
id: responseButton
visible: responseText.text !== ""
flat: true
Layout.fillWidth: true
implicitHeight: responseContent.implicitHeight

padding: Style.standardSpacing
leftPadding: Style.standardSpacing
rightPadding: Style.standardSpacing
spacing: Style.standardSpacing

background: Rectangle {
id: responseBorder
radius: root.windowRadius
border.width: Style.trayWindowBorderWidth
border.color: palette.dark
color: palette.window
Layout.fillWidth: true
}

contentItem: RowLayout {
id: responseContent
anchors.fill: parent
anchors.margins: Style.smallSpacing
spacing: Style.standardSpacing
Layout.fillWidth: true
Layout.minimumHeight: Style.accountAvatarStateIndicatorSize

Image {
source: "image://svgimage-custom-color/backup.svg/" + palette.windowText
// Layout.preferredWidth: Style.accountAvatarStateIndicatorSize
// Layout.preferredHeight: Style.accountAvatarStateIndicatorSize
Layout.minimumWidth: Style.accountAvatarStateIndicatorSize
Layout.minimumHeight: Style.accountAvatarStateIndicatorSize
fillMode: Image.PreserveAspectFit
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.leftMargin: Style.standardSpacing
}

Text {
id: responseText
text: fileActionModel.responseLabel
textFormat: Text.RichText
color: palette.text
font.pointSize: Style.pixelSize
font.underline: true
wrapMode: Text.WordWrap
Layout.fillWidth: true
bottomPadding: Style.standardSpacing
Layout.alignment: Qt.AlignVCenter
}
}

MouseArea {
id: responseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Qt.openUrlExternally(fileActionModel.responseUrl)
}
}
}
}

Component {
id: fileActionsDelegate

RowLayout {
id: fileAction
Layout.fillWidth: true
height: implicitHeight
width: parent.width

required property string name
required property int index
required property string icon

Button {
id: fileActionButton
flat: true
Layout.fillWidth: true
implicitHeight: Style.activityListButtonHeight

padding: Style.standardSpacing

contentItem: Row {
id: fileActionsContent
anchors.fill: parent
anchors.topMargin: Style.standardSpacing
anchors.rightMargin: Style.standardSpacing
anchors.bottomMargin: Style.standardSpacing
anchors.leftMargin: Style.smallSpacing
spacing: Style.standardSpacing
Layout.fillWidth: true

Image {
source: fileAction.icon + palette.windowText
width: Style.activityListButtonIconSize
height: Style.activityListButtonIconSize
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
}

Label {
text: fileAction.name
color: palette.text
font.pixelSize: Style.defaultFontPtSize
verticalAlignment: Text.AlignVCenter
anchors.verticalCenter: parent.verticalCenter
}
}

background: Rectangle {
color: "transparent"
radius: root.windowRadius
border.width: fileActionButton.hovered ? Style.trayWindowBorderWidth : 0
border.color: palette.dark
anchors.margins: Style.standardSpacing
height: parent.height
width: parent.width
}

MouseArea {
id: fileActionMouseArea
anchors.fill: parent
anchors.margins: Style.standardSpacing
cursorShape: Qt.PointingHandCursor
onClicked: fileActionModel.createRequest(fileAction.index)
}
}
}
}
}
68 changes: 68 additions & 0 deletions src/gui/declarativeui/declarativeui.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "declarativeui.h"
#include "networkjobs.h"
#include "accountfwd.h"
#include "account.h"

namespace OCC {

Q_LOGGING_CATEGORY(lcDeclarativeUi, "nextcloud.gui.declarativeui", QtInfoMsg)

Check warning on line 13 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:13:1 [cppcoreguidelines-avoid-non-const-global-variables]

variable 'Q_LOGGING_CATEGORY' is non-const and globally accessible, consider making it const

DeclarativeUi::DeclarativeUi(QObject *parent)

Check warning on line 15 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:15:30 [cppcoreguidelines-avoid-non-const-global-variables]

variable 'QObject' is non-const and globally accessible, consider making it const
: QObject(parent)

Check warning on line 16 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:16:15 [cppcoreguidelines-avoid-non-const-global-variables]

variable 'parent' is non-const and globally accessible, consider making it const
{
}

void DeclarativeUi::setAccountState(AccountState *accountState)

Check warning on line 20 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:20:21 [readability-convert-member-functions-to-static]

method 'setAccountState' can be made static
{
if (accountState == nullptr) {
return;
}

if (accountState == _accountState) {
return;
}

_accountState = accountState;
_declarativeUiModel = std::make_unique<DeclarativeUiModel>(_accountState->account(), this);
connect(_declarativeUiModel.get(), &DeclarativeUiModel::pageFetched,
this, &DeclarativeUi::declarativeUiFetched);
connect(this, &DeclarativeUi::declarativeUiFetched,
this, &DeclarativeUi::declarativeUiModelChanged);

Q_EMIT accountStateChanged();

Check warning on line 37 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:37:12 [modernize-use-trailing-return-type]

use a trailing return type for this function
}

void DeclarativeUi::setLocalPath(const QString &localPath)

Check warning on line 40 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:40:21 [readability-convert-member-functions-to-static]

method 'setLocalPath' can be made static
{
if (localPath.isEmpty()) {
return;
}

if (localPath == _localPath) {
return;
}

_localPath = localPath;
Q_EMIT localPathChanged();

Check warning on line 51 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:51:12 [modernize-use-trailing-return-type]

use a trailing return type for this function
}

AccountState *DeclarativeUi::accountState() const

Check warning on line 54 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:54:30 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
return _accountState;
}

QString DeclarativeUi::localPath() const

Check warning on line 59 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:59:24 [modernize-use-trailing-return-type]

use a trailing return type for this function
{
return _localPath;
}

DeclarativeUiModel *DeclarativeUi::declarativeUiModel() const {

Check warning on line 64 in src/gui/declarativeui/declarativeui.cpp

View workflow job for this annotation

GitHub Actions / build

src/gui/declarativeui/declarativeui.cpp:64:36 [modernize-use-trailing-return-type]

use a trailing return type for this function
return _declarativeUiModel.get();
}

}
Loading
Loading