diff --git a/res/fonts/NotoSansCJK-Subset.ttf b/res/fonts/NotoSansCJK-Subset.ttf index 8d27198..0564b4a 100644 Binary files a/res/fonts/NotoSansCJK-Subset.ttf and b/res/fonts/NotoSansCJK-Subset.ttf differ diff --git a/res/lang/de.json b/res/lang/de.json index 21a529b..ccc7d88 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -229,6 +229,19 @@ "console_no_daemon": "Kein Daemon", "console_not_connected": "Fehler: Nicht mit Daemon verbunden", "console_quit_note": "'quit'/'exit' werden hier nicht benötigt — schließen Sie einfach das Fenster.", + "console_ref_cancel": "Abbrechen", + "console_ref_destructive": "Folgenreich", + "console_ref_example": "Beispiel", + "console_ref_insert": "In Konsole einfügen", + "console_ref_insert_run": "Einfügen & ausführen", + "console_ref_no_match": "Keine Befehle gefunden.", + "console_ref_no_params": "Erwartet keine Parameter.", + "console_ref_optional": "optional", + "console_ref_parameters": "Parameter", + "console_ref_run": "Ausführen", + "console_ref_run_confirm": "%s jetzt ausführen? Dies ist ein folgenreicher Befehl.", + "console_ref_search_hint": "Nach Name oder Aufgabe suchen…", + "console_ref_select_hint": "Wählen Sie einen Befehl, um zu sehen, was er tut.", "console_rpc_reference": "RPC-Befehlsreferenz", "console_rpc_trace": "RPC", "console_scanline": "Konsolen-Scanline", diff --git a/res/lang/es.json b/res/lang/es.json index 8ab8215..25f856c 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -229,6 +229,19 @@ "console_no_daemon": "Sin daemon", "console_not_connected": "Error: No conectado al daemon", "console_quit_note": "'quit'/'exit' no son necesarios aquí — simplemente cierra la ventana.", + "console_ref_cancel": "Cancelar", + "console_ref_destructive": "Delicado", + "console_ref_example": "Ejemplo", + "console_ref_insert": "Insertar en la consola", + "console_ref_insert_run": "Insertar y ejecutar", + "console_ref_no_match": "Ningún comando coincide.", + "console_ref_no_params": "No requiere parámetros.", + "console_ref_optional": "opcional", + "console_ref_parameters": "Parámetros", + "console_ref_run": "Ejecutar", + "console_ref_run_confirm": "¿Ejecutar %s ahora? Es un comando con consecuencias.", + "console_ref_search_hint": "Buscar por nombre o tarea…", + "console_ref_select_hint": "Selecciona un comando para ver qué hace.", "console_rpc_reference": "Referencia de Comandos RPC", "console_rpc_trace": "RPC", "console_scanline": "Líneas de consola", diff --git a/res/lang/fr.json b/res/lang/fr.json index 8f096a7..805ebf1 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -229,6 +229,19 @@ "console_no_daemon": "Pas de daemon", "console_not_connected": "Erreur : Non connecté au daemon", "console_quit_note": "'quit'/'exit' ne sont pas nécessaires ici — fermez simplement la fenêtre.", + "console_ref_cancel": "Annuler", + "console_ref_destructive": "Sensible", + "console_ref_example": "Exemple", + "console_ref_insert": "Insérer dans la console", + "console_ref_insert_run": "Insérer et exécuter", + "console_ref_no_match": "Aucune commande ne correspond.", + "console_ref_no_params": "Ne prend aucun paramètre.", + "console_ref_optional": "optionnel", + "console_ref_parameters": "Paramètres", + "console_ref_run": "Exécuter", + "console_ref_run_confirm": "Exécuter %s maintenant ? C'est une commande à conséquences.", + "console_ref_search_hint": "Rechercher par nom ou tâche…", + "console_ref_select_hint": "Sélectionnez une commande pour voir ce qu'elle fait.", "console_rpc_reference": "Référence des commandes RPC", "console_rpc_trace": "RPC", "console_scanline": "Scanline de la console", diff --git a/res/lang/ja.json b/res/lang/ja.json index 859b1a9..50364dd 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -229,6 +229,19 @@ "console_no_daemon": "デーモンなし", "console_not_connected": "エラー:デーモンに接続されていません", "console_quit_note": "ここでは 'quit'/'exit' は不要です — ウィンドウを閉じるだけで構いません。", + "console_ref_cancel": "キャンセル", + "console_ref_destructive": "要注意", + "console_ref_example": "例", + "console_ref_insert": "コンソールに挿入", + "console_ref_insert_run": "挿入して実行", + "console_ref_no_match": "一致するコマンドはありません。", + "console_ref_no_params": "パラメーターは不要です。", + "console_ref_optional": "任意", + "console_ref_parameters": "パラメーター", + "console_ref_run": "実行", + "console_ref_run_confirm": "%s を今すぐ実行しますか? 影響の大きいコマンドです。", + "console_ref_search_hint": "名前または用途で検索…", + "console_ref_select_hint": "コマンドを選ぶと内容が表示されます。", "console_rpc_reference": "RPCコマンドリファレンス", "console_rpc_trace": "RPC", "console_scanline": "コンソールスキャンライン", diff --git a/res/lang/ko.json b/res/lang/ko.json index 976156f..40fb139 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -229,6 +229,19 @@ "console_no_daemon": "데몬 없음", "console_not_connected": "오류: 데몬에 연결되지 않았습니다", "console_quit_note": "여기서는 'quit'/'exit'가 필요 없습니다 — 그냥 창을 닫으세요.", + "console_ref_cancel": "취소", + "console_ref_destructive": "주의", + "console_ref_example": "예시", + "console_ref_insert": "콘솔에 삽입", + "console_ref_insert_run": "삽입 후 실행", + "console_ref_no_match": "일치하는 명령이 없습니다.", + "console_ref_no_params": "매개변수가 없습니다.", + "console_ref_optional": "선택", + "console_ref_parameters": "매개변수", + "console_ref_run": "실행", + "console_ref_run_confirm": "%s 을(를) 지금 실행할까요? 영향이 큰 명령입니다.", + "console_ref_search_hint": "이름 또는 용도로 검색…", + "console_ref_select_hint": "명령을 선택하면 설명이 표시됩니다.", "console_rpc_reference": "RPC 명령어 참조", "console_rpc_trace": "RPC", "console_scanline": "콘솔 스캔라인", diff --git a/res/lang/pt.json b/res/lang/pt.json index fd3f988..cbd2aad 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -229,6 +229,19 @@ "console_no_daemon": "Sem daemon", "console_not_connected": "Erro: Não conectado ao daemon", "console_quit_note": "'quit'/'exit' não são necessários aqui — basta fechar a janela.", + "console_ref_cancel": "Cancelar", + "console_ref_destructive": "Sensível", + "console_ref_example": "Exemplo", + "console_ref_insert": "Inserir no console", + "console_ref_insert_run": "Inserir e executar", + "console_ref_no_match": "Nenhum comando corresponde.", + "console_ref_no_params": "Não requer parâmetros.", + "console_ref_optional": "opcional", + "console_ref_parameters": "Parâmetros", + "console_ref_run": "Executar", + "console_ref_run_confirm": "Executar %s agora? Este é um comando com consequências.", + "console_ref_search_hint": "Pesquisar por nome ou tarefa…", + "console_ref_select_hint": "Selecione um comando para ver o que ele faz.", "console_rpc_reference": "Referência de Comandos RPC", "console_rpc_trace": "RPC", "console_scanline": "Scanline do console", diff --git a/res/lang/ru.json b/res/lang/ru.json index 1d36dae..66f8940 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -229,6 +229,19 @@ "console_no_daemon": "Нет daemon", "console_not_connected": "Ошибка: Не подключено к daemon", "console_quit_note": "Здесь не нужны 'quit'/'exit' — просто закройте окно.", + "console_ref_cancel": "Отмена", + "console_ref_destructive": "Осторожно", + "console_ref_example": "Пример", + "console_ref_insert": "Вставить в консоль", + "console_ref_insert_run": "Вставить и выполнить", + "console_ref_no_match": "Нет подходящих команд.", + "console_ref_no_params": "Не требует параметров.", + "console_ref_optional": "необязательно", + "console_ref_parameters": "Параметры", + "console_ref_run": "Выполнить", + "console_ref_run_confirm": "Выполнить %s сейчас? Это ответственная команда.", + "console_ref_search_hint": "Поиск по названию или задаче…", + "console_ref_select_hint": "Выберите команду, чтобы увидеть, что она делает.", "console_rpc_reference": "Справочник RPC-команд", "console_rpc_trace": "RPC", "console_scanline": "Скан-линия консоли", diff --git a/res/lang/zh.json b/res/lang/zh.json index 11c761b..cdecf49 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -229,6 +229,19 @@ "console_no_daemon": "无守护进程", "console_not_connected": "错误:未连接到守护进程", "console_quit_note": "这里不需要 'quit'/'exit'——直接关闭窗口即可。", + "console_ref_cancel": "取消", + "console_ref_destructive": "谨慎", + "console_ref_example": "示例", + "console_ref_insert": "插入到控制台", + "console_ref_insert_run": "插入并运行", + "console_ref_no_match": "没有匹配的命令。", + "console_ref_no_params": "无需参数。", + "console_ref_optional": "可选", + "console_ref_parameters": "参数", + "console_ref_run": "运行", + "console_ref_run_confirm": "立即运行 %s?这是一个有重大影响的命令。", + "console_ref_search_hint": "按名称或用途搜索…", + "console_ref_select_hint": "选择一个命令以查看其功能。", "console_rpc_reference": "RPC 命令参考", "console_rpc_trace": "RPC", "console_scanline": "控制台扫描线", diff --git a/src/ui/windows/console_command_reference.cpp b/src/ui/windows/console_command_reference.cpp index 9b0892f..42ea65b 100644 --- a/src/ui/windows/console_command_reference.cpp +++ b/src/ui/windows/console_command_reference.cpp @@ -14,28 +14,50 @@ constexpr int CountOf(const ConsoleCommandEntry (&)[N]) } const ConsoleCommandEntry kControlCommands[] = { - {"help", "List all commands, or get help for a specified command", "[\"command\"]"}, - {"getinfo", "Get general info about the node", ""}, - {"stop", "Stop the daemon", ""}, + {"help", "List all commands, or get help for a specified command", "[\"command\"]", + "Lists every RPC command. Pass a command name to see the node's detailed help for just that one.", + "help \"getinfo\"", "commands list documentation what available"}, + {"getinfo", "Get general info about the node", "", + "A quick snapshot of the node: version, block height, connection count, difficulty and wallet balance.", + "getinfo", "status version summary overview node health"}, + {"stop", "Stop the daemon", "", + "Shuts the DragonX node down cleanly. The wallet stays disconnected until the node is started again.", + "stop", "quit shutdown exit close halt turn off", true}, }; const ConsoleCommandEntry kNetworkCommands[] = { - {"getnetworkinfo", "Return P2P network state info", ""}, - {"getpeerinfo", "Get data about each connected peer", ""}, - {"getconnectioncount", "Get number of peer connections", ""}, + {"getnetworkinfo", "Return P2P network state info", "", + "The node's networking state: protocol version, which networks are active, listening addresses and relay fee.", + "getnetworkinfo", "network p2p connection status internet"}, + {"getpeerinfo", "Get data about each connected peer", "", + "Detailed data for every peer you are connected to \xE2\x80\x94 address, version, ping time and bytes exchanged.", + "getpeerinfo", "peers connections nodes who connected"}, + {"getconnectioncount", "Get number of peer connections", "", + "How many peers the node is currently connected to. Zero usually means the node is still starting or offline.", + "getconnectioncount", "peers connections count how many"}, {"getnettotals", "Get network traffic statistics", ""}, {"addnode", "Add, remove, or connect to a node", "\"node\" \"add|remove|onetry\""}, - {"setban", "Add or remove an IP/subnet from the ban list", "\"ip\" \"add|remove\" [bantime] [absolute]"}, + {"setban", "Add or remove an IP/subnet from the ban list", "\"ip\" \"add|remove\" [bantime] [absolute]", + "Bans or unbans an IP or subnet from connecting. bantime is in seconds (0 = the default 24h); absolute treats it as a Unix timestamp.", + "setban \"192.168.0.6\" \"add\" 86400", "ban block ip peer firewall reject", true}, {"listbanned", "List all banned IPs/subnets", ""}, - {"clearbanned", "Clear all banned IPs", ""}, + {"clearbanned", "Clear all banned IPs", "", + "Removes every entry from the ban list, letting all previously-banned peers connect again.", + "clearbanned", "unban reset bans allow clear", true}, {"ping", "Ping all peers to measure round-trip time", ""}, }; const ConsoleCommandEntry kBlockchainCommands[] = { - {"getblockchaininfo", "Get current blockchain state", ""}, - {"getblockcount", "Get number of blocks in longest chain", ""}, + {"getblockchaininfo", "Get current blockchain state", "", + "State of the chain: height, best block hash, difficulty, verification progress and how far the node has synced.", + "getblockchaininfo", "chain sync height status blockchain progress"}, + {"getblockcount", "Get number of blocks in longest chain", "", + "The height of the longest chain \xE2\x80\x94 how many blocks the node has. Compare with the network to check sync.", + "getblockcount", "height blocks how many chain length synced"}, {"getbestblockhash", "Get hash of the tip block", ""}, - {"getblock", "Get block data for a given hash or height", "\"hash|height\" [verbosity]"}, + {"getblock", "Get block data for a given hash or height", "\"hash|height\" [verbosity]", + "Returns a block by its hash or height. verbosity 0 = raw hex, 1 = decoded header + txids, 2 = full transactions.", + "getblock \"0000000000abc123\" 1", "block details transactions header"}, {"getblockhash", "Get block hash at a given height", "height"}, {"getblockheader", "Get block header for a given hash", "\"hash\" [verbose]"}, {"getdifficulty", "Get proof-of-work difficulty", ""}, @@ -51,8 +73,12 @@ const ConsoleCommandEntry kBlockchainCommands[] = { }; const ConsoleCommandEntry kMiningCommands[] = { - {"getmininginfo", "Get mining-related information", ""}, - {"setgenerate", "Turn mining on or off (true/false [threads])", "generate [genproclimit]"}, + {"getmininginfo", "Get mining-related information", "", + "Mining status: current difficulty, estimated network hash rate and whether this node is generating blocks.", + "getmininginfo", "mining hashrate difficulty generate"}, + {"setgenerate", "Turn mining on or off (true/false [threads])", "generate [genproclimit]", + "Turns built-in CPU mining on or off. Pass true or false, and optionally the number of threads (-1 = all cores).", + "setgenerate true 4", "mine mining generate cpu start stop", true}, {"getgenerate", "Check if the node is mining", ""}, {"getnetworkhashps", "Get estimated network hash rate", "[blocks] [height]"}, {"getblocksubsidy", "Get block reward at a given height", "[height]"}, @@ -61,39 +87,75 @@ const ConsoleCommandEntry kMiningCommands[] = { }; const ConsoleCommandEntry kWalletCommands[] = { - {"getbalance", "Get wallet transparent balance", "[\"account\"] [minconf]"}, - {"z_gettotalbalance", "Get total transparent + shielded balance", "[minconf]"}, - {"z_getbalances", "Get all balances (transparent + shielded)", ""}, - {"getnewaddress", "Generate a new transparent address", ""}, - {"z_getnewaddress", "Generate a new shielded address", "[\"type\"]"}, - {"listaddresses", "List all transparent addresses", ""}, - {"z_listaddresses", "List all z-addresses", ""}, - {"sendtoaddress", "Send to a specific address", "\"address\" amount"}, - {"z_sendmany", "Send to multiple z/t-addresses with shielded support", "\"fromaddress\" [{\"address\":\"...\",\"amount\":...}]"}, + {"getbalance", "Get wallet transparent balance", "[\"account\"] [minconf]", + "Your confirmed transparent (t-address) balance in DRGX. Shielded funds are NOT included \xE2\x80\x94 use z_gettotalbalance for everything.", + "getbalance", "balance money funds how much transparent"}, + {"z_gettotalbalance", "Get total transparent + shielded balance", "[minconf]", + "Your complete balance in DRGX \xE2\x80\x94 transparent plus shielded (private) funds together.", + "z_gettotalbalance", "balance total money funds shielded private how much"}, + {"z_getbalances", "Get all balances (transparent + shielded)", "", + "Breaks your balance down across each transparent and shielded address the wallet holds.", + "z_getbalances", "balance breakdown addresses funds per address"}, + {"getnewaddress", "Generate a new transparent address", "", + "Creates a fresh transparent (t-) address you can share to receive DRGX.", + "getnewaddress", "receive new address deposit transparent create get paid"}, + {"z_getnewaddress", "Generate a new shielded address", "[\"type\"]", + "Creates a fresh shielded (z-) address for receiving DRGX privately.", + "z_getnewaddress", "receive new address private shielded create"}, + {"listaddresses", "List all transparent addresses", "", + "Lists the transparent (t-) addresses in your wallet.", + "listaddresses", "addresses list my accounts transparent"}, + {"z_listaddresses", "List all z-addresses", "", + "Lists the shielded (z-) addresses in your wallet.", + "z_listaddresses", "addresses list my shielded private"}, + {"sendtoaddress", "Send to a specific address", "\"address\" amount", + "Sends DRGX to a transparent address. amount is in DRGX. Double-check the address \xE2\x80\x94 sent transactions cannot be reversed.", + "sendtoaddress \"RyourRecipientAddr\" 1.5", "send pay transfer money spend", true}, + {"z_sendmany", "Send to multiple z/t-addresses with shielded support", "\"fromaddress\" [{\"address\":\"...\",\"amount\":...}]", + "Sends DRGX from one address to one or more recipients, with shielded (private) support. Amounts are in DRGX.", + "z_sendmany \"RfromAddr\" [{\"address\":\"zs1toAddr\",\"amount\":1.0}]", "send pay private shielded transfer money", true}, {"z_shieldcoinbase", "Shield transparent coinbase funds to a z-address", "\"fromaddress\" \"tozaddress\" [fee] [limit]"}, {"z_mergetoaddress", "Merge multiple UTXOs/notes to one address", "[\"fromaddress\",...] \"toaddress\" [fee] [limit]"}, - {"listtransactions", "List recent wallet transactions", "[\"account\"] [count] [from]"}, + {"listtransactions", "List recent wallet transactions", "[\"account\"] [count] [from]", + "Your most recent wallet transactions, newest first \xE2\x80\x94 amounts, addresses and confirmations.", + "listtransactions", "transactions history recent payments received sent"}, {"listunspent", "List unspent transaction outputs", "[minconf] [maxconf]"}, {"z_listunspent", "List unspent shielded notes", "[minconf] [maxconf]"}, {"z_getoperationstatus", "Get status of async z operations", "[\"operationid\",...]"}, {"z_getoperationresult", "Get result of completed z operations", "[\"operationid\",...]"}, {"z_listoperationids", "List all async z operation IDs", ""}, - {"getwalletinfo", "Get wallet state info", ""}, - {"backupwallet", "Back up wallet to a file", "\"destination\""}, - {"dumpprivkey", "Dump private key for an address", "\"address\""}, - {"importprivkey", "Import a private key into the wallet", "\"privkey\" [\"label\"] [rescan]"}, - {"dumpwallet", "Dump all wallet keys to a file", "\"filename\""}, - {"importwallet", "Import wallet from a dump file", "\"filename\""}, - {"z_exportkey", "Export spending key for a z-address", "\"zaddr\""}, - {"z_importkey", "Import a z-address spending key", "\"zkey\" [rescan] [startheight]"}, + {"getwalletinfo", "Get wallet state info", "", + "Wallet summary: balances, transaction count, key-pool size and whether the wallet is encrypted and locked.", + "getwalletinfo", "wallet status info balance encrypted locked"}, + {"backupwallet", "Back up wallet to a file", "\"destination\"", + "Saves a copy of your wallet.dat to the given path. Keep the backup somewhere safe and private.", + "backupwallet \"/home/you/drgx-backup.dat\"", "backup save wallet copy protect"}, + {"dumpprivkey", "Dump private key for an address", "\"address\"", + "Reveals the private key for an address. Anyone with this key can spend from the address \xE2\x80\x94 never share it.", + "dumpprivkey \"RyourAddr\"", "private key export reveal secret spend", true}, + {"importprivkey", "Import a private key into the wallet", "\"privkey\" [\"label\"] [rescan]", + "Imports a private key so the wallet can spend from its address, then rescans the chain for its history (can be slow).", + "importprivkey \"Uxxxxxxxxxxxx\"", "import private key restore recover add", true}, + {"dumpwallet", "Dump all wallet keys to a file", "\"filename\"", "", "", "", true}, + {"importwallet", "Import wallet from a dump file", "\"filename\"", "", "", "", true}, + {"z_exportkey", "Export spending key for a z-address", "\"zaddr\"", + "Reveals the spending key for a shielded address. Anyone with it can spend your private funds \xE2\x80\x94 keep it secret.", + "z_exportkey \"zs1yourAddr\"", "private key export shielded secret spend", true}, + {"z_importkey", "Import a z-address spending key", "\"zkey\" [rescan] [startheight]", "", "", "", true}, {"z_exportviewingkey", "Export viewing key for a z-address", "\"zaddr\""}, - {"z_importviewingkey", "Import a z-address viewing key", "\"vkey\" [rescan] [startheight]"}, - {"z_exportwallet", "Export all wallet keys (including z-keys) to file", "\"filename\""}, - {"signmessage", "Sign a message with an address key", "\"address\" \"message\""}, + {"z_importviewingkey", "Import a z-address viewing key", "\"vkey\" [rescan] [startheight]", "", "", "", true}, + {"z_exportwallet", "Export all wallet keys (including z-keys) to file", "\"filename\"", "", "", "", true}, + {"signmessage", "Sign a message with an address key", "\"address\" \"message\"", + "Signs a message with the private key of one of your addresses, proving you control that address.", + "signmessage \"RyourAddr\" \"hello world\"", "sign message prove ownership"}, {"settxfee", "Set the transaction fee per kB", "amount"}, - {"walletpassphrase", "Unlock the wallet with passphrase", "\"passphrase\" timeout"}, + {"walletpassphrase", "Unlock the wallet with passphrase", "\"passphrase\" timeout", + "Unlocks an encrypted wallet for the given number of seconds so you can send funds. Your passphrase is typed in plain text \xE2\x80\x94 be sure no one is watching.", + "walletpassphrase \"your passphrase\" 60", "unlock passphrase password encrypted open", true}, {"walletlock", "Lock the wallet", ""}, - {"encryptwallet", "Encrypt the wallet with a passphrase", "\"passphrase\""}, + {"encryptwallet", "Encrypt the wallet with a passphrase", "\"passphrase\"", + "Encrypts the wallet with a passphrase. The node shuts down afterward and you will need the passphrase to send. Encryption cannot be undone \xE2\x80\x94 keep the passphrase safe.", + "encryptwallet \"a strong passphrase\"", "encrypt password protect secure lock", true}, }; const ConsoleCommandEntry kRawTransactionCommands[] = { @@ -107,13 +169,15 @@ const ConsoleCommandEntry kRawTransactionCommands[] = { }; const ConsoleCommandEntry kUtilityCommands[] = { - {"validateaddress", "Validate a transparent address", "\"address\""}, + {"validateaddress", "Validate a transparent address", "\"address\"", + "Checks whether a transparent address is well-formed and whether it belongs to your wallet.", + "validateaddress \"RsomeAddr\"", "validate check address valid mine"}, {"z_validateaddress", "Validate a z-address", "\"zaddr\""}, {"estimatefee", "Estimate fee for a transaction", "nblocks"}, {"verifymessage", "Verify a signed message", "\"address\" \"signature\" \"message\""}, {"createmultisig", "Create a multisig address", "nrequired [\"key\",...]"}, - {"invalidateblock", "Mark a block as invalid", "\"hash\""}, - {"reconsiderblock", "Reconsider a previously invalidated block", "\"hash\""}, + {"invalidateblock", "Mark a block as invalid", "\"hash\"", "", "", "", true}, + {"reconsiderblock", "Reconsider a previously invalidated block", "\"hash\"", "", "", "", true}, }; } // namespace diff --git a/src/ui/windows/console_command_reference.h b/src/ui/windows/console_command_reference.h index e5fd520..47ad00c 100644 --- a/src/ui/windows/console_command_reference.h +++ b/src/ui/windows/console_command_reference.h @@ -7,8 +7,14 @@ namespace ui { struct ConsoleCommandEntry { const char* name; - const char* desc; - const char* params; + const char* desc; // terse one-line summary (always set) + const char* params; // parameter template, e.g. "\"address\" amount [comment]" + // Optional novice-facing enrichment (C++17 aggregate defaults — entries that omit these keep + // the empty/false fallbacks, and the command explorer falls back to `desc`): + const char* details = ""; // longer plain-language explanation + const char* example = ""; // one concrete, runnable example line + const char* keywords = ""; // space-separated search synonyms ("balance money funds") + bool destructive = false; // consequential/sensitive -> safety badge + run confirmation }; struct ConsoleCommandCategory { diff --git a/src/ui/windows/console_tab.cpp b/src/ui/windows/console_tab.cpp index 0d85732..301715f 100644 --- a/src/ui/windows/console_tab.cpp +++ b/src/ui/windows/console_tab.cpp @@ -284,6 +284,14 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec) } } + // Run a command the RPC-reference modal's "Insert & run" queued (the modal has no executor, so it + // defers to here). Done before drain so the echoed "> cmd" line surfaces this frame. + if (!pending_submit_.empty()) { + std::string cmd; + cmd.swap(pending_submit_); + submitConsoleCommand(exec, cmd); + } + // Pull passive log lines (daemon/xmrig output, or the lite diagnostics ring) and any // completed command results from the backend executor. exec.pollLogLines([this](const std::string& l, ConsoleChannel c) { addLine(l, c); }); @@ -1439,54 +1447,52 @@ bool ConsoleTab::submitConsoleCommand(ConsoleCommandExecutor& exec, const std::s namespace { // True if any of a command's name/desc/params contains `filterLower` (already lowercased). // An empty filter matches everything. -bool consoleCommandMatchesFilter(const char* name, const char* desc, const char* params, - const std::string& filterLower) +static bool lcContains(const char* s, const std::string& needleLower) { - if (filterLower.empty()) return true; - auto contains = [&filterLower](const char* s) { - std::string v(s); - std::transform(v.begin(), v.end(), v.begin(), ::tolower); - return v.find(filterLower) != std::string::npos; - }; - return contains(name) || contains(desc) || contains(params); + std::string v(s); + std::transform(v.begin(), v.end(), v.begin(), ::tolower); + return v.find(needleLower) != std::string::npos; +} +static bool lcStartsWith(const char* s, const std::string& needleLower) +{ + std::string v(s); + std::transform(v.begin(), v.end(), v.begin(), ::tolower); + return v.rfind(needleLower, 0) == 0; } -// Draw a command's parameter string into the current table cell, dimming optional [params]. -void drawConsoleCommandParams(const char* params) +// Search relevance of a command for a lowercased query. -1 = no match; higher = better. Keywords +// let novices find a command by intent ("balance" -> getbalance) even without the exact name. +int consoleCommandRank(const ConsoleCommandEntry& cmd, const std::string& q) { - using namespace material; - const char* p = params; - bool first = true; - while (*p) { - const char* bracketStart = strchr(p, '['); - if (bracketStart) { - // Draw the required part before the bracket. - if (bracketStart > p) { - if (!first) ImGui::SameLine(0, 0); - std::string req(p, bracketStart); - ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", req.c_str()); - first = false; - } - const char* bracketEnd = strchr(bracketStart, ']'); - if (bracketEnd) { - if (!first) ImGui::SameLine(0, 0); - std::string opt(bracketStart, bracketEnd + 1); - ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", opt.c_str()); - first = false; - p = bracketEnd + 1; - } else { - if (!first) ImGui::SameLine(0, 0); - ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", bracketStart); - first = false; - break; - } - } else { - if (!first) ImGui::SameLine(0, 0); - ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", p); - first = false; - break; - } + if (q.empty()) return 0; + if (lcStartsWith(cmd.name, q)) return 100; + if (lcContains(cmd.name, q)) return 60; + if (cmd.keywords[0] && lcContains(cmd.keywords, q)) return 40; + if (lcContains(cmd.desc, q)) return 25; + if (cmd.details[0] && lcContains(cmd.details, q)) return 15; + if (lcContains(cmd.params, q)) return 10; + return -1; +} + +// Split a parameter template into top-level tokens, respecting quote/bracket nesting so a space +// inside "..." or [{...}] doesn't split (e.g. `"address" [{"a":1}]` -> {`"address"`, `[{"a":1}]`}). +std::vector splitParamTemplate(const char* params) +{ + std::vector out; + std::string tok; + int depth = 0; + char q = 0; + for (const char* p = params; *p; ++p) { + char c = *p; + if (q) { tok += c; if (c == q) q = 0; continue; } + if (c == '"' || c == '\'') { q = c; tok += c; continue; } + if (c == '[' || c == '{') { depth++; tok += c; continue; } + if (c == ']' || c == '}') { if (depth > 0) depth--; tok += c; continue; } + if (c == ' ' && depth == 0) { if (!tok.empty()) { out.push_back(tok); tok.clear(); } continue; } + tok += c; } + if (!tok.empty()) out.push_back(tok); + return out; } // Translate a command-category name from the static reference tables (English) for display. The @@ -1504,157 +1510,279 @@ const char* consoleCategoryLabel(const char* name) } } // namespace +void ConsoleTab::insertCommandToInput(const ConsoleCommandEntry& cmd) +{ + // Fill the console input with the command (+ its param template) and close the modal — the user + // reviews/edits it and presses Enter to run. + if (cmd.params[0] != '\0') + snprintf(input_buffer_, sizeof(input_buffer_), "%s %s", cmd.name, cmd.params); + else { + strncpy(input_buffer_, cmd.name, sizeof(input_buffer_) - 1); + input_buffer_[sizeof(input_buffer_) - 1] = '\0'; + } + command_search_[0] = '\0'; + run_confirm_cmd_ = nullptr; + show_commands_popup_ = false; +} + +void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel) +{ + using namespace material; + float dp = Layout::dpiScale(); + ImFont* mono = Type().mono(); + + // Heading: command name, then category + a safety badge for consequential commands. + ImGui::PushFont(Type().subtitle1()); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(IM_COL32(120, 190, 255, 255)), "%s", cmd.name); + ImGui::PopFont(); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", catLabel); + if (cmd.destructive) { + ImGui::SameLine(0, Layout::spacingMd()); + ImVec4 warn = ImGui::ColorConvertU32ToFloat4(Warning()); + ImGui::PushFont(Type().iconSmall()); + ImGui::TextColored(warn, ICON_MD_WARNING); + ImGui::PopFont(); + ImGui::SameLine(0, Layout::spacingXs()); + ImGui::TextColored(warn, "%s", TR("console_ref_destructive")); + } + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + + // Plain-language explanation (falls back to the terse summary when not enriched). + ImGui::PushTextWrapPos(0.0f); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurface()), "%s", + cmd.details[0] ? cmd.details : cmd.desc); + ImGui::PopTextWrapPos(); + ImGui::Dummy(ImVec2(0, Layout::spacingMd())); + + // Parameters — each token with a rough type tag; optional ([...]) tokens are dimmed. + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", TR("console_ref_parameters")); + std::vector toks = splitParamTemplate(cmd.params); + if (toks.empty()) { + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), "%s", TR("console_ref_no_params")); + } else { + ImGui::Indent(Layout::spacingMd()); + for (const std::string& t : toks) { + bool optional = (!t.empty() && t.front() == '['); + char c0 = t.empty() ? 0 : t[0]; + if (optional && t.size() >= 2) c0 = t[1]; + const char* type = (c0 == '"' || c0 == '\'') ? "string" + : (c0 == '{' || c0 == '[') ? "json" : "number"; + ImGui::PushFont(mono); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(optional ? OnSurfaceMedium() : OnSurface()), + "%s", t.c_str()); + ImGui::PopFont(); + ImGui::SameLine(); + if (optional) + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), + " %s \xC2\xB7 %s", type, TR("console_ref_optional")); + else + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceDisabled()), " %s", type); + } + ImGui::Unindent(Layout::spacingMd()); + } + ImGui::Dummy(ImVec2(0, Layout::spacingMd())); + + // Example (curated commands only). + if (cmd.example[0]) { + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), "%s", TR("console_ref_example")); + ImGui::PushFont(mono); + ImGui::PushTextWrapPos(0.0f); + ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(IM_COL32(150, 200, 150, 255)), "%s", cmd.example); + ImGui::PopTextWrapPos(); + ImGui::PopFont(); + ImGui::Dummy(ImVec2(0, Layout::spacingMd())); + } + + // Actions (or the destructive run confirmation). + ImGui::Dummy(ImVec2(0, Layout::spacingSm())); + if (run_confirm_cmd_ == &cmd) { + ImVec4 warn = ImGui::ColorConvertU32ToFloat4(Warning()); + ImGui::PushFont(Type().iconSmall()); + ImGui::TextColored(warn, ICON_MD_WARNING); + ImGui::PopFont(); + ImGui::SameLine(0, Layout::spacingXs()); + ImGui::TextColored(warn, TR("console_ref_run_confirm"), cmd.name); + ImGui::Dummy(ImVec2(0, Layout::spacingXs())); + float cbw = 120.0f * dp; + if (material::TactileButton(TR("console_ref_cancel"), ImVec2(cbw, 0))) run_confirm_cmd_ = nullptr; + ImGui::SameLine(); + if (material::TactileButton(TR("console_ref_run"), ImVec2(cbw, 0))) { + pending_submit_ = cmd.name; + command_search_[0] = '\0'; + run_confirm_cmd_ = nullptr; + show_commands_popup_ = false; + } + } else { + if (material::TactileButton(TR("console_ref_insert"), ImVec2(200.0f * dp, 0))) + insertCommandToInput(cmd); + // "Insert & run" only for commands that take no parameters — running a template with + // placeholders ("address", amount) would just error. + if (cmd.params[0] == '\0') { + ImGui::SameLine(); + if (material::TactileButton(TR("console_ref_insert_run"), ImVec2(140.0f * dp, 0))) { + if (cmd.destructive) { + run_confirm_cmd_ = &cmd; + } else { + pending_submit_ = cmd.name; + command_search_[0] = '\0'; + show_commands_popup_ = false; + } + } + } + } +} + void ConsoleTab::renderCommandsPopup() { using namespace material; - - float popW = std::min(schema::UI().drawElement("tabs.console", "popup-max-width").size, ImGui::GetMainViewport()->Size.x * schema::UI().drawElement("tabs.console", "popup-width-ratio").size); - material::OverlayDialogSpec ov; - ov.title = TR("console_rpc_reference"); ov.p_open = &show_commands_popup_; - ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading - ov.cardWidth = popW; ov.cardBottomViewportRatio = 0.94f; // keep authored width - // FIXED height (fills most of the viewport) instead of auto-height: the command list's - // fill-height child reads GetContentRegionAvail() inside the dialog child, which is only stable - // when the child is fixed-height. Auto-height made it self-referential and it slid content - // off-screen after a monitor move (same class of bug as the Wallets modal). - ov.cardHeight = ImGui::GetMainViewport()->Size.y * 0.84f / Layout::dpiScale(); - if (!material::BeginOverlayDialog(ov)) { - return; - } - // Esc dismisses — this overlay is a plain Begin/BeginChild, not an ImGui popup, so it has no - // built-in Esc handling (matches the other editor overlays). - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) show_commands_popup_ = false; + float dp = Layout::dpiScale(); - // Search filter — auto-focus on open so the user can type immediately (like the console input). + material::OverlayDialogSpec ov; + ov.title = TR("console_rpc_reference"); + ov.p_open = &show_commands_popup_; + ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading + ov.cardWidth = 960.0f; // wide enough for two panes + ov.cardHeight = ImGui::GetMainViewport()->Size.y * 0.74f / dp; // fixed; both panes fill it + ov.idSuffix = "cmdref"; + if (!material::BeginOverlayDialog(ov)) return; + + // Esc dismisses (a first Esc cancels a pending run-confirm). Not built into the overlay. + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + if (run_confirm_cmd_) run_confirm_cmd_ = nullptr; + else show_commands_popup_ = false; + } + + // Search box — auto-focus on open so the user can type immediately. ImGui::SetNextItemWidth(-1); if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere(); - ImGui::InputTextWithHint("##CmdSearch", TR("console_search_commands"), command_search_, sizeof(command_search_)); + bool searchChanged = ImGui::InputTextWithHint("##CmdSearch", TR("console_ref_search_hint"), + command_search_, sizeof(command_search_)); ImGui::Dummy(ImVec2(0, Layout::spacingXs())); - + + std::string q(command_search_); + std::transform(q.begin(), q.end(), q.begin(), ::tolower); + const bool searching = !q.empty(); + const auto& categories = consoleCommandCategories(); - - std::string filter(command_search_); - std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower); - - ImGui::BeginChild("CmdListScroll", ImVec2(0, -ImGui::GetFrameHeightWithSpacing() - Layout::spacingXs()), - false); - - ImGui::PushFont(Type().caption()); - - // cmd-min-width is authored in logical px; popupInnerW (GetContentRegionAvail) is physical/DPI- - // scaled, so scale the breakpoint to match — otherwise the 2-vs-3 column choice trips at the - // wrong width on HiDPI. - float cmdMinWidth = schema::UI().drawElement("tabs.console", "cmd-min-width").sizeOr(500.0f) * Layout::dpiScale(); - float popupInnerW = ImGui::GetContentRegionAvail().x; - bool showParams = popupInnerW >= cmdMinWidth; - int catIdx = 0; - for (const auto& cat : categories) { - // Count matching commands in this category - int matchCount = 0; - if (filter.empty()) { - matchCount = cat.count; - } else { - for (int i = 0; i < cat.count; i++) { - if (consoleCommandMatchesFilter(cat.commands[i].name, cat.commands[i].desc, - cat.commands[i].params, filter)) { - matchCount++; - } + // Flat display order of (cat,idx): ranked when searching, category order when browsing. Drives + // keyboard nav + auto-selection; the browse view still renders grouped headers below. + std::vector> order; + if (searching) { + std::vector>> scored; // (score, (cat,idx)) + for (int c = 0; c < (int)categories.size(); c++) + for (int i = 0; i < categories[c].count; i++) { + int s = consoleCommandRank(categories[c].commands[i], q); + if (s >= 0) scored.push_back({s, {c, i}}); } + std::stable_sort(scored.begin(), scored.end(), + [](const auto& a, const auto& b) { return a.first > b.first; }); + for (auto& s : scored) order.push_back(s.second); + } else { + for (int c = 0; c < (int)categories.size(); c++) + for (int i = 0; i < categories[c].count; i++) order.push_back({c, i}); + } + + // Keep a valid selection: reset to the top when the query changes or the current selection falls + // out of the visible set, so the detail pane is always populated. + int selPos = -1; + for (int k = 0; k < (int)order.size(); k++) + if (order[k].first == cmd_sel_cat_ && order[k].second == cmd_sel_idx_) { selPos = k; break; } + if (searchChanged || selPos < 0) { + if (!order.empty()) { cmd_sel_cat_ = order[0].first; cmd_sel_idx_ = order[0].second; selPos = 0; } + else { cmd_sel_cat_ = cmd_sel_idx_ = -1; } + } + + // Keyboard nav: Up/Down move the selection, Enter inserts (disabled while a run-confirm shows). + if (!run_confirm_cmd_ && !order.empty() && selPos >= 0) { + if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && selPos + 1 < (int)order.size()) selPos++; + else if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && selPos > 0) selPos--; + cmd_sel_cat_ = order[selPos].first; + cmd_sel_idx_ = order[selPos].second; + if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)) + insertCommandToInput(categories[cmd_sel_cat_].commands[cmd_sel_idx_]); + } + + // Two-pane body sized above the footer. + float footerH = ImGui::GetFrameHeightWithSpacing() + Layout::spacingXs(); + float bodyH = std::max(120.0f, ImGui::GetContentRegionAvail().y - footerH); + float contentW = ImGui::GetContentRegionAvail().x; + float gap = Layout::spacingLg(); + float masterW = std::min(std::max(contentW * 0.34f, 280.0f * dp), 380.0f * dp); + float detailW = contentW - masterW - gap; + + ImFont* mono = Type().mono(); + ImU32 nameCol = IM_COL32(100, 180, 255, 255); + float rowH = std::max(20.0f * dp, mono->LegacySize + 6.0f * dp); + + // One master row: mono command name + a warning dot for consequential commands. Click selects + // (drives the detail pane); double-click inserts. + auto drawRow = [&](int c, int i) { + const ConsoleCommandEntry& cmd = categories[c].commands[i]; + bool sel = (cmd_sel_cat_ == c && cmd_sel_idx_ == i); + ImGui::PushID(c * 1000 + i); + ImVec2 rmn = ImGui::GetCursorScreenPos(); + if (ImGui::Selectable("##cmdrow", sel, ImGuiSelectableFlags_SpanAvailWidth, ImVec2(0, rowH))) + { cmd_sel_cat_ = c; cmd_sel_idx_ = i; } + if (ImGui::IsItemHovered()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) insertCommandToInput(cmd); } - if (matchCount == 0) { catIdx++; continue; } + ImDrawList* dl = ImGui::GetWindowDrawList(); + float tx = rmn.x + Layout::spacingSm(); + if (cmd.destructive) { + float r = 3.0f * dp; + dl->AddCircleFilled(ImVec2(tx + r, rmn.y + rowH * 0.5f), r, Warning()); + tx += r * 2.0f + Layout::spacingXs(); + } + dl->AddText(mono, mono->LegacySize, ImVec2(tx, rmn.y + (rowH - mono->LegacySize) * 0.5f), + sel ? OnSurface() : nameCol, cmd.name); + ImGui::PopID(); + }; - // Default-open only the first category (Control); collapse the rest - ImGuiTreeNodeFlags headerFlags = (catIdx == 0) ? ImGuiTreeNodeFlags_DefaultOpen : 0; - // When filtering, open all matching categories - if (!filter.empty()) headerFlags = ImGuiTreeNodeFlags_DefaultOpen; - - ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Primary())); - // Show match count badge when filtering - const char* catLabel = consoleCategoryLabel(cat.name); - char headerLabel[128]; - if (!filter.empty()) { - snprintf(headerLabel, sizeof(headerLabel), "%s (%d)", catLabel, matchCount); + // MASTER pane. + ImGui::BeginChild("##cmdMaster", ImVec2(masterW, bodyH), true); + { + if (order.empty()) { + Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_ref_no_match")); + } else if (searching) { + for (auto& p : order) drawRow(p.first, p.second); } else { - snprintf(headerLabel, sizeof(headerLabel), "%s", catLabel); - } - bool open = ImGui::CollapsingHeader(headerLabel, headerFlags); - ImGui::PopStyleColor(); - catIdx++; - - if (open) { - float nameColW = schema::UI().drawElement("tabs.console", "cmd-name-col-width").size * Layout::hScale(); - float paramsColW = schema::UI().drawElement("tabs.console", "cmd-params-col-width").size * Layout::hScale(); - int numCols = showParams ? 3 : 2; - char tableId[24]; - snprintf(tableId, sizeof(tableId), "##cmds%d", catIdx); // unique id per category table - if (ImGui::BeginTable(tableId, numCols, ImGuiTableFlags_None)) { - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, nameColW); - if (showParams) - ImGui::TableSetupColumn("Parameters", ImGuiTableColumnFlags_WidthFixed, paramsColW); - ImGui::TableSetupColumn("Desc", ImGuiTableColumnFlags_WidthStretch); - - for (int i = 0; i < cat.count; i++) { - const auto& cmd = cat.commands[i]; - if (!consoleCommandMatchesFilter(cmd.name, cmd.desc, cmd.params, filter)) - continue; - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - // Link-like blue for the command name. Readable on all skins: the RPC-reference - // modal card is a dark glass panel regardless of theme, so no light-floor is needed. - ImGui::PushStyleColor(ImGuiCol_Text, - ImGui::ColorConvertU32ToFloat4(IM_COL32(100, 180, 255, 255))); - ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0)); - ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.2f, 0.4f, 0.6f, 0.3f)); - char selId[128]; - snprintf(selId, sizeof(selId), "%s##cmdRef", cmd.name); - if (ImGui::Selectable(selId, false)) { - if (cmd.params[0] != '\0') { - snprintf(input_buffer_, sizeof(input_buffer_), "%s %s", cmd.name, cmd.params); - } else { - strncpy(input_buffer_, cmd.name, sizeof(input_buffer_) - 1); - input_buffer_[sizeof(input_buffer_) - 1] = '\0'; - } - // This overlay isn't an ImGui popup, so CloseCurrentPopup() is a no-op here — - // close it the way the framework honors, revealing the inserted command. - show_commands_popup_ = false; - } - if (ImGui::IsItemHovered()) { - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - if (cmd.params[0] != '\0') - material::Tooltip(TR("console_click_insert_params"), cmd.name, cmd.params); - else - material::Tooltip(TR("console_click_insert"), cmd.name); - } - ImGui::PopStyleColor(3); - if (showParams) { - ImGui::TableNextColumn(); - drawConsoleCommandParams(cmd.params); - } - ImGui::TableNextColumn(); - ImGui::TextColored( - ImGui::ColorConvertU32ToFloat4(OnSurfaceMedium()), - "%s", cmd.desc); - } - - ImGui::EndTable(); + for (int c = 0; c < (int)categories.size(); c++) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(Primary())); + bool open = ImGui::CollapsingHeader(consoleCategoryLabel(categories[c].name), + ImGuiTreeNodeFlags_DefaultOpen); + ImGui::PopStyleColor(); + if (open) + for (int i = 0; i < categories[c].count; i++) drawRow(c, i); } - ImGui::Spacing(); } } - - ImGui::PopFont(); ImGui::EndChild(); - + + ImGui::SameLine(0, gap); + + // DETAIL pane. + ImGui::BeginChild("##cmdDetail", ImVec2(detailW, bodyH), true); + if (cmd_sel_cat_ >= 0 && cmd_sel_cat_ < (int)categories.size() && + cmd_sel_idx_ >= 0 && cmd_sel_idx_ < categories[cmd_sel_cat_].count) { + renderCommandDetail(categories[cmd_sel_cat_].commands[cmd_sel_idx_], + consoleCategoryLabel(categories[cmd_sel_cat_].name)); + } else { + ImVec2 av = ImGui::GetContentRegionAvail(); + ImGui::SetCursorPosY(av.y * 0.4f); + Type().textColored(TypeStyle::Caption, OnSurfaceDisabled(), TR("console_ref_select_hint")); + } + ImGui::EndChild(); + + // Footer. ImGui::Dummy(ImVec2(0, Layout::spacingXs())); - - // Close button (the filter is also reset when the modal next opens). if (material::TactileButton(TR("console_close"), ImVec2(-1, 0))) { command_search_[0] = '\0'; + run_confirm_cmd_ = nullptr; show_commands_popup_ = false; } - + material::EndOverlayDialog(); } diff --git a/src/ui/windows/console_tab.h b/src/ui/windows/console_tab.h index 321b884..5c2f56a 100644 --- a/src/ui/windows/console_tab.h +++ b/src/ui/windows/console_tab.h @@ -25,6 +25,7 @@ namespace ui { class ConsoleCommandExecutor; struct ConsoleLogFilterCaps; +struct ConsoleCommandEntry; /** * @brief Console tab — a rich terminal shared by the full-node and lite variants. @@ -115,6 +116,8 @@ private: void addFormattedResult(const std::string& result, bool is_error); void renderStatusHeader(ConsoleCommandExecutor& exec); void renderCommandsPopup(); + void renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel); // right pane + void insertCommandToInput(const ConsoleCommandEntry& cmd); // fill input + close the modal // renderToolbar() draws the top bar; these are its sub-steps: void renderToolbar(ConsoleCommandExecutor& exec); @@ -183,9 +186,13 @@ private: // consumed by the renderer + hit-testing. mutable ConsoleLayout layout_; - // Commands popup + // Commands popup (RPC command explorer) bool show_commands_popup_ = false; char command_search_[128] = {0}; // RPC-reference search filter (cleared when the modal opens) + int cmd_sel_cat_ = -1; // detail-pane selection: category index into consoleCommandCategories() + int cmd_sel_idx_ = -1; // detail-pane selection: command index within that category + std::string pending_submit_; // command to run next frame (deferred so the modal needs no executor) + const ConsoleCommandEntry* run_confirm_cmd_ = nullptr; // destructive "Insert & run" awaiting confirmation }; } // namespace ui diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index d8dd986..ebb29fb 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1301,6 +1301,19 @@ void I18n::loadBuiltinEnglish() strings_["console_cat_wallet"] = "Wallet"; strings_["console_cat_raw_transactions"] = "Raw Transactions"; strings_["console_cat_utility"] = "Utility"; + strings_["console_ref_search_hint"] = "Search by name or task\xE2\x80\xA6"; + strings_["console_ref_parameters"] = "Parameters"; + strings_["console_ref_no_params"] = "Takes no parameters."; + strings_["console_ref_optional"] = "optional"; + strings_["console_ref_example"] = "Example"; + strings_["console_ref_destructive"] = "Consequential"; + strings_["console_ref_run_confirm"] = "Run %s now? This is a consequential command."; + strings_["console_ref_cancel"] = "Cancel"; + strings_["console_ref_run"] = "Run"; + strings_["console_ref_insert"] = "Insert into console"; + strings_["console_ref_insert_run"] = "Insert & run"; + strings_["console_ref_select_hint"] = "Select a command to see what it does."; + strings_["console_ref_no_match"] = "No commands match."; // --- Export All Keys Dialog --- strings_["export_keys_btn"] = "Export Keys";