diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ffd096..edc3d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog - [Changelog](#changelog) - - [0.7.0](#070) + - [0.6.1](#061) - [0.6.0](#060) - [0.5.1](#051) - [0.5.0](#050) @@ -20,14 +20,22 @@ --- -## 0.7.0 +## 0.6.1 Released on ?? -> 🍁 Autumn update 2021 🍇 - +- Enhancements: + - Now that tui-rs supports title alignment, UI has been improved + - Added new `Directory already exists` variant for file transfer errors - Bugfix: - Fixed [Issue 58](https://github.com/veeso/termscp/issues/58):When uploading a directory, create directory only if it doesn't exist +- Dependencies: + - Updated `bitflags` to `1.3.2` + - Updated `bytesize` to `1.1.0` + - Updated `crossterm` to `0.20` + - Updated `open` to `2.0.1` + - Added `tui-realm-stdlib 0.6.0` + - Updated `tui-realm` to `0.6.0` ## 0.6.0 diff --git a/Cargo.lock b/Cargo.lock index 3dab2b9..1517e69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "block-buffer" @@ -138,9 +138,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytesize" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a18687293a1546b67c246452202bbbf143d239cb43494cc163da14979082da" +checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70" [[package]] name = "cassowary" @@ -150,9 +150,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] name = "cc" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" @@ -264,34 +264,34 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c36c10130df424b2f3552fcc2ddcd9b28a27b1e54b358b45874f88d1ca6888c" +checksum = "c0ebde6a9dd5e331cd6c6f48253254d117642c31653baa475e394657c59c1f7d" dependencies = [ "bitflags", "crossterm_winapi", - "lazy_static", "libc", "mio", "parking_lot 0.11.1", "signal-hook", + "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da8964ace4d3e4a044fd027919b2237000b24315a37c916f61809f1ff2140b9" +checksum = "3a6966607622438301997d3dac0d2f6e9a90c68bb6bc1785ea98456ab93c0507" dependencies = [ "winapi", ] [[package]] name = "crypto-mac" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" dependencies = [ "generic-array", "subtle", @@ -506,9 +506,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" dependencies = [ "cfg-if 1.0.0", ] @@ -521,9 +521,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" -version = "0.3.51" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062" +checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" dependencies = [ "wasm-bindgen", ] @@ -548,9 +548,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.98" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" +checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765" [[package]] name = "libssh2-sys" @@ -630,9 +630,9 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "md-5" @@ -675,9 +675,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static", "libc", @@ -790,11 +790,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "open" -version = "1.7.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1711eb4b31ce4ad35b0f316d8dfba4fe5c7ad601c448446d84aae7a896627b20" +checksum = "b46b233de7d83bc167fe43ae2dda3b5b84e80e09cceba581e4decb958a4896bf" dependencies = [ - "which", + "pathdiff", "winapi", ] @@ -884,7 +884,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", "smallvec", "winapi", ] @@ -895,6 +895,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cacbb3c4ff353b534a67fb8d7524d00229da4cb1dc8c79f4db96e375ab5b619" +[[package]] +name = "pathdiff" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877630b3de15c0b64cc52f659345724fbf6bdad9bd9566699fc53688f3c34a34" + [[package]] name = "percent-encoding" version = "2.1.0" @@ -927,9 +933,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" dependencies = [ "unicode-xid", ] @@ -1032,9 +1038,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] @@ -1046,7 +1052,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.3", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", ] [[package]] @@ -1209,18 +1215,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.126" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" dependencies = [ "proc-macro2", "quote", @@ -1229,9 +1235,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" dependencies = [ "itoa", "ryu", @@ -1253,13 +1259,23 @@ dependencies = [ [[package]] name = "signal-hook" -version = "0.1.17" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729" +checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fd5867f1c4f2c5be079aee7a2adf1152ebb04a4bc4d341f504b7dece607ed4" dependencies = [ "libc", "mio", - "signal-hook-registry", + "signal-hook", ] [[package]] @@ -1314,15 +1330,15 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", @@ -1338,7 +1354,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "rand 0.8.4", - "redox_syscall 0.2.9", + "redox_syscall 0.2.10", "remove_dir_all", "winapi", ] @@ -1354,7 +1370,7 @@ dependencies = [ [[package]] name = "termscp" -version = "0.6.0" +version = "0.6.1" dependencies = [ "argh", "bitflags", @@ -1383,6 +1399,7 @@ dependencies = [ "textwrap", "thiserror", "toml", + "tui-realm-stdlib", "tuirealm", "ureq", "users", @@ -1445,9 +1462,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" dependencies = [ "tinyvec_macros", ] @@ -1469,9 +1486,9 @@ dependencies = [ [[package]] name = "tui" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "861d8f3ad314ede6219bcb2ab844054b1de279ee37a9bc38e3d606f9d3fb2a71" +checksum = "39c8ce4e27049eed97cfa363a5048b09d995e209994634a0efc26a14ab6c0c23" dependencies = [ "bitflags", "cassowary", @@ -1481,15 +1498,24 @@ dependencies = [ ] [[package]] -name = "tuirealm" -version = "0.4.3" +name = "tui-realm-stdlib" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcbd06f2aa6a2424aaa245c10e8767fe3f0fee234ac8c144cb15eaf2ee37ce9" +checksum = "6bff91e1cdc741a7487d8cb20ac038e5ba926a0ec97b0f2ea918ac75640b9da5" +dependencies = [ + "textwrap", + "tuirealm", + "unicode-width", +] + +[[package]] +name = "tuirealm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634ad8e6a4b80ef032d31356b55964a995da5d05a9cf3a1bd134bae1ba7c197a" dependencies = [ "crossterm", - "textwrap", "tui", - "unicode-width", ] [[package]] @@ -1500,18 +1526,15 @@ checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085" [[package]] name = "unicode-linebreak" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05a31f45d18a3213b918019f78fe6a73a14ab896807f0aaf5622aa0684749455" +checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f" dependencies = [ "regex", ] @@ -1615,9 +1638,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd" +checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -1625,9 +1648,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900" +checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" dependencies = [ "bumpalo", "lazy_static", @@ -1640,9 +1663,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4" +checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1650,9 +1673,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97" +checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" dependencies = [ "proc-macro2", "quote", @@ -1663,15 +1686,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.74" +version = "0.2.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f" +checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" [[package]] name = "web-sys" -version = "0.3.51" +version = "0.3.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582" +checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" dependencies = [ "js-sys", "wasm-bindgen", @@ -1698,11 +1721,12 @@ dependencies = [ [[package]] name = "which" -version = "4.1.0" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" dependencies = [ "either", + "lazy_static", "libc", ] diff --git a/Cargo.toml b/Cargo.toml index de7ea32..7216e80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" name = "termscp" readme = "README.md" repository = "https://github.com/veeso/termscp" -version = "0.6.0" +version = "0.6.1" [package.metadata.rpm] package = "termscp" @@ -28,11 +28,11 @@ path = "src/main.rs" [dependencies] argh = "0.1.5" -bitflags = "1.2.1" -bytesize = "1.0.1" +bitflags = "1.3.2" +bytesize = "1.1.0" chrono = "0.4.19" content_inspector = "0.2.4" -crossterm = "0.19.0" +crossterm = "0.20" dirs = "3.0.1" edit = "0.1.3" ftp4 = { version = "4.0.2", features = [ "secure" ] } @@ -41,7 +41,7 @@ keyring = { version = "0.10.1", optional = true } lazy_static = "1.4.0" log = "0.4.14" magic-crypt = "3.1.7" -open = "1.7.0" +open = "2.0.1" rand = "0.8.4" regex = "1.5.4" rpassword = "5.0.1" @@ -52,7 +52,8 @@ tempfile = "3.1.0" textwrap = "0.14.2" thiserror = "^1.0.0" toml = "0.5.8" -tuirealm = { version = "0.4.3", features = [ "with-components" ] } +tui-realm-stdlib = "0.6.0" +tuirealm = "0.6.0" ureq = { version = "2.1.0", features = [ "json" ] } whoami = "1.1.1" wildmatch = "2.0.0" diff --git a/README.md b/README.md index f69dcf6..341ef9b 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,9 @@

Developed by @veeso

-

Current version: 0.6.0 (23/07/2021)

+

Current version: 0.6.1 (FIXME: 23/07/2021)

-[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.6.0-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) +[![License: MIT](https://img.shields.io/badge/License-MIT-teal.svg)](https://opensource.org/licenses/MIT) [![Stars](https://img.shields.io/github/stars/veeso/termscp.svg)](https://github.com/veeso/termscp) [![Downloads](https://img.shields.io/crates/d/termscp.svg)](https://crates.io/crates/termscp) [![Crates.io](https://img.shields.io/badge/crates.io-v0.6.1-orange.svg)](https://crates.io/crates/termscp) [![Docs](https://docs.rs/termscp/badge.svg)](https://docs.rs/termscp) [![Linux](https://github.com/veeso/termscp/workflows/Linux/badge.svg)](https://github.com/veeso/termscp/actions) [![MacOs](https://github.com/veeso/termscp/workflows/MacOS/badge.svg)](https://github.com/veeso/termscp/actions) [![Windows](https://github.com/veeso/termscp/workflows/Windows/badge.svg)](https://github.com/veeso/termscp/actions) [![FreeBSD](https://github.com/veeso/termscp/workflows/FreeBSD/badge.svg)](https://github.com/veeso/termscp/actions) [![Coverage Status](https://coveralls.io/repos/github/veeso/termscp/badge.svg)](https://coveralls.io/github/veeso/termscp) @@ -122,13 +122,14 @@ Major termscp releases will now be seasonal, so expect 4 major updates during th Planned for *🍁 Autumn update 🍇*: -- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration. - **Self-update ⬇️**: In order to increase users updating termscp, I want to provide the possibility to update termscp directly from application, when a new update is available. - **AWS S3 support 🪣**: I'll use `rust-s3` library to implement this. This is really big **Maybe** for the autumn update and might be moved to the Winter update. +- **Prompt before replacing files ☢️**: Possibility to configure whether a prompt should be displayed before replacing files. Planned for *❄️ Winter update ⛄*: - **SMB Support 🎉**: This will require a long time to be implemented, since I'm currently working on a Rust native SMB library, since I don't want to add new C-bindings. ~~Fear the 🦚~~ +- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration. Along to new features, termscp developments is now focused on UX and performance improvements, so if you have any suggestion, feel free to open an issue. diff --git a/dist/pkgs/freebsd/manifest b/dist/pkgs/freebsd/manifest index a63fcb2..c4b0d18 100644 --- a/dist/pkgs/freebsd/manifest +++ b/dist/pkgs/freebsd/manifest @@ -1,5 +1,5 @@ name: "termscp" -version: 0.5.1 +version: 0.6.1 origin: veeso/termscp comment: "A feature rich terminal UI file transfer and explorer with support for SCP/SFTP/FTP" desc: < &ft_params, + Some(ft_params) => ft_params, None => { error!("Failed to start FileTransferActivity: file transfer params is None"); return None; diff --git a/src/config/serialization.rs b/src/config/serialization.rs index fa8db24..eacd88f 100644 --- a/src/config/serialization.rs +++ b/src/config/serialization.rs @@ -44,13 +44,13 @@ pub struct SerializerError { #[derive(Error, Debug)] pub enum SerializerErrorKind { #[error("Operation failed")] - GenericError, + Generic, #[error("IO error")] - IoError, + Io, #[error("Serialization error")] - SerializationError, + Serialization, #[error("Syntax error")] - SyntaxError, + Syntax, } impl SerializerError { @@ -92,7 +92,7 @@ where Ok(dt) => dt, Err(err) => { return Err(SerializerError::new_ex( - SerializerErrorKind::SerializationError, + SerializerErrorKind::Serialization, err.to_string(), )) } @@ -102,7 +102,7 @@ where match writable.write_all(data.as_bytes()) { Ok(_) => Ok(()), Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )), } @@ -119,7 +119,7 @@ where let mut data: String = String::new(); if let Err(err) = readable.read_to_string(&mut data) { return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )); } @@ -131,7 +131,7 @@ where Ok(deserialized) } Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::SyntaxError, + SerializerErrorKind::Syntax, err.to_string(), )), } @@ -154,11 +154,11 @@ mod tests { #[test] fn test_config_serialization_errors() { - let error: SerializerError = SerializerError::new(SerializerErrorKind::SyntaxError); + let error: SerializerError = SerializerError::new(SerializerErrorKind::Syntax); assert!(error.msg.is_none()); assert_eq!(format!("{}", error), String::from("Syntax error")); let error: SerializerError = - SerializerError::new_ex(SerializerErrorKind::SyntaxError, String::from("bad syntax")); + SerializerError::new_ex(SerializerErrorKind::Syntax, String::from("bad syntax")); assert!(error.msg.is_some()); assert_eq!( format!("{}", error), @@ -166,20 +166,17 @@ mod tests { ); // Fmt assert_eq!( - format!( - "{}", - SerializerError::new(SerializerErrorKind::GenericError) - ), + format!("{}", SerializerError::new(SerializerErrorKind::Generic)), String::from("Operation failed") ); assert_eq!( - format!("{}", SerializerError::new(SerializerErrorKind::IoError)), + format!("{}", SerializerError::new(SerializerErrorKind::Io)), String::from("IO error") ); assert_eq!( format!( "{}", - SerializerError::new(SerializerErrorKind::SerializationError) + SerializerError::new(SerializerErrorKind::Serialization) ), String::from("Serialization error") ); diff --git a/src/filetransfer/ftp_transfer.rs b/src/filetransfer/ftp_transfer.rs index 20088c7..829da89 100644 --- a/src/filetransfer/ftp_transfer.rs +++ b/src/filetransfer/ftp_transfer.rs @@ -189,7 +189,7 @@ impl FtpFileTransfer { FsEntry::Directory(FsDirectory { name: p .file_name() - .unwrap_or(&std::ffi::OsStr::new("")) + .unwrap_or_else(|| std::ffi::OsStr::new("")) .to_string_lossy() .to_string(), abs_path: p.clone(), @@ -206,7 +206,7 @@ impl FtpFileTransfer { false => FsEntry::File(FsFile { name: p .file_name() - .unwrap_or(&std::ffi::OsStr::new("")) + .unwrap_or_else(|| std::ffi::OsStr::new("")) .to_string_lossy() .to_string(), abs_path: p.clone(), @@ -659,7 +659,7 @@ impl FileTransfer for FtpFileTransfer { // Remove recursively files debug!("Removing {} entries from directory...", files.len()); for file in files.iter() { - if let Err(err) = self.remove(&file) { + if let Err(err) = self.remove(file) { return Err(FileTransferError::new_ex( FileTransferErrorType::PexError, err.to_string(), diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index fc5e7f6..79d4a7c 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -44,7 +44,7 @@ pub use params::FileTransferParams; /// /// This enum defines the different transfer protocol available in termscp -#[derive(PartialEq, std::fmt::Debug, std::clone::Clone, Copy)] +#[derive(PartialEq, Debug, std::clone::Clone, Copy)] pub enum FileTransferProtocol { Sftp, Scp, @@ -54,7 +54,7 @@ pub enum FileTransferProtocol { /// ## FileTransferError /// /// FileTransferError defines the possible errors available for a file transfer -#[derive(std::fmt::Debug)] +#[derive(Debug)] pub struct FileTransferError { code: FileTransferErrorType, msg: Option, @@ -84,6 +84,8 @@ pub enum FileTransferErrorType { SslError, #[error("Could not stat directory")] DirStatFailed, + #[error("Directory already exists")] + DirectoryAlreadyExists, #[error("Failed to create file")] FileCreateDenied, #[error("No such file or directory")] @@ -180,7 +182,7 @@ pub trait FileTransfer { /// ### mkdir /// /// Make directory - /// You must return error in case the directory already exists + /// It MUSTN'T return error in case the directory already exists fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError>; /// ### remove diff --git a/src/filetransfer/scp_transfer.rs b/src/filetransfer/scp_transfer.rs index dbeeca1..e08c0d0 100644 --- a/src/filetransfer/scp_transfer.rs +++ b/src/filetransfer/scp_transfer.rs @@ -169,7 +169,7 @@ impl ScpFileTransfer { // Get symlink; PATH mustn't be equal to filename let symlink: Option> = match symlink_path { None => None, - Some(p) => match p.file_name().unwrap_or(&std::ffi::OsStr::new("")) + Some(p) => match p.file_name().unwrap_or_else(|| std::ffi::OsStr::new("")) == file_name.as_str() { // If name is equal, don't stat path; otherwise it would get stuck @@ -339,7 +339,7 @@ impl FileTransfer for ScpFileTransfer { // Try addresses for socket_addr in socket_addresses.iter() { debug!("Trying socket address {}", socket_addr); - match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) { + match TcpStream::connect_timeout(socket_addr, Duration::from_secs(30)) { Ok(stream) => { debug!("{} succeded", socket_addr); tcp = Some(stream); @@ -643,13 +643,20 @@ impl FileTransfer for ScpFileTransfer { /// ### mkdir /// /// Make directory - /// You must return error in case the directory already exists + /// It MUSTN'T return error in case the directory already exists fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError> { match self.is_connected() { true => { let dir: PathBuf = Self::resolve(dir); info!("Making directory {}", dir.display()); let p: PathBuf = self.wrkdir.clone(); + // If directory already exists, return Err + if let Ok(_) = self.stat(dir.as_path()) { + error!("Directory {} already exists", dir.display()); + return Err(FileTransferError::new( + FileTransferErrorType::DirectoryAlreadyExists, + )); + } // Mkdir dir && echo 0 match self.perform_shell_cmd_with_path( p.as_path(), diff --git a/src/filetransfer/sftp_transfer.rs b/src/filetransfer/sftp_transfer.rs index 54dcf51..b067ee3 100644 --- a/src/filetransfer/sftp_transfer.rs +++ b/src/filetransfer/sftp_transfer.rs @@ -282,7 +282,7 @@ impl FileTransfer for SftpFileTransfer { // Try addresses for socket_addr in socket_addresses.iter() { debug!("Trying socket address {}", socket_addr); - match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) { + match TcpStream::connect_timeout(socket_addr, Duration::from_secs(30)) { Ok(stream) => { tcp = Some(stream); break; @@ -559,6 +559,13 @@ impl FileTransfer for SftpFileTransfer { Some(sftp) => { // Make directory let path: PathBuf = self.get_abs_path(PathBuf::from(dir).as_path()); + // If directory already exists, return Err + if let Ok(_) = sftp.stat(path.as_path()) { + error!("Directory {} already exists", path.display()); + return Err(FileTransferError::new( + FileTransferErrorType::DirectoryAlreadyExists, + )); + } info!("Making directory {}", path.display()); match sftp.mkdir(path.as_path(), 0o775) { Ok(_) => Ok(()), @@ -602,7 +609,7 @@ impl FileTransfer for SftpFileTransfer { // Get directory files let directory_content: Vec = self.list_dir(d.abs_path.as_path())?; for entry in directory_content.iter() { - if let Err(err) = self.remove(&entry) { + if let Err(err) = self.remove(entry) { return Err(err); } } diff --git a/src/fs/explorer/builder.rs b/src/fs/explorer/builder.rs index 9ba2dce..4bb2185 100644 --- a/src/fs/explorer/builder.rs +++ b/src/fs/explorer/builder.rs @@ -124,7 +124,7 @@ mod tests { let explorer: FileExplorer = FileExplorerBuilder::new().build(); // Verify assert!(!explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES)); - assert_eq!(explorer.file_sorting, FileSorting::ByName); // Default + assert_eq!(explorer.file_sorting, FileSorting::Name); // Default assert_eq!(explorer.group_dirs, None); assert_eq!(explorer.stack_size, 16); } @@ -132,7 +132,7 @@ mod tests { #[test] fn test_fs_explorer_builder_new_all() { let explorer: FileExplorer = FileExplorerBuilder::new() - .with_file_sorting(FileSorting::ByModifyTime) + .with_file_sorting(FileSorting::ModifyTime) .with_group_dirs(Some(GroupDirs::First)) .with_hidden_files(true) .with_stack_size(24) @@ -140,7 +140,7 @@ mod tests { .build(); // Verify assert!(explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES)); - assert_eq!(explorer.file_sorting, FileSorting::ByModifyTime); // Default + assert_eq!(explorer.file_sorting, FileSorting::ModifyTime); // Default assert_eq!(explorer.group_dirs, Some(GroupDirs::First)); assert_eq!(explorer.stack_size, 24); } diff --git a/src/fs/explorer/mod.rs b/src/fs/explorer/mod.rs index 3d33421..27b74f8 100644 --- a/src/fs/explorer/mod.rs +++ b/src/fs/explorer/mod.rs @@ -52,10 +52,10 @@ bitflags! { /// FileSorting defines the criteria for sorting files #[derive(Copy, Clone, PartialEq, std::fmt::Debug)] pub enum FileSorting { - ByName, - ByModifyTime, - ByCreationTime, - BySize, + Name, + ModifyTime, + CreationTime, + Size, } /// ## GroupDirs @@ -87,7 +87,7 @@ impl Default for FileExplorer { wrkdir: PathBuf::from("/"), dirstack: VecDeque::with_capacity(16), stack_size: 16, - file_sorting: FileSorting::ByName, + file_sorting: FileSorting::Name, group_dirs: None, opts: ExplorerOpts::empty(), fmt: Formatter::default(), @@ -237,10 +237,10 @@ impl FileExplorer { fn sort(&mut self) { // Choose sorting method match &self.file_sorting { - FileSorting::ByName => self.sort_files_by_name(), - FileSorting::ByCreationTime => self.sort_files_by_creation_time(), - FileSorting::ByModifyTime => self.sort_files_by_mtime(), - FileSorting::BySize => self.sort_files_by_size(), + FileSorting::Name => self.sort_files_by_name(), + FileSorting::CreationTime => self.sort_files_by_creation_time(), + FileSorting::ModifyTime => self.sort_files_by_mtime(), + FileSorting::Size => self.sort_files_by_size(), } // Directories first (NOTE: MUST COME AFTER OTHER SORTING) // Group directories if necessary @@ -318,10 +318,10 @@ impl FileExplorer { impl ToString for FileSorting { fn to_string(&self) -> String { String::from(match self { - FileSorting::ByCreationTime => "by_creation_time", - FileSorting::ByModifyTime => "by_mtime", - FileSorting::ByName => "by_name", - FileSorting::BySize => "by_size", + FileSorting::CreationTime => "by_creation_time", + FileSorting::ModifyTime => "by_mtime", + FileSorting::Name => "by_name", + FileSorting::Size => "by_size", }) } } @@ -330,10 +330,10 @@ impl FromStr for FileSorting { type Err = (); fn from_str(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { - "by_creation_time" => Ok(FileSorting::ByCreationTime), - "by_mtime" => Ok(FileSorting::ByModifyTime), - "by_name" => Ok(FileSorting::ByName), - "by_size" => Ok(FileSorting::BySize), + "by_creation_time" => Ok(FileSorting::CreationTime), + "by_mtime" => Ok(FileSorting::ModifyTime), + "by_name" => Ok(FileSorting::Name), + "by_size" => Ok(FileSorting::Size), _ => Err(()), } } @@ -380,8 +380,8 @@ mod tests { assert_eq!(explorer.wrkdir, PathBuf::from("/")); assert_eq!(explorer.stack_size, 16); assert_eq!(explorer.group_dirs, None); - assert_eq!(explorer.file_sorting, FileSorting::ByName); - assert_eq!(explorer.get_file_sorting(), FileSorting::ByName); + assert_eq!(explorer.file_sorting, FileSorting::Name); + assert_eq!(explorer.get_file_sorting(), FileSorting::Name); } #[test] @@ -459,7 +459,7 @@ mod tests { make_fs_entry("Cargo.lock", false), make_fs_entry("codecov.yml", false), ]); - explorer.sort_by(FileSorting::ByName); + explorer.sort_by(FileSorting::Name); // First entry should be "Cargo.lock" assert_eq!(explorer.files.get(0).unwrap().get_name(), "Cargo.lock"); // Last should be "src/" @@ -475,7 +475,7 @@ mod tests { let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false); // Create files (files are then sorted by name) explorer.set_files(vec![entry1, entry2]); - explorer.sort_by(FileSorting::ByModifyTime); + explorer.sort_by(FileSorting::ModifyTime); // First entry should be "CODE_OF_CONDUCT.md" assert_eq!( explorer.files.get(0).unwrap().get_name(), @@ -494,7 +494,7 @@ mod tests { let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false); // Create files (files are then sorted by name) explorer.set_files(vec![entry1, entry2]); - explorer.sort_by(FileSorting::ByCreationTime); + explorer.sort_by(FileSorting::CreationTime); // First entry should be "CODE_OF_CONDUCT.md" assert_eq!( explorer.files.get(0).unwrap().get_name(), @@ -513,7 +513,7 @@ mod tests { make_fs_entry("src/", true), make_fs_entry_with_size("CONTRIBUTING.md", false, 256), ]); - explorer.sort_by(FileSorting::BySize); + explorer.sort_by(FileSorting::Size); // Directory has size 4096 assert_eq!(explorer.files.get(0).unwrap().get_name(), "src/"); assert_eq!(explorer.files.get(1).unwrap().get_name(), "README.md"); @@ -536,7 +536,7 @@ mod tests { make_fs_entry("Cargo.lock", false), make_fs_entry("codecov.yml", false), ]); - explorer.sort_by(FileSorting::ByName); + explorer.sort_by(FileSorting::Name); explorer.group_dirs_by(Some(GroupDirs::First)); // First entry should be "docs" assert_eq!(explorer.files.get(0).unwrap().get_name(), "docs/"); @@ -563,7 +563,7 @@ mod tests { make_fs_entry("Cargo.lock", false), make_fs_entry("codecov.yml", false), ]); - explorer.sort_by(FileSorting::ByName); + explorer.sort_by(FileSorting::Name); explorer.group_dirs_by(Some(GroupDirs::Last)); // Last entry should be "src" assert_eq!(explorer.files.get(8).unwrap().get_name(), "docs/"); @@ -614,25 +614,25 @@ mod tests { #[test] fn test_fs_explorer_to_string_from_str_traits() { // File Sorting - assert_eq!(FileSorting::ByCreationTime.to_string(), "by_creation_time"); - assert_eq!(FileSorting::ByModifyTime.to_string(), "by_mtime"); - assert_eq!(FileSorting::ByName.to_string(), "by_name"); - assert_eq!(FileSorting::BySize.to_string(), "by_size"); + assert_eq!(FileSorting::CreationTime.to_string(), "by_creation_time"); + assert_eq!(FileSorting::ModifyTime.to_string(), "by_mtime"); + assert_eq!(FileSorting::Name.to_string(), "by_name"); + assert_eq!(FileSorting::Size.to_string(), "by_size"); assert_eq!( FileSorting::from_str("by_creation_time").ok().unwrap(), - FileSorting::ByCreationTime + FileSorting::CreationTime ); assert_eq!( FileSorting::from_str("by_mtime").ok().unwrap(), - FileSorting::ByModifyTime + FileSorting::ModifyTime ); assert_eq!( FileSorting::from_str("by_name").ok().unwrap(), - FileSorting::ByName + FileSorting::Name ); assert_eq!( FileSorting::from_str("by_size").ok().unwrap(), - FileSorting::BySize + FileSorting::Size ); assert!(FileSorting::from_str("omar").is_err()); // Group dirs diff --git a/src/lib.rs b/src/lib.rs index b6e3d0e..48bed11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,7 @@ extern crate regex; extern crate ssh2; extern crate tempfile; extern crate textwrap; +extern crate tui_realm_stdlib; extern crate tuirealm; extern crate ureq; #[cfg(target_family = "unix")] diff --git a/src/system/bookmarks_client.rs b/src/system/bookmarks_client.rs index ed40f65..4fc4805 100644 --- a/src/system/bookmarks_client.rs +++ b/src/system/bookmarks_client.rs @@ -115,7 +115,7 @@ impl BookmarksClient { if let Err(e) = key_storage.set_key(service_id, key.as_str()) { error!("Failed to set new key into storage: {}", e); return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, format!("Could not write key to storage: {}", e), )); } @@ -125,7 +125,7 @@ impl BookmarksClient { _ => { error!("Failed to get key from storage: {}", e); return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, format!("Could not get key from storage: {}", e), )); } @@ -328,7 +328,7 @@ impl BookmarksClient { Err(err) => { error!("Failed to write bookmarks: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -358,7 +358,7 @@ impl BookmarksClient { Err(err) => { error!("Failed to read bookmarks: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -407,7 +407,7 @@ impl BookmarksClient { match crypto::aes128_b64_decrypt(self.key.as_str(), secret) { Ok(txt) => Ok(txt), Err(err) => Err(SerializerError::new_ex( - SerializerErrorKind::SyntaxError, + SerializerErrorKind::Syntax, err.to_string(), )), } diff --git a/src/system/config_client.rs b/src/system/config_client.rs index 12a77d3..2a484e2 100644 --- a/src/system/config_client.rs +++ b/src/system/config_client.rs @@ -76,7 +76,7 @@ impl ConfigClient { if let Err(err) = create_dir(ssh_key_dir) { error!("Failed to create SSH key dir: {}", err); return Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, format!( "Could not create SSH key directory \"{}\": {}", ssh_key_dir.display(), @@ -252,7 +252,7 @@ impl ConfigClient { ) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be saved, since in degraded mode"), )); } @@ -291,7 +291,7 @@ impl ConfigClient { pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be saved, since in degraded mode"), )); } @@ -351,7 +351,7 @@ impl ConfigClient { pub fn write_config(&self) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be saved, since in degraded mode"), )); } @@ -366,7 +366,7 @@ impl ConfigClient { Err(err) => { error!("Failed to write configuration file: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -379,7 +379,7 @@ impl ConfigClient { pub fn read_config(&mut self) -> Result<(), SerializerError> { if self.degraded { return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Configuration won't be loaded, since in degraded mode"), )); } @@ -401,7 +401,7 @@ impl ConfigClient { Err(err) => { error!("Failed to read configuration: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -432,7 +432,7 @@ impl ConfigClient { /// Make serializer error from `std::io::Error` fn make_io_err(err: std::io::Error) -> Result<(), SerializerError> { Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } diff --git a/src/system/theme_provider.rs b/src/system/theme_provider.rs index d878eb4..643687b 100644 --- a/src/system/theme_provider.rs +++ b/src/system/theme_provider.rs @@ -116,7 +116,7 @@ impl ThemeProvider { warn!("Configuration won't be loaded, since degraded; reloading default..."); self.theme = Theme::default(); return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Can't access theme file"), )); } @@ -139,7 +139,7 @@ impl ThemeProvider { Err(err) => { error!("Failed to read theme: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } @@ -153,7 +153,7 @@ impl ThemeProvider { if self.degraded { warn!("Configuration won't be saved, since in degraded mode"); return Err(SerializerError::new_ex( - SerializerErrorKind::GenericError, + SerializerErrorKind::Generic, String::from("Can't access theme file"), )); } @@ -169,7 +169,7 @@ impl ThemeProvider { Err(err) => { error!("Failed to write theme: {}", err); Err(SerializerError::new_ex( - SerializerErrorKind::IoError, + SerializerErrorKind::Io, err.to_string(), )) } diff --git a/src/ui/activities/auth/bookmarks.rs b/src/ui/activities/auth/bookmarks.rs index 44c4499..dcd9946 100644 --- a/src/ui/activities/auth/bookmarks.rs +++ b/src/ui/activities/auth/bookmarks.rs @@ -32,7 +32,7 @@ use crate::system::environment; // Ext use std::path::PathBuf; -use tuirealm::components::{input::InputPropsBuilder, radio::RadioPropsBuilder}; +use tui_realm_stdlib::{input::InputPropsBuilder, radio::RadioPropsBuilder}; use tuirealm::{Payload, PropsBuilder, Value}; impl AuthActivity { @@ -44,7 +44,7 @@ impl AuthActivity { // Iterate over kyes let name: Option<&String> = self.bookmarks_list.get(idx); if let Some(name) = name { - bookmarks_cli.del_bookmark(&name); + bookmarks_cli.del_bookmark(name); // Write bookmarks self.write_bookmarks(); } @@ -60,7 +60,7 @@ impl AuthActivity { if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() { // Iterate over bookmarks if let Some(key) = self.bookmarks_list.get(idx) { - if let Some(bookmark) = bookmarks_cli.get_bookmark(&key) { + if let Some(bookmark) = bookmarks_cli.get_bookmark(key) { // Load parameters into components self.load_bookmark_into_gui( bookmark.0, bookmark.1, bookmark.2, bookmark.3, bookmark.4, @@ -104,7 +104,7 @@ impl AuthActivity { if let Some(client) = self.bookmarks_client.as_mut() { let name: Option<&String> = self.recents_list.get(idx); if let Some(name) = name { - client.del_recent(&name); + client.del_recent(name); // Write bookmarks self.write_bookmarks(); } diff --git a/src/ui/activities/auth/update.rs b/src/ui/activities/auth/update.rs index 9c6822a..dd2275d 100644 --- a/src/ui/activities/auth/update.rs +++ b/src/ui/activities/auth/update.rs @@ -35,7 +35,7 @@ use super::{ COMPONENT_TEXT_HELP, COMPONENT_TEXT_NEW_VERSION_NOTES, COMPONENT_TEXT_SIZE_ERR, }; use crate::ui::keymap::*; -use tuirealm::components::InputPropsBuilder; +use tui_realm_stdlib::InputPropsBuilder; use tuirealm::{Msg, Payload, PropsBuilder, Update, Value}; // -- update @@ -52,53 +52,53 @@ impl Update for AuthActivity { None => None, // Exit after None Some(msg) => match msg { // Focus ( DOWN ) - (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_ADDR); None } - (COMPONENT_INPUT_ADDR, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_PORT); None } - (COMPONENT_INPUT_PORT, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_USERNAME); None } - (COMPONENT_INPUT_USERNAME, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_INPUT_PASSWORD); None } - (COMPONENT_INPUT_PASSWORD, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_DOWN => { // Give focus to port self.view.active(COMPONENT_RADIO_PROTOCOL); None } // Focus ( UP ) - (COMPONENT_INPUT_PASSWORD, &MSG_KEY_UP) => { + (COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_USERNAME); None } - (COMPONENT_INPUT_USERNAME, &MSG_KEY_UP) => { + (COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_PORT); None } - (COMPONENT_INPUT_PORT, &MSG_KEY_UP) => { + (COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_ADDR); None } - (COMPONENT_INPUT_ADDR, &MSG_KEY_UP) => { + (COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_RADIO_PROTOCOL); None } - (COMPONENT_RADIO_PROTOCOL, &MSG_KEY_UP) => { + (COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_UP => { // Give focus to port self.view.active(COMPONENT_INPUT_PASSWORD); None @@ -118,25 +118,25 @@ impl Update for AuthActivity { } // Bookmarks commands // / - (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_RIGHT) => { + (COMPONENT_BOOKMARKS_LIST, key) if key == &MSG_KEY_RIGHT => { // Give focus to recents self.view.active(COMPONENT_RECENTS_LIST); None } - (COMPONENT_RECENTS_LIST, &MSG_KEY_LEFT) => { + (COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_LEFT => { // Give focus to bookmarks self.view.active(COMPONENT_BOOKMARKS_LIST); None } // - (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_DEL) - | (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_CHAR_E) => { + (COMPONENT_BOOKMARKS_LIST, key) + if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E => + { // Show delete popup self.mount_bookmark_del_dialog(); None } - (COMPONENT_RECENTS_LIST, &MSG_KEY_DEL) - | (COMPONENT_RECENTS_LIST, &MSG_KEY_CHAR_E) => { + (COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E => { // Show delete popup self.mount_recent_del_dialog(); None @@ -203,67 +203,68 @@ impl Update for AuthActivity { } } // hide tab - (COMPONENT_RADIO_BOOKMARK_DEL_RECENT, &MSG_KEY_ESC) => { + (COMPONENT_RADIO_BOOKMARK_DEL_RECENT, key) if key == &MSG_KEY_ESC => { self.umount_recent_del_dialog(); None } (COMPONENT_RADIO_BOOKMARK_DEL_RECENT, _) => None, - (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, &MSG_KEY_ESC) => { + (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, key) if key == &MSG_KEY_ESC => { self.umount_bookmark_del_dialog(); None } (COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, _) => None, // Error message - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => { // Umount text error self.umount_error(); None } (COMPONENT_TEXT_ERROR, _) => None, - (COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ESC) - | (COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_NEW_VERSION_NOTES, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount release notes self.umount_release_notes(); None } (COMPONENT_TEXT_NEW_VERSION_NOTES, _) => None, // Help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } // Release notes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Show release notes self.mount_release_notes(); None } - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => { // Hide text help self.umount_help(); None } (COMPONENT_TEXT_HELP, _) => None, // Enter setup - (_, &MSG_KEY_CTRL_C) => { + (_, key) if key == &MSG_KEY_CTRL_C => { self.exit_reason = Some(super::ExitReason::EnterSetup); None } // Save bookmark; show popup - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show popup self.mount_bookmark_save_dialog(); // Give focus to bookmark name self.view.active(COMPONENT_INPUT_BOOKMARK_NAME); None } - (COMPONENT_INPUT_BOOKMARK_NAME, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_BOOKMARK_NAME, key) if key == &MSG_KEY_DOWN => { // Give focus to pwd self.view.active(COMPONENT_RADIO_BOOKMARK_SAVE_PWD); None } - (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, &MSG_KEY_UP) => { + (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key) if key == &MSG_KEY_UP => { // Give focus to pwd self.view.active(COMPONENT_INPUT_BOOKMARK_NAME); None @@ -291,8 +292,9 @@ impl Update for AuthActivity { self.view_bookmarks() } // Hide save bookmark - (COMPONENT_INPUT_BOOKMARK_NAME, &MSG_KEY_ESC) - | (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_BOOKMARK_NAME, key) | (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key) + if key == &MSG_KEY_ESC => + { // Umount popup self.umount_bookmark_save_dialog(); None @@ -307,45 +309,30 @@ impl Update for AuthActivity { self.umount_quit(); None } - (COMPONENT_RADIO_QUIT, &MSG_KEY_ESC) => { + (COMPONENT_RADIO_QUIT, key) if key == &MSG_KEY_ESC => { self.umount_quit(); None } // -- text size error; block everything (COMPONENT_TEXT_SIZE_ERR, _) => None, // bookmarks - (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_TAB) - | (COMPONENT_RECENTS_LIST, &MSG_KEY_TAB) => { + (COMPONENT_BOOKMARKS_LIST, key) | (COMPONENT_RECENTS_LIST, key) + if key == &MSG_KEY_TAB => + { // Give focus to address self.view.active(COMPONENT_INPUT_ADDR); None } // Any , go to bookmarks - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { self.view.active(COMPONENT_BOOKMARKS_LIST); None } // On submit on any unhandled (connect) - (_, Msg::OnSubmit(_)) | (_, &MSG_KEY_ENTER) => { - // Validate fields - match self.collect_host_params() { - Err(err) => { - // mount error - self.mount_error(err); - } - Ok(params) => { - self.save_recent(); - // Set file transfer params to context - self.context_mut().set_ftparams(params); - // Set exit reason - self.exit_reason = Some(super::ExitReason::Connect); - } - } - // Return None - None - } + (_, Msg::OnSubmit(_)) => self.on_unhandled_submit(), + (_, key) if key == &MSG_KEY_ENTER => self.on_unhandled_submit(), // => Quit - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.mount_quit(); None } @@ -367,4 +354,23 @@ impl AuthActivity { } } } + + fn on_unhandled_submit(&mut self) -> Option<(String, Msg)> { + // Validate fields + match self.collect_host_params() { + Err(err) => { + // mount error + self.mount_error(err); + } + Ok(params) => { + self.save_recent(); + // Set file transfer params to context + self.context_mut().set_ftparams(params); + // Set exit reason + self.exit_reason = Some(super::ExitReason::Connect); + } + } + // Return None + None + } } diff --git a/src/ui/activities/auth/view.rs b/src/ui/activities/auth/view.rs index 8ec4763..ba3adc8 100644 --- a/src/ui/activities/auth/view.rs +++ b/src/ui/activities/auth/view.rs @@ -27,17 +27,15 @@ */ // Locals use super::{AuthActivity, Context, FileTransferProtocol}; -use crate::ui::components::{ - bookmark_list::{BookmarkList, BookmarkListPropsBuilder}, - msgbox::{MsgBox, MsgBoxPropsBuilder}, -}; +use crate::ui::components::bookmark_list::{BookmarkList, BookmarkListPropsBuilder}; use crate::utils::ui::draw_area_in; // Ext -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, label::{Label, LabelPropsBuilder}, + list::{List, ListPropsBuilder}, + paragraph::{Paragraph, ParagraphPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, textarea::{Textarea, TextareaPropsBuilder}, }; @@ -47,7 +45,7 @@ use tuirealm::tui::{ widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{InputType, PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}, + props::{Alignment, InputType, PropsBuilder, TableBuilder, TextSpan}, Msg, Payload, Value, }; @@ -91,19 +89,11 @@ impl AuthActivity { Box::new(Span::new( SpanPropsBuilder::default() .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - TextSpanBuilder::new(" to show keybindings; ") - .bold() - .build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - TextSpanBuilder::new(" to enter setup").bold().build(), + TextSpan::new("Press ").bold(), + TextSpan::new("").bold().fg(key_color), + TextSpan::new(" to show keybindings; ").bold(), + TextSpan::new("").bold().fg(key_color), + TextSpan::new(" to enter setup").bold(), ]) .build(), )), @@ -118,16 +108,10 @@ impl AuthActivity { .with_color(protocol_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, protocol_color) - .with_options( - Some(String::from("Protocol")), - vec![ - String::from("SFTP"), - String::from("SCP"), - String::from("FTP"), - String::from("FTPS"), - ], - ) + .with_title("Protocol", Alignment::Left) + .with_options(&["SFTP", "SCP", "FTP", "FTPS"]) .with_value(Self::protocol_enum_to_opt(default_protocol)) + .rewind(true) .build(), )), ); @@ -138,7 +122,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(addr_color) .with_borders(Borders::ALL, BorderType::Rounded, addr_color) - .with_label(String::from("Remote host")) + .with_label("Remote host", Alignment::Left) .build(), )), ); @@ -149,7 +133,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(port_color) .with_borders(Borders::ALL, BorderType::Rounded, port_color) - .with_label(String::from("Port number")) + .with_label("Port number", Alignment::Left) .with_input(InputType::Number) .with_input_len(5) .with_value(Self::get_default_port_for_protocol(default_protocol).to_string()) @@ -163,7 +147,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(username_color) .with_borders(Borders::ALL, BorderType::Rounded, username_color) - .with_label(String::from("Username")) + .with_label("Username", Alignment::Left) .build(), )), ); @@ -174,7 +158,7 @@ impl AuthActivity { InputPropsBuilder::default() .with_foreground(password_color) .with_borders(Borders::ALL, BorderType::Rounded, password_color) - .with_label(String::from("Password")) + .with_label("Password", Alignment::Left) .with_input(InputType::Password) .build(), )), @@ -193,7 +177,7 @@ impl AuthActivity { .with_foreground(Color::Yellow) .with_spans(vec![ TextSpan::from("termscp "), - TextSpanBuilder::new(version.as_str()).underlined().bold().build(), + TextSpan::new(version.as_str()).underlined().bold(), TextSpan::from(" is NOW available! Get it from ; view release notes with "), ]) .build(), @@ -208,7 +192,7 @@ impl AuthActivity { .with_background(bookmarks_color) .with_foreground(Color::Black) .with_borders(Borders::ALL, BorderType::Plain, bookmarks_color) - .with_bookmarks(Some(String::from("Bookmarks")), vec![]) + .with_title("Bookmarks", Alignment::Left) .build(), )), ); @@ -220,7 +204,7 @@ impl AuthActivity { .with_background(recents_color) .with_foreground(Color::Black) .with_borders(Borders::ALL, BorderType::Plain, recents_color) - .with_bookmarks(Some(String::from("Recent connections")), vec![]) + .with_title("Recent connections", Alignment::Left) .build(), )), ); @@ -426,7 +410,7 @@ impl AuthActivity { let msg = self.view.update( super::COMPONENT_BOOKMARKS_LIST, BookmarkListPropsBuilder::from(props) - .with_bookmarks(Some(String::from("Bookmarks")), bookmarks) + .with_bookmarks(bookmarks) .build(), ); msg @@ -464,7 +448,7 @@ impl AuthActivity { let msg = self.view.update( super::COMPONENT_RECENTS_LIST, BookmarkListPropsBuilder::from(props) - .with_bookmarks(Some(String::from("Recent connections")), bookmarks) + .with_bookmarks(bookmarks) .build(), ); msg @@ -482,12 +466,13 @@ impl AuthActivity { let err_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_ERROR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(err_color) .with_borders(Borders::ALL, BorderType::Thick, err_color) .bold() - .with_texts(None, vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]) .build(), )), ); @@ -510,17 +495,15 @@ impl AuthActivity { let err_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_SIZE_ERR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(err_color) .with_borders(Borders::ALL, BorderType::Thick, err_color) .bold() - .with_texts( - None, - vec![TextSpan::from( - "termscp requires at least 24 lines of height to run", - )], - ) + .with_texts(vec![TextSpan::from( + "termscp requires at least 24 lines of height to run", + )]) + .with_text_alignment(Alignment::Center) .build(), )), ); @@ -548,10 +531,9 @@ impl AuthActivity { .with_color(quit_color) .with_borders(Borders::ALL, BorderType::Rounded, quit_color) .with_inverted_color(Color::Black) - .with_options( - Some(String::from("Quit termscp?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Quit termscp?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -577,11 +559,10 @@ impl AuthActivity { .with_color(warn_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, warn_color) - .with_options( - Some(String::from("Delete bookmark?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete bookmark?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) + .rewind(true) .build(), )), ); @@ -610,11 +591,10 @@ impl AuthActivity { .with_color(warn_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, warn_color) - .with_options( - Some(String::from("Delete bookmark?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete bookmark?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) + .rewind(true) .build(), )), ); @@ -640,7 +620,7 @@ impl AuthActivity { Box::new(Input::new( InputPropsBuilder::default() .with_foreground(save_color) - .with_label(String::from("Save bookmark as…")) + .with_label("Save bookmark as…", Alignment::Center) .with_borders( Borders::TOP | Borders::RIGHT | Borders::LEFT, BorderType::Rounded, @@ -659,10 +639,9 @@ impl AuthActivity { BorderType::Rounded, Color::Reset, ) - .with_options( - Some(String::from("Save password?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Save password?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -685,77 +664,38 @@ impl AuthActivity { let key_color = self.theme().misc_keys; self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Scrolltable::new( - ScrollTablePropsBuilder::default() + Box::new(List::new( + ListPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .with_highlighted_str(Some("?")) .with_max_scroll_step(8) + .scrollable(true) .bold() - .with_table( - Some(String::from("Help")), + .with_title("Help", Alignment::Center) + .with_rows( TableBuilder::default() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Quit termscp")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Switch from form and bookmarks")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Switch bookmark tab")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Move up/down in current tab")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Connect/Load bookmark")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Delete selected bookmark")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Enter setup")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Save bookmark")) .build(), ) @@ -786,7 +726,8 @@ impl AuthActivity { Box::new(Textarea::new( TextareaPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) - .with_texts(Some(String::from("Release notes")), spans) + .with_title("Release notes", Alignment::Center) + .with_texts(spans) .build(), )), ); diff --git a/src/ui/activities/filetransfer/actions/delete.rs b/src/ui/activities/filetransfer/actions/delete.rs index 3d95a19..4c1bba5 100644 --- a/src/ui/activities/filetransfer/actions/delete.rs +++ b/src/ui/activities/filetransfer/actions/delete.rs @@ -72,7 +72,7 @@ impl FileTransferActivity { } pub(crate) fn local_remove_file(&mut self, entry: &FsEntry) { - match self.host.remove(&entry) { + match self.host.remove(entry) { Ok(_) => { // Log self.log( @@ -94,7 +94,7 @@ impl FileTransferActivity { } pub(crate) fn remote_remove_file(&mut self, entry: &FsEntry) { - match self.client.remove(&entry) { + match self.client.remove(entry) { Ok(_) => { self.log( LogLevel::Info, diff --git a/src/ui/activities/filetransfer/lib/browser.rs b/src/ui/activities/filetransfer/lib/browser.rs index df49198..501ab92 100644 --- a/src/ui/activities/filetransfer/lib/browser.rs +++ b/src/ui/activities/filetransfer/lib/browser.rs @@ -142,7 +142,7 @@ impl Browser { let mut builder: FileExplorerBuilder = FileExplorerBuilder::new(); // Set common keys builder - .with_file_sorting(FileSorting::ByName) + .with_file_sorting(FileSorting::Name) .with_stack_size(16) .with_group_dirs(cli.get_group_dirs()) .with_hidden_files(cli.get_show_hidden_files()); @@ -154,7 +154,7 @@ impl Browser { /// Build explorer reading from `ConfigClient`, for found result (has some differences) fn build_found_explorer() -> FileExplorer { FileExplorerBuilder::new() - .with_file_sorting(FileSorting::ByName) + .with_file_sorting(FileSorting::Name) .with_group_dirs(Some(GroupDirs::First)) .with_hidden_files(true) .with_stack_size(0) diff --git a/src/ui/activities/filetransfer/mod.rs b/src/ui/activities/filetransfer/mod.rs index 91da013..3d3a655 100644 --- a/src/ui/activities/filetransfer/mod.rs +++ b/src/ui/activities/filetransfer/mod.rs @@ -228,7 +228,7 @@ impl FileTransferActivity { /// /// Returns config client reference fn config(&self) -> &ConfigClient { - &self.context().config() + self.context().config() } /// ### theme diff --git a/src/ui/activities/filetransfer/session.rs b/src/ui/activities/filetransfer/session.rs index d4e6049..77eee6b 100644 --- a/src/ui/activities/filetransfer/session.rs +++ b/src/ui/activities/filetransfer/session.rs @@ -27,7 +27,7 @@ */ // Locals use super::{FileTransferActivity, LogLevel}; -use crate::filetransfer::FileTransferError; +use crate::filetransfer::{FileTransferError, FileTransferErrorType}; use crate::fs::{FsEntry, FsFile}; use crate::host::HostError; use crate::utils::fmt::fmt_millis; @@ -363,26 +363,33 @@ impl FileTransferActivity { } } FsEntry::Directory(dir) => { - // Check whether should create directory - if self.client.list_dir(remote_path.as_path()).is_err() { - match self.client.mkdir(remote_path.as_path()) { - Ok(_) => { - self.log( - LogLevel::Info, - format!("Created directory \"{}\"", remote_path.display()), - ); - } - Err(err) => { - self.log_and_alert( - LogLevel::Error, - format!( - "Failed to create directory \"{}\": {}", - remote_path.display(), - err - ), - ); - return; - } + // Create directory on remote first + match self.client.mkdir(remote_path.as_path()) { + Ok(_) => { + self.log( + LogLevel::Info, + format!("Created directory \"{}\"", remote_path.display()), + ); + } + Err(err) if err.kind() == FileTransferErrorType::DirectoryAlreadyExists => { + self.log( + LogLevel::Info, + format!( + "Directory \"{}\" already exists on remote", + remote_path.display() + ), + ); + } + Err(err) => { + self.log_and_alert( + LogLevel::Error, + format!( + "Failed to create directory \"{}\": {}", + remote_path.display(), + err + ), + ); + return; } } // Get files in dir @@ -395,7 +402,7 @@ impl FileTransferActivity { break; } // Send entry; name is always None after first call - self.filetransfer_send_recurse(&entry, remote_path.as_path(), None); + self.filetransfer_send_recurse(entry, remote_path.as_path(), None); } } Err(err) => { @@ -729,7 +736,7 @@ impl FileTransferActivity { // Receive entry; name is always None after first call // Local path becomes local_dir_path self.filetransfer_recv_recurse( - &entry, + entry, local_dir_path.as_path(), None, ); diff --git a/src/ui/activities/filetransfer/update.rs b/src/ui/activities/filetransfer/update.rs index 723a0ff..3dc40fc 100644 --- a/src/ui/activities/filetransfer/update.rs +++ b/src/ui/activities/filetransfer/update.rs @@ -42,9 +42,9 @@ use crate::ui::components::{file_list::FileListPropsBuilder, logbox::LogboxProps use crate::ui::keymap::*; use crate::utils::fmt::fmt_path_elide_ex; // externals +use tui_realm_stdlib::progress_bar::ProgressBarPropsBuilder; use tuirealm::{ - components::progress_bar::ProgressBarPropsBuilder, - props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}, + props::{Alignment, PropsBuilder, TableBuilder, TextSpan}, tui::style::Color, Msg, Payload, Update, Value, }; @@ -63,13 +63,13 @@ impl Update for FileTransferActivity { None => None, // Exit after None Some(msg) => match msg { // -- local tab - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_RIGHT) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_RIGHT => { // Change tab self.view.active(COMPONENT_EXPLORER_REMOTE); self.browser.change_tab(FileExplorerTab::Remote); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_BACKSPACE) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_BACKSPACE => { // Go to previous directory self.action_go_to_previous_local_dir(false); if self.browser.sync_browsing { @@ -98,11 +98,11 @@ impl Update for FileTransferActivity { None } } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_SPACE) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_SPACE => { self.action_local_send(); self.update_remote_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_A => { // Toggle hidden files self.local_mut().toggle_hidden_files(); // Update status bar @@ -110,24 +110,24 @@ impl Update for FileTransferActivity { // Reload file list component self.update_local_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_I) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_I => { if let SelectedEntry::One(file) = self.get_local_selected_entries() { self.mount_file_info(&file); } None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_L) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_L => { // Reload directory self.reload_local_dir(); // Reload file list component self.update_local_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_O) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_O => { self.action_edit_local_file(); // Reload file list component self.update_local_filelist() } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_U) => { + (COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_U => { self.action_go_to_local_upper_dir(false); if self.browser.sync_browsing { let _ = self.update_remote_filelist(); @@ -136,7 +136,7 @@ impl Update for FileTransferActivity { self.update_local_filelist() } // -- remote tab - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_LEFT) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_LEFT => { // Change tab self.view.active(COMPONENT_EXPLORER_LOCAL); self.browser.change_tab(FileExplorerTab::Local); @@ -162,11 +162,11 @@ impl Update for FileTransferActivity { None } } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_SPACE) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_SPACE => { self.action_remote_recv(); self.update_local_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_BACKSPACE) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_BACKSPACE => { // Go to previous directory self.action_go_to_previous_remote_dir(false); // If sync is enabled update local too @@ -176,7 +176,7 @@ impl Update for FileTransferActivity { // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_A) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_A => { // Toggle hidden files self.remote_mut().toggle_hidden_files(); // Update status bar @@ -184,25 +184,25 @@ impl Update for FileTransferActivity { // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_I) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_I => { if let SelectedEntry::One(file) = self.get_remote_selected_entries() { self.mount_file_info(&file); } None } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_L) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_L => { // Reload directory self.reload_remote_dir(); // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_O) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_O => { // Edit file self.action_edit_remote_file(); // Reload file list component self.update_remote_filelist() } - (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_U) => { + (COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_U => { self.action_go_to_remote_upper_dir(false); if self.browser.sync_browsing { let _ = self.update_local_filelist(); @@ -211,64 +211,78 @@ impl Update for FileTransferActivity { self.update_remote_filelist() } // -- common explorer keys - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_B) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_B) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_B => + { // Show sorting file self.mount_file_sorting(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_C) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_C) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_C => + { self.mount_copy(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_D) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_D) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_D => + { self.mount_mkdir(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_F) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_F) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_F => + { self.mount_find_input(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_G) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_G) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_G => + { self.mount_goto(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_H) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_H) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_H => + { self.mount_help(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_N) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_N) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_N => + { self.mount_newfile(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_Q) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_Q) - | (COMPONENT_LOG_BOX, &MSG_KEY_CHAR_Q) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_LOG_BOX, key) + if key == &MSG_KEY_CHAR_Q => + { self.mount_quit(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_R) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_R) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_R => + { // Mount rename self.mount_rename(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_S) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_S) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_S) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_S => + { // Mount save as self.mount_saveas(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_V) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_V) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_V) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_V => + { // View match self.browser.tab() { FileExplorerTab::Local => self.action_open_local(), @@ -279,44 +293,49 @@ impl Update for FileTransferActivity { } None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_W) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_W) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_W) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_W => + { // Open with self.mount_openwith(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_X) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_X) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_X => + { // Mount exec self.mount_exec(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_Y) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_Y) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_CHAR_Y => + { // Toggle browser sync self.browser.toggle_sync_browsing(); // Update status bar self.refresh_remote_status_bar(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_ESC) - | (COMPONENT_LOG_BOX, &MSG_KEY_ESC) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_LOG_BOX, key) + if key == &MSG_KEY_ESC => + { self.mount_disconnect(); None } - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_DEL) - | (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_E) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_DEL) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_E) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_DEL) - | (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_E) => { + (COMPONENT_EXPLORER_LOCAL, key) + | (COMPONENT_EXPLORER_REMOTE, key) + | (COMPONENT_EXPLORER_FIND, key) + if key == &MSG_KEY_CHAR_E || key == &MSG_KEY_DEL => + { self.mount_radio_delete(); None } // -- find result explorer - (COMPONENT_EXPLORER_FIND, &MSG_KEY_ESC) => { + (COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_ESC => { // Umount find self.umount_find(); // Finalize find @@ -337,7 +356,7 @@ impl Update for FileTransferActivity { _ => None, } } - (COMPONENT_EXPLORER_FIND, &MSG_KEY_SPACE) => { + (COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_SPACE => { // Get entry self.action_find_transfer(None); // Reload files @@ -349,18 +368,19 @@ impl Update for FileTransferActivity { } } // -- switch to log - (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_TAB) - | (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_TAB) => { + (COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key) + if key == &MSG_KEY_TAB => + { self.view.active(COMPONENT_LOG_BOX); // Active log box None } // -- Log box - (COMPONENT_LOG_BOX, &MSG_KEY_TAB) => { + (COMPONENT_LOG_BOX, key) if key == &MSG_KEY_TAB => { self.view.blur(); // Blur log box None } // -- copy popup - (COMPONENT_INPUT_COPY, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_COPY, key) if key == &MSG_KEY_ESC => { self.umount_copy(); None } @@ -383,7 +403,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_COPY, _) => None, // -- exec popup - (COMPONENT_INPUT_EXEC, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_EXEC, key) if key == &MSG_KEY_ESC => { self.umount_exec(); None } @@ -406,7 +426,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_EXEC, _) => None, // -- find popup - (COMPONENT_INPUT_FIND, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_FIND, key) if key == &MSG_KEY_ESC => { self.umount_find_input(); None } @@ -441,7 +461,7 @@ impl Update for FileTransferActivity { None } // -- goto popup - (COMPONENT_INPUT_GOTO, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_GOTO, key) if key == &MSG_KEY_ESC => { self.umount_goto(); None } @@ -474,7 +494,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_GOTO, _) => None, // -- make directory - (COMPONENT_INPUT_MKDIR, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_MKDIR, key) if key == &MSG_KEY_ESC => { self.umount_mkdir(); None } @@ -494,7 +514,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_MKDIR, _) => None, // -- new file - (COMPONENT_INPUT_NEWFILE, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_NEWFILE, key) if key == &MSG_KEY_ESC => { self.umount_newfile(); None } @@ -514,7 +534,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_NEWFILE, _) => None, // -- open with - (COMPONENT_INPUT_OPEN_WITH, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_OPEN_WITH, key) if key == &MSG_KEY_ESC => { self.umount_openwith(); None } @@ -531,7 +551,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_OPEN_WITH, _) => None, // -- rename - (COMPONENT_INPUT_RENAME, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_RENAME, key) if key == &MSG_KEY_ESC => { self.umount_rename(); None } @@ -553,7 +573,7 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_RENAME, _) => None, // -- save as - (COMPONENT_INPUT_SAVEAS, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_SAVEAS, key) if key == &MSG_KEY_ESC => { self.umount_saveas(); None } @@ -578,15 +598,18 @@ impl Update for FileTransferActivity { } (COMPONENT_INPUT_SAVEAS, _) => None, // -- fileinfo - (COMPONENT_LIST_FILEINFO, &MSG_KEY_ENTER) - | (COMPONENT_LIST_FILEINFO, &MSG_KEY_ESC) => { + (COMPONENT_LIST_FILEINFO, key) | (COMPONENT_LIST_FILEINFO, key) + if key == &MSG_KEY_ENTER || key == &MSG_KEY_ESC => + { self.umount_file_info(); None } (COMPONENT_LIST_FILEINFO, _) => None, // -- delete - (COMPONENT_RADIO_DELETE, &MSG_KEY_ESC) - | (COMPONENT_RADIO_DELETE, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { + (COMPONENT_RADIO_DELETE, key) + if key == &MSG_KEY_ESC + || key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) => + { self.umount_radio_delete(); None } @@ -631,8 +654,10 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_DELETE, _) => None, // -- disconnect - (COMPONENT_RADIO_DISCONNECT, &MSG_KEY_ESC) - | (COMPONENT_RADIO_DISCONNECT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { + (COMPONENT_RADIO_DISCONNECT, key) + if key == &MSG_KEY_ESC + || key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) => + { self.umount_disconnect(); None } @@ -643,8 +668,10 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_DISCONNECT, _) => None, // -- quit - (COMPONENT_RADIO_QUIT, &MSG_KEY_ESC) - | (COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => { + (COMPONENT_RADIO_QUIT, key) + if key == &MSG_KEY_ESC + || key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) => + { self.umount_quit(); None } @@ -655,18 +682,21 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_QUIT, _) => None, // -- sorting - (COMPONENT_RADIO_SORTING, &MSG_KEY_ESC) - | (COMPONENT_RADIO_SORTING, Msg::OnSubmit(_)) => { + (COMPONENT_RADIO_SORTING, key) if key == &MSG_KEY_ESC => { + self.umount_file_sorting(); + None + } + (COMPONENT_RADIO_SORTING, Msg::OnSubmit(_)) => { self.umount_file_sorting(); None } (COMPONENT_RADIO_SORTING, Msg::OnChange(Payload::One(Value::Usize(mode)))) => { // Get sorting mode let sorting: FileSorting = match mode { - 1 => FileSorting::ByModifyTime, - 2 => FileSorting::ByCreationTime, - 3 => FileSorting::BySize, - _ => FileSorting::ByName, + 1 => FileSorting::ModifyTime, + 2 => FileSorting::CreationTime, + 3 => FileSorting::Size, + _ => FileSorting::Name, }; match self.browser.tab() { FileExplorerTab::Local => self.local_mut().sort_by(sorting), @@ -688,25 +718,31 @@ impl Update for FileTransferActivity { } (COMPONENT_RADIO_SORTING, _) => None, // -- error - (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { self.umount_error(); None } (COMPONENT_TEXT_ERROR, _) => None, // -- fatal - (COMPONENT_TEXT_FATAL, &MSG_KEY_ESC) | (COMPONENT_TEXT_FATAL, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_FATAL, key) | (COMPONENT_TEXT_FATAL, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { self.exit_reason = Some(super::ExitReason::Disconnect); None } (COMPONENT_TEXT_FATAL, _) => None, // -- help - (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) | (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { self.umount_help(); None } (COMPONENT_TEXT_HELP, _) => None, // -- progress bar - (COMPONENT_PROGRESS_BAR_PARTIAL, &MSG_KEY_CTRL_C) => { + (COMPONENT_PROGRESS_BAR_PARTIAL, key) if key == &MSG_KEY_CTRL_C => { // Set transfer aborted to True self.transfer.abort(); None @@ -752,7 +788,8 @@ impl FileTransferActivity { .collect(); // Update let props = FileListPropsBuilder::from(props) - .with_files(Some(hostname), files) + .with_files(files) + .with_title(hostname, Alignment::Left) .build(); // Update self.view.update(super::COMPONENT_EXPLORER_LOCAL, props) @@ -790,7 +827,8 @@ impl FileTransferActivity { .collect(); // Update let props = FileListPropsBuilder::from(props) - .with_files(Some(hostname), files) + .with_files(files) + .with_title(hostname, Alignment::Left) .build(); self.view.update(super::COMPONENT_EXPLORER_REMOTE, props) } @@ -823,7 +861,7 @@ impl FileTransferActivity { ))) .add_col(TextSpan::from(" [")) .add_col( - TextSpanBuilder::new( + TextSpan::new( format!( "{:5}", match record.level { @@ -834,16 +872,13 @@ impl FileTransferActivity { ) .as_str(), ) - .with_foreground(fg) - .build(), + .fg(fg), ) .add_col(TextSpan::from("]: ")) .add_col(TextSpan::from(record.msg.as_ref())); } let table = table.build(); - let props = LogboxPropsBuilder::from(props) - .with_log(Some(String::from("Log")), table) - .build(); + let props = LogboxPropsBuilder::from(props).with_log(table).build(); self.view.update(super::COMPONENT_LOG_BOX, props) } None => None, @@ -852,9 +887,8 @@ impl FileTransferActivity { pub(super) fn update_progress_bar(&mut self, filename: String) -> Option<(String, Msg)> { if let Some(props) = self.view.get_props(COMPONENT_PROGRESS_BAR_FULL) { - let root_name: String = props.texts.title.as_deref().unwrap_or("").to_string(); let props = ProgressBarPropsBuilder::from(props) - .with_texts(Some(root_name), self.transfer.full.to_string()) + .with_label(self.transfer.full.to_string()) .with_progress(self.transfer.full.calc_progress()) .build(); let _ = self.view.update(COMPONENT_PROGRESS_BAR_FULL, props); @@ -862,7 +896,8 @@ impl FileTransferActivity { match self.view.get_props(COMPONENT_PROGRESS_BAR_PARTIAL) { Some(props) => { let props = ProgressBarPropsBuilder::from(props) - .with_texts(Some(filename), self.transfer.partial.to_string()) + .with_title(filename, Alignment::Center) + .with_label(self.transfer.partial.to_string()) .with_progress(self.transfer.partial.calc_progress()) .build(); self.view.update(COMPONENT_PROGRESS_BAR_PARTIAL, props) @@ -889,7 +924,6 @@ impl FileTransferActivity { match self.view.get_props(COMPONENT_EXPLORER_FIND) { None => None, Some(props) => { - let title: String = props.texts.title.clone().unwrap_or_default(); // Prepare files let files: Vec = self .found() @@ -897,9 +931,7 @@ impl FileTransferActivity { .iter_files() .map(|x: &FsEntry| self.found().unwrap().fmt_file(x)) .collect(); - let props = FileListPropsBuilder::from(props) - .with_files(Some(title), files) - .build(); + let props = FileListPropsBuilder::from(props).with_files(files).build(); self.view.update(COMPONENT_EXPLORER_FIND, props) } } diff --git a/src/ui/activities/filetransfer/view.rs b/src/ui/activities/filetransfer/view.rs index e9046a5..840345b 100644 --- a/src/ui/activities/filetransfer/view.rs +++ b/src/ui/activities/filetransfer/view.rs @@ -32,7 +32,6 @@ use crate::fs::FsEntry; use crate::ui::components::{ file_list::{FileList, FileListPropsBuilder}, logbox::{LogBox, LogboxPropsBuilder}, - msgbox::{MsgBox, MsgBoxPropsBuilder}, }; use crate::ui::store::Store; use crate::utils::fmt::fmt_time; @@ -40,15 +39,16 @@ use crate::utils::ui::draw_area_in; // Ext use bytesize::ByteSize; use std::path::PathBuf; -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, + list::{List, ListPropsBuilder}, + paragraph::{Paragraph, ParagraphPropsBuilder}, progress_bar::{ProgressBar, ProgressBarPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - scrolltable::{ScrollTablePropsBuilder, Scrolltable}, span::{Span, SpanPropsBuilder}, table::{Table, TablePropsBuilder}, }; -use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}; +use tuirealm::props::{Alignment, PropsBuilder, TableBuilder, TextSpan}; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, style::Color, @@ -101,6 +101,7 @@ impl FileTransferActivity { super::COMPONENT_LOG_BOX, Box::new(LogBox::new( LogboxPropsBuilder::default() + .with_title("Log", Alignment::Left) .with_background(log_background) .with_borders(Borders::ALL, BorderType::Plain, log_panel) .build(), @@ -383,12 +384,13 @@ impl FileTransferActivity { let error_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_ERROR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(error_color) .with_borders(Borders::ALL, BorderType::Rounded, error_color) .bold() - .with_texts(None, vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]) .build(), )), ); @@ -408,12 +410,13 @@ impl FileTransferActivity { let error_color = self.theme().misc_error_dialog; self.view.mount( super::COMPONENT_TEXT_FATAL, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(error_color) .with_borders(Borders::ALL, BorderType::Rounded, error_color) .bold() - .with_texts(None, vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]) .build(), )), ); @@ -422,28 +425,26 @@ impl FileTransferActivity { } pub(super) fn mount_wait(&mut self, text: &str) { - self.mount_wait_ex(text, false, Color::Reset); + self.mount_wait_ex(text, Color::Reset); } pub(super) fn mount_blocking_wait(&mut self, text: &str) { - self.mount_wait_ex(text, true, Color::Reset); + self.mount_wait_ex(text, Color::Reset); self.view(); } - fn mount_wait_ex(&mut self, text: &str, blink: bool, color: Color) { + fn mount_wait_ex(&mut self, text: &str, color: Color) { // Mount - let mut builder: MsgBoxPropsBuilder = MsgBoxPropsBuilder::default(); + let mut builder: ParagraphPropsBuilder = ParagraphPropsBuilder::default(); builder .with_foreground(color) .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .bold() - .with_texts(None, vec![TextSpan::from(text)]); - if blink { - builder.blink(); - } + .with_text_alignment(Alignment::Center) + .with_texts(vec![TextSpan::from(text)]); self.view.mount( super::COMPONENT_TEXT_WAIT, - Box::new(MsgBox::new(builder.build())), + Box::new(Paragraph::new(builder.build())), ); // Give focus to info self.view.active(super::COMPONENT_TEXT_WAIT); @@ -466,10 +467,9 @@ impl FileTransferActivity { .with_color(quit_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, quit_color) - .with_options( - Some(String::from("Are you sure you want to quit?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Are you sure you want to quit?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -496,10 +496,9 @@ impl FileTransferActivity { .with_color(quit_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, quit_color) - .with_options( - Some(String::from("Are you sure you want to disconnect?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Are you sure you want to disconnect?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -521,7 +520,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Copy file(s) to…")) + .with_label("Copy file(s) to…", Alignment::Center) .build(), )), ); @@ -540,7 +539,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Execute command")) + .with_label("Execute command", Alignment::Center) .build(), )), ); @@ -570,7 +569,10 @@ impl FileTransferActivity { super::COMPONENT_EXPLORER_FIND, Box::new(FileList::new( FileListPropsBuilder::default() - .with_files(Some(format!("Search results for \"{}\"", search)), vec![]) + .with_title( + format!("Search results for \"{}\"", search), + Alignment::Left, + ) .with_borders(Borders::ALL, BorderType::Plain, hg) .with_highlight_color(hg) .with_background(bg) @@ -594,7 +596,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Search files by name")) + .with_label("Search files by name", Alignment::Center) .build(), )), ); @@ -615,7 +617,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Change working directory")) + .with_label("Change working directory", Alignment::Center) .build(), )), ); @@ -634,7 +636,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Insert directory name")) + .with_label("Insert directory name", Alignment::Center) .build(), )), ); @@ -653,7 +655,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("New file name")) + .with_label("New file name", Alignment::Center) .build(), )), ); @@ -672,7 +674,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Open file with…")) + .with_label("Open file with…", Alignment::Center) .build(), )), ); @@ -691,7 +693,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Move file(s) to…")) + .with_label("Move file(s) to…", Alignment::Center) .build(), )), ); @@ -710,7 +712,7 @@ impl FileTransferActivity { InputPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, input_color) .with_foreground(input_color) - .with_label(String::from("Save as…")) + .with_label("Save as…", Alignment::Center) .build(), )), ); @@ -735,7 +737,7 @@ impl FileTransferActivity { BorderType::Rounded, Color::Reset, ) - .with_texts(Some(root_name), String::new()) + .with_title(root_name, Alignment::Center) .build(), )), ); @@ -750,7 +752,7 @@ impl FileTransferActivity { BorderType::Rounded, Color::Reset, ) - .with_texts(Some(String::from("Please wait")), String::new()) + .with_title("Please wait", Alignment::Center) .build(), )), ); @@ -770,10 +772,10 @@ impl FileTransferActivity { _ => panic!("You can't mount file sorting when in found result"), }; let index: usize = match sorting { - FileSorting::ByCreationTime => 2, - FileSorting::ByModifyTime => 1, - FileSorting::ByName => 0, - FileSorting::BySize => 3, + FileSorting::CreationTime => 2, + FileSorting::ModifyTime => 1, + FileSorting::Name => 0, + FileSorting::Size => 3, }; self.view.mount( super::COMPONENT_RADIO_SORTING, @@ -782,15 +784,13 @@ impl FileTransferActivity { .with_color(sorting_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, sorting_color) - .with_options( - Some(String::from("Sort files by")), - vec![ - String::from("Name"), - String::from("Modify time"), - String::from("Creation time"), - String::from("Size"), - ], - ) + .with_title("Sort files by", Alignment::Center) + .with_options(&[ + String::from("Name"), + String::from("Modify time"), + String::from("Creation time"), + String::from("Size"), + ]) .with_value(index) .build(), )), @@ -811,11 +811,10 @@ impl FileTransferActivity { .with_color(warn_color) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Plain, warn_color) - .with_options( - Some(String::from("Delete file")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete file", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) + .rewind(true) .build(), )), ); @@ -841,54 +840,35 @@ impl FileTransferActivity { None => format!("{}", file.get_abs_path().display()), }; // Make texts - texts.add_col(TextSpan::from("Path: ")).add_col( - TextSpanBuilder::new(path.as_str()) - .with_foreground(Color::Yellow) - .build(), - ); + texts + .add_col(TextSpan::from("Path: ")) + .add_col(TextSpan::new(path.as_str()).fg(Color::Yellow)); if let Some(filetype) = file.get_ftype() { texts .add_row() .add_col(TextSpan::from("File type: ")) - .add_col( - TextSpanBuilder::new(filetype.as_str()) - .with_foreground(Color::LightGreen) - .build(), - ); + .add_col(TextSpan::new(filetype.as_str()).fg(Color::LightGreen)); } let (bsize, size): (ByteSize, usize) = (ByteSize(file.get_size() as u64), file.get_size()); - texts.add_row().add_col(TextSpan::from("Size: ")).add_col( - TextSpanBuilder::new(format!("{} ({})", bsize, size).as_str()) - .with_foreground(Color::Cyan) - .build(), - ); + texts + .add_row() + .add_col(TextSpan::from("Size: ")) + .add_col(TextSpan::new(format!("{} ({})", bsize, size).as_str()).fg(Color::Cyan)); let ctime: String = fmt_time(file.get_creation_time(), "%b %d %Y %H:%M:%S"); let atime: String = fmt_time(file.get_last_access_time(), "%b %d %Y %H:%M:%S"); let mtime: String = fmt_time(file.get_creation_time(), "%b %d %Y %H:%M:%S"); texts .add_row() .add_col(TextSpan::from("Creation time: ")) - .add_col( - TextSpanBuilder::new(ctime.as_str()) - .with_foreground(Color::LightGreen) - .build(), - ); + .add_col(TextSpan::new(ctime.as_str()).fg(Color::LightGreen)); texts .add_row() .add_col(TextSpan::from("Last modified time: ")) - .add_col( - TextSpanBuilder::new(mtime.as_str()) - .with_foreground(Color::LightBlue) - .build(), - ); + .add_col(TextSpan::new(mtime.as_str()).fg(Color::LightBlue)); texts .add_row() .add_col(TextSpan::from("Last access time: ")) - .add_col( - TextSpanBuilder::new(atime.as_str()) - .with_foreground(Color::LightRed) - .build(), - ); + .add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed)); // User #[cfg(target_family = "unix")] let username: String = match file.get_user() { @@ -911,22 +891,21 @@ impl FileTransferActivity { }; #[cfg(target_os = "windows")] let group: String = format!("{}", file.get_group().unwrap_or(0)); - texts.add_row().add_col(TextSpan::from("User: ")).add_col( - TextSpanBuilder::new(username.as_str()) - .with_foreground(Color::LightYellow) - .build(), - ); - texts.add_row().add_col(TextSpan::from("Group: ")).add_col( - TextSpanBuilder::new(group.as_str()) - .with_foreground(Color::Blue) - .build(), - ); + texts + .add_row() + .add_col(TextSpan::from("User: ")) + .add_col(TextSpan::new(username.as_str()).fg(Color::LightYellow)); + texts + .add_row() + .add_col(TextSpan::from("Group: ")) + .add_col(TextSpan::new(group.as_str()).fg(Color::Blue)); self.view.mount( super::COMPONENT_LIST_FILEINFO, Box::new(Table::new( TablePropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) - .with_table(Some(file.get_name().to_string()), texts.build()) + .with_title(file.get_name(), Alignment::Left) + .with_table(texts.build()) .build(), )), ); @@ -941,22 +920,16 @@ impl FileTransferActivity { let sorting_color = self.theme().transfer_status_sorting; let hidden_color = self.theme().transfer_status_hidden; let local_bar_spans: Vec = vec![ - TextSpanBuilder::new("File sorting: ") - .with_foreground(sorting_color) - .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting())) - .with_foreground(sorting_color) - .reversed() - .build(), - TextSpanBuilder::new(" Hidden files: ") - .with_foreground(hidden_color) - .build(), - TextSpanBuilder::new(Self::get_hidden_files_str( + TextSpan::new("File sorting: ").fg(sorting_color), + TextSpan::new(Self::get_file_sorting_str(self.local().get_file_sorting())) + .fg(sorting_color) + .reversed(), + TextSpan::new(" Hidden files: ").fg(hidden_color), + TextSpan::new(Self::get_hidden_files_str( self.local().hidden_files_visible(), )) - .with_foreground(hidden_color) - .reversed() - .build(), + .fg(hidden_color) + .reversed(), ]; if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_LOCAL) { self.view.update( @@ -973,32 +946,23 @@ impl FileTransferActivity { let hidden_color = self.theme().transfer_status_hidden; let sync_color = self.theme().transfer_status_sync_browsing; let remote_bar_spans: Vec = vec![ - TextSpanBuilder::new("File sorting: ") - .with_foreground(sorting_color) - .build(), - TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting())) - .with_foreground(sorting_color) - .reversed() - .build(), - TextSpanBuilder::new(" Hidden files: ") - .with_foreground(hidden_color) - .build(), - TextSpanBuilder::new(Self::get_hidden_files_str( + TextSpan::new("File sorting: ").fg(sorting_color), + TextSpan::new(Self::get_file_sorting_str(self.remote().get_file_sorting())) + .fg(sorting_color) + .reversed(), + TextSpan::new(" Hidden files: ").fg(hidden_color), + TextSpan::new(Self::get_hidden_files_str( self.remote().hidden_files_visible(), )) - .with_foreground(hidden_color) - .reversed() - .build(), - TextSpanBuilder::new(" Sync Browsing: ") - .with_foreground(sync_color) - .build(), - TextSpanBuilder::new(match self.browser.sync_browsing { + .fg(hidden_color) + .reversed(), + TextSpan::new(" Sync Browsing: ").fg(sync_color), + TextSpan::new(match self.browser.sync_browsing { true => "ON ", false => "OFF", }) - .with_foreground(sync_color) - .reversed() - .build(), + .fg(sync_color) + .reversed(), ]; if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_REMOTE) { self.view.update( @@ -1017,253 +981,109 @@ impl FileTransferActivity { let key_color = self.theme().misc_keys; self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Scrolltable::new( - ScrollTablePropsBuilder::default() + Box::new(List::new( + ListPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .with_highlighted_str(Some("?")) .with_max_scroll_step(8) .bold() - .with_table( - Some(String::from("Help")), + .scrollable(true) + .with_title("Help", Alignment::Center) + .with_rows( TableBuilder::default() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Disconnect")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Switch between explorer and logs", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Go to previous directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Change explorer tab")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Move up/down in list")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Enter directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Upload/Download file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Toggle hidden files")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Change file sorting mode")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Copy")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Make directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Go to path")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Show help")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Show info about selected file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Reload directory content")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Select file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Create new file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Open text file with preferred editor", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Quit termscp")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Rename file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Save file as")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Go to parent directory")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Open file with default application for file type", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from( " Open file with specified application", )) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Execute shell command")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Toggle synchronized browsing")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Delete selected file")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Select all files")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(key_color) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(key_color)) .add_col(TextSpan::from(" Interrupt file transfer")) .build(), ) @@ -1280,10 +1100,10 @@ impl FileTransferActivity { fn get_file_sorting_str(mode: FileSorting) -> &'static str { match mode { - FileSorting::ByName => "By name", - FileSorting::ByCreationTime => "By creation time", - FileSorting::ByModifyTime => "By modify time", - FileSorting::BySize => "By size", + FileSorting::Name => "By name", + FileSorting::CreationTime => "By creation time", + FileSorting::ModifyTime => "By modify time", + FileSorting::Size => "By size", } } diff --git a/src/ui/activities/setup/mod.rs b/src/ui/activities/setup/mod.rs index 1a07b39..d615189 100644 --- a/src/ui/activities/setup/mod.rs +++ b/src/ui/activities/setup/mod.rs @@ -152,7 +152,7 @@ impl SetupActivity { } fn config(&self) -> &ConfigClient { - &self.context().config() + self.context().config() } fn config_mut(&mut self) -> &mut ConfigClient { diff --git a/src/ui/activities/setup/update.rs b/src/ui/activities/setup/update.rs index 0f70114..06593e8 100644 --- a/src/ui/activities/setup/update.rs +++ b/src/ui/activities/setup/update.rs @@ -74,65 +74,67 @@ impl SetupActivity { None => None, Some(msg) => match msg { // Input field - (COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_TEXT_EDITOR, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL); None } - (COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_DEFAULT_PROTOCOL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_HIDDEN_FILES); None } - (COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_HIDDEN_FILES, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_UPDATES); None } - (COMPONENT_RADIO_UPDATES, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_UPDATES, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_RADIO_GROUP_DIRS); None } - (COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => { + (COMPONENT_RADIO_GROUP_DIRS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT); None } - (COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_LOCAL_FILE_FMT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT); None } - (COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_REMOTE_FILE_FMT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_TEXT_EDITOR); None } // Input field - (COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_UP) => { + (COMPONENT_INPUT_REMOTE_FILE_FMT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT); None } - (COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_UP) => { + (COMPONENT_INPUT_LOCAL_FILE_FMT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_GROUP_DIRS); None } - (COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_UP) => { + (COMPONENT_RADIO_GROUP_DIRS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_UPDATES); None } - (COMPONENT_RADIO_UPDATES, &MSG_KEY_UP) => { + (COMPONENT_RADIO_UPDATES, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_HIDDEN_FILES); None } - (COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_UP) => { + (COMPONENT_RADIO_HIDDEN_FILES, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL); None } - (COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_UP) => { + (COMPONENT_RADIO_DEFAULT_PROTOCOL, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_INPUT_TEXT_EDITOR); None } - (COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_UP) => { + (COMPONENT_INPUT_TEXT_EDITOR, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT); None } // Error or - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount text error self.umount_error(); None @@ -161,7 +163,9 @@ impl SetupActivity { } (COMPONENT_RADIO_QUIT, _) => None, // Close help - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount help self.umount_help(); None @@ -189,12 +193,12 @@ impl SetupActivity { None } // Show help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { // Change view if let Err(err) = self.action_change_tab(ViewLayout::SshKeys) { self.mount_error(err.as_str()); @@ -202,7 +206,7 @@ impl SetupActivity { None } // Revert changes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Revert changes if let Err(err) = self.action_reset_config() { self.mount_error(err.as_str()); @@ -210,13 +214,13 @@ impl SetupActivity { None } // Save - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show save self.mount_save_popup(); None } // - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.action_on_esc(); None } @@ -232,7 +236,9 @@ impl SetupActivity { None => None, Some(msg) => match msg { // Error or - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount text error self.umount_error(); None @@ -261,7 +267,9 @@ impl SetupActivity { } (COMPONENT_RADIO_QUIT, _) => None, // Close help - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount help self.umount_help(); None @@ -300,28 +308,30 @@ impl SetupActivity { (COMPONENT_RADIO_SAVE, _) => None, // Edit SSH Key // Show help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } // New key - (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_SSH_HOST, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_SSH_USERNAME); None } - (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_DOWN) => { + (COMPONENT_INPUT_SSH_USERNAME, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_INPUT_SSH_HOST); None } // New key - (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_UP) - | (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_TAB) => { + (COMPONENT_INPUT_SSH_USERNAME, key) | (COMPONENT_INPUT_SSH_USERNAME, key) + if key == &MSG_KEY_UP || key == &MSG_KEY_TAB => + { self.view.active(COMPONENT_INPUT_SSH_HOST); None } - (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_UP) - | (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_TAB) => { + (COMPONENT_INPUT_SSH_HOST, key) | (COMPONENT_INPUT_SSH_HOST, key) + if key == &MSG_KEY_UP || key == &MSG_KEY_TAB => + { self.view.active(COMPONENT_INPUT_SSH_USERNAME); None } @@ -335,14 +345,15 @@ impl SetupActivity { None } // New key - (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_ESC) - | (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_ESC) => { + (COMPONENT_INPUT_SSH_HOST, key) | (COMPONENT_INPUT_SSH_USERNAME, key) + if key == &MSG_KEY_ESC => + { // Umount new ssh key self.umount_new_ssh_key(); None } // New key - (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_N) => { + (COMPONENT_LIST_SSH_KEYS, key) if key == &MSG_KEY_CTRL_N => { // Show new key popup self.mount_new_ssh_key(); None @@ -356,13 +367,14 @@ impl SetupActivity { None } // Show delete - (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_E) - | (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_DEL) => { + (COMPONENT_LIST_SSH_KEYS, key) | (COMPONENT_LIST_SSH_KEYS, key) + if key == &MSG_KEY_CTRL_E || key == &MSG_KEY_DEL => + { // Show delete key self.mount_del_ssh_key(); None } - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { // Change view if let Err(err) = self.action_change_tab(ViewLayout::Theme) { self.mount_error(err.as_str()); @@ -370,7 +382,7 @@ impl SetupActivity { None } // Revert changes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Revert changes if let Err(err) = self.action_reset_config() { self.mount_error(err.as_str()); @@ -378,13 +390,13 @@ impl SetupActivity { None } // Save - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show save self.mount_save_popup(); None } // - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.action_on_esc(); None } @@ -400,217 +412,217 @@ impl SetupActivity { None => None, Some(msg) => match msg { // Input fields - (COMPONENT_COLOR_AUTH_PROTOCOL, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_PROTOCOL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_ADDR); None } - (COMPONENT_COLOR_AUTH_ADDR, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_ADDR, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_PORT); None } - (COMPONENT_COLOR_AUTH_PORT, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_PORT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_USERNAME); None } - (COMPONENT_COLOR_AUTH_USERNAME, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_USERNAME, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_PASSWORD); None } - (COMPONENT_COLOR_AUTH_PASSWORD, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_PASSWORD, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_BOOKMARKS); None } - (COMPONENT_COLOR_AUTH_BOOKMARKS, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_BOOKMARKS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_RECENTS); None } - (COMPONENT_COLOR_AUTH_RECENTS, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_AUTH_RECENTS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_ERROR); None } - (COMPONENT_COLOR_MISC_ERROR, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_ERROR, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_INPUT); None } - (COMPONENT_COLOR_MISC_INPUT, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_INPUT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_KEYS); None } - (COMPONENT_COLOR_MISC_KEYS, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_KEYS, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_QUIT); None } - (COMPONENT_COLOR_MISC_QUIT, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_QUIT, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_SAVE); None } - (COMPONENT_COLOR_MISC_SAVE, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_SAVE, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_MISC_WARN); None } - (COMPONENT_COLOR_MISC_WARN, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_MISC_WARN, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, key) if key == &MSG_KEY_DOWN => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, key) if key == &MSG_KEY_DOWN => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, key) if key == &MSG_KEY_DOWN => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_BG); None } - (COMPONENT_COLOR_TRANSFER_LOG_BG, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_LOG_BG, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_WIN); None } - (COMPONENT_COLOR_TRANSFER_LOG_WIN, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_LOG_WIN, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SORTING); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN); None } - (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SYNC); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, &MSG_KEY_DOWN) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, key) if key == &MSG_KEY_DOWN => { self.view.active(COMPONENT_COLOR_AUTH_PROTOCOL); None } - (COMPONENT_COLOR_AUTH_PROTOCOL, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_PROTOCOL, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SYNC); None } - (COMPONENT_COLOR_AUTH_ADDR, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_ADDR, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_PROTOCOL); None } - (COMPONENT_COLOR_AUTH_PORT, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_PORT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_ADDR); None } - (COMPONENT_COLOR_AUTH_USERNAME, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_USERNAME, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_PORT); None } - (COMPONENT_COLOR_AUTH_PASSWORD, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_PASSWORD, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_USERNAME); None } - (COMPONENT_COLOR_AUTH_BOOKMARKS, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_BOOKMARKS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_PASSWORD); None } - (COMPONENT_COLOR_AUTH_RECENTS, &MSG_KEY_UP) => { + (COMPONENT_COLOR_AUTH_RECENTS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_BOOKMARKS); None } - (COMPONENT_COLOR_MISC_ERROR, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_ERROR, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_AUTH_RECENTS); None } - (COMPONENT_COLOR_MISC_INPUT, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_INPUT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_ERROR); None } - (COMPONENT_COLOR_MISC_KEYS, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_KEYS, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_INPUT); None } - (COMPONENT_COLOR_MISC_QUIT, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_QUIT, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_KEYS); None } - (COMPONENT_COLOR_MISC_SAVE, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_SAVE, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_QUIT); None } - (COMPONENT_COLOR_MISC_WARN, &MSG_KEY_UP) => { + (COMPONENT_COLOR_MISC_WARN, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_SAVE); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_MISC_WARN); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, key) if key == &MSG_KEY_UP => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG); None } - (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, key) if key == &MSG_KEY_UP => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, key) if key == &MSG_KEY_UP => { self.view .active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG); None } - (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL); None } - (COMPONENT_COLOR_TRANSFER_LOG_BG, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_LOG_BG, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL); None } - (COMPONENT_COLOR_TRANSFER_LOG_WIN, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_LOG_WIN, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_BG); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SORTING, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_LOG_WIN); None } - (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SORTING); None } - (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, &MSG_KEY_UP) => { + (COMPONENT_COLOR_TRANSFER_STATUS_SYNC, key) if key == &MSG_KEY_UP => { self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN); None } @@ -624,7 +636,9 @@ impl SetupActivity { None } // Error or - (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount text error self.umount_error(); None @@ -653,7 +667,9 @@ impl SetupActivity { } (COMPONENT_RADIO_QUIT, _) => None, // Close help - (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => { + (COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key) + if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => + { // Umount help self.umount_help(); None @@ -676,12 +692,12 @@ impl SetupActivity { (COMPONENT_RADIO_SAVE, _) => None, // Edit SSH Key // Show help - (_, &MSG_KEY_CTRL_H) => { + (_, key) if key == &MSG_KEY_CTRL_H => { // Show help self.mount_help(); None } - (_, &MSG_KEY_TAB) => { + (_, key) if key == &MSG_KEY_TAB => { // Change view if let Err(err) = self.action_change_tab(ViewLayout::SetupForm) { self.mount_error(err.as_str()); @@ -689,7 +705,7 @@ impl SetupActivity { None } // Revert changes - (_, &MSG_KEY_CTRL_R) => { + (_, key) if key == &MSG_KEY_CTRL_R => { // Revert changes if let Err(err) = self.action_reset_theme() { self.mount_error(err.as_str()); @@ -697,13 +713,13 @@ impl SetupActivity { None } // Save - (_, &MSG_KEY_CTRL_S) => { + (_, key) if key == &MSG_KEY_CTRL_S => { // Show save self.mount_save_popup(); None } // - (_, &MSG_KEY_ESC) => { + (_, key) if key == &MSG_KEY_ESC => { self.action_on_esc(); None } diff --git a/src/ui/activities/setup/view/mod.rs b/src/ui/activities/setup/view/mod.rs index a4b6784..994ae8f 100644 --- a/src/ui/activities/setup/view/mod.rs +++ b/src/ui/activities/setup/view/mod.rs @@ -34,14 +34,14 @@ use super::*; pub use setup::*; pub use ssh_keys::*; pub use theme::*; -// Locals -use crate::ui::components::msgbox::{MsgBox, MsgBoxPropsBuilder}; // Ext -use tuirealm::components::{ +use tui_realm_stdlib::{ + list::{List, ListPropsBuilder}, + paragraph::{Paragraph, ParagraphPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - scrolltable::{ScrollTablePropsBuilder, Scrolltable}, + span::{Span, SpanPropsBuilder}, }; -use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder}; +use tuirealm::props::{Alignment, PropsBuilder, TableBuilder, TextSpan}; use tuirealm::tui::{ style::Color, widgets::{BorderType, Borders}, @@ -79,12 +79,13 @@ impl SetupActivity { // Mount self.view.mount( super::COMPONENT_TEXT_ERROR, - Box::new(MsgBox::new( - MsgBoxPropsBuilder::default() + Box::new(Paragraph::new( + ParagraphPropsBuilder::default() .with_foreground(Color::Red) .bold() .with_borders(Borders::ALL, BorderType::Rounded, Color::Red) - .with_texts(None, vec![TextSpan::from(text)]) + .with_texts(vec![TextSpan::from(text)]) + .with_text_alignment(Alignment::Center) .build(), )), ); @@ -110,16 +111,16 @@ impl SetupActivity { .with_color(Color::LightRed) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed) - .with_options( - Some(String::from( - "There are unsaved changes! Save changes before leaving?", - )), - vec![ - String::from("Save"), - String::from("Don't save"), - String::from("Cancel"), - ], + .with_title( + "There are unsaved changes! Save changes before leaving?", + Alignment::Center, ) + .with_options(&[ + String::from("Save"), + String::from("Don't save"), + String::from("Cancel"), + ]) + .rewind(true) .build(), )), ); @@ -145,10 +146,9 @@ impl SetupActivity { .with_color(Color::LightYellow) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) - .with_options( - Some(String::from("Save changes?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Save changes?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -163,91 +163,82 @@ impl SetupActivity { self.view.umount(super::COMPONENT_RADIO_SAVE); } + pub(self) fn mount_header_tab(&mut self, idx: usize) { + self.view.mount( + super::COMPONENT_RADIO_TAB, + Box::new(Radio::new( + RadioPropsBuilder::default() + .with_color(Color::LightYellow) + .with_inverted_color(Color::Black) + .with_borders(Borders::BOTTOM, BorderType::Thick, Color::White) + .with_options(&[ + String::from("User Interface"), + String::from("SSH Keys"), + String::from("Theme"), + ]) + .with_value(idx) + .rewind(true) + .build(), + )), + ); + } + + pub(self) fn mount_footer(&mut self) { + self.view.mount( + super::COMPONENT_TEXT_FOOTER, + Box::new(Span::new( + SpanPropsBuilder::default() + .with_spans(vec![ + TextSpan::new("Press ").bold(), + TextSpan::new("").bold().fg(Color::Cyan), + TextSpan::new(" to show keybindings").bold(), + ]) + .build(), + )), + ); + } + /// ### mount_help /// /// Mount help pub(super) fn mount_help(&mut self) { self.view.mount( super::COMPONENT_TEXT_HELP, - Box::new(Scrolltable::new( - ScrollTablePropsBuilder::default() + Box::new(List::new( + ListPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::White) .with_highlighted_str(Some("?")) .with_max_scroll_step(8) .bold() - .with_table( - Some(String::from("Help")), + .with_title("Help", Alignment::Center) + .scrollable(true) + .with_rows( TableBuilder::default() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Exit setup")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Change setup page")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Change cursor")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Change input field")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Select / Dismiss popup")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Delete SSH key")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" New SSH key")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Revert changes")) .add_row() - .add_col( - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - ) + .add_col(TextSpan::new("").bold().fg(Color::Cyan)) .add_col(TextSpan::from(" Save configuration")) .build(), ) diff --git a/src/ui/activities/setup/view/setup.rs b/src/ui/activities/setup/view/setup.rs index 9a8c316..218c8fb 100644 --- a/src/ui/activities/setup/view/setup.rs +++ b/src/ui/activities/setup/view/setup.rs @@ -33,10 +33,9 @@ use crate::fs::explorer::GroupDirs; use crate::utils::ui::draw_area_in; // Ext use std::path::PathBuf; -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - span::{Span, SpanPropsBuilder}, }; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, @@ -44,7 +43,7 @@ use tuirealm::tui::{ widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{PropsBuilder, TextSpanBuilder}, + props::{Alignment, PropsBuilder}, Payload, Value, View, }; @@ -59,41 +58,9 @@ impl SetupActivity { self.view = View::init(); // Common stuff // Radio tab - self.view.mount( - super::COMPONENT_RADIO_TAB, - Box::new(Radio::new( - RadioPropsBuilder::default() - .with_color(Color::LightYellow) - .with_inverted_color(Color::Black) - .with_borders(Borders::BOTTOM, BorderType::Thick, Color::White) - .with_options( - None, - vec![ - String::from("User Interface"), - String::from("SSH Keys"), - String::from("Theme"), - ], - ) - .with_value(0) - .build(), - )), - ); + self.mount_header_tab(0); // Footer - self.view.mount( - super::COMPONENT_TEXT_FOOTER, - Box::new(Span::new( - SpanPropsBuilder::default() - .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - TextSpanBuilder::new(" to show keybindings").bold().build(), - ]) - .build(), - )), - ); + self.mount_footer(); // Input fields self.view.mount( super::COMPONENT_INPUT_TEXT_EDITOR, @@ -101,7 +68,7 @@ impl SetupActivity { InputPropsBuilder::default() .with_foreground(Color::LightGreen) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen) - .with_label(String::from("Text editor")) + .with_label("Text editor", Alignment::Left) .build(), )), ); @@ -113,15 +80,14 @@ impl SetupActivity { .with_color(Color::LightCyan) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightCyan) - .with_options( - Some(String::from("Default file transfer protocol")), - vec![ - String::from("SFTP"), - String::from("SCP"), - String::from("FTP"), - String::from("FTPS"), - ], - ) + .with_title("Default file transfer protocol", Alignment::Left) + .with_options(&[ + String::from("SFTP"), + String::from("SCP"), + String::from("FTP"), + String::from("FTPS"), + ]) + .rewind(true) .build(), )), ); @@ -132,10 +98,9 @@ impl SetupActivity { .with_color(Color::LightRed) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed) - .with_options( - Some(String::from("Show hidden files (by default)")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Show hidden files (by default)?", Alignment::Left) + .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -146,10 +111,9 @@ impl SetupActivity { .with_color(Color::LightYellow) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow) - .with_options( - Some(String::from("Check for updates?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Check for updates?", Alignment::Left) + .with_options(&[String::from("Yes"), String::from("No")]) + .rewind(true) .build(), )), ); @@ -160,14 +124,13 @@ impl SetupActivity { .with_color(Color::LightMagenta) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightMagenta) - .with_options( - Some(String::from("Group directories")), - vec![ - String::from("Display first"), - String::from("Display Last"), - String::from("No"), - ], - ) + .with_title("Group directories", Alignment::Left) + .with_options(&[ + String::from("Display first"), + String::from("Display Last"), + String::from("No"), + ]) + .rewind(true) .build(), )), ); @@ -177,7 +140,7 @@ impl SetupActivity { InputPropsBuilder::default() .with_foreground(Color::LightBlue) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightBlue) - .with_label(String::from("File formatter syntax (local)")) + .with_label("File formatter syntax (local)", Alignment::Left) .build(), )), ); @@ -187,7 +150,7 @@ impl SetupActivity { InputPropsBuilder::default() .with_foreground(Color::LightGreen) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen) - .with_label(String::from("File formatter syntax (remote)")) + .with_label("File formatter syntax (remote)", Alignment::Left) .build(), )), ); diff --git a/src/ui/activities/setup/view/ssh_keys.rs b/src/ui/activities/setup/view/ssh_keys.rs index 3517178..756c618 100644 --- a/src/ui/activities/setup/view/ssh_keys.rs +++ b/src/ui/activities/setup/view/ssh_keys.rs @@ -31,10 +31,9 @@ use super::{Context, SetupActivity}; use crate::ui::components::bookmark_list::{BookmarkList, BookmarkListPropsBuilder}; use crate::utils::ui::draw_area_in; // Ext -use tuirealm::components::{ +use tui_realm_stdlib::{ input::{Input, InputPropsBuilder}, radio::{Radio, RadioPropsBuilder}, - span::{Span, SpanPropsBuilder}, }; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, @@ -42,7 +41,7 @@ use tuirealm::tui::{ widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{PropsBuilder, TextSpanBuilder}, + props::{Alignment, PropsBuilder}, View, }; @@ -57,46 +56,15 @@ impl SetupActivity { self.view = View::init(); // Common stuff // Radio tab - self.view.mount( - super::COMPONENT_RADIO_TAB, - Box::new(Radio::new( - RadioPropsBuilder::default() - .with_color(Color::LightYellow) - .with_inverted_color(Color::Black) - .with_borders(Borders::BOTTOM, BorderType::Thick, Color::LightYellow) - .with_options( - None, - vec![ - String::from("User Interface"), - String::from("SSH Keys"), - String::from("Theme"), - ], - ) - .with_value(1) - .build(), - )), - ); + // Radio tab + self.mount_header_tab(1); // Footer - self.view.mount( - super::COMPONENT_TEXT_FOOTER, - Box::new(Span::new( - SpanPropsBuilder::default() - .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - TextSpanBuilder::new(" to show keybindings").bold().build(), - ]) - .build(), - )), - ); + self.mount_footer(); self.view.mount( super::COMPONENT_LIST_SSH_KEYS, Box::new(BookmarkList::new( BookmarkListPropsBuilder::default() - .with_bookmarks(Some(String::from("SSH Keys")), vec![]) + .with_title("SSH keys", Alignment::Left) .with_borders(Borders::ALL, BorderType::Plain, Color::LightGreen) .with_background(Color::LightGreen) .with_foreground(Color::Black) @@ -211,11 +179,10 @@ impl SetupActivity { .with_color(Color::LightRed) .with_inverted_color(Color::Black) .with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed) - .with_options( - Some(String::from("Delete key?")), - vec![String::from("Yes"), String::from("No")], - ) + .with_title("Delete key?", Alignment::Center) + .with_options(&[String::from("Yes"), String::from("No")]) .with_value(1) // Default: No + .rewind(true) .build(), )), ); @@ -238,7 +205,7 @@ impl SetupActivity { super::COMPONENT_INPUT_SSH_HOST, Box::new(Input::new( InputPropsBuilder::default() - .with_label(String::from("Hostname or address")) + .with_label("Hostname or address", Alignment::Center) .with_borders( Borders::TOP | Borders::RIGHT | Borders::LEFT, BorderType::Plain, @@ -251,7 +218,7 @@ impl SetupActivity { super::COMPONENT_INPUT_SSH_USERNAME, Box::new(Input::new( InputPropsBuilder::default() - .with_label(String::from("Username")) + .with_label("Username", Alignment::Center) .with_borders( Borders::BOTTOM | Borders::RIGHT | Borders::LEFT, BorderType::Plain, @@ -287,7 +254,7 @@ impl SetupActivity { }) .collect(); let props = BookmarkListPropsBuilder::from(props) - .with_bookmarks(Some(String::from("SSH Keys")), keys) + .with_bookmarks(keys) .build(); self.view.update(super::COMPONENT_LIST_SSH_KEYS, props); } diff --git a/src/ui/activities/setup/view/theme.rs b/src/ui/activities/setup/view/theme.rs index 5bb092b..e3af5dc 100644 --- a/src/ui/activities/setup/view/theme.rs +++ b/src/ui/activities/setup/view/theme.rs @@ -33,18 +33,14 @@ use crate::ui::components::color_picker::{ColorPicker, ColorPickerPropsBuilder}; use crate::utils::parser::parse_color; use crate::utils::ui::draw_area_in; // Ext -use tuirealm::components::{ - label::{Label, LabelPropsBuilder}, - radio::{Radio, RadioPropsBuilder}, - span::{Span, SpanPropsBuilder}, -}; +use tui_realm_stdlib::label::{Label, LabelPropsBuilder}; use tuirealm::tui::{ layout::{Constraint, Direction, Layout}, style::Color, widgets::{BorderType, Borders, Clear}, }; use tuirealm::{ - props::{PropsBuilder, TextSpanBuilder}, + props::{Alignment, PropsBuilder}, Payload, Value, View, }; @@ -59,41 +55,9 @@ impl SetupActivity { self.view = View::init(); // Common stuff // Radio tab - self.view.mount( - super::COMPONENT_RADIO_TAB, - Box::new(Radio::new( - RadioPropsBuilder::default() - .with_color(Color::LightYellow) - .with_inverted_color(Color::Black) - .with_borders(Borders::BOTTOM, BorderType::Thick, Color::White) - .with_options( - None, - vec![ - String::from("User Interface"), - String::from("SSH Keys"), - String::from("Theme"), - ], - ) - .with_value(2) - .build(), - )), - ); + self.mount_header_tab(2); // Footer - self.view.mount( - super::COMPONENT_TEXT_FOOTER, - Box::new(Span::new( - SpanPropsBuilder::default() - .with_spans(vec![ - TextSpanBuilder::new("Press ").bold().build(), - TextSpanBuilder::new("") - .bold() - .with_foreground(Color::Cyan) - .build(), - TextSpanBuilder::new(" to show keybindings").bold().build(), - ]) - .build(), - )), - ); + self.mount_footer(); // auth colors self.mount_title(super::COMPONENT_COLOR_AUTH_TITLE, "Authentication styles"); self.mount_color_picker(super::COMPONENT_COLOR_AUTH_PROTOCOL, "Protocol"); @@ -653,7 +617,7 @@ impl SetupActivity { Box::new(ColorPicker::new( ColorPickerPropsBuilder::default() .with_borders(Borders::ALL, BorderType::Rounded, Color::Reset) - .with_label(label.to_string()) + .with_label(label.to_string(), Alignment::Left) .build(), )), ); diff --git a/src/ui/components/bookmark_list.rs b/src/ui/components/bookmark_list.rs index a541db7..01d81c0 100644 --- a/src/ui/components/bookmark_list.rs +++ b/src/ui/components/bookmark_list.rs @@ -26,18 +26,19 @@ * SOFTWARE. */ // ext -use tuirealm::components::utils::get_block; +use tui_realm_stdlib::utils::get_block; use tuirealm::event::{Event, KeyCode}; -use tuirealm::props::{BordersProps, Props, PropsBuilder, TextParts, TextSpan}; +use tuirealm::props::{Alignment, BlockTitle, BordersProps, Props, PropsBuilder}; use tuirealm::tui::{ layout::{Corner, Rect}, style::{Color, Style}, text::Span, widgets::{BorderType, Borders, List, ListItem, ListState}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, PropPayload, PropValue, Value}; // -- props +const PROP_BOOKMARKS: &str = "bookmarks"; pub struct BookmarkListPropsBuilder { props: Option, @@ -117,10 +118,19 @@ impl BookmarkListPropsBuilder { self } - pub fn with_bookmarks(&mut self, title: Option, bookmarks: Vec) -> &mut Self { + pub fn with_title>(&mut self, text: S, alignment: Alignment) -> &mut Self { if let Some(props) = self.props.as_mut() { - let bookmarks: Vec = bookmarks.into_iter().map(TextSpan::from).collect(); - props.texts = TextParts::new(title, Some(bookmarks)); + props.title = Some(BlockTitle::new(text, alignment)); + } + self + } + + pub fn with_bookmarks(&mut self, bookmarks: Vec) -> &mut Self { + if let Some(props) = self.props.as_mut() { + let bookmarks: Vec = bookmarks.into_iter().map(PropValue::Str).collect(); + props + .own + .insert(PROP_BOOKMARKS, PropPayload::Vec(bookmarks)); } self } @@ -210,25 +220,30 @@ impl BookmarkList { // Initialize states let mut states: OwnStates = OwnStates::default(); // Set list length - states.set_list_len(match &props.texts.spans { - Some(tokens) => tokens.len(), - None => 0, - }); + states.set_list_len(Self::bookmarks_len(&props)); BookmarkList { props, states } } + + fn bookmarks_len(props: &Props) -> usize { + match props.own.get(PROP_BOOKMARKS) { + None => 0, + Some(bookmarks) => bookmarks.unwrap_vec().len(), + } + } } impl Component for BookmarkList { #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, area: Rect) { if self.props.visible { // Make list - let list_item: Vec = match self.props.texts.spans.as_ref() { - None => vec![], - Some(lines) => lines + let list_item: Vec = match self.props.own.get(PROP_BOOKMARKS) { + Some(PropPayload::Vec(lines)) => lines .iter() - .map(|line| ListItem::new(Span::from(line.content.to_string()))) + .map(|x| x.unwrap_str()) + .map(|x| ListItem::new(Span::from(x.to_string()))) .collect(), + _ => vec![], }; let (fg, bg): (Color, Color) = match self.states.focus { true => (self.props.foreground, self.props.background), @@ -241,7 +256,7 @@ impl Component for BookmarkList { List::new(list_item) .block(get_block( &self.props.borders, - &self.props.texts.title, + self.props.title.as_ref(), self.states.focus, )) .start_corner(Corner::TopLeft) @@ -260,10 +275,7 @@ impl Component for BookmarkList { fn update(&mut self, props: Props) -> Msg { self.props = props; // re-Set list length - self.states.set_list_len(match &self.props.texts.spans { - Some(tokens) => tokens.len(), - None => 0, - }); + self.states.set_list_len(Self::bookmarks_len(&self.props)); // Reset list index self.states.reset_list_index(); Msg::None @@ -347,20 +359,24 @@ mod tests { .with_foreground(Color::Red) .with_background(Color::Blue) .with_borders(Borders::ALL, BorderType::Double, Color::Red) - .with_bookmarks( - Some(String::from("filelist")), - vec![String::from("file1"), String::from("file2")], - ) + .with_title("filelist", Alignment::Left) + .with_bookmarks(vec![String::from("file1"), String::from("file2")]) .build(), ); assert_eq!(component.props.foreground, Color::Red); assert_eq!(component.props.background, Color::Blue); assert_eq!(component.props.visible, true); + assert_eq!(component.props.title.as_ref().unwrap().text(), "filelist"); assert_eq!( - component.props.texts.title.as_ref().unwrap().as_str(), - "filelist" + component + .props + .own + .get(PROP_BOOKMARKS) + .unwrap() + .unwrap_vec() + .len(), + 2 ); - assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 2); // Verify states assert_eq!(component.states.list_index, 0); assert_eq!(component.states.list_len, 2); @@ -384,14 +400,11 @@ mod tests { // Update component.update( BookmarkListPropsBuilder::from(component.get_props()) - .with_bookmarks( - Some(String::from("filelist")), - vec![ - String::from("file1"), - String::from("file2"), - String::from("file3"), - ], - ) + .with_bookmarks(vec![ + String::from("file1"), + String::from("file2"), + String::from("file3"), + ]) .build(), ); // Verify states diff --git a/src/ui/components/color_picker.rs b/src/ui/components/color_picker.rs index 2fc9df8..c1ba2bb 100644 --- a/src/ui/components/color_picker.rs +++ b/src/ui/components/color_picker.rs @@ -30,15 +30,15 @@ use crate::utils::fmt::fmt_color; use crate::utils::parser::parse_color; // ext -use tuirealm::components::input::{Input, InputPropsBuilder}; +use tui_realm_stdlib::input::{Input, InputPropsBuilder}; use tuirealm::event::Event; -use tuirealm::props::{Props, PropsBuilder}; +use tuirealm::props::{Alignment, Props, PropsBuilder}; use tuirealm::tui::{ layout::Rect, style::Color, widgets::{BorderType, Borders}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, Value}; // -- props @@ -98,8 +98,8 @@ impl ColorPickerPropsBuilder { /// ### with_label /// /// Set input label - pub fn with_label(&mut self, label: String) -> &mut Self { - self.puppet.with_label(label); + pub fn with_label>(&mut self, label: S, alignment: Alignment) -> &mut Self { + self.puppet.with_label(label, alignment); self } @@ -149,7 +149,7 @@ impl Component for ColorPicker { /// Based on the current properties and states, renders a widget using the provided render engine in the provided Area /// If focused, cursor is also set (if supported by widget) #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, area: Rect) { self.input.render(render, area); } @@ -260,6 +260,7 @@ mod test { .visible() .with_color(&Color::Rgb(204, 170, 0)) .with_borders(Borders::ALL, BorderType::Double, Color::Rgb(204, 170, 0)) + .with_label("omar", Alignment::Left) .build(), ); // Focus diff --git a/src/ui/components/file_list.rs b/src/ui/components/file_list.rs index a0db059..9600659 100644 --- a/src/ui/components/file_list.rs +++ b/src/ui/components/file_list.rs @@ -26,10 +26,10 @@ * SOFTWARE. */ // ext -use tuirealm::components::utils::get_block; +use tui_realm_stdlib::utils::get_block; use tuirealm::event::{Event, KeyCode, KeyModifiers}; use tuirealm::props::{ - BordersProps, PropPayload, PropValue, Props, PropsBuilder, TextParts, TextSpan, + Alignment, BlockTitle, BordersProps, PropPayload, PropValue, Props, PropsBuilder, }; use tuirealm::tui::{ layout::{Corner, Rect}, @@ -37,11 +37,12 @@ use tuirealm::tui::{ text::Span, widgets::{BorderType, Borders, List, ListItem, ListState}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, Value}; // -- props -const PROP_HIGHLIGHT_COLOR: &str = "props-highlight-color"; +const PROP_FILES: &str = "files"; +const PALETTE_HIGHLIGHT_COLOR: &str = "props-highlight-color"; pub struct FileListPropsBuilder { props: Option, @@ -107,10 +108,7 @@ impl FileListPropsBuilder { /// Set highlighted color pub fn with_highlight_color(&mut self, color: Color) -> &mut Self { if let Some(props) = self.props.as_mut() { - props.own.insert( - PROP_HIGHLIGHT_COLOR, - PropPayload::One(PropValue::Color(color)), - ); + props.palette.insert(PALETTE_HIGHLIGHT_COLOR, color); } self } @@ -134,10 +132,17 @@ impl FileListPropsBuilder { self } - pub fn with_files(&mut self, title: Option, files: Vec) -> &mut Self { + pub fn with_title>(&mut self, text: S, alignment: Alignment) -> &mut Self { if let Some(props) = self.props.as_mut() { - let files: Vec = files.into_iter().map(TextSpan::from).collect(); - props.texts = TextParts::new(title, Some(files)); + props.title = Some(BlockTitle::new(text, alignment)); + } + self + } + + pub fn with_files(&mut self, files: Vec) -> &mut Self { + if let Some(props) = self.props.as_mut() { + let files: Vec = files.into_iter().map(PropValue::Str).collect(); + props.own.insert(PROP_FILES, PropPayload::Vec(files)); } self } @@ -299,32 +304,39 @@ impl FileList { // Initialize states let mut states: OwnStates = OwnStates::default(); // Init list states - states.init_list_states(props.texts.spans.as_ref().map(|x| x.len()).unwrap_or(0)); + states.init_list_states(Self::files_len(&props)); FileList { props, states } } + + fn files_len(props: &Props) -> usize { + match props.own.get(PROP_FILES) { + None => 0, + Some(files) => files.unwrap_vec().len(), + } + } } impl Component for FileList { #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, area: Rect) { if self.props.visible { // Make list - let list_item: Vec = match self.props.texts.spans.as_ref() { - None => vec![], - Some(lines) => lines + let list_item: Vec = match self.props.own.get(PROP_FILES) { + Some(PropPayload::Vec(lines)) => lines .iter() .enumerate() .map(|(num, line)| { let to_display: String = match self.states.is_selected(num) { - true => format!("*{}", line.content), - false => line.content.to_string(), + true => format!("*{}", line.unwrap_str()), + false => line.unwrap_str().to_string(), }; ListItem::new(Span::from(to_display)) }) .collect(), + _ => vec![], }; - let highlighted_color: Color = match self.props.own.get(PROP_HIGHLIGHT_COLOR) { - Some(PropPayload::One(PropValue::Color(c))) => *c, + let highlighted_color: Color = match self.props.palette.get(PALETTE_HIGHLIGHT_COLOR) { + Some(c) => *c, _ => Color::Reset, }; let (h_fg, h_bg): (Color, Color) = match self.states.focus { @@ -338,7 +350,7 @@ impl Component for FileList { List::new(list_item) .block(get_block( &self.props.borders, - &self.props.texts.title, + self.props.title.as_ref(), self.states.focus, )) .start_corner(Corner::TopLeft) @@ -362,14 +374,7 @@ impl Component for FileList { fn update(&mut self, props: Props) -> Msg { self.props = props; // re-Set list states - self.states.init_list_states( - self.props - .texts - .spans - .as_ref() - .map(|x| x.len()) - .unwrap_or(0), - ); + self.states.init_list_states(Self::files_len(&self.props)); Msg::None } @@ -551,24 +556,33 @@ mod tests { .with_background(Color::Blue) .with_highlight_color(Color::LightRed) .with_borders(Borders::ALL, BorderType::Double, Color::Red) - .with_files( - Some(String::from("files")), - vec![String::from("file1"), String::from("file2")], - ) + .with_title("files", Alignment::Left) + .with_files(vec![String::from("file1"), String::from("file2")]) .build(), ); assert_eq!( - *component.props.own.get(PROP_HIGHLIGHT_COLOR).unwrap(), - PropPayload::One(PropValue::Color(Color::LightRed)) + *component + .props + .palette + .get(PALETTE_HIGHLIGHT_COLOR) + .unwrap(), + Color::LightRed ); assert_eq!(component.props.foreground, Color::Red); assert_eq!(component.props.background, Color::Blue); assert_eq!(component.props.visible, true); + assert_eq!(component.props.title.as_ref().unwrap().text(), "files"); assert_eq!( - component.props.texts.title.as_ref().unwrap().as_str(), - "files" + component + .props + .own + .get(PROP_FILES) + .as_ref() + .unwrap() + .unwrap_vec() + .len(), + 2 ); - assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 2); // Verify states assert_eq!(component.states.list_index, 0); assert_eq!(component.states.selected.len(), 0); @@ -594,14 +608,11 @@ mod tests { // Update component.update( FileListPropsBuilder::from(component.get_props()) - .with_files( - Some(String::from("filelist")), - vec![ - String::from("file1"), - String::from("file2"), - String::from("file3"), - ], - ) + .with_files(vec![ + String::from("file1"), + String::from("file2"), + String::from("file3"), + ]) .build(), ); // Verify states @@ -670,14 +681,11 @@ mod tests { // Make component let mut component: FileList = FileList::new( FileListPropsBuilder::default() - .with_files( - Some(String::from("files")), - vec![ - String::from("file1"), - String::from("file2"), - String::from("file3"), - ], - ) + .with_files(vec![ + String::from("file1"), + String::from("file2"), + String::from("file3"), + ]) .build(), ); // Get state @@ -735,10 +743,7 @@ mod tests { // Update files component.update( FileListPropsBuilder::from(component.get_props()) - .with_files( - Some(String::from("filelist")), - vec![String::from("file1"), String::from("file2")], - ) + .with_files(vec![String::from("file1"), String::from("file2")]) .build(), ); // Selection should now be empty diff --git a/src/ui/components/logbox.rs b/src/ui/components/logbox.rs index 8e134a5..69f5117 100644 --- a/src/ui/components/logbox.rs +++ b/src/ui/components/logbox.rs @@ -26,18 +26,22 @@ * SOFTWARE. */ // ext -use tuirealm::components::utils::{get_block, wrap_spans}; +use tui_realm_stdlib::utils::{get_block, wrap_spans}; use tuirealm::event::{Event, KeyCode}; -use tuirealm::props::{BordersProps, Props, PropsBuilder, Table as TextTable, TextParts}; +use tuirealm::props::{ + Alignment, BlockTitle, BordersProps, Props, PropsBuilder, Table as TextTable, +}; use tuirealm::tui::{ layout::{Corner, Rect}, style::{Color, Style}, widgets::{BorderType, Borders, List, ListItem, ListState}, }; -use tuirealm::{Canvas, Component, Msg, Payload, Value}; +use tuirealm::{Component, Frame, Msg, Payload, PropPayload, PropValue, Value}; // -- props +const PROP_TABLE: &str = "table"; + pub struct LogboxPropsBuilder { props: Option, } @@ -106,9 +110,18 @@ impl LogboxPropsBuilder { self } - pub fn with_log(&mut self, title: Option, table: TextTable) -> &mut Self { + pub fn with_title>(&mut self, text: S, alignment: Alignment) -> &mut Self { if let Some(props) = self.props.as_mut() { - props.texts = TextParts::table(title, table); + props.title = Some(BlockTitle::new(text, alignment)); + } + self + } + + pub fn with_log(&mut self, table: TextTable) -> &mut Self { + if let Some(props) = self.props.as_mut() { + props + .own + .insert(PROP_TABLE, PropPayload::One(PropValue::Table(table))); } self } @@ -198,33 +211,37 @@ impl LogBox { // Initialize states let mut states: OwnStates = OwnStates::default(); // Set list length - states.set_list_len(match &props.texts.table { - Some(rows) => rows.len(), - None => 0, - }); + states.set_list_len(Self::table_len(&props)); // Reset list index states.reset_list_index(); LogBox { props, states } } + + fn table_len(props: &Props) -> usize { + match props.own.get(PROP_TABLE) { + Some(PropPayload::One(PropValue::Table(table))) => table.len(), + _ => 0, + } + } } impl Component for LogBox { #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { + fn render(&self, render: &mut Frame, area: Rect) { if self.props.visible { let width: usize = area.width as usize - 4; // Make list - let list_items: Vec = match self.props.texts.table.as_ref() { - None => Vec::new(), - Some(table) => table + let list_items: Vec = match self.props.own.get(PROP_TABLE) { + Some(PropPayload::One(PropValue::Table(table))) => table .iter() .map(|row| ListItem::new(wrap_spans(row, width, &self.props))) .collect(), // Make List item from TextSpan + _ => Vec::new(), }; let w = List::new(list_items) .block(get_block( &self.props.borders, - &self.props.texts.title, + self.props.title.as_ref(), self.states.focus, )) .start_corner(Corner::BottomLeft) @@ -240,10 +257,7 @@ impl Component for LogBox { fn update(&mut self, props: Props) -> Msg { self.props = props; // re-Set list length - self.states.set_list_len(match &self.props.texts.table { - Some(rows) => rows.len(), - None => 0, - }); + self.states.set_list_len(Self::table_len(&self.props)); // Reset list index self.states.reset_list_index(); Msg::None @@ -323,8 +337,8 @@ mod tests { .visible() .with_borders(Borders::ALL, BorderType::Double, Color::Red) .with_background(Color::Blue) + .with_title("log", Alignment::Left) .with_log( - Some(String::from("Log")), TableBuilder::default() .add_col(TextSpan::from("12:29")) .add_col(TextSpan::from("system crashed")) @@ -337,11 +351,7 @@ mod tests { ); assert_eq!(component.props.visible, true); assert_eq!(component.props.background, Color::Blue); - assert_eq!( - component.props.texts.title.as_ref().unwrap().as_str(), - "Log" - ); - assert_eq!(component.props.texts.table.as_ref().unwrap().len(), 2); + assert_eq!(component.props.title.as_ref().unwrap().text(), "Log"); // Verify states assert_eq!(component.states.list_index, 0); assert_eq!(component.states.list_len, 2); @@ -364,7 +374,6 @@ mod tests { component.update( LogboxPropsBuilder::from(component.get_props()) .with_log( - Some(String::from("Log")), TableBuilder::default() .add_col(TextSpan::from("12:29")) .add_col(TextSpan::from("system crashed")) diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index a613ff9..bcb878a 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -30,4 +30,3 @@ pub mod bookmark_list; pub mod color_picker; pub mod file_list; pub mod logbox; -pub mod msgbox; diff --git a/src/ui/components/msgbox.rs b/src/ui/components/msgbox.rs deleted file mode 100644 index 226864a..0000000 --- a/src/ui/components/msgbox.rs +++ /dev/null @@ -1,268 +0,0 @@ -//! ## MsgBox -//! -//! `MsgBox` component renders a simple readonly no event associated centered text - -/** - * MIT License - * - * termscp - Copyright (c) 2021 Christian Visintin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -// locals -use crate::utils::fmt::align_text_center; -// ext -use tuirealm::components::utils::{get_block, use_or_default_styles}; -use tuirealm::event::Event; -use tuirealm::props::{BordersProps, Props, PropsBuilder, TextParts, TextSpan}; -use tuirealm::tui::{ - layout::{Corner, Rect}, - style::{Color, Modifier, Style}, - text::{Span, Spans}, - widgets::{BorderType, Borders, List, ListItem}, -}; -use tuirealm::{Canvas, Component, Msg, Payload}; - -// -- Props - -pub struct MsgBoxPropsBuilder { - props: Option, -} - -impl Default for MsgBoxPropsBuilder { - fn default() -> Self { - MsgBoxPropsBuilder { - props: Some(Props::default()), - } - } -} - -impl PropsBuilder for MsgBoxPropsBuilder { - fn build(&mut self) -> Props { - self.props.take().unwrap() - } - - fn hidden(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.visible = false; - } - self - } - - fn visible(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.visible = true; - } - self - } -} - -impl From for MsgBoxPropsBuilder { - fn from(props: Props) -> Self { - MsgBoxPropsBuilder { props: Some(props) } - } -} - -impl MsgBoxPropsBuilder { - pub fn with_foreground(&mut self, color: Color) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.foreground = color; - } - self - } - - pub fn bold(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.modifiers |= Modifier::BOLD; - } - self - } - - pub fn blink(&mut self) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.modifiers |= Modifier::SLOW_BLINK; - } - self - } - - pub fn with_borders( - &mut self, - borders: Borders, - variant: BorderType, - color: Color, - ) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.borders = BordersProps { - borders, - variant, - color, - } - } - self - } - - pub fn with_texts(&mut self, title: Option, texts: Vec) -> &mut Self { - if let Some(props) = self.props.as_mut() { - props.texts = TextParts::new(title, Some(texts)); - } - self - } -} - -// -- component - -pub struct MsgBox { - props: Props, -} - -impl MsgBox { - /// ### new - /// - /// Instantiate a new Text component - pub fn new(props: Props) -> Self { - MsgBox { props } - } -} - -impl Component for MsgBox { - #[cfg(not(tarpaulin_include))] - fn render(&self, render: &mut Canvas, area: Rect) { - // Make a Span - if self.props.visible { - let lines: Vec = match self.props.texts.spans.as_ref() { - None => Vec::new(), - Some(rows) => { - let mut lines: Vec = Vec::new(); - for line in rows.iter() { - // Keep line color, or use default - let (fg, bg, modifiers) = use_or_default_styles(&self.props, line); - let message_row = - textwrap::wrap(line.content.as_str(), area.width as usize); - for msg in message_row.iter() { - lines.push(ListItem::new(Spans::from(vec![Span::styled( - align_text_center(msg, area.width), - Style::default().add_modifier(modifiers).fg(fg).bg(bg), - )]))); - } - } - lines - } - }; - render.render_widget( - List::new(lines) - .block(get_block( - &self.props.borders, - &self.props.texts.title, - true, - )) - .start_corner(Corner::TopLeft) - .style( - Style::default() - .fg(self.props.foreground) - .bg(self.props.background), - ), - area, - ); - } - } - - fn update(&mut self, props: Props) -> Msg { - self.props = props; - // Return None - Msg::None - } - - fn get_props(&self) -> Props { - self.props.clone() - } - - fn on(&mut self, ev: Event) -> Msg { - // Return key - if let Event::Key(key) = ev { - Msg::OnKey(key) - } else { - Msg::None - } - } - - fn get_state(&self) -> Payload { - Payload::None - } - - fn blur(&mut self) {} - - fn active(&mut self) {} -} - -#[cfg(test)] -mod tests { - - use super::*; - - use pretty_assertions::assert_eq; - use tuirealm::event::{KeyCode, KeyEvent}; - use tuirealm::props::{TextSpan, TextSpanBuilder}; - use tuirealm::tui::style::Color; - - #[test] - fn test_ui_components_msgbox() { - let mut component: MsgBox = MsgBox::new( - MsgBoxPropsBuilder::default() - .hidden() - .visible() - .with_foreground(Color::Red) - .bold() - .blink() - .with_borders(Borders::ALL, BorderType::Double, Color::Red) - .with_texts( - None, - vec![ - TextSpan::from("Press "), - TextSpanBuilder::new("") - .with_foreground(Color::Cyan) - .bold() - .build(), - TextSpan::from(" to quit"), - ], - ) - .build(), - ); - assert_eq!(component.props.foreground, Color::Red); - assert!(component.props.modifiers.intersects(Modifier::BOLD)); - assert_eq!(component.props.visible, true); - assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 3); - component.active(); - component.blur(); - // Update - let props = MsgBoxPropsBuilder::from(component.get_props()) - .hidden() - .with_foreground(Color::Yellow) - .build(); - assert_eq!(component.update(props), Msg::None); - assert_eq!(component.props.visible, false); - assert_eq!(component.props.foreground, Color::Yellow); - // Get value - assert_eq!(component.get_state(), Payload::None); - // Event - assert_eq!( - component.on(Event::Key(KeyEvent::from(KeyCode::Delete))), - Msg::OnKey(KeyEvent::from(KeyCode::Delete)) - ); - } -} diff --git a/src/utils/fmt.rs b/src/utils/fmt.rs index 703cd91..44f8f6a 100644 --- a/src/utils/fmt.rs +++ b/src/utils/fmt.rs @@ -100,23 +100,6 @@ pub fn fmt_millis(duration: Duration) -> String { format!("{}.{:0width$}", seconds, millis, width = 3) } -/// align_text_center -/// -/// Align text to center for a given width -pub fn align_text_center(text: &str, width: u16) -> String { - let indent_size: usize = match (width as usize) >= text.len() { - // NOTE: The check prevents underflow - true => (width as usize - text.len()) / 2, - false => 0, - }; - textwrap::indent( - text, - (0..indent_size).map(|_| " ").collect::().as_str(), - ) - .trim_end() - .to_string() -} - /// ### elide_path /// /// Elide a path if longer than width @@ -362,18 +345,6 @@ mod tests { ); } - #[test] - fn test_utils_align_text_center() { - assert_eq!( - align_text_center("hello world!", 24), - String::from(" hello world!") - ); - // Bad case - assert_eq!( - align_text_center("hello world!", 8), - String::from("hello world!") - ); - } #[test] fn test_utils_fmt_millis() { assert_eq!(