mirror of
https://github.com/logos-blockchain/logos-blockchain-ui.git
synced 2026-04-01 17:03:31 +00:00
Merge pull request #13 from logos-blockchain/feat/addCopyButton
feat: add copy button to addresses
This commit is contained in:
commit
9f6c0a5e91
@ -85,6 +85,8 @@ endif()
|
||||
|
||||
# Source files
|
||||
set(SOURCES
|
||||
src/AccountsModel.cpp
|
||||
src/AccountsModel.h
|
||||
src/BlockchainPlugin.cpp
|
||||
src/BlockchainPlugin.h
|
||||
src/BlockchainBackend.cpp
|
||||
|
||||
73
src/AccountsModel.cpp
Normal file
73
src/AccountsModel.cpp
Normal file
@ -0,0 +1,73 @@
|
||||
#include "AccountsModel.h"
|
||||
|
||||
int AccountsModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return m_entries.size();
|
||||
}
|
||||
|
||||
QVariant AccountsModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size())
|
||||
return QVariant();
|
||||
const Entry& e = m_entries.at(index.row());
|
||||
switch (role) {
|
||||
case AddressRole:
|
||||
return e.address;
|
||||
case BalanceRole:
|
||||
return e.balance;
|
||||
case Qt::DisplayRole:
|
||||
return e.address;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> AccountsModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> names;
|
||||
names[AddressRole] = "address";
|
||||
names[BalanceRole] = "balance";
|
||||
return names;
|
||||
}
|
||||
|
||||
void AccountsModel::setAddresses(const QStringList& addresses)
|
||||
{
|
||||
QHash<QString, QString> balanceCache;
|
||||
for (const Entry& e : m_entries)
|
||||
balanceCache.insert(e.address, e.balance);
|
||||
|
||||
QVector<Entry> newEntries;
|
||||
newEntries.reserve(addresses.size());
|
||||
for (const QString& addr : addresses) {
|
||||
Entry e;
|
||||
e.address = addr;
|
||||
e.balance = balanceCache.value(addr, QStringLiteral("---"));
|
||||
newEntries.append(e);
|
||||
}
|
||||
|
||||
if (m_entries == newEntries)
|
||||
return;
|
||||
|
||||
beginResetModel();
|
||||
m_entries = std::move(newEntries);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void AccountsModel::setBalanceForAddress(const QString& address, const QString& balance)
|
||||
{
|
||||
const QString valueToSet = balance.trimmed().startsWith(QStringLiteral("Error"))
|
||||
? QStringLiteral("---")
|
||||
: balance;
|
||||
for (int i = 0; i < m_entries.size(); ++i) {
|
||||
if (m_entries[i].address == address) {
|
||||
if (m_entries[i].balance != valueToSet) {
|
||||
m_entries[i].balance = valueToSet;
|
||||
const QModelIndex idx = index(i, 0);
|
||||
emit dataChanged(idx, idx, { BalanceRole });
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/AccountsModel.h
Normal file
31
src/AccountsModel.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
class AccountsModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Roles { AddressRole = Qt::UserRole + 1, BalanceRole };
|
||||
|
||||
explicit AccountsModel(QObject* parent = nullptr) : QAbstractListModel(parent) {}
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
void setAddresses(const QStringList& addresses);
|
||||
Q_INVOKABLE void setBalanceForAddress(const QString& address, const QString& balance);
|
||||
|
||||
private:
|
||||
struct Entry {
|
||||
QString address;
|
||||
QString balance;
|
||||
bool operator==(const Entry& other) const {
|
||||
return address == other.address && balance == other.balance;
|
||||
}
|
||||
};
|
||||
QVector<Entry> m_entries;
|
||||
};
|
||||
@ -7,6 +7,7 @@
|
||||
#include <QGuiApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QModelIndex>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
@ -26,6 +27,7 @@ BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent)
|
||||
m_userConfig(""),
|
||||
m_deploymentConfig(""),
|
||||
m_logModel(new LogModel(this)),
|
||||
m_accountsModel(new AccountsModel(this)),
|
||||
m_logosAPI(nullptr),
|
||||
m_blockchainClient(nullptr)
|
||||
{
|
||||
@ -122,11 +124,15 @@ void BlockchainBackend::copyToClipboard(const QString& text)
|
||||
|
||||
QString BlockchainBackend::getBalance(const QString& addressHex)
|
||||
{
|
||||
QString result;
|
||||
if (!m_blockchainClient) {
|
||||
return QStringLiteral("Error: Module not initialized.");
|
||||
result = QStringLiteral("Error: Module not initialized.");
|
||||
} else {
|
||||
QVariant v = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex);
|
||||
result = v.isValid() ? v.toString() : QStringLiteral("Error: Call failed.");
|
||||
}
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex);
|
||||
return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed.");
|
||||
m_accountsModel->setBalanceForAddress(addressHex, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString BlockchainBackend::transferFunds(const QString& fromKeyHex, const QString& toKeyHex, const QString& amountStr)
|
||||
@ -160,7 +166,7 @@ void BlockchainBackend::startBlockchain()
|
||||
|
||||
if (resultCode == 0 || resultCode == 1) {
|
||||
setStatus(Running);
|
||||
QTimer::singleShot(500, this, [this]() { refreshKnownAddresses(); });
|
||||
QTimer::singleShot(500, this, [this]() { refreshAccounts(); });
|
||||
} else if (resultCode == 2) {
|
||||
setStatus(ErrorConfigMissing);
|
||||
} else if (resultCode == 3) {
|
||||
@ -170,15 +176,25 @@ void BlockchainBackend::startBlockchain()
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::refreshKnownAddresses()
|
||||
void BlockchainBackend::refreshAccounts()
|
||||
{
|
||||
if (!m_blockchainClient) return;
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses");
|
||||
QStringList list = result.isValid() && result.canConvert<QStringList>() ? result.toStringList() : QStringList();
|
||||
qDebug() << "BlockchainBackend: received from blockchain lib: type=QStringList, count=" << list.size();
|
||||
if (m_knownAddresses != list) {
|
||||
m_knownAddresses = std::move(list);
|
||||
emit knownAddressesChanged();
|
||||
m_accountsModel->setAddresses(list);
|
||||
QTimer::singleShot(0, this, [this, list]() { fetchBalancesForAccounts(list); });
|
||||
}
|
||||
|
||||
void BlockchainBackend::fetchBalancesForAccounts(const QStringList& list)
|
||||
{
|
||||
if (!m_blockchainClient) return;
|
||||
const int n = list.size();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const QString address = list[i];
|
||||
if (address.isEmpty()) continue;
|
||||
const QString balance = getBalance(address);
|
||||
m_accountsModel->setBalanceForAddress(address, balance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <QVariant>
|
||||
#include "logos_api.h"
|
||||
#include "logos_api_client.h"
|
||||
#include "AccountsModel.h"
|
||||
#include "LogModel.h"
|
||||
|
||||
class BlockchainBackend : public QObject {
|
||||
@ -32,7 +33,7 @@ public:
|
||||
Q_PROPERTY(QString deploymentConfig READ deploymentConfig WRITE setDeploymentConfig NOTIFY deploymentConfigChanged)
|
||||
Q_PROPERTY(bool useGeneratedConfig READ useGeneratedConfig WRITE setUseGeneratedConfig NOTIFY useGeneratedConfigChanged)
|
||||
Q_PROPERTY(LogModel* logModel READ logModel CONSTANT)
|
||||
Q_PROPERTY(QStringList knownAddresses READ knownAddresses NOTIFY knownAddressesChanged)
|
||||
Q_PROPERTY(AccountsModel* accountsModel READ accountsModel CONSTANT)
|
||||
Q_PROPERTY(QString generatedUserConfigPath READ generatedUserConfigPath CONSTANT)
|
||||
|
||||
explicit BlockchainBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
|
||||
@ -43,7 +44,7 @@ public:
|
||||
QString deploymentConfig() const { return m_deploymentConfig; }
|
||||
bool useGeneratedConfig() const { return m_useGeneratedConfig; }
|
||||
LogModel* logModel() const { return m_logModel; }
|
||||
QStringList knownAddresses() const { return m_knownAddresses; }
|
||||
AccountsModel* accountsModel() const { return m_accountsModel; }
|
||||
|
||||
void setUserConfig(const QString& path);
|
||||
void setDeploymentConfig(const QString& path);
|
||||
@ -57,7 +58,7 @@ public:
|
||||
const QString& amountStr);
|
||||
Q_INVOKABLE void startBlockchain();
|
||||
Q_INVOKABLE void stopBlockchain();
|
||||
Q_INVOKABLE void refreshKnownAddresses();
|
||||
Q_INVOKABLE void refreshAccounts();
|
||||
Q_INVOKABLE int generateConfig(const QString& outputPath,
|
||||
const QStringList& initialPeers,
|
||||
int netPort,
|
||||
@ -78,17 +79,17 @@ signals:
|
||||
void userConfigChanged();
|
||||
void deploymentConfigChanged();
|
||||
void useGeneratedConfigChanged();
|
||||
void knownAddressesChanged();
|
||||
|
||||
private:
|
||||
void setStatus(BlockchainStatus newStatus);
|
||||
void fetchBalancesForAccounts(const QStringList& list);
|
||||
|
||||
BlockchainStatus m_status;
|
||||
QString m_userConfig;
|
||||
QString m_deploymentConfig;
|
||||
bool m_useGeneratedConfig = false;
|
||||
LogModel* m_logModel;
|
||||
QStringList m_knownAddresses;
|
||||
AccountsModel* m_accountsModel;
|
||||
|
||||
LogosAPI* m_logosAPI;
|
||||
LogosAPIClient* m_blockchainClient;
|
||||
|
||||
@ -8,6 +8,11 @@
|
||||
<file>qml/views/GenerateConfigView.qml</file>
|
||||
<file>qml/views/ConfigChoiceView.qml</file>
|
||||
<file>qml/views/SetConfigPathView.qml</file>
|
||||
<file>qml/controls/AccountDelegate.qml</file>
|
||||
<file>qml/controls/LogosCopyButton.qml</file>
|
||||
<file>icons/blockchain.png</file>
|
||||
<file>icons/copy.svg</file>
|
||||
<file>icons/checkmark.svg</file>
|
||||
<file>icons/refresh.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
1
src/icons/checkmark.svg
Normal file
1
src/icons/checkmark.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" height="17" viewBox="0 0 16 17" width="16" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m6.76413 10.1901 5.56067-5.41122c.382-.37171 1-.37297 1.3863.00299.3837.37336.3857.97674.0031 1.34906l-6.25845 6.09017c-.18993.1849-.43822.2781-.68737.2789-.25655-.0019-.50554-.0937-.69254-.2756l-2.79161-2.7166c-.38013-.36991-.37994-.96985.00641-1.34581.38367-.37336 1.00799-.37115 1.38299-.00623z" fill="#000" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 462 B |
1
src/icons/copy.svg
Normal file
1
src/icons/copy.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path d="m4.16634 7c.27614 0 .5-.22386.5-.5s-.22386-.5-.5-.5h-.16667c-1.47275 0-2.66666 1.19391-2.66666 2.66667v3.33333c0 1.4728 1.19391 2.6667 2.66666 2.6667h3.33334c1.47276 0 2.66666-1.1939 2.66666-2.6667v-.1667c0-.2761-.22385-.5-.5-.5-.27614 0-.5.2239-.5.5v.1667c0 .9205-.74619 1.6667-1.66666 1.6667h-3.33334c-.92047 0-1.66666-.7462-1.66666-1.6667v-3.33333c0-.92048.74619-1.66667 1.66666-1.66667z"/><path clip-rule="evenodd" d="m5.99967 4c0-1.47276 1.19391-2.66666 2.66667-2.66666h3.33336c1.4727 0 2.6666 1.1939 2.6666 2.66666v3.33334c0 1.47276-1.1939 2.66666-2.6666 2.66666h-3.33336c-1.47276 0-2.66667-1.1939-2.66667-2.66666zm2.66667-1.66666h3.33336c.9204 0 1.6666.74619 1.6666 1.66666v3.33334c0 .92047-.7462 1.66666-1.6666 1.66666h-3.33336c-.92047 0-1.66667-.74619-1.66667-1.66666v-3.33334c0-.92047.7462-1.66666 1.66667-1.66666z" fill-rule="evenodd"/></g></svg>
|
||||
|
After Width: | Height: | Size: 976 B |
1
src/icons/refresh.svg
Normal file
1
src/icons/refresh.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m12 6.25c4.2802 0 7.75 3.46979 7.75 7.75 0 4.2802-3.4698 7.75-7.75 7.75-4.28021 0-7.75-3.4698-7.75-7.75 0-.4142.33579-.75.75-.75s.75.3358.75.75c0 3.4518 2.79822 6.25 6.25 6.25 3.4518 0 6.25-2.7982 6.25-6.25s-2.7982-6.25-6.25-6.25h-.9822c-.4455 0-.6686.53857-.3536.85355l1.8661 1.86615c.2929.2929.2929.7677 0 1.0606s-.7677.2929-1.0606 0l-4.00003-3.99997c-.29289-.29289-.29289-.76777 0-1.06066l4.00003-4c.2929-.29289.7677-.29289 1.0606 0s.2929.76777 0 1.06066l-1.8661 1.86612c-.315.31498-.0919.85355.3536.85355z" fill="#000"/></svg>
|
||||
|
After Width: | Height: | Size: 634 B |
@ -90,12 +90,13 @@ Rectangle {
|
||||
SplitView {
|
||||
orientation: Qt.Vertical
|
||||
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumHeight: 200
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
StatusConfigView {
|
||||
Layout.preferredWidth: parent.width / 2
|
||||
Layout.fillWidth: true
|
||||
statusText: _d.getStatusString(backend.status)
|
||||
statusColor: _d.getStatusColor(backend.status)
|
||||
userConfig: backend.userConfig
|
||||
@ -113,16 +114,28 @@ Rectangle {
|
||||
|
||||
WalletView {
|
||||
id: walletView
|
||||
Layout.preferredWidth: parent.width / 2
|
||||
knownAddresses: backend.knownAddresses
|
||||
accountsModel: backend.accountsModel
|
||||
|
||||
onGetBalanceRequested: function(addressHex) {
|
||||
walletView.setBalanceResult(backend.getBalance(addressHex))
|
||||
var result = backend.getBalance(addressHex)
|
||||
if ((result || "").indexOf("Error") === 0) {
|
||||
lastBalanceErrorAddress = addressHex
|
||||
lastBalanceError = result
|
||||
}
|
||||
else {
|
||||
lastBalanceErrorAddress = ""
|
||||
lastBalanceError = ""
|
||||
}
|
||||
}
|
||||
onCopyToClipboard: (text) => backend.copyToClipboard(text)
|
||||
onTransferRequested: function(fromKeyHex, toKeyHex, amount) {
|
||||
walletView.setTransferResult(backend.transferFunds(fromKeyHex, toKeyHex, amount))
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredHeight: Theme.spacing.small
|
||||
}
|
||||
}
|
||||
|
||||
LogsView {
|
||||
|
||||
76
src/qml/controls/AccountDelegate.qml
Normal file
76
src/qml/controls/AccountDelegate.qml
Normal file
@ -0,0 +1,76 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
ItemDelegate {
|
||||
id: root
|
||||
|
||||
property string balanceError: ""
|
||||
|
||||
signal getBalanceRequested(string addressHex)
|
||||
signal copyRequested(string text)
|
||||
|
||||
width: ListView.view ? ListView.view.width : implicitWidth
|
||||
|
||||
background: Rectangle {
|
||||
color: root.hovered ? Theme.palette.backgroundSecondary : "transparent"
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: model.address || ""
|
||||
elide: Text.ElideMiddle
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.preferredWidth: contentWidth
|
||||
Layout.alignment: Qt.AlignRight
|
||||
visible: (model.balance || "").length > 0
|
||||
text: model.balance || ""
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.leftMargin: parent.spacing
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 40
|
||||
display: AbstractButton.IconOnly
|
||||
flat: true
|
||||
icon.source: "qrc:/icons/refresh.svg"
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
padding: 4
|
||||
onClicked: root.getBalanceRequested(model.address || "")
|
||||
}
|
||||
|
||||
LogosCopyButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 40
|
||||
onCopyText: root.copyRequested(model.address || "")
|
||||
}
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: !!text
|
||||
text: root.balanceError || ""
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.error
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/qml/controls/LogosCopyButton.qml
Normal file
36
src/qml/controls/LogosCopyButton.qml
Normal file
@ -0,0 +1,36 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
Button {
|
||||
id: root
|
||||
|
||||
signal copyText()
|
||||
|
||||
implicitWidth: 24
|
||||
implicitHeight: 24
|
||||
display: AbstractButton.IconOnly
|
||||
flat: true
|
||||
|
||||
property string iconSource: "qrc:/icons/copy.svg"
|
||||
|
||||
icon.source: root.iconSource
|
||||
icon.width: 24
|
||||
icon.height: 24
|
||||
|
||||
function reset() {
|
||||
iconSource = "qrc:/icons/copy.svg"
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: resetTimer
|
||||
interval: 1500
|
||||
repeat: false
|
||||
onTriggered: root.reset()
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
root.copyText()
|
||||
root.iconSource = "qrc:/icons/checkmark.svg"
|
||||
resetTimer.restart()
|
||||
}
|
||||
}
|
||||
@ -94,6 +94,7 @@ ColumnLayout {
|
||||
placeholderText: qsTr("Peer addresses, one per line")
|
||||
placeholderTextColor: Theme.palette.textTertiary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.text
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import QtQuick.Layouts
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
ColumnLayout {
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
// --- Public API ---
|
||||
@ -21,124 +21,108 @@ ColumnLayout {
|
||||
signal stopRequested()
|
||||
signal changeConfigRequested()
|
||||
|
||||
spacing: Theme.spacing.large
|
||||
implicitHeight: contentLayout.height + Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
// Status Card
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.preferredWidth: parent.width * 0.9
|
||||
Layout.preferredHeight: implicitHeight
|
||||
implicitHeight: statusContent.implicitHeight + 2 * Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
|
||||
ColumnLayout {
|
||||
id: statusContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacing.large
|
||||
// Status Card
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosText {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
font.bold: true
|
||||
text: root.statusText
|
||||
color: root.statusColor
|
||||
ColumnLayout {
|
||||
LogosText {
|
||||
font.bold: true
|
||||
text: root.statusText
|
||||
color: root.statusColor
|
||||
}
|
||||
LogosText {
|
||||
text: qsTr("Mainnet - chain ID 1")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.topMargin: -Theme.spacing.medium
|
||||
text: qsTr("Mainnet - chain ID 1")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 50
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 100
|
||||
enabled: root.canStart
|
||||
text: root.isRunning ? qsTr("Stop Node") : qsTr("Start Node")
|
||||
onClicked: root.isRunning ? root.stopRequested() : root.startRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Config Card
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width * 0.9
|
||||
Layout.preferredHeight: implicitHeight
|
||||
implicitHeight: contentLayout.implicitHeight + 2 * Theme.spacing.large
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 1
|
||||
Layout.fillHeight: true
|
||||
color: Theme.palette.borderSecondary
|
||||
}
|
||||
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: contentLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacing.large
|
||||
// Config Card
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Config")
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
LogosText {
|
||||
text: qsTr("User Config: ")
|
||||
font.bold: true
|
||||
}
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: (root.userConfig || qsTr("No file selected")) +
|
||||
(root.useGeneratedConfig ? " " + qsTr("(Generated)") : "")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -Theme.spacing.small
|
||||
spacing: Theme.spacing.small
|
||||
LogosText {
|
||||
text: qsTr("Deployment Config: ")
|
||||
font.bold: true
|
||||
}
|
||||
LogosText {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
text: (root.useGeneratedConfig && root.deploymentConfig ? root.deploymentConfig :
|
||||
root.useGeneratedConfig ? qsTr("Devnet (default)") :
|
||||
(root.deploymentConfig || qsTr("No file selected")))
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
spacing: Theme.spacing.small
|
||||
LogosText {
|
||||
text: qsTr("User Config: ")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: (root.userConfig || qsTr("No file selected")) +
|
||||
(root.useGeneratedConfig ? " " + qsTr("(Generated)") : "")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -Theme.spacing.small
|
||||
spacing: Theme.spacing.small
|
||||
LogosText {
|
||||
text: qsTr("Deployment Config: ")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: (root.useGeneratedConfig && root.deploymentConfig ?
|
||||
root.deploymentConfig :
|
||||
root.useGeneratedConfig ?
|
||||
qsTr("Devnet (default)") :
|
||||
(root.deploymentConfig || qsTr("No file selected")))
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 50
|
||||
Layout.preferredWidth: 100
|
||||
Layout.preferredHeight: 40
|
||||
text: qsTr("Change")
|
||||
onClicked: root.changeConfigRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
|
||||
@ -5,20 +5,20 @@ import QtQuick.Layouts
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
ColumnLayout {
|
||||
import "../controls"
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
// list of known wallet addresses for Get balance dropdown
|
||||
property var knownAddresses: []
|
||||
required property var accountsModel
|
||||
|
||||
property string lastBalanceError: ""
|
||||
property string lastBalanceErrorAddress: ""
|
||||
|
||||
// --- Public API ---
|
||||
signal getBalanceRequested(string addressHex)
|
||||
signal transferRequested(string fromKeyHex, string toKeyHex, string amount)
|
||||
signal copyToClipboard(string text)
|
||||
|
||||
// Call these from the parent to display results
|
||||
function setBalanceResult(text) {
|
||||
balanceResultText.text = text
|
||||
}
|
||||
function setTransferResult(text) {
|
||||
transferResultText.text = text
|
||||
}
|
||||
@ -28,8 +28,8 @@ ColumnLayout {
|
||||
// Get balance card
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: balanceCol.implicitHeight + 2 * Theme.spacing.large
|
||||
Layout.preferredHeight: implicitHeight
|
||||
implicitHeight: transferRect.height
|
||||
Layout.preferredHeight: Math.min(implicitHeight, 400)
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
@ -44,40 +44,32 @@ ColumnLayout {
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Get balance")
|
||||
text: qsTr("Accounts")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
// Dropdown of known addresses, or type a custom address
|
||||
StyledAddressComboBox {
|
||||
id: balanceAddressCombo
|
||||
model: knownAddresses
|
||||
LogosText {
|
||||
text: qsTr("Start node to see accounts here.")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
visible: balanceListView.count === 0
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
ListView {
|
||||
id: balanceListView
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: balanceButton.implicitHeight
|
||||
spacing: Theme.spacing.large
|
||||
Layout.preferredHeight: Math.min(contentHeight, 320)
|
||||
clip: true
|
||||
model: root.accountsModel
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosButton {
|
||||
id: balanceButton
|
||||
text: qsTr("Get balance")
|
||||
onClicked: root.getBalanceRequested(balanceAddressCombo.currentText.trim())
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
enabled: false
|
||||
padding: Theme.spacing.medium
|
||||
contentItem: Text {
|
||||
id: balanceResultText
|
||||
width: parent.width
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.weight: Theme.typography.weightMedium
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
delegate: AccountDelegate {
|
||||
balanceError: root.lastBalanceErrorAddress === model.address ?
|
||||
root.lastBalanceError: ""
|
||||
onGetBalanceRequested: (addr) => root.getBalanceRequested(addr)
|
||||
onCopyRequested: (text) => root.copyToClipboard(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -85,6 +77,7 @@ ColumnLayout {
|
||||
|
||||
// Transfer funds card
|
||||
Rectangle {
|
||||
id: transferRect
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: transferCol.height + 2 * Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
@ -108,7 +101,8 @@ ColumnLayout {
|
||||
|
||||
StyledAddressComboBox {
|
||||
id: transferFromCombo
|
||||
model: knownAddresses
|
||||
model: root.accountsModel
|
||||
textRole: "address"
|
||||
}
|
||||
|
||||
LogosTextField {
|
||||
@ -128,37 +122,44 @@ ColumnLayout {
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: transferButton.implicitHeight
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
LogosButton {
|
||||
id: transferButton
|
||||
text: qsTr("Transfer")
|
||||
Layout.preferredWidth: 60
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: qsTr("Send")
|
||||
onClicked: root.transferRequested(transferFromCombo.currentText.trim(), transferToField.text.trim(), transferAmountField.text)
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.fillWidth: true
|
||||
enabled: false
|
||||
padding: Theme.spacing.medium
|
||||
contentItem: Text {
|
||||
id: transferResultText
|
||||
enabled: true
|
||||
padding: Theme.spacing.small
|
||||
contentItem: RowLayout {
|
||||
width: parent.width
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.weight: Theme.typography.weightMedium
|
||||
wrapMode: Text.WordWrap
|
||||
anchors.centerIn: parent
|
||||
LogosText {
|
||||
id: transferResultText
|
||||
Layout.fillWidth: true
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.weight: Theme.typography.weightMedium
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
LogosCopyButton {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 40
|
||||
onCopyText: root.copyRequested(transferResultText.text)
|
||||
visible: transferResultText.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Theme.spacing.small
|
||||
}
|
||||
|
||||
component StyledAddressComboBox: ComboBox {
|
||||
id: comboControl
|
||||
|
||||
@ -195,7 +196,6 @@ ColumnLayout {
|
||||
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
|
||||
@ -219,7 +219,7 @@ ColumnLayout {
|
||||
height: contentHeight + Theme.spacing.large
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
text: modelData
|
||||
text: (typeof model.address !== "undefined" ? model.address : modelData) || ""
|
||||
elide: Text.ElideMiddle
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user