mirror of
https://github.com/wownero/lwcli-wow.git
synced 2026-10-17 06:51:25 +02:00
Merge remote-tracking branch 'upstream/main'
# Conflicts: # external/lwsf
This commit is contained in:
Vendored
+1
-1
Submodule external/lwsf updated: bd58c505e0...db85219a97
@@ -26,6 +26,7 @@
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctime>
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/event.hpp>
|
||||
@@ -196,8 +197,6 @@ namespace lwcli { namespace component
|
||||
row.Decorate(ftxui::focus);
|
||||
}
|
||||
}
|
||||
else
|
||||
selected_ = -1;
|
||||
|
||||
if (selected_ != highlighted_ && min_row() <= highlighted_ && highlighted_ < rows_)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
@@ -289,6 +290,7 @@ int main(int, const char* argv[])
|
||||
|
||||
auto window = ftxui::CatchEvent(lwcli::view::manager(std::move(wm), std::move(prog.file)), [&] (ftxui::Event event)
|
||||
{
|
||||
if (event != lwcli::event::refresh_wallet)
|
||||
state.last_event = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
if (event == ftxui::Event::CtrlC)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "wallet.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/event.hpp>
|
||||
#include <ftxui/dom/table.hpp>
|
||||
|
||||
+127
-110
@@ -26,6 +26,7 @@
|
||||
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/event.hpp>
|
||||
@@ -60,37 +61,72 @@ namespace lwcli { namespace view
|
||||
Monero::TransactionHistory* const history_;
|
||||
Monero::TransactionInfo const* info_;
|
||||
std::string note_;
|
||||
std::string hash_;
|
||||
std::vector<Monero::TransactionInfo::Transfer> transfers_;
|
||||
ftxui::Component note_input_;
|
||||
const ftxui::Component cancel_;
|
||||
ftxui::Component ok_;
|
||||
ftxui::Component buttons_;
|
||||
ftxui::Component container_;
|
||||
ftxui::Element title_;
|
||||
ftxui::Elements timestamp_;
|
||||
ftxui::Elements payment_id_;
|
||||
ftxui::Elements amount_;
|
||||
ftxui::Elements fee_;
|
||||
ftxui::Elements minors_;
|
||||
ftxui::Elements coinbase_;
|
||||
|
||||
bool Focusable() const override final { return true; }
|
||||
ftxui::Component ActiveChild() override final { return container_; }
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
if (event == ftxui::Event::CtrlQ)
|
||||
if (event == event::refresh_wallet)
|
||||
return on_refresh(history_->transaction(hash_));
|
||||
else if (event == ftxui::Event::CtrlQ)
|
||||
throw event::close{};
|
||||
return container_->OnEvent(std::move(event));
|
||||
}
|
||||
|
||||
virtual ftxui::Element OnRender() override final
|
||||
{
|
||||
auto buttons = ftxui::hcenter(buttons_->Render());
|
||||
if (!info_)
|
||||
{
|
||||
return ftxui::window(
|
||||
ftxui::text(_("Tx ") + hash_),
|
||||
ftxui::vbox(std::move(buttons), ftxui::separator(), ftxui::text("No longer available"))
|
||||
);
|
||||
}
|
||||
|
||||
ftxui::Elements minors;
|
||||
{
|
||||
std::string minors_text;
|
||||
for (const std::uint32_t minor : info_->subaddrIndex())
|
||||
{
|
||||
if (!minors.empty())
|
||||
minors_text.append(", ");
|
||||
minors_text.append(std::to_string(minor));
|
||||
}
|
||||
minors = {ftxui::text(_("Subaddress Minor: ")), ftxui::text(std::move(minors_text))};
|
||||
}
|
||||
|
||||
ftxui::Elements timestamp;
|
||||
{
|
||||
std::tm expanded{};
|
||||
const std::time_t raw = info_->timestamp();
|
||||
if (gmtime_r(std::addressof(raw), std::addressof(expanded)))
|
||||
{
|
||||
char buf[100] = {0};
|
||||
if (sizeof(buf) <= std::strftime(buf, sizeof(buf), "%B %d %Y %I:%M:%S UTC", std::addressof(expanded)))
|
||||
throw std::runtime_error{"strftime failed"};
|
||||
timestamp = {ftxui::text(_("Timestamp: ")), ftxui::text(buf)};
|
||||
}
|
||||
else
|
||||
timestamp = {ftxui::text(_("Timestamp: ")), ftxui::text("gmtime failure")};
|
||||
}
|
||||
|
||||
std::vector<ftxui::Elements> grid{
|
||||
{ftxui::text(_("Description: ")), note_input_->Render()},
|
||||
timestamp_,
|
||||
payment_id_,
|
||||
std::move(timestamp),
|
||||
{ftxui::text(_("Payment ID: ")), ftxui::text(info_->paymentId())},
|
||||
{ftxui::text(_("Confirmations: ")), ftxui::text(std::to_string(info_->confirmations()))},
|
||||
amount_,
|
||||
fee_,
|
||||
{ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info_))},
|
||||
{ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info_->fee()))},
|
||||
{ftxui::text(_("Block Height: ")), ftxui::text(std::to_string(info_->blockHeight()))},
|
||||
minors_,
|
||||
coinbase_
|
||||
std::move(minors),
|
||||
{ftxui::text(_("Coinbase: ")), ftxui::text(info_->isCoinbase() ? _("Yes"): _("No"))},
|
||||
};
|
||||
|
||||
const std::size_t base_size = grid.size();
|
||||
@@ -110,7 +146,7 @@ namespace lwcli { namespace view
|
||||
ftxui::Elements vertical;
|
||||
vertical.reserve(4);
|
||||
|
||||
vertical.push_back(ftxui::hcenter(ftxui::hbox(cancel_->Render(), ok_->Render())));
|
||||
vertical.push_back(std::move(buttons));
|
||||
if (info_->isFailed())
|
||||
vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text(_("FAILED")))));
|
||||
else if (info_->isPending())
|
||||
@@ -120,74 +156,48 @@ namespace lwcli { namespace view
|
||||
|
||||
vertical.push_back(ftxui::gridbox(std::move(grid)));
|
||||
|
||||
return ftxui::window(title_, ftxui::vbox(std::move(vertical)));
|
||||
return ftxui::window(ftxui::text(_("Tx ") + hash_), ftxui::vbox(std::move(vertical)));
|
||||
}
|
||||
|
||||
bool on_refresh(const Monero::TransactionInfo* info)
|
||||
{
|
||||
info_ = info;
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit tx_details(Monero::TransactionHistory* history, std::size_t index)
|
||||
explicit tx_details(Monero::TransactionHistory* history, const Monero::TransactionInfo* info)
|
||||
: history_(history),
|
||||
info_(nullptr),
|
||||
note_(),
|
||||
hash_(),
|
||||
transfers_(),
|
||||
note_input_(nullptr),
|
||||
cancel_(ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ftxui::ButtonOption::Ascii())),
|
||||
ok_(nullptr),
|
||||
container_(),
|
||||
title_(nullptr),
|
||||
timestamp_(),
|
||||
payment_id_(),
|
||||
amount_(),
|
||||
fee_(),
|
||||
minors_(),
|
||||
coinbase_()
|
||||
buttons_(),
|
||||
container_()
|
||||
{
|
||||
if (!history_)
|
||||
throw std::runtime_error{"unexpected nullptr"};
|
||||
|
||||
info_ = history->transaction(index);
|
||||
if (!info_)
|
||||
throw std::runtime_error{"unexpected nullptr"};
|
||||
on_refresh(info);
|
||||
hash_ = info->hash();
|
||||
|
||||
title_ = ftxui::text(_("Tx ") + info_->hash());
|
||||
{
|
||||
std::tm expanded{};
|
||||
const std::time_t timestamp = info_->timestamp();
|
||||
if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded)))
|
||||
throw std::runtime_error{"gmtime failure"};
|
||||
|
||||
char buf[100] = {0};
|
||||
if (sizeof(buf) <= std::strftime(buf, sizeof(buf), "%B %d %Y %I:%M:%S UTC", std::addressof(expanded)))
|
||||
throw std::runtime_error{"strftime failed"};
|
||||
timestamp_ = {ftxui::text(_("Timestamp: ")), ftxui::text(buf)};
|
||||
}
|
||||
payment_id_ = {ftxui::text(_("Payment ID: ")), ftxui::text(info_->paymentId())};
|
||||
amount_ = {ftxui::text(_("Amount: ")), ftxui::text(print_amount(*info_))};
|
||||
fee_ = {ftxui::text(_("Fee: ")), ftxui::text(lwsf::displayAmount(info_->fee()))};
|
||||
{
|
||||
std::string minors;
|
||||
for (const std::uint32_t minor : info_->subaddrIndex())
|
||||
{
|
||||
if (!minors.empty())
|
||||
minors.append(", ");
|
||||
minors.append(std::to_string(minor));
|
||||
}
|
||||
minors_ = {ftxui::text(_("Subaddress Minor: ")), ftxui::text(std::move(minors))};
|
||||
}
|
||||
coinbase_ = {ftxui::text(_("Coinbase: ")), ftxui::text(info_->isCoinbase() ? _("Yes"): _("No"))};
|
||||
|
||||
note_ = info_->description();
|
||||
note_ = info->description();
|
||||
auto options = ftxui::InputOption::Default();
|
||||
options.cursor_position = note_.size();
|
||||
options.multiline = false;
|
||||
note_input_ = ftxui::Input(¬e_, std::move(options));
|
||||
|
||||
ok_ = ftxui::Button(_("OK"), [this] () {
|
||||
history_->setTxNote(info_->hash(), note_);
|
||||
buttons_ = ftxui::Container::Horizontal({
|
||||
ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ftxui::ButtonOption::Ascii()),
|
||||
ftxui::Button(_("OK"), [this] () {
|
||||
history_->setTxNote(hash_, note_);
|
||||
throw event::close{};
|
||||
}, ftxui::ButtonOption::Ascii());
|
||||
|
||||
container_ = ftxui::Container::Vertical({
|
||||
ftxui::Container::Horizontal({cancel_, ok_}),
|
||||
note_input_
|
||||
}, ftxui::ButtonOption::Ascii())
|
||||
});
|
||||
|
||||
container_ = ftxui::Container::Vertical({buttons_, note_input_});
|
||||
Add(container_);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -196,10 +206,9 @@ namespace lwcli { namespace view
|
||||
const std::shared_ptr<Monero::Wallet> wallet_;
|
||||
ftxui::Component table_;
|
||||
ftxui::Component overlay_;
|
||||
ftxui::Element table_cached_; //!< Does not redraw when child is enabled
|
||||
const std::string title1_;
|
||||
const std::string title2_;
|
||||
std::unordered_map<std::size_t, std::size_t> row_map_;
|
||||
std::vector<const Monero::TransactionInfo*> row_map_;
|
||||
const std::uint32_t account_;
|
||||
|
||||
bool Focusable() const override final { return true; }
|
||||
@@ -216,13 +225,36 @@ namespace lwcli { namespace view
|
||||
return ftxui::text(title1_ + wallet_->getSubaddressLabel(account_, 0) + title2_);
|
||||
}
|
||||
|
||||
void load_history()
|
||||
{
|
||||
Monero::TransactionHistory* tx_history = wallet_->history();
|
||||
if (!tx_history)
|
||||
throw std::runtime_error{"unexpeted history nullptr"};
|
||||
tx_history->refresh();
|
||||
|
||||
std::map<std::pair<std::uint64_t, std::string>, const Monero::TransactionInfo*, std::greater<>> ordered;
|
||||
const auto history = tx_history->getAll();
|
||||
for (const Monero::TransactionInfo* tx : history)
|
||||
{
|
||||
if (!tx)
|
||||
throw std::runtime_error{"unexpected tx_info nullptr"};
|
||||
|
||||
if (tx->subaddrAccount() == account_)
|
||||
ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx);
|
||||
}
|
||||
|
||||
row_map_.clear();
|
||||
row_map_.reserve(ordered.size());
|
||||
for (const auto& entry : ordered)
|
||||
row_map_.push_back(entry.second);
|
||||
}
|
||||
|
||||
public:
|
||||
explicit history_(std::shared_ptr<Monero::Wallet>&& wallet, std::uint32_t account)
|
||||
: ftxui::ComponentBase(),
|
||||
wallet_(std::move(wallet)),
|
||||
table_(),
|
||||
overlay_(nullptr),
|
||||
table_cached_(nullptr),
|
||||
title1_(_("Account #") + std::to_string(account) + " / "),
|
||||
title2_(" / " + wallet_->address(account, 0).substr(0, 20) + "..."),
|
||||
row_map_(),
|
||||
@@ -231,6 +263,8 @@ namespace lwcli { namespace view
|
||||
if (!wallet_)
|
||||
throw std::invalid_argument{"lwcli::view::history given nullptr"};
|
||||
|
||||
load_history();
|
||||
|
||||
// perform transalation lookup once
|
||||
table_ = component::table(
|
||||
{_("Date"), _("Amount"), _("Payment ID"), _("Label"), _("Desription"), _("Block"), _("Fee"), _("Hash")},
|
||||
@@ -256,7 +290,14 @@ namespace lwcli { namespace view
|
||||
{
|
||||
try
|
||||
{
|
||||
if (event == event::refresh_wallet)
|
||||
{
|
||||
load_history();
|
||||
if (overlay_)
|
||||
overlay_->OnEvent(std::move(event));
|
||||
return true;
|
||||
}
|
||||
else if (overlay_)
|
||||
{
|
||||
overlay_->OnEvent(std::move(event));
|
||||
return true;
|
||||
@@ -280,45 +321,28 @@ namespace lwcli { namespace view
|
||||
|
||||
std::vector<std::vector<std::string>> transaction_list()
|
||||
{
|
||||
Monero::TransactionHistory* tx_history = wallet_->history();
|
||||
if (!tx_history)
|
||||
throw std::runtime_error{"unexpected history nullptr"};
|
||||
|
||||
std::map<std::pair<std::uint64_t, std::string>, std::pair<const Monero::TransactionInfo*, std::size_t>, std::greater<>> ordered;
|
||||
tx_history->refresh();
|
||||
const auto history = tx_history->getAll();
|
||||
for (std::size_t i = 0; i < history.size(); ++i)
|
||||
{
|
||||
const Monero::TransactionInfo* tx = history[i];
|
||||
if (!tx)
|
||||
throw std::runtime_error{"unexpected tx_info nullptr"};
|
||||
|
||||
if (tx->subaddrAccount() == account_)
|
||||
ordered.try_emplace({tx->blockHeight(), tx->hash()}, tx, i);
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> rows;
|
||||
rows.reserve(ordered.size() + 2);
|
||||
rows.reserve(row_map_.size() + 2);
|
||||
|
||||
std::size_t i = 0;
|
||||
row_map_.clear();
|
||||
for (const auto& entry : ordered)
|
||||
for (const Monero::TransactionInfo* tx : row_map_)
|
||||
{
|
||||
const Monero::TransactionInfo* tx = entry.second.first;
|
||||
row_map_.emplace(i, entry.second.second);
|
||||
|
||||
const std::uint64_t amount = tx->amount();
|
||||
const int direction = tx->direction();
|
||||
const std::uint64_t fee = tx->fee();
|
||||
|
||||
char date[12] = {0};
|
||||
std::tm expanded{};
|
||||
const std::time_t timestamp = tx->timestamp();
|
||||
if (!gmtime_r(std::addressof(timestamp), std::addressof(expanded)))
|
||||
throw std::runtime_error{"gmtime failure"};
|
||||
|
||||
char date[12] = {0};
|
||||
if (gmtime_r(std::addressof(timestamp), std::addressof(expanded)))
|
||||
{
|
||||
if (sizeof(date) - 1 != std::strftime(date, sizeof(date), "%Y/%m/%d ", std::addressof(expanded)))
|
||||
throw std::runtime_error{"strftime failed"};
|
||||
}
|
||||
else
|
||||
{
|
||||
std::strncpy(date, "gmtime fail", sizeof(date));
|
||||
date[sizeof(date) - 1] = 0;
|
||||
}
|
||||
|
||||
std::string payment_id = tx->paymentId();
|
||||
if (payment_id.size() == 16 && payment_id.find_first_not_of('0') == std::string::npos)
|
||||
@@ -346,8 +370,6 @@ namespace lwcli { namespace view
|
||||
lwsf::displayAmount(tx->fee()),
|
||||
tx->hash().substr(0, 16) + "..."
|
||||
});
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return rows;
|
||||
@@ -355,19 +377,14 @@ namespace lwcli { namespace view
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
/* Do not redraw table when showing tx. the
|
||||
history()->refresh() call can invalidate pointers */
|
||||
if (!overlay_)
|
||||
{
|
||||
auto table = table_->Render(); // compute balance first in callback
|
||||
table_cached_ = ftxui::vbox({
|
||||
auto table = ftxui::vbox({
|
||||
get_title(),
|
||||
ftxui::text(_("Balance: ") + lwsf::displayAmount(wallet_->balance(account_))),
|
||||
std::move(table) | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
|
||||
table_->Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
|
||||
});
|
||||
return table_cached_;
|
||||
}
|
||||
return ftxui::dbox(table_cached_, decorate::overlay(overlay_->Render()));
|
||||
if (!overlay_)
|
||||
return table;
|
||||
return ftxui::dbox(std::move(table), decorate::overlay(overlay_->Render()));
|
||||
}
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
@@ -152,7 +152,7 @@ namespace lwcli { namespace view
|
||||
const bool has_overlay = bool(state_.overlay);
|
||||
|
||||
if (event == event::refresh_wallet)
|
||||
return true;
|
||||
return history_->OnEvent(std::move(event));
|
||||
else if (state_.overlay)
|
||||
state_.overlay->OnEvent(std::move(event));
|
||||
else if (event == ftxui::Event::CtrlQ)
|
||||
|
||||
Reference in New Issue
Block a user