feat(wallets): in-app folder picker + roomier, full-width list controls
Wallets dialog refinements: - More space between wallet cards (cardGap: spacingSm -> spacingMd). - "Scan another folder" is now always visible as a full-width button (no expandable toggle) that opens the new picker. - The Create-wallet row and the scan button span the full list width, so their right edges align with the wallet cards / Open|Import buttons. New in-app folder picker (folder_picker.h): a Material-styled modal replacing the raw path input. Browse the filesystem (Up / Home / click a sub-folder), see the *.dat wallet files present + a count, and pick a folder to scan. The picker takes over the modal surface while open (the overlay framework doesn't nest); it remembers the last browsed location across opens. Fixed-height card (no auto-height feedback), UTF-8-aware truncation, full-node icons confirmed in the embedded MaterialIcons range. Added a modal-folder-picker sweep surface; verified at 100% + 150%, dark + light. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -404,6 +404,22 @@ void App::buildSweepCatalog()
|
||||
add("modal-console-commands", ui::NavPage::Console,
|
||||
[](App& a) { a.console_tab_.sweepSetCommandsPopup(true); },
|
||||
[](App& a) { a.console_tab_.sweepSetCommandsPopup(false); });
|
||||
|
||||
// In-app folder picker (Wallets → Scan another folder). Populate a small demo tree so the
|
||||
// list shows sub-folders + wallet files, then open the wallets dialog + the picker over it.
|
||||
add("modal-folder-picker", ui::NavPage::Settings,
|
||||
[](App& a) {
|
||||
std::error_code ec;
|
||||
const std::string demo = util::Platform::getConfigDir() + "picker-demo";
|
||||
for (const char* sub : {"Documents", "Downloads", "Backups", "wallet-archive"})
|
||||
fs::create_directories(demo + "/" + sub, ec);
|
||||
for (const char* f : {"wallet-cold.dat", "wallet-2023.dat"})
|
||||
if (!fs::exists(demo + "/" + f, ec))
|
||||
std::ofstream(demo + "/" + f, std::ios::binary) << std::string(66000, '\0');
|
||||
ui::WalletsDialog::show(&a);
|
||||
ui::FolderPicker::open(demo, [](const std::string&) {});
|
||||
},
|
||||
[](App&) { ui::FolderPicker::close(); ui::WalletsDialog::hide(); });
|
||||
}
|
||||
|
||||
// ── State machine ───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
277
src/ui/windows/folder_picker.h
Normal file
277
src/ui/windows/folder_picker.h
Normal file
@@ -0,0 +1,277 @@
|
||||
// DragonX Wallet - ImGui Edition
|
||||
// Copyright 2024-2026 The Hush Developers
|
||||
// Released under the GPLv3
|
||||
//
|
||||
// In-app, Material-styled folder picker. A modal overlay that lets the user browse the filesystem
|
||||
// and pick a directory (used by the Wallets dialog's "Scan another folder" action instead of a
|
||||
// raw path input field). Single overlay at a time — the overlay framework doesn't nest, so the
|
||||
// caller suppresses its own overlay while FolderPicker::isOpen().
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
#include "../../util/i18n.h"
|
||||
#include "../../util/platform.h"
|
||||
#include "../../embedded/IconsMaterialDesign.h"
|
||||
#include "../layout.h"
|
||||
#include "../material/colors.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
|
||||
class FolderPicker {
|
||||
public:
|
||||
// Open the picker starting at `startDir` (falls back to home if empty/invalid). `onPick` is
|
||||
// invoked with the chosen absolute path when the user confirms; nothing runs on cancel.
|
||||
static void open(const std::string& startDir, std::function<void(const std::string&)> onPick) {
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code ec;
|
||||
if (startDir.empty()) {
|
||||
// Reopen where the user last browsed (nice for repeated scans); else start at home.
|
||||
if (s_dir.empty() || !fs::is_directory(fs::path(s_dir), ec))
|
||||
navigate(util::Platform::getHomeDir());
|
||||
else
|
||||
navigate(s_dir);
|
||||
} else {
|
||||
fs::path p(startDir);
|
||||
if (!fs::is_directory(p, ec)) p = fs::path(util::Platform::getHomeDir());
|
||||
navigate(p.string());
|
||||
}
|
||||
s_onPick = std::move(onPick);
|
||||
s_open = true;
|
||||
}
|
||||
|
||||
static bool isOpen() { return s_open; }
|
||||
static void close() { s_open = false; }
|
||||
|
||||
static void render() {
|
||||
if (!s_open) return;
|
||||
using namespace material;
|
||||
const float dp = Layout::dpiScale();
|
||||
|
||||
ImFont* rowFont = Type().body1();
|
||||
ImFont* icoFont = Type().iconSmall();
|
||||
ImFont* metaFont = Type().caption();
|
||||
|
||||
// ---- Fixed-height card sized to a stable row budget (no auto-height feedback) ----------
|
||||
const int kVisRows = 9;
|
||||
const float rowH = rowFont->LegacySize + Layout::spacingMd();
|
||||
const float listPad = Layout::spacingXs();
|
||||
const float listH = kVisRows * rowH + listPad * 2.0f;
|
||||
|
||||
const float headH = Type().h6()->LegacySize + Layout::spacingXs();
|
||||
const float pathH = ImGui::GetFrameHeight() + Layout::spacingSm();
|
||||
const float statusH = metaFont->LegacySize + Layout::spacingXs();
|
||||
const float footH = Layout::spacingSm() + 1.0f + Layout::spacingSm()
|
||||
+ ImGui::GetFrameHeightWithSpacing();
|
||||
const float padV = 52.0f;
|
||||
const float cardH = (headH + pathH + listH + statusH + footH + padV) / dp;
|
||||
|
||||
OverlayDialogSpec ov;
|
||||
ov.title = TR("picker_title");
|
||||
ov.p_open = &s_open;
|
||||
ov.style = OverlayStyle::BlurFloat;
|
||||
ov.cardWidth = 720.0f;
|
||||
ov.cardHeight = cardH;
|
||||
ov.idSuffix = "folderpicker";
|
||||
if (!BeginOverlayDialog(ov)) return;
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Escape)) s_open = false;
|
||||
|
||||
const float fullW = ImGui::GetContentRegionAvail().x;
|
||||
auto tw = [](ImFont* f, const std::string& s){ return f->CalcTextSizeA(f->LegacySize, FLT_MAX, 0, s.c_str()).x; };
|
||||
// End-truncate, popping whole UTF-8 code points.
|
||||
auto fit = [&](std::string s, ImFont* f, float maxW){
|
||||
bool t=false;
|
||||
while (s.size()>1 && tw(f,s)>maxW){
|
||||
while (s.size()>1 && (static_cast<unsigned char>(s.back()) & 0xC0) == 0x80) s.pop_back();
|
||||
if (s.size()>1) s.pop_back();
|
||||
t=true;
|
||||
}
|
||||
if (t) s += "\xE2\x80\xA6";
|
||||
return s;
|
||||
};
|
||||
// Left-truncate a path (keep the tail — the current folder + nearest parents).
|
||||
auto fitPath = [&](std::string s, ImFont* f, float maxW){
|
||||
const std::string ell = "\xE2\x80\xA6";
|
||||
if (tw(f,s) <= maxW) return s;
|
||||
while (s.size()>1 && tw(f, ell+s) > maxW){
|
||||
s.erase(s.begin());
|
||||
while (!s.empty() && (static_cast<unsigned char>(s.front()) & 0xC0) == 0x80) s.erase(s.begin());
|
||||
}
|
||||
return ell + s;
|
||||
};
|
||||
|
||||
// ---- Path bar: Up + Home + current path strip -----------------------------------------
|
||||
std::string pendingNav;
|
||||
{
|
||||
const float bh = ImGui::GetFrameHeight();
|
||||
IconButtonStyle st; st.color = OnSurfaceMedium(); st.hoverBg = StateHover();
|
||||
st.bgRounding = 5.0f * dp;
|
||||
std::filesystem::path cur(s_dir);
|
||||
const bool hasParent = cur.has_parent_path() && cur.parent_path() != cur;
|
||||
st.tooltip = TR("picker_up");
|
||||
if (IconButton("##pickerUp", ICON_MD_ARROW_UPWARD, icoFont, ImVec2(bh, bh), st) && hasParent)
|
||||
pendingNav = cur.parent_path().string();
|
||||
ImGui::SameLine(0.0f, Layout::spacingXs());
|
||||
IconButtonStyle hst = st; hst.tooltip = TR("picker_home");
|
||||
if (IconButton("##pickerHome", ICON_MD_HOME, icoFont, ImVec2(bh, bh), hst))
|
||||
pendingNav = util::Platform::getHomeDir();
|
||||
ImGui::SameLine(0.0f, Layout::spacingSm());
|
||||
|
||||
// Framed path strip
|
||||
ImVec2 sMin = ImGui::GetCursorScreenPos();
|
||||
const float stripW = ImGui::GetContentRegionAvail().x;
|
||||
ImVec2 sMax(sMin.x + stripW, sMin.y + bh);
|
||||
ImDrawList* wdl = ImGui::GetWindowDrawList();
|
||||
wdl->AddRectFilled(sMin, sMax, WithAlpha(OnSurface(), 14), 6.0f * dp);
|
||||
wdl->AddRect(sMin, sMax, WithAlpha(OnSurface(), 40), 6.0f * dp, 0, 1.0f);
|
||||
const float tpad = Layout::spacingSm();
|
||||
wdl->AddText(rowFont, rowFont->LegacySize,
|
||||
ImVec2(sMin.x + tpad, sMin.y + (bh - rowFont->LegacySize) * 0.5f),
|
||||
OnSurface(), fitPath(s_dir, rowFont, stripW - tpad * 2.0f).c_str());
|
||||
ImGui::Dummy(ImVec2(stripW, bh));
|
||||
}
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// ---- Directory list: folders (clickable) then *.dat wallets present (informational) ----
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(listPad, listPad));
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, WithAlpha(OnSurface(), 20));
|
||||
ImGui::BeginChild("##pickerList", ImVec2(fullW, listH), true);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
const float rowW = ImGui::GetContentRegionAvail().x;
|
||||
const float padX = Layout::spacingSm();
|
||||
|
||||
if (s_subdirs.empty() && s_datfiles.empty()) {
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::Indent(padX);
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("picker_empty"));
|
||||
ImGui::Unindent(padX);
|
||||
}
|
||||
|
||||
// Folder rows
|
||||
for (std::size_t i = 0; i < s_subdirs.size(); ++i) {
|
||||
ImVec2 rMin = ImGui::GetCursorScreenPos();
|
||||
ImVec2 rMax(rMin.x + rowW, rMin.y + rowH);
|
||||
ImGui::PushID((int)i);
|
||||
const bool clicked = ImGui::InvisibleButton("##dir", ImVec2(rowW, rowH));
|
||||
const bool hov = ImGui::IsItemHovered();
|
||||
ImGui::PopID();
|
||||
if (hov) {
|
||||
dl->AddRectFilled(rMin, rMax, WithAlpha(OnSurface(), 22), 6.0f * dp);
|
||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
}
|
||||
const float midY = (rMin.y + rMax.y) * 0.5f;
|
||||
const float icoSz = rowFont->LegacySize * 1.05f;
|
||||
float x = rMin.x + padX;
|
||||
dl->AddText(icoFont, icoSz, ImVec2(x, midY - icoSz * 0.5f),
|
||||
hov ? Primary() : OnSurfaceMedium(), ICON_MD_FOLDER);
|
||||
x += icoSz + Layout::spacingSm();
|
||||
// chevron affordance on the right
|
||||
const float chSz = metaFont->LegacySize;
|
||||
const float chX = rMax.x - padX - chSz;
|
||||
if (hov)
|
||||
dl->AddText(icoFont, chSz, ImVec2(chX, midY - chSz * 0.5f), OnSurfaceMedium(), ICON_MD_CHEVRON_RIGHT);
|
||||
std::string nm = fit(s_subdirs[i], rowFont, (chX - Layout::spacingSm()) - x);
|
||||
dl->AddText(rowFont, rowFont->LegacySize, ImVec2(x, midY - rowFont->LegacySize * 0.5f),
|
||||
OnSurface(), nm.c_str());
|
||||
if (clicked) pendingNav = (std::filesystem::path(s_dir) / s_subdirs[i]).string();
|
||||
}
|
||||
|
||||
// Wallet-file rows (informational — dimmed, not clickable)
|
||||
for (std::size_t i = 0; i < s_datfiles.size(); ++i) {
|
||||
ImVec2 rMin = ImGui::GetCursorScreenPos();
|
||||
const float midY = rMin.y + rowH * 0.5f;
|
||||
const float icoSz = rowFont->LegacySize * 1.05f;
|
||||
float x = rMin.x + padX;
|
||||
dl->AddText(icoFont, icoSz, ImVec2(x, midY - icoSz * 0.5f), OnSurfaceDisabled(),
|
||||
ICON_MD_ACCOUNT_BALANCE_WALLET);
|
||||
x += icoSz + Layout::spacingSm();
|
||||
std::string nm = fit(s_datfiles[i], rowFont, (rMin.x + rowW - padX) - x);
|
||||
dl->AddText(rowFont, rowFont->LegacySize, ImVec2(x, midY - rowFont->LegacySize * 0.5f),
|
||||
OnSurfaceDisabled(), nm.c_str());
|
||||
ImGui::Dummy(ImVec2(rowW, rowH));
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(); // ItemSpacing
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleColor(); // ChildBg
|
||||
ImGui::PopStyleVar(); // WindowPadding
|
||||
|
||||
// ---- Status: how many wallet files are in the selected folder --------------------------
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||
if (!s_datfiles.empty()) {
|
||||
char buf[96];
|
||||
snprintf(buf, sizeof(buf), TR("picker_dat_count"), (int)s_datfiles.size());
|
||||
Type().textColored(TypeStyle::Caption, Success(), buf);
|
||||
} else {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("picker_dat_none"));
|
||||
}
|
||||
|
||||
// ---- Footer: Select this folder / Cancel ----------------------------------------------
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
ImGui::Separator();
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
if (StyledButton(TR("picker_select"), ImVec2(200.0f * dp, 0))) {
|
||||
if (s_onPick) s_onPick(s_dir);
|
||||
s_open = false;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (StyledButton(TR("cancel"), ImVec2(120.0f * dp, 0))) s_open = false;
|
||||
|
||||
EndOverlayDialog();
|
||||
|
||||
if (!pendingNav.empty()) navigate(pendingNav);
|
||||
}
|
||||
|
||||
private:
|
||||
static void navigate(const std::string& dir) {
|
||||
namespace fs = std::filesystem;
|
||||
std::error_code ec;
|
||||
fs::path p(dir);
|
||||
if (!fs::is_directory(p, ec)) return;
|
||||
fs::path abs = fs::absolute(p, ec);
|
||||
s_dir = ec ? dir : abs.lexically_normal().string();
|
||||
s_subdirs.clear();
|
||||
s_datfiles.clear();
|
||||
fs::directory_iterator it(p, ec), end;
|
||||
int guard = 0;
|
||||
for (; !ec && it != end && guard < 4000; it.increment(ec), ++guard) {
|
||||
std::error_code fec;
|
||||
std::string name = it->path().filename().string();
|
||||
if (name.empty()) continue;
|
||||
if (it->is_directory(fec)) {
|
||||
s_subdirs.push_back(name);
|
||||
} else if (it->is_regular_file(fec)) {
|
||||
if (name.size() > 4 && name.substr(name.size() - 4) == ".dat")
|
||||
s_datfiles.push_back(name);
|
||||
}
|
||||
}
|
||||
auto ci = [](const std::string& a, const std::string& b){
|
||||
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
|
||||
[](char x, char y){ return std::tolower((unsigned char)x) < std::tolower((unsigned char)y); });
|
||||
};
|
||||
std::sort(s_subdirs.begin(), s_subdirs.end(), ci);
|
||||
std::sort(s_datfiles.begin(), s_datfiles.end(), ci);
|
||||
}
|
||||
|
||||
static inline bool s_open = false;
|
||||
static inline std::string s_dir;
|
||||
static inline std::vector<std::string> s_subdirs;
|
||||
static inline std::vector<std::string> s_datfiles;
|
||||
static inline std::function<void(const std::string&)> s_onPick;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace dragonx
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "../material/colors.h"
|
||||
#include "../material/draw_helpers.h"
|
||||
#include "../material/type.h"
|
||||
#include "folder_picker.h"
|
||||
|
||||
namespace dragonx {
|
||||
namespace ui {
|
||||
@@ -39,13 +40,15 @@ public:
|
||||
s_open = true;
|
||||
s_app = app;
|
||||
s_needScan = true;
|
||||
s_newFolder[0] = '\0';
|
||||
}
|
||||
|
||||
static void hide() { s_open = false; }
|
||||
|
||||
static void render() {
|
||||
if (!s_open || !s_app) return;
|
||||
// The folder picker (opened by "Scan another folder") takes over the modal surface while
|
||||
// it's up — the overlay framework doesn't nest, so only one overlay renders at a time.
|
||||
if (FolderPicker::isOpen()) { FolderPicker::render(); return; }
|
||||
using namespace material;
|
||||
App* app = s_app;
|
||||
const float dp = Layout::dpiScale();
|
||||
@@ -62,16 +65,16 @@ public:
|
||||
const int visRows = std::min(std::max(1, (int)s_rows.size()), kMaxVisibleRows);
|
||||
const float cardPadY = Layout::spacingMd(); // roomier cards (was spacingSm)
|
||||
const float walRowH = cardPadY * 2.0f + nameFont->LegacySize + Layout::spacingSm() + metaFont->LegacySize;
|
||||
const float cardGap = Layout::spacingSm();
|
||||
const float cardGap = Layout::spacingMd(); // more breathing room between wallet cards
|
||||
// ItemSpacing is zeroed inside the list child, so the content is exactly cards + inter-card
|
||||
// gaps; a small bottom pad keeps the last card off the clip edge (no spurious scrollbar).
|
||||
const float listH = visRows * walRowH + (float)std::max(0, visRows - 1) * cardGap + Layout::spacingSm();
|
||||
|
||||
const float ctrlRow = ImGui::GetFrameHeightWithSpacing();
|
||||
const float capRow = Type().caption()->LegacySize + style.ItemSpacing.y;
|
||||
const float belowH = 3.0f * Layout::spacingSm()
|
||||
const float belowH = 4.0f * Layout::spacingSm()
|
||||
+ (capRow + ctrlRow) // Create label + input row
|
||||
+ (s_showAddFolder ? (capRow + ctrlRow) : capRow) // folder row OR toggle link
|
||||
+ ctrlRow // full-width "scan folder" button
|
||||
+ (style.ItemSpacing.y + 1.0f) // separator
|
||||
+ ctrlRow // footer buttons
|
||||
+ 6.0f * style.ItemSpacing.y; // uncounted inter-item gaps
|
||||
@@ -206,12 +209,13 @@ public:
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// ---- Create a new wallet (primary) ----------------------------------------------------
|
||||
// ---- Create a new wallet (primary) — the input + button span the full list width -----
|
||||
const float createBtnW = 140.0f * dp;
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("wallets_new_label"));
|
||||
ImGui::SetNextItemWidth(-150.0f * dp);
|
||||
ImGui::SetNextItemWidth(listW - createBtnW - style.ItemSpacing.x);
|
||||
ImGui::InputTextWithHint("##newWalletName", TR("wallets_new_hint"), s_newName, sizeof(s_newName));
|
||||
ImGui::SameLine();
|
||||
if (StyledButton(TR("wallets_create"), ImVec2(120.0f * dp, 0))) {
|
||||
if (StyledButton(TR("wallets_create"), ImVec2(createBtnW, 0))) {
|
||||
std::string name = normalizeWalletName(s_newName);
|
||||
std::error_code ec;
|
||||
if (name.empty()) {
|
||||
@@ -228,29 +232,17 @@ public:
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
|
||||
// ---- Add-folder tucked behind a toggle so it doesn't compete with Create --------------
|
||||
if (!s_showAddFolder) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, OnSurfaceMedium());
|
||||
ImGui::TextUnformatted(TR("wallets_add_folder_toggle"));
|
||||
ImGui::PopStyleColor();
|
||||
if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||
if (ImGui::IsItemClicked()) s_showAddFolder = true;
|
||||
} else {
|
||||
Type().textColored(TypeStyle::Caption, OnSurfaceMedium(), TR("wallets_add_folder"));
|
||||
ImGui::SetNextItemWidth(-150.0f * dp);
|
||||
ImGui::InputTextWithHint("##walletFolder", TR("wallets_folder_hint"), s_newFolder, sizeof(s_newFolder));
|
||||
ImGui::SameLine();
|
||||
if (StyledButton(TR("wallets_add"), ImVec2(120.0f * dp, 0))) {
|
||||
// ---- Scan another folder — always visible, full-width, opens the in-app folder picker -
|
||||
if (StyledButton(TR("wallets_scan_folder"), ImVec2(listW, 0))) {
|
||||
FolderPicker::open("", [app](const std::string& dir) {
|
||||
std::error_code ec;
|
||||
std::string dir = s_newFolder;
|
||||
if (!dir.empty() && std::filesystem::is_directory(dir, ec)) {
|
||||
if (app->walletIndex().addExtraFolder(dir)) app->walletIndex().save();
|
||||
s_newFolder[0] = '\0';
|
||||
s_needScan = true;
|
||||
} else {
|
||||
Notifications::instance().warning(TR("wallets_folder_invalid"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||
@@ -352,9 +344,7 @@ private:
|
||||
static inline bool s_open = false;
|
||||
static inline App* s_app = nullptr;
|
||||
static inline bool s_needScan = false;
|
||||
static inline char s_newFolder[512] = "";
|
||||
static inline char s_newName[128] = "";
|
||||
static inline bool s_showAddFolder = false;
|
||||
static inline std::vector<WalletRow> s_rows;
|
||||
};
|
||||
|
||||
|
||||
@@ -266,11 +266,8 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["wallets_never"] = "Never";
|
||||
strings_["wallets_import_hint"] = "Import to open";
|
||||
strings_["wallets_import_tt"] = "This wallet is outside the data directory. Opening it will copy it in first (coming soon).";
|
||||
strings_["wallets_add_folder"] = "Also scan another folder for wallet files:";
|
||||
strings_["wallets_add_folder_toggle"] = "+ Scan another folder for wallets\xE2\x80\xA6";
|
||||
strings_["wallets_external_tt"] = "Outside your data directory \xE2\x80\x94 Import to copy it in.";
|
||||
strings_["wallets_folder_hint"] = "/path/to/folder with wallet .dat files";
|
||||
strings_["wallets_add"] = "Add folder";
|
||||
strings_["wallets_scan_folder"] = "Scan another folder for wallets\xE2\x80\xA6";
|
||||
strings_["wallets_folder_invalid"] = "That folder doesn't exist.";
|
||||
strings_["wallets_import"] = "Import";
|
||||
strings_["wallets_new_label"] = "Create a new wallet:";
|
||||
@@ -282,6 +279,14 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["wallets_import_failed"] = "Could not import that wallet file.";
|
||||
strings_["wallets_creating"] = "Creating wallet — the node will restart…";
|
||||
strings_["wallets_reveal"] = "Reveal folder";
|
||||
// In-app folder picker (Scan another folder)
|
||||
strings_["picker_title"] = "Select a folder to scan";
|
||||
strings_["picker_up"] = "Up one level";
|
||||
strings_["picker_home"] = "Home folder";
|
||||
strings_["picker_empty"] = "This folder has no sub-folders or wallet files.";
|
||||
strings_["picker_dat_count"] = "%d wallet file(s) in this folder";
|
||||
strings_["picker_dat_none"] = "No wallet files directly in this folder";
|
||||
strings_["picker_select"] = "Scan this folder";
|
||||
strings_["tt_seed_migrate"] = "Create a new seed-phrase wallet and move your funds into it";
|
||||
// Migrate-to-seed modal
|
||||
strings_["mig_title"] = "Migrate to a seed wallet";
|
||||
|
||||
Reference in New Issue
Block a user