mirror of
https://codeberg.org/wownero/lwcli-wow.git
synced 2026-10-17 05:51:26 +02:00
Add CLI flags, updated UI loading routine, and moved files around
This commit is contained in:
@@ -26,8 +26,8 @@
|
|||||||
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
# 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.
|
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
set(lwcli-views_sources history.cpp keys.cpp manager.cpp wallet.cpp)
|
set(lwcli-views_sources history.cpp keys.cpp manager.cpp settings.cpp wallet.cpp)
|
||||||
set(lwscli-views_headers history.h keys.h manager.h wallet.h)
|
set(lwscli-views_headers history.h keys.h manager.h settings.h wallet.h)
|
||||||
|
|
||||||
add_library(lwcli-views ${lwcli-views_sources} ${lwcli-views_headers})
|
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-decorate lwsf-api)
|
||||||
|
|||||||
+58
-36
@@ -31,6 +31,8 @@
|
|||||||
#include <ftxui/component/event.hpp>
|
#include <ftxui/component/event.hpp>
|
||||||
#include <ftxui/dom/table.hpp>
|
#include <ftxui/dom/table.hpp>
|
||||||
#include <lws_frontend.h>
|
#include <lws_frontend.h>
|
||||||
|
#include <map>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "decorate/overlay.h"
|
#include "decorate/overlay.h"
|
||||||
#include "events.h"
|
#include "events.h"
|
||||||
@@ -130,13 +132,13 @@ namespace lwcli { namespace view
|
|||||||
virtual ftxui::Element OnRender() override final
|
virtual ftxui::Element OnRender() override final
|
||||||
{
|
{
|
||||||
std::vector<ftxui::Elements> grid{
|
std::vector<ftxui::Elements> grid{
|
||||||
{ftxui::text("Description: "), note_input_->Render()},
|
{ftxui::text(_("Description: ")), note_input_->Render()},
|
||||||
timestamp_,
|
timestamp_,
|
||||||
payment_id_,
|
payment_id_,
|
||||||
{ftxui::text("Confirmations: "), ftxui::text(std::to_string(info_->confirmations()))},
|
{ftxui::text(_("Confirmations: ")), ftxui::text(std::to_string(info_->confirmations()))},
|
||||||
amount_,
|
amount_,
|
||||||
fee_,
|
fee_,
|
||||||
{ftxui::text("Block Height: "), ftxui::text(std::to_string(info_->blockHeight()))},
|
{ftxui::text(_("Block Height: ")), ftxui::text(std::to_string(info_->blockHeight()))},
|
||||||
minors_,
|
minors_,
|
||||||
coinbase_
|
coinbase_
|
||||||
};
|
};
|
||||||
@@ -148,7 +150,7 @@ namespace lwcli { namespace view
|
|||||||
std::string{_(" to Unknown Address")} : _(" to ") + transfer.address;
|
std::string{_(" to Unknown Address")} : _(" to ") + transfer.address;
|
||||||
std::string text;
|
std::string text;
|
||||||
if (base_size == grid.size())
|
if (base_size == grid.size())
|
||||||
text = "Transfers: ";
|
text = _("Transfers: ");
|
||||||
grid.push_back({
|
grid.push_back({
|
||||||
ftxui::text(std::move(text)),
|
ftxui::text(std::move(text)),
|
||||||
ftxui::text(print_money(transfer.amount) + address)
|
ftxui::text(print_money(transfer.amount) + address)
|
||||||
@@ -156,15 +158,17 @@ namespace lwcli { namespace view
|
|||||||
}
|
}
|
||||||
|
|
||||||
ftxui::Elements vertical;
|
ftxui::Elements vertical;
|
||||||
vertical.reserve(3);
|
vertical.reserve(4);
|
||||||
|
|
||||||
|
vertical.push_back(ftxui::hcenter(ftxui::hbox(cancel_->Render(), ok_->Render())));
|
||||||
if (info_->isFailed())
|
if (info_->isFailed())
|
||||||
vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text("FAILED"))));
|
vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text(_("FAILED")))));
|
||||||
else if (info_->isPending())
|
else if (info_->isPending())
|
||||||
vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text("PENDING"))));
|
vertical.push_back(ftxui::inverted(ftxui::hcenter(ftxui::text(_("PENDING")))));
|
||||||
|
else
|
||||||
|
vertical.push_back(ftxui::separator());
|
||||||
|
|
||||||
vertical.push_back(ftxui::gridbox(std::move(grid)));
|
vertical.push_back(ftxui::gridbox(std::move(grid)));
|
||||||
vertical.push_back(ftxui::hcenter(ftxui::hbox(cancel_->Render(), ok_->Render())));
|
|
||||||
|
|
||||||
return ftxui::window(title_, ftxui::vbox(std::move(vertical)));
|
return ftxui::window(title_, ftxui::vbox(std::move(vertical)));
|
||||||
}
|
}
|
||||||
@@ -231,8 +235,8 @@ namespace lwcli { namespace view
|
|||||||
}, ftxui::ButtonOption::Ascii());
|
}, ftxui::ButtonOption::Ascii());
|
||||||
|
|
||||||
container_ = ftxui::Container::Vertical({
|
container_ = ftxui::Container::Vertical({
|
||||||
note_input_,
|
ftxui::Container::Horizontal({cancel_, ok_}),
|
||||||
ftxui::Container::Horizontal({cancel_, ok_})
|
note_input_
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -245,7 +249,7 @@ namespace lwcli { namespace view
|
|||||||
ftxui::Element table_; //!< Does not redraw when child is enabled
|
ftxui::Element table_; //!< Does not redraw when child is enabled
|
||||||
std::vector<std::vector<std::string>> base_rows;
|
std::vector<std::vector<std::string>> base_rows;
|
||||||
std::ptrdiff_t highlighted_;
|
std::ptrdiff_t highlighted_;
|
||||||
std::size_t row_count_;
|
std::unordered_map<std::size_t, int> row_map_;
|
||||||
const std::uint32_t account_;
|
const std::uint32_t account_;
|
||||||
|
|
||||||
static constexpr std::size_t min_row() noexcept { return 2; };
|
static constexpr std::size_t min_row() noexcept { return 2; };
|
||||||
@@ -261,7 +265,7 @@ namespace lwcli { namespace view
|
|||||||
table_(nullptr),
|
table_(nullptr),
|
||||||
base_rows(),
|
base_rows(),
|
||||||
highlighted_(min_row() - 1),
|
highlighted_(min_row() - 1),
|
||||||
row_count_(0),
|
row_map_(),
|
||||||
account_(account)
|
account_(account)
|
||||||
{
|
{
|
||||||
Monero::Subaddress* subaddress = wallet_->subaddress();
|
Monero::Subaddress* subaddress = wallet_->subaddress();
|
||||||
@@ -294,17 +298,19 @@ namespace lwcli { namespace view
|
|||||||
}
|
}
|
||||||
else if (event == ftxui::Event::CtrlQ)
|
else if (event == ftxui::Event::CtrlQ)
|
||||||
throw event::close{};
|
throw event::close{};
|
||||||
else if (event == ftxui::Event::ArrowDown && highlighted_ + 1 < row_count_)
|
else if (event.is_character())
|
||||||
|
return false;
|
||||||
|
else if (event == ftxui::Event::ArrowDown && highlighted_ < row_map_.size() + min_row() - 1)
|
||||||
++highlighted_;
|
++highlighted_;
|
||||||
else if (event == ftxui::Event::ArrowUp && min_row() <= highlighted_)
|
else if (event == ftxui::Event::ArrowUp && min_row() <= highlighted_)
|
||||||
--highlighted_;
|
--highlighted_;
|
||||||
else if (event == ftxui::Event::PageDown)
|
else if (event == ftxui::Event::PageDown)
|
||||||
highlighted_ = std::min(std::ptrdiff_t(row_count_), highlighted_ + 10);
|
highlighted_ = std::min(std::ptrdiff_t(row_map_.size() + min_row()), highlighted_ + 10);
|
||||||
else if (event == ftxui::Event::PageUp)
|
else if (event == ftxui::Event::PageUp)
|
||||||
highlighted_ = std::max(std::ptrdiff_t(min_row()), highlighted_ - 10);
|
highlighted_ = std::max(std::ptrdiff_t(min_row()), highlighted_ - 10);
|
||||||
else if (event == ftxui::Event::Return)
|
else if (event == ftxui::Event::Return)
|
||||||
{
|
{
|
||||||
overlay_ = ftxui::Make<tx_details>(wallet_->history(), row_count_ - highlighted_ - 1);
|
overlay_ = ftxui::Make<tx_details>(wallet_->history(), row_map_.at(highlighted_ - min_row()));
|
||||||
Add(overlay_);
|
Add(overlay_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +321,7 @@ namespace lwcli { namespace view
|
|||||||
overlay_->Detach();
|
overlay_->Detach();
|
||||||
overlay_.reset();
|
overlay_.reset();
|
||||||
}
|
}
|
||||||
return min_row() <= highlighted_ && highlighted_ < row_count_;
|
return min_row() <= highlighted_ && highlighted_ - min_row() < row_map_.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
ftxui::Element OnRender() override final
|
ftxui::Element OnRender() override final
|
||||||
@@ -324,8 +330,8 @@ namespace lwcli { namespace view
|
|||||||
{
|
{
|
||||||
if (highlighted_ < min_row())
|
if (highlighted_ < min_row())
|
||||||
highlighted_ = min_row();
|
highlighted_ = min_row();
|
||||||
else if (row_count_ <= highlighted_)
|
else if (row_map_.size() <= highlighted_ - min_row())
|
||||||
highlighted_ = row_count_ - 1;
|
highlighted_ = row_map_.size() - 1 + min_row();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Do not redraw table when showing tx. the
|
/* Do not redraw table when showing tx. the
|
||||||
@@ -335,33 +341,47 @@ namespace lwcli { namespace view
|
|||||||
Monero::TransactionHistory* tx_history = wallet_->history();
|
Monero::TransactionHistory* tx_history = wallet_->history();
|
||||||
if (!tx_history)
|
if (!tx_history)
|
||||||
throw std::runtime_error{"unexpected history nullptr"};
|
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();
|
tx_history->refresh();
|
||||||
std::vector<std::vector<std::string>> rows = base_rows;
|
|
||||||
|
|
||||||
std::uint64_t balance = 0;
|
|
||||||
|
|
||||||
const auto history = tx_history->getAll();
|
const auto history = tx_history->getAll();
|
||||||
for (auto it = history.rbegin(); it != history.rend(); ++it)
|
for (std::size_t i = 0; i < history.size(); ++i)
|
||||||
{
|
{
|
||||||
const Monero::TransactionInfo* tx = *it;
|
const Monero::TransactionInfo* tx = history[i];
|
||||||
if (!tx)
|
if (!tx)
|
||||||
throw std::runtime_error{"unexpected tx_info nullptr"};
|
throw std::runtime_error{"unexpected tx_info nullptr"};
|
||||||
|
|
||||||
if (tx->subaddrAccount() != account_)
|
if (tx->subaddrAccount() == account_)
|
||||||
continue;
|
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 std::uint64_t amount = tx->amount();
|
||||||
const int direction = tx->direction();
|
const int direction = tx->direction();
|
||||||
const std::uint64_t fee = tx->fee();
|
const std::uint64_t fee = tx->fee();
|
||||||
|
|
||||||
if (direction == Monero::TransactionInfo::Direction_Out)
|
|
||||||
|
if (!tx->isFailed())
|
||||||
{
|
{
|
||||||
balance -= amount;
|
if (direction == Monero::TransactionInfo::Direction_Out)
|
||||||
balance -= fee;
|
{
|
||||||
|
balance -= amount;
|
||||||
|
balance -= fee;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
balance += amount;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
balance += amount;
|
|
||||||
|
|
||||||
std::tm expanded{};
|
std::tm expanded{};
|
||||||
const std::time_t timestamp = tx->timestamp();
|
const std::time_t timestamp = tx->timestamp();
|
||||||
@@ -386,15 +406,16 @@ namespace lwcli { namespace view
|
|||||||
print_money(tx->fee()),
|
print_money(tx->fee()),
|
||||||
tx->hash().substr(0, 16)
|
tx->hash().substr(0, 16)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
row_count_ = rows.size();
|
|
||||||
ftxui::Table table{std::move(rows)};
|
ftxui::Table table{std::move(rows)};
|
||||||
table.SelectRow(0).Decorate(ftxui::bold);
|
table.SelectRow(0).Decorate(ftxui::bold);
|
||||||
table.SelectRow(0).SeparatorVertical(ftxui::LIGHT);
|
table.SelectRow(0).SeparatorVertical(ftxui::LIGHT);
|
||||||
table.SelectRow(0).Border(ftxui::LIGHT);
|
table.SelectRow(0).Border(ftxui::LIGHT);
|
||||||
table.SelectColumn(3).DecorateCells(ftxui::xflex_grow);
|
//table.SelectColumn(3).DecorateCells(ftxui::xflex_grow);
|
||||||
if (min_row() <= highlighted_ && highlighted_ < row_count_)
|
if (min_row() <= highlighted_ && highlighted_ - min_row() < row_map_.size())
|
||||||
{
|
{
|
||||||
auto row = table.SelectRow(highlighted_);
|
auto row = table.SelectRow(highlighted_);
|
||||||
row.Decorate(ftxui::inverted);
|
row.Decorate(ftxui::inverted);
|
||||||
@@ -407,7 +428,8 @@ namespace lwcli { namespace view
|
|||||||
table_ = ftxui::vbox({
|
table_ = ftxui::vbox({
|
||||||
title_,
|
title_,
|
||||||
ftxui::text(_("Balance: ") + print_money(balance)),
|
ftxui::text(_("Balance: ") + print_money(balance)),
|
||||||
ftxui::yframe(ftxui::vscroll_indicator(table.Render()))
|
table.Render() | ftxui::vscroll_indicator | ftxui::yframe | ftxui::center | ftxui::flex
|
||||||
|
//ftxui::flex(ftxui::yframe(ftxui::vscroll_indicator(table.Render())))
|
||||||
});
|
});
|
||||||
return table_;
|
return table_;
|
||||||
}
|
}
|
||||||
|
|||||||
+110
-69
@@ -58,11 +58,21 @@ namespace lwcli { namespace view
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ftxui::Component password(std::string* pass)
|
std::string get_home()
|
||||||
|
{
|
||||||
|
const char* home = std::getenv("HOME");
|
||||||
|
if (home)
|
||||||
|
return home;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ftxui::Component password(std::string* pass, std::function<void()> on_enter = nullptr)
|
||||||
{
|
{
|
||||||
auto opt = ftxui::InputOption::Default();
|
auto opt = ftxui::InputOption::Default();
|
||||||
opt.password = true;
|
opt.password = true;
|
||||||
opt.multiline = false;
|
opt.multiline = false;
|
||||||
|
if (on_enter)
|
||||||
|
opt.on_enter = std::move(on_enter);
|
||||||
return ftxui::Input(pass, std::move(opt));
|
return ftxui::Input(pass, std::move(opt));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,13 +104,9 @@ namespace lwcli { namespace view
|
|||||||
std::string file;
|
std::string file;
|
||||||
std::string password;
|
std::string password;
|
||||||
|
|
||||||
wallet_base()
|
wallet_base(std::string default_file)
|
||||||
: file(), password()
|
: file(std::move(default_file)), password()
|
||||||
{
|
{}
|
||||||
const char* home = std::getenv("HOME");
|
|
||||||
if (home)
|
|
||||||
file = home;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct new_wallet : wallet_base
|
struct new_wallet : wallet_base
|
||||||
@@ -111,8 +117,8 @@ namespace lwcli { namespace view
|
|||||||
std::string proxy;
|
std::string proxy;
|
||||||
bool ssl;
|
bool ssl;
|
||||||
|
|
||||||
new_wallet()
|
new_wallet(std::string default_file)
|
||||||
: wallet_base(), confirm(), language(config::default_language), server(config::server::default_url), proxy(), ssl(false)
|
: wallet_base(std::move(default_file)), confirm(), language(config::default_language), server(config::server::default_url), proxy(), ssl(false)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void setup(Monero::Wallet& wal)
|
void setup(Monero::Wallet& wal)
|
||||||
@@ -136,7 +142,7 @@ namespace lwcli { namespace view
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<std::uint64_t> from_string(const std::string& str) noexcept
|
std::optional<std::uint64_t> from_string(const std::string_view str) noexcept
|
||||||
{
|
{
|
||||||
std::uint64_t height = 0;
|
std::uint64_t height = 0;
|
||||||
const auto end = str.data() + str.size();
|
const auto end = str.data() + str.size();
|
||||||
@@ -159,57 +165,63 @@ namespace lwcli { namespace view
|
|||||||
|
|
||||||
if (!wal.init(wal.getCacheAttribute(std::string{config::server::url}), 0, "", "", *ssl, true, wal.getCacheAttribute(std::string{config::server::proxy})))
|
if (!wal.init(wal.getCacheAttribute(std::string{config::server::url}), 0, "", "", *ssl, true, wal.getCacheAttribute(std::string{config::server::proxy})))
|
||||||
{
|
{
|
||||||
*error = wal.errorString();
|
*error = "Failure to initialize" + wal.errorString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::vector<std::pair<ftxui::Element, ftxui::Component>>, ftxui::Component> get_load_options(start_state* state)
|
using option_set = std::pair<std::vector<std::pair<ftxui::Element, ftxui::Component>>, ftxui::Component>;
|
||||||
|
|
||||||
|
option_set get_load_options(std::string default_file, start_state* state)
|
||||||
{
|
{
|
||||||
struct options
|
struct options
|
||||||
{
|
{
|
||||||
start_state* state;
|
start_state* state;
|
||||||
wallet_base config;
|
wallet_base config;
|
||||||
|
|
||||||
options(start_state* state)
|
options(std::string default_file, start_state* state)
|
||||||
: state(state), config()
|
: state(state), config(std::move(default_file))
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
auto enclosed = std::make_shared<options>(state);
|
auto enclosed = std::make_shared<options>(std::move(default_file), state);
|
||||||
auto load = ftxui::Button("Load", [enclosed] () {
|
const auto load_action = [enclosed] () {
|
||||||
auto prepped = prep_wallet(
|
auto prepped = prep_wallet(
|
||||||
enclosed->state->wm,
|
enclosed->state->wm,
|
||||||
enclosed->state->wm->openWallet(enclosed->config.file, enclosed->config.password, Monero::TESTNET),
|
enclosed->state->wm->openWallet(enclosed->config.file, enclosed->config.password, config::network),
|
||||||
&enclosed->state->error
|
&enclosed->state->error
|
||||||
);
|
);
|
||||||
if (prepped)
|
if (prepped)
|
||||||
{
|
{
|
||||||
enclosed->config.password.clear();
|
if (init_wallet(*prepped, &enclosed->state->error))
|
||||||
init_wallet(*prepped, &enclosed->state->error);
|
{
|
||||||
enclosed->state->wal = prepped;
|
enclosed->config.password.clear();
|
||||||
|
prepped->startRefresh();
|
||||||
|
enclosed->state->wal = prepped;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, ftxui::ButtonOption::Ascii());
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
{ftxui::text(_("Filename: ")), last_input(&enclosed->config.file)},
|
{ftxui::text(_("Filename: ")), last_input(&enclosed->config.file)},
|
||||||
{ftxui::text(_("Password: ")), password(&enclosed->config.password)}
|
{ftxui::text(_("Password: ")), password(&enclosed->config.password, load_action)}
|
||||||
},
|
},
|
||||||
load
|
ftxui::Button(_("Load"), load_action, ftxui::ButtonOption::Ascii())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
std::pair<std::vector<std::pair<ftxui::Element, ftxui::Component>>, ftxui::Component> get_create_options(start_state* state)
|
option_set get_create_options(std::string default_file, start_state* state)
|
||||||
{
|
{
|
||||||
struct options
|
struct options
|
||||||
{
|
{
|
||||||
start_state* state;
|
start_state* state;
|
||||||
new_wallet config;
|
new_wallet config;
|
||||||
|
|
||||||
options(start_state* state)
|
options(std::string default_file, start_state* state)
|
||||||
: state(state), config()
|
: state(state), config(std::move(default_file))
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
auto enclosed = std::make_shared<options>(state);
|
auto enclosed = std::make_shared<options>(std::move(default_file), state);
|
||||||
auto create = ftxui::Button("Create", [enclosed] () {
|
auto create = ftxui::Button("Create", [enclosed] () {
|
||||||
if (!enclosed->config.file.empty())
|
if (!enclosed->config.file.empty())
|
||||||
{
|
{
|
||||||
@@ -223,19 +235,21 @@ namespace lwcli { namespace view
|
|||||||
{
|
{
|
||||||
auto prepped = prep_wallet(
|
auto prepped = prep_wallet(
|
||||||
enclosed->state->wm,
|
enclosed->state->wm,
|
||||||
enclosed->state->wm->createWallet(enclosed->config.file, enclosed->config.password, enclosed->config.language, Monero::TESTNET),
|
enclosed->state->wm->createWallet(enclosed->config.file, enclosed->config.password, enclosed->config.language, config::network),
|
||||||
&enclosed->state->error
|
&enclosed->state->error
|
||||||
);
|
);
|
||||||
if (prepped)
|
if (prepped)
|
||||||
{
|
{
|
||||||
if (prepped->store({}))
|
if (prepped->store({}))
|
||||||
{
|
{
|
||||||
enclosed->config.password.clear();
|
|
||||||
enclosed->config.confirm.clear();
|
|
||||||
enclosed->config.setup(*prepped);
|
enclosed->config.setup(*prepped);
|
||||||
init_wallet(*prepped, &enclosed->state->error);
|
if (init_wallet(*prepped, &enclosed->state->error))
|
||||||
enclosed->state->overlay = view::keys(prepped, true /* show warning */);
|
{
|
||||||
enclosed->state->wal = prepped;
|
enclosed->config.password.clear();
|
||||||
|
enclosed->config.confirm.clear();
|
||||||
|
enclosed->state->overlay = view::keys(prepped, true /* show warning */);
|
||||||
|
enclosed->state->wal = prepped;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
enclosed->state->error = "Unable to create file: " + prepped->errorString();
|
enclosed->state->error = "Unable to create file: " + prepped->errorString();
|
||||||
@@ -253,14 +267,14 @@ namespace lwcli { namespace view
|
|||||||
{ftxui::text(_("Password: ")), password(&enclosed->config.password)},
|
{ftxui::text(_("Password: ")), password(&enclosed->config.password)},
|
||||||
{ftxui::text(_("Confirm: ")), password(&enclosed->config.confirm)},
|
{ftxui::text(_("Confirm: ")), password(&enclosed->config.confirm)},
|
||||||
{ftxui::text(_("Language: ")), last_input(&enclosed->config.language)},
|
{ftxui::text(_("Language: ")), last_input(&enclosed->config.language)},
|
||||||
{ftxui::text(_("LWS Server: ")), last_input(&enclosed->config.server)},
|
{ftxui::text(_("API Server: ")), last_input(&enclosed->config.server)},
|
||||||
{ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)},
|
{ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)},
|
||||||
{ftxui::text(_("Server Options: ")), ftxui::Checkbox(_("SSL"), &enclosed->config.ssl)}
|
{ftxui::text(_("Options: ")), ftxui::Checkbox(_("TLS/SSL Cert Check"), &enclosed->config.ssl)}
|
||||||
},
|
},
|
||||||
create
|
create
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
std::pair<std::vector<std::pair<ftxui::Element, ftxui::Component>>, ftxui::Component> get_seed_options(start_state* state)
|
option_set get_seed_options(std::string default_file, start_state* state)
|
||||||
{
|
{
|
||||||
struct options
|
struct options
|
||||||
{
|
{
|
||||||
@@ -269,11 +283,11 @@ namespace lwcli { namespace view
|
|||||||
std::string height;
|
std::string height;
|
||||||
new_wallet config;
|
new_wallet config;
|
||||||
|
|
||||||
options(start_state* state)
|
options(std::string default_file, start_state* state)
|
||||||
: state(state), mnemonic(), height("0"), config()
|
: state(state), mnemonic(), height("0"), config(std::move(default_file))
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
auto enclosed = std::make_shared<options>(state);
|
auto enclosed = std::make_shared<options>(std::move(default_file), state);
|
||||||
auto recover = ftxui::Button("Recover", [enclosed] () {
|
auto recover = ftxui::Button("Recover", [enclosed] () {
|
||||||
const auto height = from_string(enclosed->height);
|
const auto height = from_string(enclosed->height);
|
||||||
if (!height)
|
if (!height)
|
||||||
@@ -294,18 +308,22 @@ namespace lwcli { namespace view
|
|||||||
{
|
{
|
||||||
auto prepped = prep_wallet(
|
auto prepped = prep_wallet(
|
||||||
enclosed->state->wm,
|
enclosed->state->wm,
|
||||||
enclosed->state->wm->recoveryWallet(enclosed->config.file, enclosed->config.password, enclosed->mnemonic, Monero::TESTNET, *height),
|
enclosed->state->wm->recoveryWallet(enclosed->config.file, enclosed->config.password, enclosed->mnemonic, config::network, *height),
|
||||||
&enclosed->state->error
|
&enclosed->state->error
|
||||||
);
|
);
|
||||||
if (prepped)
|
if (prepped)
|
||||||
{
|
{
|
||||||
if (prepped->store({}))
|
if (prepped->store({}))
|
||||||
{
|
{
|
||||||
enclosed->config.password.clear();
|
|
||||||
enclosed->config.confirm.clear();
|
|
||||||
enclosed->config.setup(*prepped);
|
enclosed->config.setup(*prepped);
|
||||||
init_wallet(*prepped, &enclosed->state->error);
|
if (init_wallet(*prepped, &enclosed->state->error))
|
||||||
enclosed->state->wal = prepped;
|
{
|
||||||
|
enclosed->mnemonic.clear();
|
||||||
|
enclosed->config.password.clear();
|
||||||
|
enclosed->config.confirm.clear();
|
||||||
|
prepped->rescanBlockchainAsync();
|
||||||
|
enclosed->state->wal = prepped;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
enclosed->state->error = "Unable to create file: " + prepped->errorString();
|
enclosed->state->error = "Unable to create file: " + prepped->errorString();
|
||||||
@@ -324,15 +342,15 @@ namespace lwcli { namespace view
|
|||||||
{ftxui::text(_("Confirm: ")), password(&enclosed->config.confirm)},
|
{ftxui::text(_("Confirm: ")), password(&enclosed->config.confirm)},
|
||||||
{ftxui::text(_("Mnemonic: ")), last_input(&enclosed->mnemonic)},
|
{ftxui::text(_("Mnemonic: ")), last_input(&enclosed->mnemonic)},
|
||||||
{ftxui::text(_("Height: ")), last_input(&enclosed->height)},
|
{ftxui::text(_("Height: ")), last_input(&enclosed->height)},
|
||||||
{ftxui::text(_("LWS Server: ")), last_input(&enclosed->config.server)},
|
{ftxui::text(_("API Server: ")), last_input(&enclosed->config.server)},
|
||||||
{ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)},
|
{ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)},
|
||||||
{ftxui::text(_("Server Options: ")), ftxui::Checkbox(_("SSL"), &enclosed->config.ssl)}
|
{ftxui::text(_("Options: ")), ftxui::Checkbox(_("TLS/SSL Cert Check"), &enclosed->config.ssl)}
|
||||||
},
|
},
|
||||||
recover
|
recover
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::vector<std::pair<ftxui::Element, ftxui::Component>>, ftxui::Component> get_key_options(start_state* state)
|
option_set get_key_options(std::string default_file, start_state* state)
|
||||||
{
|
{
|
||||||
struct options
|
struct options
|
||||||
{
|
{
|
||||||
@@ -341,12 +359,12 @@ namespace lwcli { namespace view
|
|||||||
std::string height;
|
std::string height;
|
||||||
new_wallet config;
|
new_wallet config;
|
||||||
|
|
||||||
options(start_state* state)
|
options(std::string default_file, start_state* state)
|
||||||
: state(state), spend_key(), height("0"), config()
|
: state(state), spend_key(), height("0"), config(std::move(default_file))
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
auto enclosed = std::make_shared<options>(state);
|
auto enclosed = std::make_shared<options>(std::move(default_file), state);
|
||||||
auto recover = ftxui::Button("Recover (Broken)", [enclosed] () {
|
auto recover = ftxui::Button(_("Recover (Broken)"), [enclosed] () {
|
||||||
const auto height = from_string(enclosed->height);
|
const auto height = from_string(enclosed->height);
|
||||||
if (!height)
|
if (!height)
|
||||||
{
|
{
|
||||||
@@ -370,7 +388,7 @@ namespace lwcli { namespace view
|
|||||||
enclosed->config.file,
|
enclosed->config.file,
|
||||||
enclosed->config.password,
|
enclosed->config.password,
|
||||||
enclosed->config.language,
|
enclosed->config.language,
|
||||||
Monero::TESTNET,
|
config::network,
|
||||||
*height,
|
*height,
|
||||||
"address",
|
"address",
|
||||||
"view_key",
|
"view_key",
|
||||||
@@ -382,11 +400,15 @@ namespace lwcli { namespace view
|
|||||||
{
|
{
|
||||||
if (prepped->store({}))
|
if (prepped->store({}))
|
||||||
{
|
{
|
||||||
enclosed->config.password.clear();
|
|
||||||
enclosed->config.confirm.clear();
|
|
||||||
enclosed->config.setup(*prepped);
|
enclosed->config.setup(*prepped);
|
||||||
init_wallet(*prepped, &enclosed->state->error);
|
if (init_wallet(*prepped, &enclosed->state->error))
|
||||||
enclosed->state->wal = prepped;
|
{
|
||||||
|
enclosed->spend_key.clear();
|
||||||
|
enclosed->config.password.clear();
|
||||||
|
enclosed->config.confirm.clear();
|
||||||
|
prepped->rescanBlockchainAsync();
|
||||||
|
enclosed->state->wal = prepped;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
enclosed->state->error = "Unable to create file: " + prepped->errorString();
|
enclosed->state->error = "Unable to create file: " + prepped->errorString();
|
||||||
@@ -406,9 +428,9 @@ namespace lwcli { namespace view
|
|||||||
{ftxui::text(_("Spend key: ")), last_input(&enclosed->spend_key)},
|
{ftxui::text(_("Spend key: ")), last_input(&enclosed->spend_key)},
|
||||||
{ftxui::text(_("Height: ")), last_input(&enclosed->height)},
|
{ftxui::text(_("Height: ")), last_input(&enclosed->height)},
|
||||||
{ftxui::text(_("Language: ")), last_input(&enclosed->config.language)},
|
{ftxui::text(_("Language: ")), last_input(&enclosed->config.language)},
|
||||||
{ftxui::text(_("LWS Server: ")), last_input(&enclosed->config.server)},
|
{ftxui::text(_("API Server: ")), last_input(&enclosed->config.server)},
|
||||||
{ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)},
|
{ftxui::text(_("Proxy: ")), last_input(&enclosed->config.proxy)},
|
||||||
{ftxui::text(_("Connection Options: ")), ftxui::Checkbox(_("SSL"), &enclosed->config.ssl)}
|
{ftxui::text(_("Options: ")), ftxui::Checkbox(_("TLS/SSL Cert Check"), &enclosed->config.ssl)}
|
||||||
},
|
},
|
||||||
recover
|
recover
|
||||||
};
|
};
|
||||||
@@ -421,6 +443,7 @@ namespace lwcli { namespace view
|
|||||||
const ftxui::Element help_;
|
const ftxui::Element help_;
|
||||||
const ftxui::Element disclaimer_;
|
const ftxui::Element disclaimer_;
|
||||||
start_state state_;
|
start_state state_;
|
||||||
|
const std::string default_file_;
|
||||||
const std::vector<std::string> options_;
|
const std::vector<std::string> options_;
|
||||||
int active_;
|
int active_;
|
||||||
int selected_;
|
int selected_;
|
||||||
@@ -437,16 +460,24 @@ namespace lwcli { namespace view
|
|||||||
return ui_;
|
return ui_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int get_selected(const std::string& file)
|
||||||
|
{
|
||||||
|
// default to load file if exists, and create if does not exist
|
||||||
|
std::error_code ec{};
|
||||||
|
return file.empty() || std::filesystem::exists(file, ec) ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit start(std::shared_ptr<Monero::WalletManager>&& wm, std::shared_ptr<Monero::Wallet>* out)
|
explicit start(std::shared_ptr<Monero::WalletManager>&& wm, std::string&& file, std::shared_ptr<Monero::Wallet>* out)
|
||||||
: out_(out),
|
: out_(out),
|
||||||
title_(ftxui::text("wmcli")),
|
title_(ftxui::text("wmcli")),
|
||||||
help_(decorate::banner(ftxui::text(_("Ctrl-Q to close active window, Ctrl-C close app immediately")))),
|
help_(decorate::banner(ftxui::text(_("Ctrl-Q to close active window, Ctrl-C close app immediately")))),
|
||||||
disclaimer_(decorate::banner(ftxui::text(_("Beware of mouse events in Tmux/Screen")))),
|
disclaimer_(decorate::banner(ftxui::text(_("Beware of mouse events in Tmux/Screen")))),
|
||||||
state_(std::move(wm)),
|
state_(std::move(wm)),
|
||||||
|
default_file_(std::move(file)),
|
||||||
options_({_("Load Wallet"), _("Create Wallet"), _("Recover from Seed"), _("Recover from Keys")}),
|
options_({_("Load Wallet"), _("Create Wallet"), _("Recover from Seed"), _("Recover from Keys")}),
|
||||||
active_(-1),
|
active_(-1),
|
||||||
selected_(0),
|
selected_(get_selected(default_file_)),
|
||||||
mode_(ftxui::Dropdown(&options_, &selected_)),
|
mode_(ftxui::Dropdown(&options_, &selected_)),
|
||||||
completion_(),
|
completion_(),
|
||||||
stack_(),
|
stack_(),
|
||||||
@@ -459,21 +490,22 @@ namespace lwcli { namespace view
|
|||||||
{
|
{
|
||||||
if (active_ != selected_)
|
if (active_ != selected_)
|
||||||
{
|
{
|
||||||
|
const bool first_load = active_ == -1;
|
||||||
active_ = selected_;
|
active_ = selected_;
|
||||||
switch(active_)
|
switch(active_)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
default:
|
default:
|
||||||
std::tie(stack_, completion_) = get_load_options(&state_);
|
std::tie(stack_, completion_) = get_load_options((default_file_.empty() ? get_home() : default_file_), &state_);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
std::tie(stack_, completion_) = get_create_options(&state_);
|
std::tie(stack_, completion_) = get_create_options((default_file_.empty() ? get_home() : default_file_), &state_);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
std::tie(stack_, completion_) = get_seed_options(&state_);
|
std::tie(stack_, completion_) = get_seed_options((default_file_.empty() ? get_home() : default_file_), &state_);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
std::tie(stack_, completion_) = get_key_options(&state_);
|
std::tie(stack_, completion_) = get_key_options((default_file_.empty() ? get_home() : default_file_), &state_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,6 +517,15 @@ namespace lwcli { namespace view
|
|||||||
ui.push_back(completion_);
|
ui.push_back(completion_);
|
||||||
|
|
||||||
ui_ = ftxui::Container::Vertical(std::move(ui));
|
ui_ = ftxui::Container::Vertical(std::move(ui));
|
||||||
|
|
||||||
|
// skip directly to password on init if default file given
|
||||||
|
if (first_load)
|
||||||
|
{
|
||||||
|
if (!default_file_.empty())
|
||||||
|
stack_.at(1).second->TakeFocus(); // jump to password field (load or create wallet)
|
||||||
|
else
|
||||||
|
stack_.at(0).second->TakeFocus(); // jump tp file (load wallet)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay showing wallet if options required overlay
|
// Delay showing wallet if options required overlay
|
||||||
@@ -566,10 +607,10 @@ namespace lwcli { namespace view
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit manager_(std::shared_ptr<Monero::WalletManager>&& wm)
|
explicit manager_(std::shared_ptr<Monero::WalletManager>&& wm, std::string&& file)
|
||||||
: ftxui::ComponentBase(),
|
: ftxui::ComponentBase(),
|
||||||
data_(nullptr),
|
data_(nullptr),
|
||||||
start_(std::make_shared<start>(std::move(wm), &data_)),
|
start_(std::make_shared<start>(std::move(wm), std::move(file), &data_)),
|
||||||
wallet_(nullptr)
|
wallet_(nullptr)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@@ -602,10 +643,10 @@ namespace lwcli { namespace view
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
ftxui::Component manager(std::shared_ptr<Monero::WalletManager> wm)
|
ftxui::Component manager(std::shared_ptr<Monero::WalletManager> wm, std::string&& file)
|
||||||
{
|
{
|
||||||
if (!wm)
|
if (!wm)
|
||||||
throw std::runtime_error{"lwcli::view::manaager given nullptr"};
|
throw std::runtime_error{"lwcli::view::manaager given nullptr"};
|
||||||
return std::make_shared<manager_>(std::move(wm));
|
return std::make_shared<manager_>(std::move(wm), std::move(file));
|
||||||
}
|
}
|
||||||
}} // lwcli // view
|
}} // lwcli // view
|
||||||
|
|||||||
+2
-1
@@ -29,10 +29,11 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <ftxui/component/component_base.hpp>
|
#include <ftxui/component/component_base.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace Monero { class WalletManager; }
|
namespace Monero { class WalletManager; }
|
||||||
namespace lwcli { namespace view
|
namespace lwcli { namespace view
|
||||||
{
|
{
|
||||||
ftxui::Component manager(std::shared_ptr<Monero::WalletManager> wm);
|
ftxui::Component manager(std::shared_ptr<Monero::WalletManager> wm, std::string&& file);
|
||||||
}} // lwscli // view
|
}} // lwscli // view
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,236 @@
|
|||||||
|
// 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 <charconv>
|
||||||
|
#include <ftxui/component/component.hpp>
|
||||||
|
#include <ftxui/component/event.hpp>
|
||||||
|
#include <ftxui/dom/table.hpp>
|
||||||
|
#include <lws_frontend.h>
|
||||||
|
|
||||||
|
#include "decorate/overlay.h"
|
||||||
|
#include "events.h"
|
||||||
|
#include "lwcli_config.h"
|
||||||
|
#include "translate.h"
|
||||||
|
#include "views/history.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace lwcli { namespace view
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::optional<std::uint64_t> from_string(const std::string_view str) noexcept
|
||||||
|
{
|
||||||
|
std::uint64_t out = 0;
|
||||||
|
const auto end = str.data() + str.size();
|
||||||
|
auto [ptr, ec] = std::from_chars(str.data(), end, out);
|
||||||
|
if (bool(ec) || ptr != end)
|
||||||
|
return std::nullopt;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_proxy(Monero::Wallet& wal, const std::string& proxy)
|
||||||
|
{
|
||||||
|
wal.setProxy(proxy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_url(Monero::Wallet& wal, const std::string& url)
|
||||||
|
{
|
||||||
|
const bool is_ssl = bool(from_string(wal.getCacheAttribute(std::string{config::server::ssl})));
|
||||||
|
wal.init(url, 0, "", "", is_ssl, true, wal.getCacheAttribute(std::string{config::server::proxy}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_refresh(Monero::Wallet& wal, const std::string& interval)
|
||||||
|
{
|
||||||
|
const auto number = from_string(interval);
|
||||||
|
if (!number)
|
||||||
|
return false;
|
||||||
|
wal.setAutoRefreshInterval(std::chrono::milliseconds{std::chrono::seconds{*number}}.count());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool set_ssl(Monero::Wallet& wal, const std::string& ssl)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct option
|
||||||
|
{
|
||||||
|
using updater = bool(Monero::Wallet&, const std::string&);
|
||||||
|
const std::string_view path;
|
||||||
|
const std::string_view description;
|
||||||
|
updater* const update;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::array<option, 4> options{{
|
||||||
|
{config::server::proxy, _("Proxy"), set_proxy},
|
||||||
|
{config::server::refresh_interval, _("Refresh Interval (seconds)"), set_refresh},
|
||||||
|
{config::server::ssl, _("TLS/SSL Cert Check"), set_ssl},
|
||||||
|
{config::server::url, _("API Server"), set_url}
|
||||||
|
}};
|
||||||
|
|
||||||
|
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(); }
|
||||||
|
|
||||||
|
struct option_state
|
||||||
|
{
|
||||||
|
ftxui::Element description;
|
||||||
|
std::string original;
|
||||||
|
std::string value;
|
||||||
|
ftxui::Component ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct configuration
|
||||||
|
{
|
||||||
|
std::array<option_state, options.size()> states;
|
||||||
|
|
||||||
|
configuration(const Monero::Wallet& wal)
|
||||||
|
: states()
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < options.size(); ++i)
|
||||||
|
{
|
||||||
|
states[i].description = ftxui::text(std::string{options[i].description} + ": ");
|
||||||
|
states[i].original = wal.getCacheAttribute(std::string{options[i].path});
|
||||||
|
states[i].value = states[i].original;
|
||||||
|
states[i].ui = last_input(&states[i].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void store(Monero::Wallet& wal, std::string& error)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < options.size(); ++i)
|
||||||
|
{
|
||||||
|
if (states[i].original != states[i].value)
|
||||||
|
{
|
||||||
|
if (!options[i].update || options[i].update(wal, states[i].value))
|
||||||
|
wal.setCacheAttribute(std::string{options[i].path}, states[i].value);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error = std::string{options[i].description} + " is invalid";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class settings_ final : public ftxui::ComponentBase
|
||||||
|
{
|
||||||
|
const std::shared_ptr<Monero::Wallet> wal_;
|
||||||
|
const ftxui::Element title_;
|
||||||
|
const std::unique_ptr<configuration> config_;
|
||||||
|
std::string error_;
|
||||||
|
ftxui::Component buttons_;
|
||||||
|
ftxui::Component ui_;
|
||||||
|
|
||||||
|
bool Focusable() const override final { return true; }
|
||||||
|
ftxui::Component ActiveChild() override final { return ui_; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit settings_(std::shared_ptr<Monero::Wallet>&& wal)
|
||||||
|
: ftxui::ComponentBase(),
|
||||||
|
wal_(std::move(wal)),
|
||||||
|
title_(ftxui::text(_("Settings"))),
|
||||||
|
config_(std::make_unique<configuration>(*wal_)),
|
||||||
|
error_(),
|
||||||
|
buttons_(),
|
||||||
|
ui_()
|
||||||
|
{
|
||||||
|
buttons_ = ftxui::Container::Horizontal({
|
||||||
|
ftxui::Button(_("Cancel"), [] () { throw event::close{}; }, ascii()),
|
||||||
|
ftxui::Button(_("Save"), [this] () {
|
||||||
|
error_.clear();
|
||||||
|
config_->store(*wal_, error_);
|
||||||
|
if (error_.empty())
|
||||||
|
throw event::close{};
|
||||||
|
}, ascii())
|
||||||
|
});
|
||||||
|
|
||||||
|
ftxui::Components ui;
|
||||||
|
ui.reserve(config_->states.size() + 1);
|
||||||
|
ui.push_back(buttons_);
|
||||||
|
for (const auto& opt : config_->states)
|
||||||
|
ui.push_back(opt.ui);
|
||||||
|
|
||||||
|
ui_ = ftxui::Container::Vertical(std::move(ui));
|
||||||
|
Add(ui_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OnEvent(ftxui::Event event) override final
|
||||||
|
{
|
||||||
|
if (event == ftxui::Event::CtrlQ)
|
||||||
|
throw event::close{};
|
||||||
|
ui_->OnEvent(std::move(event));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ftxui::Element OnRender() override final
|
||||||
|
{
|
||||||
|
const auto min_size = ftxui::size(ftxui::WIDTH, ftxui::GREATER_THAN, 5);
|
||||||
|
std::vector<ftxui::Elements> grid;
|
||||||
|
grid.reserve(config_->states.size());
|
||||||
|
for (const auto& opt : config_->states)
|
||||||
|
grid.push_back({opt.description, min_size(opt.ui->Render())});
|
||||||
|
|
||||||
|
ftxui::Element highlighted;
|
||||||
|
if (error_.empty())
|
||||||
|
highlighted = ftxui::separator();
|
||||||
|
else
|
||||||
|
highlighted = ftxui::inverted(ftxui::text(error_));
|
||||||
|
|
||||||
|
return ftxui::window(title_, ftxui::vbox({
|
||||||
|
ftxui::hcenter(buttons_->Render()),
|
||||||
|
highlighted,
|
||||||
|
ftxui::gridbox(std::move(grid))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ftxui::Component settings(std::shared_ptr<Monero::Wallet> wal)
|
||||||
|
{
|
||||||
|
return std::make_shared<settings_>(std::move(wal));
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // lwcli //
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
// 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 <ftxui/component/component_base.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Monero { class Wallet; }
|
||||||
|
namespace lwcli { namespace view
|
||||||
|
{
|
||||||
|
ftxui::Component settings(std::shared_ptr<Monero::Wallet> wallet);
|
||||||
|
}} // lwscli // view
|
||||||
|
|
||||||
+16
-199
@@ -39,198 +39,14 @@
|
|||||||
#include "lwcli_config.h"
|
#include "lwcli_config.h"
|
||||||
#include "translate.h"
|
#include "translate.h"
|
||||||
#include "views/history.h"
|
#include "views/history.h"
|
||||||
|
#include "views/settings.h"
|
||||||
|
|
||||||
namespace lwcli { namespace view
|
namespace lwcli { namespace view
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::optional<std::uint64_t> from_string(const std::string_view str) noexcept
|
|
||||||
{
|
|
||||||
std::uint64_t out = 0;
|
|
||||||
const auto end = str.data() + str.size();
|
|
||||||
auto [ptr, ec] = std::from_chars(str.data(), end, out);
|
|
||||||
if (bool(ec) || ptr != end)
|
|
||||||
return std::nullopt;
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_proxy(Monero::Wallet& wal, const std::string& proxy)
|
|
||||||
{
|
|
||||||
wal.setProxy(proxy);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_url(Monero::Wallet& wal, const std::string& url)
|
|
||||||
{
|
|
||||||
const bool is_ssl = bool(from_string(wal.getCacheAttribute(std::string{config::server::ssl})));
|
|
||||||
wal.init(url, 0, "", "", is_ssl, true, wal.getCacheAttribute(std::string{config::server::proxy}));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_refresh(Monero::Wallet& wal, const std::string& interval)
|
|
||||||
{
|
|
||||||
const auto number = from_string(interval);
|
|
||||||
if (!number)
|
|
||||||
return false;
|
|
||||||
wal.setAutoRefreshInterval(std::chrono::milliseconds{std::chrono::seconds{*number}}.count());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set_ssl(Monero::Wallet& wal, const std::string& ssl)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct option
|
|
||||||
{
|
|
||||||
using updater = bool(Monero::Wallet&, const std::string&);
|
|
||||||
const std::string_view path;
|
|
||||||
const std::string_view description;
|
|
||||||
updater* const update;
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::array<option, 4> options{{
|
|
||||||
{config::server::proxy, _("LWS Proxy"), set_proxy},
|
|
||||||
{config::server::refresh_interval, _("LWS Refresh Interval (seconds)"), set_refresh},
|
|
||||||
{config::server::ssl, _("LWS SSL Verify"), set_ssl},
|
|
||||||
{config::server::url, _("LWS URL"), set_url}
|
|
||||||
}};
|
|
||||||
|
|
||||||
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(); }
|
ftxui::ButtonOption ascii() { return ftxui::ButtonOption::Ascii(); }
|
||||||
|
|
||||||
struct option_state
|
|
||||||
{
|
|
||||||
ftxui::Element description;
|
|
||||||
std::string original;
|
|
||||||
std::string value;
|
|
||||||
ftxui::Component ui;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct configuration
|
|
||||||
{
|
|
||||||
std::array<option_state, options.size()> states;
|
|
||||||
|
|
||||||
configuration(const Monero::Wallet& wal)
|
|
||||||
: states()
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i < options.size(); ++i)
|
|
||||||
{
|
|
||||||
states[i].description = ftxui::text(std::string{options[i].description} + ": ");
|
|
||||||
states[i].original = wal.getCacheAttribute(std::string{options[i].path});
|
|
||||||
states[i].value = states[i].original;
|
|
||||||
states[i].ui = last_input(&states[i].value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void store(Monero::Wallet& wal, std::string& error)
|
|
||||||
{
|
|
||||||
for (std::size_t i = 0; i < options.size(); ++i)
|
|
||||||
{
|
|
||||||
if (states[i].original != states[i].value)
|
|
||||||
{
|
|
||||||
if (!options[i].update || options[i].update(wal, states[i].value))
|
|
||||||
wal.setCacheAttribute(std::string{options[i].path}, states[i].value);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
error = std::string{options[i].description} + " is invalid";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class settings_ final : public ftxui::ComponentBase
|
|
||||||
{
|
|
||||||
const std::shared_ptr<Monero::Wallet> wal_;
|
|
||||||
const ftxui::Element title_;
|
|
||||||
const std::unique_ptr<configuration> config_;
|
|
||||||
std::string error_;
|
|
||||||
ftxui::Component buttons_;
|
|
||||||
ftxui::Component ui_;
|
|
||||||
|
|
||||||
bool Focusable() const override final { return true; }
|
|
||||||
ftxui::Component ActiveChild() override final { return ui_; }
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit settings_(std::shared_ptr<Monero::Wallet>&& wal)
|
|
||||||
: ftxui::ComponentBase(),
|
|
||||||
wal_(std::move(wal)),
|
|
||||||
title_(ftxui::text(_("Settings"))),
|
|
||||||
config_(std::make_unique<configuration>(*wal_)),
|
|
||||||
error_(),
|
|
||||||
buttons_(),
|
|
||||||
ui_()
|
|
||||||
{
|
|
||||||
buttons_ = ftxui::Container::Horizontal({
|
|
||||||
ftxui::Button("Cancel", [] () { throw event::close{}; }, ascii()),
|
|
||||||
ftxui::Button("Save", [this] () {
|
|
||||||
error_.clear();
|
|
||||||
config_->store(*wal_, error_);
|
|
||||||
if (error_.empty())
|
|
||||||
throw event::close{};
|
|
||||||
}, ascii())
|
|
||||||
});
|
|
||||||
|
|
||||||
ftxui::Components ui;
|
|
||||||
ui.reserve(config_->states.size() + 1);
|
|
||||||
ui.push_back(buttons_);
|
|
||||||
for (const auto& opt : config_->states)
|
|
||||||
ui.push_back(opt.ui);
|
|
||||||
|
|
||||||
ui_ = ftxui::Container::Vertical(std::move(ui));
|
|
||||||
Add(ui_);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool OnEvent(ftxui::Event event) override final
|
|
||||||
{
|
|
||||||
if (event == ftxui::Event::CtrlQ)
|
|
||||||
throw event::close{};
|
|
||||||
ui_->OnEvent(std::move(event));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ftxui::Element OnRender() override final
|
|
||||||
{
|
|
||||||
const auto min_size = ftxui::size(ftxui::WIDTH, ftxui::GREATER_THAN, 5);
|
|
||||||
std::vector<ftxui::Elements> grid;
|
|
||||||
grid.reserve(config_->states.size());
|
|
||||||
for (const auto& opt : config_->states)
|
|
||||||
grid.push_back({opt.description, min_size(opt.ui->Render())});
|
|
||||||
|
|
||||||
ftxui::Element highlighted;
|
|
||||||
if (error_.empty())
|
|
||||||
highlighted = ftxui::separator();
|
|
||||||
else
|
|
||||||
highlighted = ftxui::inverted(ftxui::text(error_));
|
|
||||||
|
|
||||||
return ftxui::window(title_, ftxui::vbox({
|
|
||||||
ftxui::hcenter(buttons_->Render()),
|
|
||||||
highlighted,
|
|
||||||
ftxui::gridbox(std::move(grid))
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ftxui::Component settings(std::shared_ptr<Monero::Wallet> wal)
|
|
||||||
{
|
|
||||||
return std::make_shared<settings_>(std::move(wal));
|
|
||||||
}
|
|
||||||
|
|
||||||
struct wallet_state
|
struct wallet_state
|
||||||
{
|
{
|
||||||
const std::shared_ptr<Monero::Wallet> wal;
|
const std::shared_ptr<Monero::Wallet> wal;
|
||||||
@@ -308,21 +124,22 @@ namespace lwcli { namespace view
|
|||||||
state_.overlay->OnEvent(std::move(event));
|
state_.overlay->OnEvent(std::move(event));
|
||||||
else if (event == ftxui::Event::CtrlQ)
|
else if (event == ftxui::Event::CtrlQ)
|
||||||
throw event::close{};
|
throw event::close{};
|
||||||
else if (event == ftxui::Event::s || event == ftxui::Event::S)
|
else if (!ui_->OnEvent(event))
|
||||||
; // state_.overlay = send(state_.wal);
|
{
|
||||||
else if (event == ftxui::Event::b || event == ftxui::Event::B)
|
if (event == ftxui::Event::s || event == ftxui::Event::S)
|
||||||
; // state_.overlay = book(state_.wal);
|
; // state_.overlay = send(state_.wal);
|
||||||
else if (event == ftxui::Event::a || event == ftxui::Event::a)
|
else if (event == ftxui::Event::b || event == ftxui::Event::B)
|
||||||
; // overlay = accounts(state_.wal, &active_account_);
|
; // state_.overlay = book(state_.wal);
|
||||||
else if (event == ftxui::Event::r || event == ftxui::Event::R)
|
else if (event == ftxui::Event::a || event == ftxui::Event::a)
|
||||||
; //state_.wal->refreshAsync();
|
; // overlay = accounts(state_.wal, &active_account_);
|
||||||
else if (event == ftxui::Event::e || event == ftxui::Event::E)
|
else if (event == ftxui::Event::r || event == ftxui::Event::R)
|
||||||
state_.overlay = settings(state_.wal);
|
state_.wal->refreshAsync();
|
||||||
else
|
else if (event == ftxui::Event::e || event == ftxui::Event::E)
|
||||||
ui_->OnEvent(std::move(event));
|
state_.overlay = settings(state_.wal);
|
||||||
|
}
|
||||||
|
|
||||||
// if (!has_overlay && state_.overlay)
|
if (!has_overlay && state_.overlay)
|
||||||
// Add(state_.overlay);
|
Add(state_.overlay);
|
||||||
|
|
||||||
update_account();
|
update_account();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user