feat(ui): migrate Explorer detail/loading + daemon-update prompt to reference design

Presentation-only reference-design migration:
- Explorer loading modal -> struct-form overlay (idSuffix explorerloading); block-
  detail modal loses its header divider; the search button + the prev/next nav
  become TactileButton; TR() the hardcoded "Previous block"/"Next block" tooltips,
  "TxID:", and the "shielded" output fallback (3 new explorer_* keys + reuse the
  existing `shielded` key), all 8 langs. Block-fetch RPC path untouched.
- Daemon-update startup prompt -> struct-form overlay (idSuffix daemonupdprompt),
  2 StyledButton -> TactileButton, drop the footer divider. The reinstall/
  prompted-size persistence logic is unchanged.

Build + ctest + hygiene clean; "DRGX" ticker left hardcoded per convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 16:33:52 -05:00
parent 304f65c3b4
commit 28288d7197
11 changed files with 44 additions and 11 deletions

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "Mempool", "explorer_mempool": "Mempool",
"explorer_mempool_size": "Größe", "explorer_mempool_size": "Größe",
"explorer_mempool_txs": "Transaktionen", "explorer_mempool_txs": "Transaktionen",
"explorer_next_block": "Nächster Block",
"explorer_no_results": "Keine passenden zwischengespeicherten Blöcke", "explorer_no_results": "Keine passenden zwischengespeicherten Blöcke",
"explorer_not_connected": "Nicht mit dem Daemon verbunden — Block- oder Transaktions-Hash kann nicht nachgeschlagen werden", "explorer_not_connected": "Nicht mit dem Daemon verbunden — Block- oder Transaktions-Hash kann nicht nachgeschlagen werden",
"explorer_prev_block": "Vorheriger Block",
"explorer_recent_blocks": "Letzte Blöcke", "explorer_recent_blocks": "Letzte Blöcke",
"explorer_search": "Suchen", "explorer_search": "Suchen",
"explorer_section": "EXPLORER", "explorer_section": "EXPLORER",
"explorer_tx_outputs": "Ausgaben", "explorer_tx_outputs": "Ausgaben",
"explorer_tx_size": "Größe", "explorer_tx_size": "Größe",
"explorer_txid": "TxID:",
"export": "Exportieren", "export": "Exportieren",
"export_csv": "CSV exportieren", "export_csv": "CSV exportieren",
"export_keys_btn": "Schlüssel exportieren", "export_keys_btn": "Schlüssel exportieren",

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "Mempool", "explorer_mempool": "Mempool",
"explorer_mempool_size": "Tamaño", "explorer_mempool_size": "Tamaño",
"explorer_mempool_txs": "Transacciones", "explorer_mempool_txs": "Transacciones",
"explorer_next_block": "Bloque siguiente",
"explorer_no_results": "No hay bloques en caché que coincidan", "explorer_no_results": "No hay bloques en caché que coincidan",
"explorer_not_connected": "Sin conexión al daemon: no se puede buscar un bloque ni un hash de transacción", "explorer_not_connected": "Sin conexión al daemon: no se puede buscar un bloque ni un hash de transacción",
"explorer_prev_block": "Bloque anterior",
"explorer_recent_blocks": "Bloques Recientes", "explorer_recent_blocks": "Bloques Recientes",
"explorer_search": "Buscar", "explorer_search": "Buscar",
"explorer_section": "EXPLORADOR", "explorer_section": "EXPLORADOR",
"explorer_tx_outputs": "Salidas", "explorer_tx_outputs": "Salidas",
"explorer_tx_size": "Tamaño", "explorer_tx_size": "Tamaño",
"explorer_txid": "TxID:",
"export": "Exportar", "export": "Exportar",
"export_csv": "Exportar CSV", "export_csv": "Exportar CSV",
"export_keys_btn": "Exportar Claves", "export_keys_btn": "Exportar Claves",

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "Mempool", "explorer_mempool": "Mempool",
"explorer_mempool_size": "Taille", "explorer_mempool_size": "Taille",
"explorer_mempool_txs": "Transactions", "explorer_mempool_txs": "Transactions",
"explorer_next_block": "Bloc suivant",
"explorer_no_results": "Aucun bloc en cache correspondant", "explorer_no_results": "Aucun bloc en cache correspondant",
"explorer_not_connected": "Non connecté au daemon — impossible de rechercher un hash de bloc ou de transaction", "explorer_not_connected": "Non connecté au daemon — impossible de rechercher un hash de bloc ou de transaction",
"explorer_prev_block": "Bloc précédent",
"explorer_recent_blocks": "Blocs récents", "explorer_recent_blocks": "Blocs récents",
"explorer_search": "Rechercher", "explorer_search": "Rechercher",
"explorer_section": "EXPLORATEUR", "explorer_section": "EXPLORATEUR",
"explorer_tx_outputs": "Sorties", "explorer_tx_outputs": "Sorties",
"explorer_tx_size": "Taille", "explorer_tx_size": "Taille",
"explorer_txid": "TxID :",
"export": "Exporter", "export": "Exporter",
"export_csv": "Exporter en CSV", "export_csv": "Exporter en CSV",
"export_keys_btn": "Exporter les clés", "export_keys_btn": "Exporter les clés",

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "メモリプール", "explorer_mempool": "メモリプール",
"explorer_mempool_size": "サイズ", "explorer_mempool_size": "サイズ",
"explorer_mempool_txs": "トランザクション", "explorer_mempool_txs": "トランザクション",
"explorer_next_block": "次のブロック",
"explorer_no_results": "一致するキャッシュ済みブロックがありません", "explorer_no_results": "一致するキャッシュ済みブロックがありません",
"explorer_not_connected": "デーモンに未接続 — ブロックまたはトランザクションハッシュを検索できません", "explorer_not_connected": "デーモンに未接続 — ブロックまたはトランザクションハッシュを検索できません",
"explorer_prev_block": "前のブロック",
"explorer_recent_blocks": "最近のブロック", "explorer_recent_blocks": "最近のブロック",
"explorer_search": "検索", "explorer_search": "検索",
"explorer_section": "エクスプローラー", "explorer_section": "エクスプローラー",
"explorer_tx_outputs": "出力", "explorer_tx_outputs": "出力",
"explorer_tx_size": "サイズ", "explorer_tx_size": "サイズ",
"explorer_txid": "TxID",
"export": "エクスポート", "export": "エクスポート",
"export_csv": "CSVエクスポート", "export_csv": "CSVエクスポート",
"export_keys_btn": "鍵をエクスポート", "export_keys_btn": "鍵をエクスポート",

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "멤풀", "explorer_mempool": "멤풀",
"explorer_mempool_size": "크기", "explorer_mempool_size": "크기",
"explorer_mempool_txs": "트랜잭션", "explorer_mempool_txs": "트랜잭션",
"explorer_next_block": "다음 블록",
"explorer_no_results": "일치하는 캐시된 블록이 없습니다", "explorer_no_results": "일치하는 캐시된 블록이 없습니다",
"explorer_not_connected": "데몬에 연결되지 않음 — 블록 또는 트랜잭션 해시를 조회할 수 없습니다", "explorer_not_connected": "데몬에 연결되지 않음 — 블록 또는 트랜잭션 해시를 조회할 수 없습니다",
"explorer_prev_block": "이전 블록",
"explorer_recent_blocks": "최근 블록", "explorer_recent_blocks": "최근 블록",
"explorer_search": "검색", "explorer_search": "검색",
"explorer_section": "탐색기", "explorer_section": "탐색기",
"explorer_tx_outputs": "출력", "explorer_tx_outputs": "출력",
"explorer_tx_size": "크기", "explorer_tx_size": "크기",
"explorer_txid": "TxID:",
"export": "내보내기", "export": "내보내기",
"export_csv": "CSV 내보내기", "export_csv": "CSV 내보내기",
"export_keys_btn": "키 내보내기", "export_keys_btn": "키 내보내기",

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "Mempool", "explorer_mempool": "Mempool",
"explorer_mempool_size": "Tamanho", "explorer_mempool_size": "Tamanho",
"explorer_mempool_txs": "Transações", "explorer_mempool_txs": "Transações",
"explorer_next_block": "Próximo bloco",
"explorer_no_results": "Nenhum bloco em cache correspondente", "explorer_no_results": "Nenhum bloco em cache correspondente",
"explorer_not_connected": "Não conectado ao daemon — não é possível procurar um bloco ou hash de transação", "explorer_not_connected": "Não conectado ao daemon — não é possível procurar um bloco ou hash de transação",
"explorer_prev_block": "Bloco anterior",
"explorer_recent_blocks": "Blocos Recentes", "explorer_recent_blocks": "Blocos Recentes",
"explorer_search": "Pesquisar", "explorer_search": "Pesquisar",
"explorer_section": "EXPLORADOR", "explorer_section": "EXPLORADOR",
"explorer_tx_outputs": "Saídas", "explorer_tx_outputs": "Saídas",
"explorer_tx_size": "Tamanho", "explorer_tx_size": "Tamanho",
"explorer_txid": "TxID:",
"export": "Exportar", "export": "Exportar",
"export_csv": "Exportar CSV", "export_csv": "Exportar CSV",
"export_keys_btn": "Exportar Chaves", "export_keys_btn": "Exportar Chaves",

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "Мемпул", "explorer_mempool": "Мемпул",
"explorer_mempool_size": "Размер", "explorer_mempool_size": "Размер",
"explorer_mempool_txs": "Транзакции", "explorer_mempool_txs": "Транзакции",
"explorer_next_block": "Следующий блок",
"explorer_no_results": "Нет подходящих кэшированных блоков", "explorer_no_results": "Нет подходящих кэшированных блоков",
"explorer_not_connected": "Нет подключения к демону — невозможно найти хеш блока или транзакции", "explorer_not_connected": "Нет подключения к демону — невозможно найти хеш блока или транзакции",
"explorer_prev_block": "Предыдущий блок",
"explorer_recent_blocks": "Последние блоки", "explorer_recent_blocks": "Последние блоки",
"explorer_search": "Поиск", "explorer_search": "Поиск",
"explorer_section": "ОБОЗРЕВАТЕЛЬ", "explorer_section": "ОБОЗРЕВАТЕЛЬ",
"explorer_tx_outputs": "Выходы", "explorer_tx_outputs": "Выходы",
"explorer_tx_size": "Размер", "explorer_tx_size": "Размер",
"explorer_txid": "TxID:",
"export": "Экспорт", "export": "Экспорт",
"export_csv": "Экспорт в CSV", "export_csv": "Экспорт в CSV",
"export_keys_btn": "Экспорт ключей", "export_keys_btn": "Экспорт ключей",

View File

@@ -386,13 +386,16 @@
"explorer_mempool": "内存池", "explorer_mempool": "内存池",
"explorer_mempool_size": "大小", "explorer_mempool_size": "大小",
"explorer_mempool_txs": "交易", "explorer_mempool_txs": "交易",
"explorer_next_block": "下一个区块",
"explorer_no_results": "没有匹配的缓存区块", "explorer_no_results": "没有匹配的缓存区块",
"explorer_not_connected": "未连接到守护进程——无法查询区块或交易哈希", "explorer_not_connected": "未连接到守护进程——无法查询区块或交易哈希",
"explorer_prev_block": "上一个区块",
"explorer_recent_blocks": "最近区块", "explorer_recent_blocks": "最近区块",
"explorer_search": "搜索", "explorer_search": "搜索",
"explorer_section": "浏览器", "explorer_section": "浏览器",
"explorer_tx_outputs": "输出", "explorer_tx_outputs": "输出",
"explorer_tx_size": "大小", "explorer_tx_size": "大小",
"explorer_txid": "TxID",
"export": "导出", "export": "导出",
"export_csv": "导出 CSV", "export_csv": "导出 CSV",
"export_keys_btn": "导出密钥", "export_keys_btn": "导出密钥",

View File

@@ -3368,7 +3368,11 @@ void App::maybeOfferDaemonUpdate()
void App::renderDaemonUpdatePrompt() void App::renderDaemonUpdatePrompt()
{ {
if (!ui::material::BeginOverlayDialog(TR("daemon_update_title"), &show_daemon_update_prompt_, 520.0f, 0.94f)) ui::material::OverlayDialogSpec ov;
ov.title = TR("daemon_update_title"); ov.p_open = &show_daemon_update_prompt_;
ov.style = ui::material::OverlayStyle::BlurFloat;
ov.cardWidth = 520.0f; ov.idSuffix = "daemonupdprompt";
if (!ui::material::BeginOverlayDialog(ov))
return; return;
const float dp = ui::Layout::dpiScale(); const float dp = ui::Layout::dpiScale();
ImGui::TextWrapped("%s", TR("daemon_update_body")); ImGui::TextWrapped("%s", TR("daemon_update_body"));
@@ -3377,13 +3381,12 @@ void App::renderDaemonUpdatePrompt()
ImGui::TextWrapped("%s", TR("daemon_update_safe")); ImGui::TextWrapped("%s", TR("daemon_update_safe"));
ImGui::PopStyleColor(); ImGui::PopStyleColor();
ImGui::Spacing(); ImGui::Spacing();
ImGui::Separator(); if (ui::material::TactileButton(TR("daemon_update_now"), ImVec2(170 * dp, 0))) {
if (ui::material::StyledButton(TR("daemon_update_now"), ImVec2(170 * dp, 0))) {
show_daemon_update_prompt_ = false; show_daemon_update_prompt_ = false;
reinstallBundledDaemon(); // stops the node (managed or external), overwrites, restarts reinstallBundledDaemon(); // stops the node (managed or external), overwrites, restarts
} }
ImGui::SameLine(); ImGui::SameLine();
if (ui::material::StyledButton(TR("daemon_update_keep"), ImVec2(150 * dp, 0))) { if (ui::material::TactileButton(TR("daemon_update_keep"), ImVec2(150 * dp, 0))) {
show_daemon_update_prompt_ = false; show_daemon_update_prompt_ = false;
} }
ui::material::EndOverlayDialog(); ui::material::EndOverlayDialog();

View File

@@ -483,7 +483,7 @@ static void renderSearchBar(App* app, float availWidth) {
// Search button — same height as input // Search button — same height as input
ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, cursorY)); ImGui::SetCursorScreenPos(ImVec2(ImGui::GetCursorScreenPos().x, cursorY));
bool clicked = material::StyledButton(TR("explorer_search"), bool clicked = material::TactileButton(TR("explorer_search"),
ImVec2(btnW, barH)); ImVec2(btnW, barH));
std::string query(s_search_buf); std::string query(s_search_buf);
@@ -1012,7 +1012,11 @@ static void renderBlockDetailModal(App* app) {
if (s_detail_loading && !s_show_detail_modal) { if (s_detail_loading && !s_show_detail_modal) {
// Show a loading modal while fetching // Show a loading modal while fetching
if (BeginOverlayDialog(TR("loading"), &s_detail_loading, 300.0f, 0.85f)) { OverlayDialogSpec lov;
lov.title = TR("loading"); lov.p_open = &s_detail_loading;
lov.style = OverlayStyle::BlurFloat;
lov.cardWidth = 300.0f; lov.idSuffix = "explorerloading";
if (BeginOverlayDialog(lov)) {
ImGui::Spacing(); ImGui::Spacing();
char loadBuf[64]; char loadBuf[64];
snprintf(loadBuf, sizeof(loadBuf), "%s%s", TR("loading"), material::LoadingDots()); snprintf(loadBuf, sizeof(loadBuf), "%s%s", TR("loading"), material::LoadingDots());
@@ -1055,7 +1059,7 @@ static void renderBlockDetailModal(App* app) {
if (app->rpc() && app->rpc()->isConnected()) if (app->rpc() && app->rpc()->isConnected())
fetchBlockDetail(app, s_detail_height - 1); fetchBlockDetail(app, s_detail_height - 1);
} }
if (ImGui::IsItemHovered()) material::Tooltip("Previous block"); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("explorer_prev_block"));
ImGui::PopID(); ImGui::PopID();
ImGui::PopFont(); ImGui::PopFont();
ImGui::SameLine(); ImGui::SameLine();
@@ -1069,7 +1073,7 @@ static void renderBlockDetailModal(App* app) {
if (app->rpc() && app->rpc()->isConnected()) if (app->rpc() && app->rpc()->isConnected())
fetchBlockDetail(app, s_detail_height + 1); fetchBlockDetail(app, s_detail_height + 1);
} }
if (ImGui::IsItemHovered()) material::Tooltip("Next block"); if (ImGui::IsItemHovered()) material::Tooltip("%s", TR("explorer_next_block"));
ImGui::PopID(); ImGui::PopID();
ImGui::PopFont(); ImGui::PopFont();
} }
@@ -1077,7 +1081,6 @@ static void renderBlockDetailModal(App* app) {
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
ImGui::Separator();
ImGui::Spacing(); ImGui::Spacing();
// Show loading state inside the modal when navigating between blocks // Show loading state inside the modal when navigating between blocks
@@ -1253,7 +1256,7 @@ static void renderBlockDetailModal(App* app) {
// Full TxID with copy // Full TxID with copy
ImGui::PushFont(capFont); ImGui::PushFont(capFont);
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "TxID:"); ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", TR("explorer_txid"));
ImGui::PopFont(); ImGui::PopFont();
ImGui::PushFont(body2); ImGui::PushFont(body2);
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(linkCol)); ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(linkCol));
@@ -1282,7 +1285,7 @@ static void renderBlockDetailModal(App* app) {
for (const auto& vout : s_tx_detail["vout"]) { for (const auto& vout : s_tx_detail["vout"]) {
double val = vout.value("value", 0.0); double val = vout.value("value", 0.0);
int n = vout.value("n", 0); int n = vout.value("n", 0);
std::string addr = "shielded"; std::string addr = TR("shielded");
if (vout.contains("scriptPubKey") && vout["scriptPubKey"].contains("addresses") && if (vout.contains("scriptPubKey") && vout["scriptPubKey"].contains("addresses") &&
vout["scriptPubKey"]["addresses"].is_array() && !vout["scriptPubKey"]["addresses"].empty()) { vout["scriptPubKey"]["addresses"].is_array() && !vout["scriptPubKey"]["addresses"].empty()) {
addr = vout["scriptPubKey"]["addresses"][0].get<std::string>(); addr = vout["scriptPubKey"]["addresses"][0].get<std::string>();

View File

@@ -1937,6 +1937,9 @@ void I18n::loadBuiltinEnglish()
strings_["explorer_block_merkle"] = "Merkle Root"; strings_["explorer_block_merkle"] = "Merkle Root";
strings_["explorer_tx_outputs"] = "Outputs"; strings_["explorer_tx_outputs"] = "Outputs";
strings_["explorer_tx_size"] = "Size"; strings_["explorer_tx_size"] = "Size";
strings_["explorer_txid"] = "TxID:";
strings_["explorer_prev_block"] = "Previous block";
strings_["explorer_next_block"] = "Next block";
strings_["explorer_invalid_query"] = "Enter a block height or 64-character hash"; strings_["explorer_invalid_query"] = "Enter a block height or 64-character hash";
strings_["explorer_invalid_response"] = "Invalid response from daemon"; strings_["explorer_invalid_response"] = "Invalid response from daemon";
strings_["explorer_hash_not_found"] = "No block or transaction found for this hash"; strings_["explorer_hash_not_found"] = "No block or transaction found for this hash";