Merged 0.6.1; added new DirectoryAlreadyExists; return new variant for SFTP/SCP
This commit is contained in:
commit
81482b47f4
16
CHANGELOG.md
16
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
|
||||
|
||||
|
|
178
Cargo.lock
generated
178
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
13
Cargo.toml
13
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"
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
</p>
|
||||
|
||||
<p align="center">Developed by <a href="https://veeso.github.io/">@veeso</a></p>
|
||||
<p align="center">Current version: 0.6.0 (23/07/2021)</p>
|
||||
<p align="center">Current version: 0.6.1 (FIXME: 23/07/2021)</p>
|
||||
|
||||
[![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.
|
||||
|
||||
|
|
2
dist/pkgs/freebsd/manifest
vendored
2
dist/pkgs/freebsd/manifest
vendored
|
@ -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: <<EOD
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
# -f, -y, --force, --yes
|
||||
# Skip the confirmation prompt during installation
|
||||
|
||||
TERMSCP_VERSION="0.6.0"
|
||||
TERMSCP_VERSION="0.6.1"
|
||||
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
|
||||
DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
|
||||
FREEBSD_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}.txz"
|
||||
|
|
|
@ -188,7 +188,7 @@ impl ActivityManager {
|
|||
};
|
||||
// If ft params is None, return None
|
||||
let ft_params: &FileTransferParams = match ctx.ft_params() {
|
||||
Some(ft_params) => &ft_params,
|
||||
Some(ft_params) => ft_params,
|
||||
None => {
|
||||
error!("Failed to start FileTransferActivity: file transfer params is None");
|
||||
return None;
|
||||
|
|
|
@ -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")
|
||||
);
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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<String>,
|
||||
|
@ -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
|
||||
|
|
|
@ -169,7 +169,7 @@ impl ScpFileTransfer {
|
|||
// Get symlink; PATH mustn't be equal to filename
|
||||
let symlink: Option<Box<FsEntry>> = 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(),
|
||||
|
|
|
@ -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<FsEntry> = 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<Self, Self::Err> {
|
||||
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
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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(),
|
||||
)),
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// <RIGHT> / <LEFT>
|
||||
(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
|
||||
}
|
||||
// <DEL | 'E'>
|
||||
(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 {
|
|||
}
|
||||
}
|
||||
// <ESC> 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,
|
||||
// <TAB> 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 <TAB>, 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(),
|
||||
// <ESC> => 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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("<CTRL+H>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
TextSpanBuilder::new(" to show keybindings; ")
|
||||
.bold()
|
||||
.build(),
|
||||
TextSpanBuilder::new("<CTRL+C>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
TextSpanBuilder::new(" to enter setup").bold().build(),
|
||||
TextSpan::new("Press ").bold(),
|
||||
TextSpan::new("<CTRL+H>").bold().fg(key_color),
|
||||
TextSpan::new(" to show keybindings; ").bold(),
|
||||
TextSpan::new("<CTRL+C>").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 <https://veeso.github.io/termscp/>; view release notes with <CTRL+R>"),
|
||||
])
|
||||
.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("<ESC>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<ESC>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Quit termscp"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<TAB>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<TAB>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Switch from form and bookmarks"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<RIGHT/LEFT>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Switch bookmark tab"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<UP/DOWN>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Move up/down in current tab"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<ENTER>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<ENTER>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Connect/Load bookmark"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<DEL|E>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<DEL|E>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Delete selected bookmark"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+C>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<CTRL+C>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Enter setup"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+S>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<CTRL+S>").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(),
|
||||
)),
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -228,7 +228,7 @@ impl FileTransferActivity {
|
|||
///
|
||||
/// Returns config client reference
|
||||
fn config(&self) -> &ConfigClient {
|
||||
&self.context().config()
|
||||
self.context().config()
|
||||
}
|
||||
|
||||
/// ### theme
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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<String> = 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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TextSpan> = 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<TextSpan> = 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("<ESC>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<ESC>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Disconnect"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<TAB>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<TAB>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
" Switch between explorer and logs",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<BACKSPACE>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<BACKSPACE>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Go to previous directory"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<RIGHT/LEFT>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Change explorer tab"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<UP/DOWN>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Move up/down in list"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<ENTER>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<ENTER>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Enter directory"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<SPACE>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<SPACE>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Upload/Download file"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<A>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<A>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Toggle hidden files"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<B>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<B>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Change file sorting mode"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<C>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<C>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Copy"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<D>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<D>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Make directory"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<G>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<G>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Go to path"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<H>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<H>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Show help"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<I>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<I>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Show info about selected file"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<L>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<L>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Reload directory content"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<M>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<M>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Select file"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<N>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<N>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Create new file"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<O>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<O>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
" Open text file with preferred editor",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<Q>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<Q>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Quit termscp"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<R>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<R>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Rename file"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<S>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<S>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Save file as"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<U>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<U>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Go to parent directory"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<V>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<V>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
" Open file with default application for file type",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<W>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<W>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
" Open file with specified application",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<X>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<X>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Execute shell command"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<Y>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<Y>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Toggle synchronized browsing"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<DEL|E>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<DEL|E>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Delete selected file"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+A>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<CTRL+A>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Select all files"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+C>")
|
||||
.bold()
|
||||
.with_foreground(key_color)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<CTRL+C>").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",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ impl SetupActivity {
|
|||
}
|
||||
|
||||
fn config(&self) -> &ConfigClient {
|
||||
&self.context().config()
|
||||
self.context().config()
|
||||
}
|
||||
|
||||
fn config_mut(&mut self) -> &mut ConfigClient {
|
||||
|
|
|
@ -74,65 +74,67 @@ impl SetupActivity {
|
|||
None => None,
|
||||
Some(msg) => match msg {
|
||||
// Input field <DOWN>
|
||||
(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 <UP>
|
||||
(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 <ENTER> or <ESC>
|
||||
(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
|
||||
}
|
||||
// <CTRL+H> 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
|
||||
}
|
||||
// <CTRL+R> 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
|
||||
}
|
||||
// <CTRL+S> Save
|
||||
(_, &MSG_KEY_CTRL_S) => {
|
||||
(_, key) if key == &MSG_KEY_CTRL_S => {
|
||||
// Show save
|
||||
self.mount_save_popup();
|
||||
None
|
||||
}
|
||||
// <ESC>
|
||||
(_, &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 <ENTER> or <ESC>
|
||||
(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
|
||||
// <CTRL+H> Show help
|
||||
(_, &MSG_KEY_CTRL_H) => {
|
||||
(_, key) if key == &MSG_KEY_CTRL_H => {
|
||||
// Show help
|
||||
self.mount_help();
|
||||
None
|
||||
}
|
||||
// New key <DOWN>
|
||||
(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 <UP>
|
||||
(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 <ESC>
|
||||
(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
|
||||
}
|
||||
// <CTRL+N> 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
|
||||
}
|
||||
// <DEL | CTRL+E> 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
|
||||
}
|
||||
// <CTRL+R> 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
|
||||
}
|
||||
// <CTRL+S> Save
|
||||
(_, &MSG_KEY_CTRL_S) => {
|
||||
(_, key) if key == &MSG_KEY_CTRL_S => {
|
||||
// Show save
|
||||
self.mount_save_popup();
|
||||
None
|
||||
}
|
||||
// <ESC>
|
||||
(_, &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 <ENTER> or <ESC>
|
||||
(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
|
||||
// <CTRL+H> 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
|
||||
}
|
||||
// <CTRL+R> 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
|
||||
}
|
||||
// <CTRL+S> Save
|
||||
(_, &MSG_KEY_CTRL_S) => {
|
||||
(_, key) if key == &MSG_KEY_CTRL_S => {
|
||||
// Show save
|
||||
self.mount_save_popup();
|
||||
None
|
||||
}
|
||||
// <ESC>
|
||||
(_, &MSG_KEY_ESC) => {
|
||||
(_, key) if key == &MSG_KEY_ESC => {
|
||||
self.action_on_esc();
|
||||
None
|
||||
}
|
||||
|
|
|
@ -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("<CTRL+H>").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("<ESC>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<ESC>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Exit setup"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<TAB>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<TAB>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Change setup page"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<RIGHT/LEFT>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Change cursor"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<UP/DOWN>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Change input field"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<ENTER>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<ENTER>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Select / Dismiss popup"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<DEL|E>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<DEL|E>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Delete SSH key"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+N>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<CTRL+N>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" New SSH key"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+R>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<CTRL+R>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Revert changes"))
|
||||
.add_row()
|
||||
.add_col(
|
||||
TextSpanBuilder::new("<CTRL+S>")
|
||||
.bold()
|
||||
.with_foreground(Color::Cyan)
|
||||
.build(),
|
||||
)
|
||||
.add_col(TextSpan::new("<CTRL+S>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Save configuration"))
|
||||
.build(),
|
||||
)
|
||||
|
|
|
@ -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("<CTRL+H>")
|
||||
.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(),
|
||||
)),
|
||||
);
|
||||
|
|
|
@ -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("<CTRL+H>")
|
||||
.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);
|
||||
}
|
||||
|
|
|
@ -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("<CTRL+H>")
|
||||
.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(),
|
||||
)),
|
||||
);
|
||||
|
|
|
@ -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<Props>,
|
||||
|
@ -117,10 +118,19 @@ impl BookmarkListPropsBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_bookmarks(&mut self, title: Option<String>, bookmarks: Vec<String>) -> &mut Self {
|
||||
pub fn with_title<S: AsRef<str>>(&mut self, text: S, alignment: Alignment) -> &mut Self {
|
||||
if let Some(props) = self.props.as_mut() {
|
||||
let bookmarks: Vec<TextSpan> = 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<String>) -> &mut Self {
|
||||
if let Some(props) = self.props.as_mut() {
|
||||
let bookmarks: Vec<PropValue> = 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<ListItem> = match self.props.texts.spans.as_ref() {
|
||||
None => vec![],
|
||||
Some(lines) => lines
|
||||
let list_item: Vec<ListItem> = 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
|
||||
|
|
|
@ -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<S: AsRef<str>>(&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
|
||||
|
|
|
@ -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<Props>,
|
||||
|
@ -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<String>, files: Vec<String>) -> &mut Self {
|
||||
pub fn with_title<S: AsRef<str>>(&mut self, text: S, alignment: Alignment) -> &mut Self {
|
||||
if let Some(props) = self.props.as_mut() {
|
||||
let files: Vec<TextSpan> = 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<String>) -> &mut Self {
|
||||
if let Some(props) = self.props.as_mut() {
|
||||
let files: Vec<PropValue> = 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<ListItem> = match self.props.texts.spans.as_ref() {
|
||||
None => vec![],
|
||||
Some(lines) => lines
|
||||
let list_item: Vec<ListItem> = 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
|
||||
|
|
|
@ -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<Props>,
|
||||
}
|
||||
|
@ -106,9 +110,18 @@ impl LogboxPropsBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_log(&mut self, title: Option<String>, table: TextTable) -> &mut Self {
|
||||
pub fn with_title<S: AsRef<str>>(&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<ListItem> = match self.props.texts.table.as_ref() {
|
||||
None => Vec::new(),
|
||||
Some(table) => table
|
||||
let list_items: Vec<ListItem> = 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"))
|
||||
|
|
|
@ -30,4 +30,3 @@ pub mod bookmark_list;
|
|||
pub mod color_picker;
|
||||
pub mod file_list;
|
||||
pub mod logbox;
|
||||
pub mod msgbox;
|
||||
|
|
|
@ -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<Props>,
|
||||
}
|
||||
|
||||
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<Props> 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<String>, texts: Vec<TextSpan>) -> &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<ListItem> = match self.props.texts.spans.as_ref() {
|
||||
None => Vec::new(),
|
||||
Some(rows) => {
|
||||
let mut lines: Vec<ListItem> = 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("<ESC>")
|
||||
.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))
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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::<String>().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!(
|
||||
|
|
Loading…
Reference in a new issue