mirror of
https://codeberg.org/wownero/lwcli-wow.git
synced 2026-10-17 05:51:26 +02:00
Update lwsf pin, and added limited account support
This commit is contained in:
Vendored
+1
-1
Submodule external/lwsf updated: d7fb7961f8...38f44770b8
@@ -27,6 +27,7 @@
|
||||
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
include_directories(.)
|
||||
add_subdirectory(components)
|
||||
add_subdirectory(decorate)
|
||||
add_subdirectory(views)
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright (c) 2025, Cifro Codes LLC
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are
|
||||
# permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
# conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
# of conditions and the following disclaimer in the documentation and/or other
|
||||
# materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
# used to endorse or promote products derived from this software without specific
|
||||
# prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
# 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.
|
||||
|
||||
set(lwcli-components_sources table.cpp)
|
||||
set(lwscli-components_headers table.h)
|
||||
|
||||
add_library(lwcli-components ${lwcli-components_sources} ${lwcli-components_headers})
|
||||
target_link_libraries(lwcli-components PRIVATE component dom)
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) 2025, Cifro Codes LLC
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// 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 <ctime>
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/event.hpp>
|
||||
#include <ftxui/dom/table.hpp>
|
||||
|
||||
#include "decorate/overlay.h"
|
||||
#include "table.h"
|
||||
|
||||
namespace lwcli { namespace component
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class table_ final : public ftxui::ComponentBase
|
||||
{
|
||||
const table_generator generator_;
|
||||
const table_enter enter_;
|
||||
const std::vector<std::vector<std::string>> title_;
|
||||
std::ptrdiff_t rows_;
|
||||
std::ptrdiff_t selected_;
|
||||
|
||||
static constexpr std::ptrdiff_t min_row() noexcept { return 0; };
|
||||
bool Focusable() const override final { return true; }
|
||||
|
||||
static std::vector<std::vector<std::string>> get_title_bar(std::vector<std::string>&& title)
|
||||
{
|
||||
std::vector<std::vector<std::string>> out;
|
||||
out.reserve(2);
|
||||
|
||||
const std::size_t count = title.size();
|
||||
out.push_back(std::move(title));
|
||||
out.push_back(std::vector<std::string>(count));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit table_(std::vector<std::string>&& title, table_generator&& generator, table_enter&& enter)
|
||||
: ftxui::ComponentBase(),
|
||||
generator_(std::move(generator)),
|
||||
enter_(std::move(enter)),
|
||||
title_(get_title_bar(std::move(title))),
|
||||
rows_(0),
|
||||
selected_(-1)
|
||||
{
|
||||
set_size(generator_().size());
|
||||
}
|
||||
|
||||
void set_size(const std::size_t rows)
|
||||
{
|
||||
if (std::numeric_limits<std::ptrdiff_t>::max() < rows)
|
||||
throw std::runtime_error{"lwcli::component::table exceeds max size"};
|
||||
rows_ = rows;
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
if (event == ftxui::Event::ArrowDown && selected_ + min_row() < rows_)
|
||||
++selected_;
|
||||
else if (event == ftxui::Event::ArrowUp && min_row() <= selected_)
|
||||
--selected_;
|
||||
else if (event == ftxui::Event::PageDown)
|
||||
selected_ = std::min(rows_ + min_row(), selected_ + 15);
|
||||
else if (event == ftxui::Event::PageUp)
|
||||
selected_ = std::max(min_row(), selected_ - 15);
|
||||
else if (event == ftxui::Event::Return && min_row() <= selected_ && selected_ < rows_)
|
||||
return enter_(std::size_t(selected_));
|
||||
|
||||
return min_row() <= selected_ && selected_ - min_row() < rows_;
|
||||
}
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
auto rows = generator_();
|
||||
set_size(rows.size());
|
||||
|
||||
rows.reserve(rows.size() + title_.size());
|
||||
rows.insert(rows.begin(), title_.begin(), title_.end());
|
||||
|
||||
ftxui::Table table{std::move(rows)};
|
||||
|
||||
{
|
||||
auto title = table.SelectRow(0);
|
||||
title.Decorate(ftxui::bold);
|
||||
title.SeparatorVertical(ftxui::LIGHT);
|
||||
//title.Border(ftxui::LIGHT);
|
||||
}
|
||||
|
||||
if (Focused())
|
||||
{
|
||||
if (selected_ < min_row())
|
||||
selected_ = min_row();
|
||||
else if (rows_ <= selected_ - min_row())
|
||||
selected_ = rows_ - 1 + min_row();
|
||||
|
||||
if (min_row() <= selected_ && selected_ < rows_)
|
||||
{
|
||||
auto row = table.SelectRow(std::size_t(selected_) + title_.size());
|
||||
row.Decorate(ftxui::inverted);
|
||||
row.Decorate(ftxui::focus);
|
||||
}
|
||||
}
|
||||
return table.Render();
|
||||
}
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
ftxui::Component table(std::vector<std::string> title, table_generator generator, table_enter enter)
|
||||
{
|
||||
if (!generator || !enter)
|
||||
throw std::invalid_argument{"lwcli::components::table was given nullptr"};
|
||||
return std::make_shared<table_>(std::move(title), std::move(generator), std::move(enter));
|
||||
}
|
||||
}} // lwcli // view
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2025, Cifro Codes LLC
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
#include <ftxui/component/component_base.hpp>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace lwcli { namespace component
|
||||
{
|
||||
//! Shows Transaction History
|
||||
using table_generator = std::function<std::vector<std::vector<std::string>>()>;
|
||||
using table_enter = std::function<bool(std::size_t)>;
|
||||
ftxui::Component table(std::vector<std::string> title, table_generator generator, table_enter enter);
|
||||
|
||||
}} // lwscli // view
|
||||
|
||||
@@ -50,6 +50,8 @@ namespace lwcli { namespace config
|
||||
{ return ( ... && verify_sso(strs)); }
|
||||
|
||||
constexpr const std::string_view default_language{"English"};
|
||||
constexpr const std::string_view default_account_name{"Untitled Account"};
|
||||
constexpr const std::string_view default_subaddress_name{"Untitled Address"};
|
||||
|
||||
//! Timeout interval for inactivity with open wallet
|
||||
constexpr const std::chrono::minutes wallet_timeout{2};
|
||||
|
||||
@@ -26,9 +26,9 @@
|
||||
# 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.
|
||||
|
||||
set(lwcli-views_sources history.cpp keys.cpp manager.cpp settings.cpp wallet.cpp)
|
||||
set(lwscli-views_headers history.h keys.h manager.h settings.h wallet.h)
|
||||
set(lwcli-views_sources accounts.cpp history.cpp keys.cpp manager.cpp settings.cpp wallet.cpp)
|
||||
set(lwscli-views_headers accounts.h history.h keys.h manager.h settings.h wallet.h)
|
||||
|
||||
add_library(lwcli-views ${lwcli-views_sources} ${lwcli-views_headers})
|
||||
target_link_libraries(lwcli-views PRIVATE component dom lwcli-decorate lwsf-api)
|
||||
target_link_libraries(lwcli-views PRIVATE component dom lwcli-components lwcli-decorate lwsf-api)
|
||||
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
// Copyright (c) 2025, Cifro Codes LLC
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// 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 "wallet.h"
|
||||
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/event.hpp>
|
||||
#include <ftxui/dom/table.hpp>
|
||||
#include <lws_frontend.h>
|
||||
|
||||
#include "components/table.h"
|
||||
#include "decorate/overlay.h"
|
||||
#include "events.h"
|
||||
#include "lwcli_config.h"
|
||||
#include "translate.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace lwcli { namespace view
|
||||
{
|
||||
namespace
|
||||
{
|
||||
ftxui::Component last_input(std::string* str)
|
||||
{
|
||||
auto opt = ftxui::InputOption::Default();
|
||||
opt.cursor_position = str->size();
|
||||
opt.multiline = false;
|
||||
return ftxui::Input(str, std::move(opt));
|
||||
}
|
||||
|
||||
ftxui::ButtonOption ascii() { return ftxui::ButtonOption::Ascii(); }
|
||||
|
||||
class account_detail final : public ftxui::ComponentBase
|
||||
{
|
||||
std::string account_name_;
|
||||
const std::shared_ptr<Monero::Wallet> wal_;
|
||||
Monero::Subaddress* const acct_;
|
||||
const ftxui::Element title_;
|
||||
const ftxui::Elements address_;
|
||||
const ftxui::Element desc_;
|
||||
ftxui::Component details_;
|
||||
ftxui::Component table_;
|
||||
ftxui::Component buttons_;
|
||||
const ftxui::Component name_;
|
||||
ftxui::Component ui_;
|
||||
std::unordered_map<std::size_t, std::size_t> row_map_;
|
||||
const std::uint32_t id_;
|
||||
|
||||
bool Focusable() const override final { return true; }
|
||||
ftxui::Component ActiveChild() override final
|
||||
{
|
||||
if (details_)
|
||||
return details_;
|
||||
return ui_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit account_detail(std::shared_ptr<Monero::Wallet> wal, Monero::Subaddress* acct, std::size_t id)
|
||||
: ftxui::ComponentBase(),
|
||||
account_name_(wal->getSubaddressLabel(id, 0)),
|
||||
wal_(std::move(wal)),
|
||||
acct_(acct),
|
||||
title_(ftxui::text(_("Account #") + std::to_string(id))),
|
||||
address_({ftxui::text("Address: "), ftxui::text(wal_->address(id, 0))}),
|
||||
desc_(ftxui::text(_("Name: "))),
|
||||
details_(),
|
||||
table_(),
|
||||
buttons_(),
|
||||
name_(last_input(&account_name_)),
|
||||
ui_(),
|
||||
id_(id)
|
||||
{
|
||||
if (!acct_)
|
||||
throw std::runtime_error{"lwcli::account_detail given nullptr"};
|
||||
if (std::numeric_limits<std::uint32_t>::max() < id_)
|
||||
throw std::runtime_error{"lwcli::account_detail given invalid id"};
|
||||
|
||||
buttons_ = ftxui::Container::Horizontal({
|
||||
ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ascii()),
|
||||
ftxui::Button(_("Save"), [this] () {
|
||||
wal_->setSubaddressLabel(id_, 0, account_name_);
|
||||
throw event::close{};
|
||||
}, ascii()),
|
||||
ftxui::Button(_("Add Subaddress"), [this] () {
|
||||
acct_->addRow(id_, std::string{config::default_subaddress_name});
|
||||
}, ascii())
|
||||
});
|
||||
|
||||
table_ = component::table(
|
||||
{_("#"), _("Label"), _("Address")},
|
||||
[this] () { return subaddress_list(); },
|
||||
[this] (std::size_t i) { return display_details(i); }
|
||||
);
|
||||
|
||||
ui_ = ftxui::Container::Vertical({buttons_, name_, table_});
|
||||
Add(ui_);
|
||||
}
|
||||
|
||||
bool display_details(const std::size_t index)
|
||||
{
|
||||
if (row_map_.find(index) == row_map_.end())
|
||||
throw std::logic_error{"DEBUG"};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
if (details_)
|
||||
return details_->OnEvent(std::move(event));
|
||||
else if (event == ftxui::Event::CtrlQ)
|
||||
throw event::close{};
|
||||
ui_->OnEvent(std::move(event));
|
||||
}
|
||||
catch (const event::close&)
|
||||
{
|
||||
if (!details_)
|
||||
throw;
|
||||
details_->Detach();
|
||||
details_.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> subaddress_list()
|
||||
{
|
||||
std::vector<std::vector<std::string>> rows{};
|
||||
|
||||
acct_->refresh(id_);
|
||||
auto all = acct_->getAll();
|
||||
|
||||
// reverse order sort
|
||||
std::sort(all.begin(), all.end(), [] (const auto x, const auto y) {
|
||||
return y->getRowId() < x->getRowId();
|
||||
});
|
||||
|
||||
std::size_t i = 0;
|
||||
rows.reserve(all.size());
|
||||
for (Monero::SubaddressRow const* const detail : all)
|
||||
{
|
||||
const std::size_t id = detail->getRowId();
|
||||
row_map_.try_emplace(i++).first->second = id;
|
||||
rows.push_back({
|
||||
std::to_string(id),
|
||||
detail->getLabel(),
|
||||
detail->getAddress().substr(0, 20) + "..."
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
return ftxui::window(title_, ftxui::vbox({
|
||||
buttons_->Render() | ftxui::hcenter,
|
||||
ftxui::separator(),
|
||||
ftxui::gridbox({address_, {desc_, name_->Render()}}),
|
||||
ftxui::separator(),
|
||||
table_->Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class accounts_ final : public ftxui::ComponentBase
|
||||
{
|
||||
const std::shared_ptr<Monero::Wallet> wal_;
|
||||
Monero::SubaddressAccount* const wal_accounts_;
|
||||
std::uint32_t* const account_;
|
||||
const ftxui::Element title_;
|
||||
ftxui::Component details_;
|
||||
ftxui::Component table_;
|
||||
ftxui::Element table_cached_;
|
||||
ftxui::Component buttons_;
|
||||
ftxui::Component ui_;
|
||||
std::unordered_map<std::size_t, std::size_t> row_map_;
|
||||
|
||||
bool Focusable() const override final { return true; }
|
||||
ftxui::Component ActiveChild() override final
|
||||
{
|
||||
if (details_)
|
||||
return details_;
|
||||
return ui_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit accounts_(std::shared_ptr<Monero::Wallet>&& wal, std::uint32_t* account)
|
||||
: ftxui::ComponentBase(),
|
||||
wal_(std::move(wal)),
|
||||
wal_accounts_(wal_->subaddressAccount()),
|
||||
account_(account),
|
||||
title_(ftxui::text(_("Accounts"))),
|
||||
details_(),
|
||||
table_(),
|
||||
table_cached_(),
|
||||
buttons_(),
|
||||
ui_(),
|
||||
row_map_()
|
||||
{
|
||||
buttons_ = ftxui::Container::Horizontal({
|
||||
ftxui::Button(_("Close"), [] () { throw event::close{}; }, ascii()),
|
||||
ftxui::Button(_("Add Account"), [this] () {
|
||||
wal_->addSubaddressAccount(std::string{config::default_account_name});
|
||||
}, ascii()),
|
||||
});
|
||||
|
||||
table_ = component::table(
|
||||
{_("#"), _("Balance"), _("Label"), _("Address")},
|
||||
[this] () { return accounts_list(); },
|
||||
[this] (std::size_t i) { return display_details(i); }
|
||||
);
|
||||
|
||||
ui_ = ftxui::Container::Vertical({buttons_, table_});
|
||||
Add(ui_);
|
||||
}
|
||||
|
||||
bool display_details(const std::size_t index)
|
||||
{
|
||||
if (details_)
|
||||
details_->Detach();
|
||||
details_ = std::make_shared<account_detail>(wal_, wal_->subaddress(), row_map_.at(index));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
{
|
||||
try
|
||||
{
|
||||
if (details_)
|
||||
return details_->OnEvent(std::move(event));
|
||||
else if (event == ftxui::Event::CtrlQ)
|
||||
throw event::close{};
|
||||
ui_->OnEvent(std::move(event));
|
||||
}
|
||||
catch (const event::close&)
|
||||
{
|
||||
if (!details_)
|
||||
throw;
|
||||
details_->Detach();
|
||||
details_.reset();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::vector<std::string>> accounts_list()
|
||||
{
|
||||
std::vector<std::vector<std::string>> rows{};
|
||||
|
||||
wal_accounts_->refresh();
|
||||
auto all = wal_accounts_->getAll();
|
||||
|
||||
// reverse order sort
|
||||
std::sort(all.begin(), all.end(), [] (const auto x, const auto y) {
|
||||
return y->getRowId() < x->getRowId();
|
||||
});
|
||||
|
||||
std::size_t i = 0;
|
||||
rows.reserve(all.size());
|
||||
for (Monero::SubaddressAccountRow const* const detail : all)
|
||||
{
|
||||
const std::size_t id = detail->getRowId();
|
||||
char const* const active = id == *account_ ? "*" : "";
|
||||
row_map_.try_emplace(i++).first->second = id;
|
||||
rows.push_back({
|
||||
active + std::to_string(id),
|
||||
detail->getBalance() + " XMR",
|
||||
detail->getLabel(),
|
||||
detail->getAddress().substr(0, 12) + "..."
|
||||
});
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
if (!details_)
|
||||
{
|
||||
table_cached_ = ftxui::window(title_, ftxui::vbox({
|
||||
buttons_->Render() | ftxui::hcenter,
|
||||
ftxui::separator(),
|
||||
table_->Render() //| ftxui::vscroll_indicator | ftxui::yframe | ftxui::center
|
||||
}));
|
||||
return table_cached_;
|
||||
}
|
||||
return ftxui::dbox(table_cached_, decorate::overlay(details_->Render()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ftxui::Component accounts(std::shared_ptr<Monero::Wallet> wal, std::uint32_t* account)
|
||||
{
|
||||
if (!wal || !account)
|
||||
throw std::invalid_argument{"views::accounts cannot be given nullptr"};
|
||||
return std::make_shared<accounts_>(std::move(wal), account);
|
||||
}
|
||||
|
||||
}} // lwcli //
|
||||
@@ -0,0 +1,39 @@
|
||||
// copyright (c) 2025, cifro codes llc
|
||||
//
|
||||
// all rights reserved.
|
||||
//
|
||||
// redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// this software is provided by the copyright holders and contributors "as is" and any
|
||||
// express or implied warranties, including, but not limited to, the implied warranties of
|
||||
// merchantability and fitness for a particular purpose are disclaimed. in no event shall
|
||||
// the copyright holder or contributors be liable for any direct, indirect, incidental,
|
||||
// special, exemplary, or consequential damages (including, but not limited to,
|
||||
// procurement of substitute goods or services; loss of use, data, or profits; or business
|
||||
// interruption) however caused and on any theory of liability, whether in contract,
|
||||
// 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.
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <ftxui/component/component_base.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace Monero { class Wallet; }
|
||||
namespace lwcli { namespace view
|
||||
{
|
||||
ftxui::Component accounts(std::shared_ptr<Monero::Wallet> wallet, std::uint32_t* account);
|
||||
}} // lwscli // view
|
||||
|
||||
+114
-129
@@ -34,6 +34,7 @@
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "components/table.h"
|
||||
#include "decorate/overlay.h"
|
||||
#include "events.h"
|
||||
#include "history.h"
|
||||
@@ -244,27 +245,30 @@ namespace lwcli { namespace view
|
||||
class history_ final : public ftxui::ComponentBase
|
||||
{
|
||||
const std::shared_ptr<Monero::Wallet> wallet_;
|
||||
ftxui::Component table_;
|
||||
ftxui::Component overlay_;
|
||||
ftxui::Element title_;
|
||||
ftxui::Element table_; //!< Does not redraw when child is enabled
|
||||
std::vector<std::vector<std::string>> base_rows;
|
||||
std::ptrdiff_t highlighted_;
|
||||
std::unordered_map<std::size_t, int> row_map_;
|
||||
ftxui::Element table_cached_; //!< Does not redraw when child is enabled
|
||||
std::unordered_map<std::size_t, std::size_t> row_map_;
|
||||
std::uint64_t balance_;
|
||||
const std::uint32_t account_;
|
||||
|
||||
static constexpr std::size_t min_row() noexcept { return 2; };
|
||||
bool Focusable() const override final { return true; }
|
||||
ftxui::Component ActiveChild() override final { return overlay_; }
|
||||
ftxui::Component ActiveChild() override final
|
||||
{
|
||||
if (overlay_)
|
||||
return overlay_;
|
||||
return table_;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit history_(std::shared_ptr<Monero::Wallet>&& wallet, std::uint32_t account)
|
||||
: ftxui::ComponentBase(),
|
||||
wallet_(std::move(wallet)),
|
||||
table_(),
|
||||
overlay_(nullptr),
|
||||
title_(nullptr),
|
||||
table_(nullptr),
|
||||
base_rows(),
|
||||
highlighted_(min_row() - 1),
|
||||
table_cached_(nullptr),
|
||||
row_map_(),
|
||||
account_(account)
|
||||
{
|
||||
@@ -281,10 +285,22 @@ namespace lwcli { namespace view
|
||||
title_ = ftxui::text(std::move(address));
|
||||
|
||||
// perform transalation lookup once
|
||||
base_rows = {
|
||||
table_ = component::table(
|
||||
{_("Date"), _("Amount"), _("Payment ID"), _("Desription"), _("Block"), _("Fee"), _("Hash")},
|
||||
{ "", "", "", "", "", "", "" }
|
||||
};
|
||||
[this] () { return transaction_list(); },
|
||||
[this] (std::size_t i) { return add_overlay(i); }
|
||||
);
|
||||
|
||||
Add(table_);
|
||||
}
|
||||
|
||||
bool add_overlay(const std::size_t i)
|
||||
{
|
||||
if (overlay_)
|
||||
overlay_->Detach();
|
||||
overlay_ = std::make_shared<tx_details>(wallet_->history(), row_map_.at(i));
|
||||
Add(overlay_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
@@ -300,19 +316,8 @@ namespace lwcli { namespace view
|
||||
throw event::close{};
|
||||
else if (event.is_character())
|
||||
return false;
|
||||
else if (event == ftxui::Event::ArrowDown && highlighted_ < row_map_.size() + min_row() - 1)
|
||||
++highlighted_;
|
||||
else if (event == ftxui::Event::ArrowUp && min_row() <= highlighted_)
|
||||
--highlighted_;
|
||||
else if (event == ftxui::Event::PageDown)
|
||||
highlighted_ = std::min(std::ptrdiff_t(row_map_.size() + min_row()), highlighted_ + 10);
|
||||
else if (event == ftxui::Event::PageUp)
|
||||
highlighted_ = std::max(std::ptrdiff_t(min_row()), highlighted_ - 10);
|
||||
else if (event == ftxui::Event::Return)
|
||||
{
|
||||
overlay_ = ftxui::Make<tx_details>(wallet_->history(), row_map_.at(highlighted_ - min_row()));
|
||||
Add(overlay_);
|
||||
}
|
||||
else
|
||||
return table_->OnEvent(std::move(event));
|
||||
}
|
||||
catch (const event::close&)
|
||||
{
|
||||
@@ -321,121 +326,101 @@ namespace lwcli { namespace view
|
||||
overlay_->Detach();
|
||||
overlay_.reset();
|
||||
}
|
||||
return min_row() <= highlighted_ && highlighted_ - min_row() < row_map_.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
balance_ = 0;
|
||||
std::vector<std::vector<std::string>> rows;
|
||||
rows.reserve(ordered.size() + 2);
|
||||
|
||||
std::size_t i = 0;
|
||||
row_map_.clear();
|
||||
for (const auto& entry : ordered)
|
||||
{
|
||||
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();
|
||||
|
||||
|
||||
if (!tx->isFailed())
|
||||
{
|
||||
if (direction == Monero::TransactionInfo::Direction_Out)
|
||||
{
|
||||
balance_ -= amount;
|
||||
balance_ -= fee;
|
||||
}
|
||||
else
|
||||
balance_ += amount;
|
||||
}
|
||||
|
||||
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 (sizeof(date) - 1 != std::strftime(date, sizeof(date), "%Y/%m/%d ", std::addressof(expanded)))
|
||||
throw std::runtime_error{"strftime failed"};
|
||||
|
||||
std::string payment_id = tx->paymentId();
|
||||
if (payment_id.size() == 16 && payment_id.find_first_not_of('0') == std::string::npos)
|
||||
payment_id.clear();
|
||||
|
||||
char const* const extended_payment_id = 16 < payment_id.size() ?
|
||||
"..." : "";
|
||||
rows.push_back({
|
||||
std::string{date},
|
||||
print_amount(amount, direction),
|
||||
payment_id.substr(0, 16) + extended_payment_id,
|
||||
tx->description(),
|
||||
std::to_string(tx->blockHeight()),
|
||||
print_money(tx->fee()),
|
||||
tx->hash().substr(0, 16) + "..."
|
||||
});
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
ftxui::Element OnRender() override final
|
||||
{
|
||||
if (Focused())
|
||||
{
|
||||
if (highlighted_ < min_row())
|
||||
highlighted_ = min_row();
|
||||
else if (row_map_.size() <= highlighted_ - min_row())
|
||||
highlighted_ = row_map_.size() - 1 + min_row();
|
||||
}
|
||||
|
||||
/* Do not redraw table when showing tx. the
|
||||
history()->refresh() call can invalidate pointers */
|
||||
if (!overlay_)
|
||||
{
|
||||
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::uint64_t balance = 0;
|
||||
std::vector<std::vector<std::string>> rows;
|
||||
rows.reserve(ordered.size() + 2);
|
||||
rows = base_rows;
|
||||
|
||||
std::size_t i = 0;
|
||||
row_map_.clear();
|
||||
for (const auto& entry : ordered)
|
||||
{
|
||||
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();
|
||||
|
||||
|
||||
if (!tx->isFailed())
|
||||
{
|
||||
if (direction == Monero::TransactionInfo::Direction_Out)
|
||||
{
|
||||
balance -= amount;
|
||||
balance -= fee;
|
||||
}
|
||||
else
|
||||
balance += amount;
|
||||
}
|
||||
|
||||
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 (sizeof(date) - 1 != std::strftime(date, sizeof(date), "%Y/%m/%d ", std::addressof(expanded)))
|
||||
throw std::runtime_error{"strftime failed"};
|
||||
|
||||
|
||||
std::string payment_id = tx->paymentId();
|
||||
if (payment_id.size() == 16 && payment_id.find_first_not_of('0') == std::string::npos)
|
||||
payment_id.clear();
|
||||
|
||||
char const* const extended_payment_id = 16 < payment_id.size() ?
|
||||
"..." : "";
|
||||
rows.push_back({
|
||||
std::string{date},
|
||||
print_amount(amount, direction),
|
||||
payment_id.substr(0, 16) + extended_payment_id,
|
||||
tx->description(),
|
||||
std::to_string(tx->blockHeight()),
|
||||
print_money(tx->fee()),
|
||||
tx->hash().substr(0, 16) + "..."
|
||||
});
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
ftxui::Table table{std::move(rows)};
|
||||
table.SelectRow(0).Decorate(ftxui::bold);
|
||||
table.SelectRow(0).SeparatorVertical(ftxui::LIGHT);
|
||||
table.SelectRow(0).Border(ftxui::LIGHT);
|
||||
//table.SelectColumn(3).DecorateCells(ftxui::xflex_grow);
|
||||
if (min_row() <= highlighted_ && highlighted_ - min_row() < row_map_.size())
|
||||
{
|
||||
auto row = table.SelectRow(highlighted_);
|
||||
row.Decorate(ftxui::inverted);
|
||||
if (Focused())
|
||||
row.Decorate(ftxui::focus);
|
||||
else if (Active())
|
||||
row.Decorate(ftxui::select);
|
||||
}
|
||||
|
||||
table_ = ftxui::vbox({
|
||||
auto table = table_->Render(); // compute balance first in callback
|
||||
table_cached_ = ftxui::vbox({
|
||||
title_,
|
||||
ftxui::text(_("Balance: ") + print_money(balance)),
|
||||
table.Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
|
||||
//ftxui::flex(ftxui::yframe(ftxui::vscroll_indicator(table.Render())))
|
||||
ftxui::text(_("Balance: ") + print_money(balance_)),
|
||||
std::move(table) | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
|
||||
});
|
||||
return table_;
|
||||
return table_cached_;
|
||||
}
|
||||
return ftxui::dbox(table_, decorate::overlay(overlay_->Render()));
|
||||
return ftxui::dbox(table_cached_, decorate::overlay(overlay_->Render()));
|
||||
}
|
||||
};
|
||||
} // anonymous
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
#include <charconv>
|
||||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/event.hpp>
|
||||
#include <ftxui/dom/table.hpp>
|
||||
#include <lws_frontend.h>
|
||||
|
||||
#include "decorate/overlay.h"
|
||||
@@ -73,8 +72,7 @@ namespace lwcli { namespace view
|
||||
const auto is_ssl = from_string(ssl);
|
||||
if (!is_ssl)
|
||||
return false;
|
||||
|
||||
// no way to forcibly select ssl without another init call. hopefully doesn't break things
|
||||
|
||||
wal.init(wal.getCacheAttribute(std::string{config::server::url}), 0, "", "", bool(*is_ssl), true, wal.getCacheAttribute(std::string{config::server::proxy}));
|
||||
return true;
|
||||
}
|
||||
@@ -251,6 +249,8 @@ namespace lwcli { namespace view
|
||||
|
||||
ftxui::Component settings(std::shared_ptr<Monero::Wallet> wal)
|
||||
{
|
||||
if (!wal)
|
||||
throw std::invalid_argument{"view::settings cannot be given nullptr"};
|
||||
return std::make_shared<settings_>(std::move(wal));
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "events.h"
|
||||
#include "lwcli_config.h"
|
||||
#include "translate.h"
|
||||
#include "views/accounts.h"
|
||||
#include "views/history.h"
|
||||
#include "views/settings.h"
|
||||
|
||||
@@ -52,6 +53,7 @@ namespace lwcli { namespace view
|
||||
{
|
||||
const std::shared_ptr<Monero::Wallet> wal;
|
||||
ftxui::Component overlay;
|
||||
std::uint32_t selected_account = 0;
|
||||
};
|
||||
|
||||
ftxui::Component menu_bar(wallet_state* state)
|
||||
@@ -60,7 +62,7 @@ namespace lwcli { namespace view
|
||||
return ftxui::Container::Horizontal({
|
||||
ftxui::Button("[s]end", [] () {}, ascii()),
|
||||
ftxui::Button("[b]ook", [] () {}, ascii()),
|
||||
ftxui::Button("[a]ccounts", [] () {}, ascii()),
|
||||
ftxui::Button("[a]ccounts", [state] () { state->overlay = accounts(state->wal, &state->selected_account); }, ascii()),
|
||||
ftxui::Button("[r]efresh", [wal] () { wal->refreshAsync(); }, ascii()),
|
||||
ftxui::Button("s[e]ttings", [state] () { state->overlay = settings(state->wal); }, ascii())
|
||||
});
|
||||
@@ -71,7 +73,6 @@ namespace lwcli { namespace view
|
||||
wallet_state state_;
|
||||
ftxui::Element title_;
|
||||
std::uint32_t active_account_;
|
||||
std::uint32_t selected_account_;
|
||||
ftxui::Component bar_;
|
||||
ftxui::Component ui_;
|
||||
ftxui::Component history_;
|
||||
@@ -112,7 +113,6 @@ namespace lwcli { namespace view
|
||||
state_{std::move(data)},
|
||||
title_(nullptr),
|
||||
active_account_(-1),
|
||||
selected_account_(0),
|
||||
bar_(),
|
||||
ui_(),
|
||||
history_(nullptr)
|
||||
@@ -132,15 +132,15 @@ namespace lwcli { namespace view
|
||||
|
||||
void update_account()
|
||||
{
|
||||
if (active_account_ != selected_account_)
|
||||
if (active_account_ != state_.selected_account)
|
||||
{
|
||||
if (ui_)
|
||||
ui_->Detach();
|
||||
history_ = view::history(state_.wal, selected_account_);
|
||||
history_ = view::history(state_.wal, state_.selected_account);
|
||||
ui_ = ftxui::Container::Vertical({bar_, history_});
|
||||
Add(ui_);
|
||||
}
|
||||
active_account_ = selected_account_;
|
||||
active_account_ = state_.selected_account;
|
||||
}
|
||||
|
||||
bool OnEvent(ftxui::Event event) override final
|
||||
@@ -162,7 +162,7 @@ namespace lwcli { namespace view
|
||||
else if (event == ftxui::Event::b || event == ftxui::Event::B)
|
||||
; // state_.overlay = book(state_.wal);
|
||||
else if (event == ftxui::Event::a || event == ftxui::Event::a)
|
||||
; // overlay = accounts(state_.wal, &active_account_);
|
||||
state_.overlay = accounts(state_.wal, &state_.selected_account);
|
||||
else if (event == ftxui::Event::r || event == ftxui::Event::R)
|
||||
state_.wal->refreshAsync();
|
||||
else if (event == ftxui::Event::e || event == ftxui::Event::E)
|
||||
|
||||
Reference in New Issue
Block a user