From d94d1c3f3e7206d383ef74b43fe536bd5fdcc417 Mon Sep 17 00:00:00 2001 From: Khushboo Mehta Date: Tue, 24 Feb 2026 11:28:28 +0100 Subject: [PATCH] feat: add deplayment config path in ui + some visual improvements --- flake.lock | 7 +- flake.nix | 2 +- src/BlockchainBackend.cpp | 56 ++++++--- src/BlockchainBackend.h | 16 ++- src/qml/BlockchainView.qml | 23 ++-- src/qml/views/LogsView.qml | 16 ++- src/qml/views/StatusConfigView.qml | 71 +++++++---- src/qml/views/WalletView.qml | 189 ++++++++++++++++------------- 8 files changed, 235 insertions(+), 145 deletions(-) diff --git a/flake.lock b/flake.lock index 4f505af..f9c7aa6 100644 --- a/flake.lock +++ b/flake.lock @@ -801,16 +801,17 @@ ] }, "locked": { - "lastModified": 1771871578, - "narHash": "sha256-6Mu3cmdhd8e7i+n8OWcaIBye+i12gwlwt1fhd9QCbCI=", + "lastModified": 1770837874, + "narHash": "sha256-wr75lv1q4U1FS5+l/6ypwzJFJe06l2RyUvx1npoRS88=", "owner": "logos-co", "repo": "logos-liblogos", - "rev": "19d29d4ef99292d9285b3a561cb7ea8029be3b74", + "rev": "e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea", "type": "github" }, "original": { "owner": "logos-co", "repo": "logos-liblogos", + "rev": "e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea", "type": "github" } }, diff --git a/flake.nix b/flake.nix index 64af90d..4af8968 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ # Follow the same nixpkgs as logos-liblogos to ensure compatibility nixpkgs.follows = "logos-liblogos/nixpkgs"; logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk"; - logos-liblogos.url = "github:logos-co/logos-liblogos"; + logos-liblogos.url = "github:logos-co/logos-liblogos?rev=e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea"; logos-blockchain-module.url = "github:logos-blockchain/logos-blockchain-module"; logos-capability-module.url = "github:logos-co/logos-capability-module"; logos-design-system.url = "github:logos-co/logos-design-system"; diff --git a/src/BlockchainBackend.cpp b/src/BlockchainBackend.cpp index 172f2e1..491ff06 100644 --- a/src/BlockchainBackend.cpp +++ b/src/BlockchainBackend.cpp @@ -1,34 +1,43 @@ #include "BlockchainBackend.h" #include +#include #include #include +#include #include #include #include +#include namespace { const char SETTINGS_ORG[] = "Logos"; const char SETTINGS_APP[] = "BlockchainUI"; - const char CONFIG_PATH_KEY[] = "configPath"; + const char USER_CONFIG_KEY[] = "userConfigPath"; + const char DEPLOYMENT_CONFIG_KEY[] = "deploymentConfigPath"; const QString BLOCKCHAIN_MODULE_NAME = QStringLiteral("liblogos_blockchain_module"); } BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent) : QObject(parent), m_status(NotStarted), - m_configPath(""), + m_userConfig(""), + m_deploymentConfig(""), m_logModel(new LogModel(this)), m_logosAPI(nullptr), m_blockchainClient(nullptr) { QSettings s(SETTINGS_ORG, SETTINGS_APP); const QString envConfigPath = QString::fromUtf8(qgetenv("LB_CONFIG_PATH")); - const QString savedConfigPath = s.value(CONFIG_PATH_KEY).toString(); + const QString savedUserConfig = s.value(USER_CONFIG_KEY).toString(); + const QString savedDeploymentConfig = s.value(DEPLOYMENT_CONFIG_KEY).toString(); if (!envConfigPath.isEmpty()) { - m_configPath = envConfigPath; - } else if (!savedConfigPath.isEmpty()) { - m_configPath = savedConfigPath; + m_userConfig = envConfigPath; + } else if (!savedUserConfig.isEmpty()) { + m_userConfig = savedUserConfig; + } + if (!savedDeploymentConfig.isEmpty()) { + m_deploymentConfig = savedDeploymentConfig; } if (!logosAPI) { @@ -67,14 +76,25 @@ void BlockchainBackend::setStatus(BlockchainStatus newStatus) } } -void BlockchainBackend::setConfigPath(const QString& path) +void BlockchainBackend::setUserConfig(const QString& path) { const QString localPath = QUrl::fromUserInput(path).toLocalFile(); - if (m_configPath != localPath) { - m_configPath = localPath; + if (m_userConfig != localPath) { + m_userConfig = localPath; QSettings s(SETTINGS_ORG, SETTINGS_APP); - s.setValue(CONFIG_PATH_KEY, m_configPath); - emit configPathChanged(); + s.setValue(USER_CONFIG_KEY, m_userConfig); + emit userConfigChanged(); + } +} + +void BlockchainBackend::setDeploymentConfig(const QString& path) +{ + const QString localPath = QUrl::fromUserInput(path).toLocalFile(); + if (m_deploymentConfig != localPath) { + m_deploymentConfig = localPath; + QSettings s(SETTINGS_ORG, SETTINGS_APP); + s.setValue(DEPLOYMENT_CONFIG_KEY, m_deploymentConfig); + emit deploymentConfigChanged(); } } @@ -83,6 +103,12 @@ void BlockchainBackend::clearLogs() m_logModel->clear(); } +void BlockchainBackend::copyToClipboard(const QString& text) +{ + if (QGuiApplication::clipboard()) + QGuiApplication::clipboard()->setText(text); +} + QString BlockchainBackend::getBalance(const QString& addressHex) { if (!m_blockchainClient) { @@ -118,7 +144,7 @@ void BlockchainBackend::startBlockchain() setStatus(Starting); QVariant result = m_blockchainClient->invokeRemoteMethod( - BLOCKCHAIN_MODULE_NAME, "start", m_configPath, QString()); + BLOCKCHAIN_MODULE_NAME, "start", m_userConfig, m_deploymentConfig); int resultCode = result.isValid() ? result.toInt() : -1; if (resultCode == 0 || resultCode == 1) { @@ -174,11 +200,7 @@ void BlockchainBackend::onNewBlock(const QVariantList& data) QString line; if (!data.isEmpty()) { QString blockInfo = data.first().toString(); - QString shortInfo = blockInfo.left(80); - if (blockInfo.length() > 80) { - shortInfo += "..."; - } - line = QString("[%1] 📦 New block: %2").arg(timestamp, shortInfo); + line = QString("[%1] 📦 New block: %2").arg(timestamp, blockInfo); } else { line = QString("[%1] 📦 New block (no data)").arg(timestamp); } diff --git a/src/BlockchainBackend.h b/src/BlockchainBackend.h index 071fec2..fb053ab 100644 --- a/src/BlockchainBackend.h +++ b/src/BlockchainBackend.h @@ -27,7 +27,8 @@ public: Q_ENUM(BlockchainStatus) Q_PROPERTY(BlockchainStatus status READ status NOTIFY statusChanged) - Q_PROPERTY(QString configPath READ configPath WRITE setConfigPath NOTIFY configPathChanged) + Q_PROPERTY(QString userConfig READ userConfig WRITE setUserConfig NOTIFY userConfigChanged) + Q_PROPERTY(QString deploymentConfig READ deploymentConfig WRITE setDeploymentConfig NOTIFY deploymentConfigChanged) Q_PROPERTY(LogModel* logModel READ logModel CONSTANT) Q_PROPERTY(QStringList knownAddresses READ knownAddresses NOTIFY knownAddressesChanged) @@ -35,12 +36,15 @@ public: ~BlockchainBackend(); BlockchainStatus status() const { return m_status; } - QString configPath() const { return m_configPath; } + QString userConfig() const { return m_userConfig; } + QString deploymentConfig() const { return m_deploymentConfig; } LogModel* logModel() const { return m_logModel; } QStringList knownAddresses() const { return m_knownAddresses; } - void setConfigPath(const QString& path); + void setUserConfig(const QString& path); + void setDeploymentConfig(const QString& path); Q_INVOKABLE void clearLogs(); + Q_INVOKABLE void copyToClipboard(const QString& text); Q_INVOKABLE QString getBalance(const QString& addressHex); Q_INVOKABLE QString transferFunds( const QString& fromKeyHex, @@ -55,14 +59,16 @@ public slots: signals: void statusChanged(); - void configPathChanged(); + void userConfigChanged(); + void deploymentConfigChanged(); void knownAddressesChanged(); private: void setStatus(BlockchainStatus newStatus); BlockchainStatus m_status; - QString m_configPath; + QString m_userConfig; + QString m_deploymentConfig; LogModel* m_logModel; QStringList m_knownAddresses; diff --git a/src/qml/BlockchainView.qml b/src/qml/BlockchainView.qml index 5b2b8f7..ff9e6bd 100644 --- a/src/qml/BlockchainView.qml +++ b/src/qml/BlockchainView.qml @@ -64,15 +64,17 @@ Rectangle { Layout.preferredWidth: parent.width / 2 statusText: _d.getStatusString(backend.status) statusColor: _d.getStatusColor(backend.status) - configPath: backend.configPath - canStart: !!backend.configPath + userConfig: backend.userConfig + deploymentConfig: backend.deploymentConfig + canStart: !!backend.userConfig && backend.status !== BlockchainBackend.Starting && backend.status !== BlockchainBackend.Stopping isRunning: backend.status === BlockchainBackend.Running onStartRequested: backend.startBlockchain() onStopRequested: backend.stopBlockchain() - onChangeConfigRequested: fileDialog.open() + onChangeUserConfigRequested: userConfigFileDialog.open() + onChangeDeploymentConfigRequested: deploymentConfigFileDialog.open() } WalletView { @@ -96,16 +98,23 @@ Rectangle { logModel: backend.logModel onClearRequested: backend.clearLogs() + onCopyToClipboard: (text) => backend.copyToClipboard(text) } } FileDialog { - id: fileDialog + id: userConfigFileDialog modality: Qt.NonModal nameFilters: ["YAML files (*.yaml)"] currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] - onAccepted: { - backend.configPath = selectedFile - } + onAccepted: backend.userConfig = selectedFile + } + + FileDialog { + id: deploymentConfigFileDialog + modality: Qt.NonModal + nameFilters: ["YAML files (*.yaml)"] + currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] + onAccepted: backend.deploymentConfig = selectedFile } } diff --git a/src/qml/views/LogsView.qml b/src/qml/views/LogsView.qml index cfbe351..a453bfa 100644 --- a/src/qml/views/LogsView.qml +++ b/src/qml/views/LogsView.qml @@ -12,6 +12,7 @@ Control { required property var logModel // LogModel (QAbstractListModel with "text" role) signal clearRequested() + signal copyToClipboard(string text) background: Rectangle { color: Theme.palette.background @@ -60,11 +61,18 @@ Control { model: root.logModel spacing: 2 - delegate: LogosText { + delegate: ItemDelegate{ width: ListView.view.width - text: model.text - font.pixelSize: Theme.typography.secondaryText - wrapMode: Text.Wrap + contentItem: LogosText { + text: model.text + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.Wrap + } + background: Rectangle { + color: hovered ? Theme.palette.background: "transparent" + radius: 2 + } + onClicked: root.copyToClipboard(model.text) } LogosText { diff --git a/src/qml/views/StatusConfigView.qml b/src/qml/views/StatusConfigView.qml index b00dcb0..455543f 100644 --- a/src/qml/views/StatusConfigView.qml +++ b/src/qml/views/StatusConfigView.qml @@ -11,13 +11,15 @@ ColumnLayout { // --- Public API --- required property string statusText required property color statusColor - required property string configPath + required property string userConfig + required property string deploymentConfig required property bool canStart required property bool isRunning signal startRequested() signal stopRequested() - signal changeConfigRequested() + signal changeUserConfigRequested() + signal changeDeploymentConfigRequested() spacing: Theme.spacing.large @@ -71,14 +73,15 @@ ColumnLayout { Rectangle { Layout.preferredWidth: parent.width * 0.9 Layout.preferredHeight: implicitHeight - implicitHeight: configContent.implicitHeight + 2 * Theme.spacing.large + implicitHeight: contentLayout.implicitHeight + 2 * Theme.spacing.large + color: Theme.palette.backgroundTertiary radius: Theme.spacing.radiusLarge border.color: Theme.palette.border border.width: 1 ColumnLayout { - id: configContent + id: contentLayout anchors.left: parent.left anchors.right: parent.right @@ -86,29 +89,55 @@ ColumnLayout { anchors.margins: Theme.spacing.large spacing: Theme.spacing.medium - LogosText { - text: qsTr("Current Config: ") - font.bold: true + // User Config Card + ConfigSelectionPanel { + id: userConfigContent + + title: qsTr("User Config: ") + configPath: root.userConfig || qsTr("No file selected") + onChangeRequested: root.changeUserConfigRequested() } - LogosText { - Layout.fillWidth: true - Layout.topMargin: -Theme.spacing.medium - text: root.configPath || qsTr("No file selected") - font.pixelSize: Theme.typography.secondaryText - color: Theme.palette.textSecondary - wrapMode: Text.WordWrap - } + // Deployment Config Card + ConfigSelectionPanel { + id: deploymentConfigContent - LogosButton { - Layout.alignment: Qt.AlignHCenter - Layout.preferredWidth: parent.width - Layout.preferredHeight: 50 - text: qsTr("Change") - onClicked: root.changeConfigRequested() + title: qsTr("Deployment Config: ") + configPath: root.deploymentConfig || qsTr("No file selected") + onChangeRequested: root.changeDeploymentConfigRequested() } } } Item { Layout.fillHeight: true } + + component ConfigSelectionPanel: ColumnLayout { + property string title + property string configPath + signal changeRequested() + + spacing: Theme.spacing.medium + + LogosText { + text: title + font.bold: true + } + + LogosText { + Layout.fillWidth: true + Layout.topMargin: -Theme.spacing.small + text: configPath + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + + LogosButton { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.preferredHeight: 50 + text: qsTr("Change") + onClicked: changeRequested() + } + } } diff --git a/src/qml/views/WalletView.qml b/src/qml/views/WalletView.qml index a0250d1..c954e15 100644 --- a/src/qml/views/WalletView.qml +++ b/src/qml/views/WalletView.qml @@ -50,80 +50,9 @@ ColumnLayout { } // Dropdown of known addresses, or type a custom address - ComboBox { + StyledAddressComboBox { id: balanceAddressCombo - - Layout.fillWidth: true - padding: Theme.spacing.large - - editable: true model: knownAddresses - font.pixelSize: Theme.typography.secondaryText - - background: Rectangle { - color: Theme.palette.backgroundTertiary - radius: Theme.spacing.radiusLarge - border.color: Theme.palette.border - border.width: 1 - } - indicator: LogosText { - id: indicatorText - text: "▼" - font.pixelSize: Theme.typography.secondaryText - color: Theme.palette.textSecondary - x: balanceAddressCombo.width - width - Theme.spacing.small - y: (balanceAddressCombo.height - height) / 2 - visible: balanceAddressCombo.count > 0 - } - contentItem: LogosText { - width: parent.width - indicatorText.width - Theme.spacing.large - font.pixelSize: Theme.typography.secondaryText - font.bold: true - text: balanceAddressCombo.displayText - elide: Text.ElideRight - } - delegate: ItemDelegate { - id: delegate - - width: balanceAddressCombo.width - contentItem: LogosText { - width: parent.width - height: contentHeight + Theme.spacing.large - font.pixelSize: Theme.typography.secondaryText - font.bold: true - text: modelData - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - background: Rectangle { - color: delegate.highlighted ? - Theme.palette.backgroundTertiary: - Theme.palette.backgroundSecondary - } - highlighted: balanceAddressCombo.highlightedIndex === index - } - popup: Popup { - y: balanceAddressCombo.height - 1 - width: balanceAddressCombo.width - height: contentItem.implicitHeight + 100 - padding: 1 - - contentItem: ListView { - clip: true - implicitHeight: contentHeight - model: balanceAddressCombo.popup.visible ? balanceAddressCombo.delegateModel : null - ScrollIndicator.vertical: ScrollIndicator { } - highlightFollowsCurrentItem: false - } - - background: Rectangle { - color: Theme.palette.backgroundSecondary - border.color: Theme.palette.border - border.width: 1 - radius: Theme.spacing.radiusLarge - } - } } RowLayout { @@ -169,7 +98,7 @@ ColumnLayout { anchors.left: parent.left anchors.right: parent.right anchors.margins: Theme.spacing.large - spacing: Theme.spacing.large + spacing: Theme.spacing.small LogosText { text: qsTr("Transfer funds") @@ -177,18 +106,22 @@ ColumnLayout { font.bold: true } - CustomTextFeild { - id: transferFromField - placeholderText: qsTr("From key (64 hex chars)") + StyledAddressComboBox { + id: transferFromCombo + model: knownAddresses } - CustomTextFeild { + LogosTextField { id: transferToField + Layout.fillWidth: true + Layout.preferredHeight: 30 placeholderText: qsTr("To key (64 hex chars)") } - CustomTextFeild { + LogosTextField { id: transferAmountField + Layout.fillWidth: true + Layout.preferredHeight: 30 placeholderText: qsTr("Amount") } @@ -201,7 +134,7 @@ ColumnLayout { id: transferButton text: qsTr("Transfer") Layout.alignment: Qt.AlignRight - onClicked: root.transferRequested(transferFromField.text, transferToField.text, transferAmountField.text) + onClicked: root.transferRequested(transferFromCombo.currentText.trim(), transferToField.text.trim(), transferAmountField.text) } LogosButton { @@ -226,18 +159,100 @@ ColumnLayout { Layout.preferredHeight: Theme.spacing.small } - component CustomTextFeild: TextField { - id: textField + component StyledAddressComboBox: ComboBox { + id: comboControl + Layout.fillWidth: true - placeholderTextColor: Theme.palette.textMuted + padding: Theme.spacing.large + editable: true font.pixelSize: Theme.typography.secondaryText background: Rectangle { - radius: Theme.spacing.radiusSmall - color: Theme.palette.backgroundSecondary - border.color: textField.activeFocus ? - Theme.palette.overlayOrange : - Theme.palette.backgroundElevated + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge + border.color: Theme.palette.border + border.width: 1 + } + indicator: LogosText { + id: comboIndicator + text: "▼" + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + x: comboControl.width - width - Theme.spacing.small + y: (comboControl.height - height) / 2 + visible: comboControl.count > 0 + } + contentItem: Item { + implicitWidth: comboControl.availableWidth + implicitHeight: 30 + + TextField { + id: comboTextField + anchors.fill: parent + leftPadding: 0 + rightPadding: comboControl.count > 0 ? comboIndicator.width + Theme.spacing.small : Theme.spacing.small + topPadding: 0 + bottomPadding: 0 + verticalAlignment: Text.AlignVCenter + font.pixelSize: Theme.typography.secondaryText + font.bold: true + text: comboControl.editText + onTextChanged: if (text !== comboControl.editText) comboControl.editText = text + selectByMouse: true + color: Theme.palette.text + background: Item { } + } + MouseArea { + anchors.fill: parent + visible: comboControl.count > 0 + z: 1 + onPressed: { + comboControl.popup.visible ? comboControl.popup.close() : comboControl.popup.open() + } + } + } + delegate: ItemDelegate { + id: comboDelegate + width: comboControl.width + contentItem: LogosText { + width: parent.width + height: contentHeight + Theme.spacing.large + font.pixelSize: Theme.typography.secondaryText + font.bold: true + text: modelData + elide: Text.ElideMiddle + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + background: Rectangle { + color: comboDelegate.highlighted ? + Theme.palette.backgroundTertiary : + Theme.palette.backgroundSecondary + } + highlighted: comboControl.highlightedIndex === index + } + popup: Popup { + y: comboControl.height - 1 + width: comboControl.width + height: contentItem.implicitHeight + padding: 1 + + onOpened: if (comboControl.count === 0) close() + + contentItem: ListView { + clip: true + implicitHeight: contentHeight + model: comboControl.popup.visible ? comboControl.delegateModel : null + ScrollIndicator.vertical: ScrollIndicator { } + highlightFollowsCurrentItem: false + } + + background: Rectangle { + color: Theme.palette.backgroundSecondary + border.color: Theme.palette.border + border.width: 1 + radius: Theme.spacing.radiusLarge + } } } }