mirror of
https://github.com/veeso/termscp.git
synced 2026-09-16 17:16:18 -07:00
build: migrate to tui-realm 4.0
Upgrade tuirealm (3.x -> 4.0.0), tui-realm-stdlib (3 -> 4), tui-term (0.2 -> 0.3). Apply all breaking changes from the 4.0 migration guide across the termscp UI. Key changes: - Root-level re-exports removed; imports moved to module-qualified paths (`tuirealm::application`, `::component`, `::event`, `::props`, `::state`, `::subscription`, `::listener`, `::ratatui`). Same for stdlib component types (`tui_realm_stdlib::components::*`). - `MockComponent` trait renamed to `Component`; old `Component` trait renamed to `AppComponent`. `#[derive(MockComponent)]` is now `#[derive(Component)]`. `Component::on` now takes `&Event<_>`. - `TextSpan` replaced with `SpanStatic`/`LineStatic`/`TextStatic` (ratatui-based); tuple `(String, Alignment)` titles replaced with the new `Title` builder; `Alignment` split into `HorizontalAlignment`/`VerticalAlignment`; stdlib components use `.alignment_horizontal` instead of `.alignment`. - `State::One`/`PropPayload::One` -> `Single`. `CmdResult::None` -> `NoChange`. `Props::get_or` removed; `Props::get` now returns a borrowed `Option<&AttrValue>` (call sites switched to `.and_then(AttrValue::as_*)`). `Component::query` returns `Option<QueryResult<'a>>`. - `Attribute::HighlightedColor` -> `HighlightStyle` (a full `Style`). `.highlighted_*` helpers renamed to `.highlight_*`. - `PollStrategy::UpTo(n)` now requires a `Duration`; tick timeout moved from `EventListenerCfg::poll_timeout` into `PollStrategy`. - `TerminalBridge` removed; `Context` now holds `CrosstermTerminalAdapter` directly and enables raw mode + alternate screen explicitly. The `TerminalAdapter` trait is imported where its methods are used. - `Update` trait removed; activity `update` methods are plain inherent functions. - `ProgressBar` replaced by stdlib `Gauge`. Paragraph `.wrap` renamed to `.wrap_trim`; `.text` now takes an `Into<Text>`. Stdlib `List` row items are now individual lines (`Vec<Span>` per row) rather than a `Table` of spans; custom `FileList`/`Log` convert between the two models. - Radio builders drop `.foreground(color)` so unselected items render with the terminal default foreground, and set `highlight_style(Style::default().fg(color).add_modifier(REVERSED))` so the selected entry is visibly highlighted only with the theme color. - Custom `FileList` keeps the selected row highlighted with the full highlight style when focused and falls back to a foreground-only style when unfocused. - Theme loading is now backwards compatible: `Theme` uses a custom `Deserialize` through an intermediate `ThemeFile` with optional fields, so missing keys, unknown values or legacy aliases (`transfer_progress_bar_full`/`_partial`) fall back to defaults on a per-field basis instead of failing the whole load.
This commit is contained in:
Generated
+96
-73
@@ -286,7 +286,7 @@ dependencies = [
|
||||
"http 0.2.12",
|
||||
"http 1.4.0",
|
||||
"http-body 1.0.1",
|
||||
"lru 0.16.3",
|
||||
"lru",
|
||||
"percent-encoding",
|
||||
"regex-lite",
|
||||
"sha2 0.10.9",
|
||||
@@ -812,12 +812,6 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.4"
|
||||
@@ -912,9 +906,9 @@ checksum = "5417da527aa9bf6a1e10a781231effd1edd3ee82f27d5f8529ac9b279babce96"
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.8.1"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
||||
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
@@ -1094,22 +1088,6 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"crossterm_winapi",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.29.0"
|
||||
@@ -2101,8 +2079,6 @@ version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash 0.1.5",
|
||||
]
|
||||
|
||||
@@ -2448,7 +2424,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.3",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -2737,9 +2713,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -2798,6 +2774,17 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kasuari"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
|
||||
dependencies = [
|
||||
"hashbrown 0.16.1",
|
||||
"portable-atomic",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.3"
|
||||
@@ -3078,6 +3065,15 @@ dependencies = [
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "line-clipping"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.15"
|
||||
@@ -3117,15 +3113,6 @@ version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.16.3"
|
||||
@@ -3682,12 +3669,6 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pastey"
|
||||
version = "0.1.1"
|
||||
@@ -4086,7 +4067,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls 0.23.37",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.3",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -4123,7 +4104,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.3",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
@@ -4216,22 +4197,64 @@ checksum = "f66ee92bc15280519ef199a274fe0cafff4245d31bc39aaa31c011ad56cb1f05"
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||
checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc"
|
||||
dependencies = [
|
||||
"instability",
|
||||
"ratatui-core",
|
||||
"ratatui-crossterm",
|
||||
"ratatui-widgets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm 0.28.1",
|
||||
"hashbrown 0.16.1",
|
||||
"indoc",
|
||||
"itertools",
|
||||
"kasuari",
|
||||
"lru",
|
||||
"strum",
|
||||
"thiserror 2.0.18",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-crossterm"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossterm",
|
||||
"instability",
|
||||
"ratatui-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui-widgets"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"hashbrown 0.16.1",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools",
|
||||
"lru 0.12.5",
|
||||
"paste",
|
||||
"line-clipping",
|
||||
"ratatui-core",
|
||||
"strum",
|
||||
"time",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
@@ -5577,23 +5600,22 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -6184,9 +6206,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "tui-realm-stdlib"
|
||||
version = "3.1.0"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "333d8b8da2b3479a68fdf4bd2621f888fa869501d6c5c72bac940d0eeb19b083"
|
||||
checksum = "16a0e19fc8b6ea3a7a88d51ae8df4d657fb4f9a1cdf79f354217e5136838d5dd"
|
||||
dependencies = [
|
||||
"textwrap",
|
||||
"tuirealm",
|
||||
@@ -6195,22 +6217,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tui-term"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72af159125ce32b02ceaced6cffae6394b0e6b6dfd4dc164a6c59a2db9b3c0b0"
|
||||
checksum = "f740cf128bd2ecffea958989646e79a546c45772dac6e42c0b86451185ec6933"
|
||||
dependencies = [
|
||||
"ratatui",
|
||||
"ratatui-core",
|
||||
"ratatui-widgets",
|
||||
"vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tuirealm"
|
||||
version = "3.3.0"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a96abaf33552e9e2487bf56f537e49fad1d1fbc55a3fd542449d4e30e7c8f3"
|
||||
checksum = "257b2348be6fc22fad4b6ba6288207016d88417c071fc96268b5865e24662305"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"crossterm 0.29.0",
|
||||
"crossterm",
|
||||
"dyn-clone",
|
||||
"lazy-regex",
|
||||
"ratatui",
|
||||
@@ -6220,9 +6243,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tuirealm_derive"
|
||||
version = "2.0.1"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad3c151090da5a036321776209b3d026df637d7f52c0984ff8d45927326ab4f"
|
||||
checksum = "19eb242ebcb03507baa4a13392bdfc019c54dbb74492fe5e8b75d1b69ea78ecf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6279,13 +6302,13 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-truncate"
|
||||
version = "1.1.0"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||
checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6721,7 +6744,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+3
-3
@@ -86,9 +86,9 @@ tempfile = "3"
|
||||
thiserror = "2"
|
||||
tokio = { version = "1", features = ["rt"] }
|
||||
toml = "1"
|
||||
tui-realm-stdlib = "3"
|
||||
tui-term = "0.2"
|
||||
tuirealm = "3"
|
||||
tui-realm-stdlib = "4"
|
||||
tui-term = "0.3"
|
||||
tuirealm = "4"
|
||||
unicode-width = "0.2"
|
||||
whoami = "2"
|
||||
wildmatch = "2"
|
||||
|
||||
@@ -611,10 +611,12 @@ mod tests {
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().rewind().unwrap();
|
||||
assert!(deserialize::<Theme>(Box::new(toml_file)).is_ok());
|
||||
// Malformed theme files must still load successfully; unknown or invalid
|
||||
// fields fall back to defaults so user themes remain backwards compatible.
|
||||
let toml_file = create_bad_toml_theme();
|
||||
toml_file.as_file().sync_all().unwrap();
|
||||
toml_file.as_file().rewind().unwrap();
|
||||
assert!(deserialize::<Theme>(Box::new(toml_file)).is_err());
|
||||
assert!(deserialize::<Theme>(Box::new(toml_file)).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+205
-118
@@ -12,142 +12,62 @@ use crate::utils::fmt::fmt_color;
|
||||
use crate::utils::parser::parse_color;
|
||||
|
||||
/// Theme contains all the colors lookup table for termscp
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
pub struct Theme {
|
||||
// -- auth
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub auth_address: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub auth_bookmarks: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub auth_password: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub auth_port: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub auth_protocol: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub auth_recents: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub auth_username: Color,
|
||||
// -- misc
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub misc_error_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub misc_info_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub misc_input_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub misc_keys: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub misc_quit_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub misc_save_dialog: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub misc_warn_dialog: Color,
|
||||
// -- transfer
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_local_explorer_background: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_local_explorer_foreground: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_local_explorer_highlighted: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_log_background: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_log_window: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color",
|
||||
alias = "transfer_progress_bar_full",
|
||||
alias = "transfer_progress_bar_partial"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_progress_bar: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_remote_explorer_background: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_remote_explorer_foreground: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_remote_explorer_highlighted: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_status_hidden: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_status_sorting: Color,
|
||||
#[serde(
|
||||
deserialize_with = "deserialize_color",
|
||||
serialize_with = "serialize_color"
|
||||
)]
|
||||
#[serde(serialize_with = "serialize_color")]
|
||||
pub transfer_status_sync_browsing: Color,
|
||||
}
|
||||
|
||||
@@ -185,16 +105,124 @@ impl Default for Theme {
|
||||
}
|
||||
|
||||
// -- deserializer
|
||||
//
|
||||
// Custom deserialization: every field is optional and falls back to `Theme::default()`
|
||||
// when missing or when the supplied color string is invalid. This keeps user themes
|
||||
// backwards compatible even when fields are added, renamed, or contain typos.
|
||||
|
||||
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
// Parse color
|
||||
match parse_color(&s) {
|
||||
None => Err(DeError::custom("Invalid color")),
|
||||
Some(color) => Ok(color),
|
||||
#[derive(Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
struct ThemeFile {
|
||||
auth_address: Option<String>,
|
||||
auth_bookmarks: Option<String>,
|
||||
auth_password: Option<String>,
|
||||
auth_port: Option<String>,
|
||||
auth_protocol: Option<String>,
|
||||
auth_recents: Option<String>,
|
||||
auth_username: Option<String>,
|
||||
misc_error_dialog: Option<String>,
|
||||
misc_info_dialog: Option<String>,
|
||||
misc_input_dialog: Option<String>,
|
||||
misc_keys: Option<String>,
|
||||
misc_quit_dialog: Option<String>,
|
||||
misc_save_dialog: Option<String>,
|
||||
misc_warn_dialog: Option<String>,
|
||||
transfer_local_explorer_background: Option<String>,
|
||||
transfer_local_explorer_foreground: Option<String>,
|
||||
transfer_local_explorer_highlighted: Option<String>,
|
||||
transfer_log_background: Option<String>,
|
||||
transfer_log_window: Option<String>,
|
||||
transfer_progress_bar: Option<String>,
|
||||
// Legacy aliases for the now-consolidated `transfer_progress_bar` field.
|
||||
transfer_progress_bar_full: Option<String>,
|
||||
transfer_progress_bar_partial: Option<String>,
|
||||
transfer_remote_explorer_background: Option<String>,
|
||||
transfer_remote_explorer_foreground: Option<String>,
|
||||
transfer_remote_explorer_highlighted: Option<String>,
|
||||
transfer_status_hidden: Option<String>,
|
||||
transfer_status_sorting: Option<String>,
|
||||
transfer_status_sync_browsing: Option<String>,
|
||||
}
|
||||
|
||||
impl ThemeFile {
|
||||
fn into_theme(self) -> Theme {
|
||||
let defaults = Theme::default();
|
||||
fn pick(value: Option<String>, fallback: Color) -> Color {
|
||||
value.as_deref().and_then(parse_color).unwrap_or(fallback)
|
||||
}
|
||||
Theme {
|
||||
auth_address: pick(self.auth_address, defaults.auth_address),
|
||||
auth_bookmarks: pick(self.auth_bookmarks, defaults.auth_bookmarks),
|
||||
auth_password: pick(self.auth_password, defaults.auth_password),
|
||||
auth_port: pick(self.auth_port, defaults.auth_port),
|
||||
auth_protocol: pick(self.auth_protocol, defaults.auth_protocol),
|
||||
auth_recents: pick(self.auth_recents, defaults.auth_recents),
|
||||
auth_username: pick(self.auth_username, defaults.auth_username),
|
||||
misc_error_dialog: pick(self.misc_error_dialog, defaults.misc_error_dialog),
|
||||
misc_info_dialog: pick(self.misc_info_dialog, defaults.misc_info_dialog),
|
||||
misc_input_dialog: pick(self.misc_input_dialog, defaults.misc_input_dialog),
|
||||
misc_keys: pick(self.misc_keys, defaults.misc_keys),
|
||||
misc_quit_dialog: pick(self.misc_quit_dialog, defaults.misc_quit_dialog),
|
||||
misc_save_dialog: pick(self.misc_save_dialog, defaults.misc_save_dialog),
|
||||
misc_warn_dialog: pick(self.misc_warn_dialog, defaults.misc_warn_dialog),
|
||||
transfer_local_explorer_background: pick(
|
||||
self.transfer_local_explorer_background,
|
||||
defaults.transfer_local_explorer_background,
|
||||
),
|
||||
transfer_local_explorer_foreground: pick(
|
||||
self.transfer_local_explorer_foreground,
|
||||
defaults.transfer_local_explorer_foreground,
|
||||
),
|
||||
transfer_local_explorer_highlighted: pick(
|
||||
self.transfer_local_explorer_highlighted,
|
||||
defaults.transfer_local_explorer_highlighted,
|
||||
),
|
||||
transfer_log_background: pick(
|
||||
self.transfer_log_background,
|
||||
defaults.transfer_log_background,
|
||||
),
|
||||
transfer_log_window: pick(self.transfer_log_window, defaults.transfer_log_window),
|
||||
transfer_progress_bar: pick(
|
||||
self.transfer_progress_bar
|
||||
.or(self.transfer_progress_bar_full)
|
||||
.or(self.transfer_progress_bar_partial),
|
||||
defaults.transfer_progress_bar,
|
||||
),
|
||||
transfer_remote_explorer_background: pick(
|
||||
self.transfer_remote_explorer_background,
|
||||
defaults.transfer_remote_explorer_background,
|
||||
),
|
||||
transfer_remote_explorer_foreground: pick(
|
||||
self.transfer_remote_explorer_foreground,
|
||||
defaults.transfer_remote_explorer_foreground,
|
||||
),
|
||||
transfer_remote_explorer_highlighted: pick(
|
||||
self.transfer_remote_explorer_highlighted,
|
||||
defaults.transfer_remote_explorer_highlighted,
|
||||
),
|
||||
transfer_status_hidden: pick(
|
||||
self.transfer_status_hidden,
|
||||
defaults.transfer_status_hidden,
|
||||
),
|
||||
transfer_status_sorting: pick(
|
||||
self.transfer_status_sorting,
|
||||
defaults.transfer_status_sorting,
|
||||
),
|
||||
transfer_status_sync_browsing: pick(
|
||||
self.transfer_status_sync_browsing,
|
||||
defaults.transfer_status_sync_browsing,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Theme {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let file = ThemeFile::deserialize(deserializer)?;
|
||||
Ok(file.into_theme())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,14 +235,29 @@ where
|
||||
serializer.serialize_str(s.as_str())
|
||||
}
|
||||
|
||||
// Kept for backwards compatibility with any external callers; no longer used directly
|
||||
// by serde derive because `Theme` now uses a custom `Deserialize` impl.
|
||||
#[allow(dead_code)]
|
||||
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s: String = Deserialize::deserialize(deserializer)?;
|
||||
match parse_color(&s) {
|
||||
None => Err(DeError::custom("Invalid color")),
|
||||
Some(color) => Ok(color),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
mod tests {
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_config_themes_default() {
|
||||
fn should_get_default_theme() {
|
||||
let theme: Theme = Theme::default();
|
||||
assert_eq!(theme.auth_address, Color::Yellow);
|
||||
assert_eq!(theme.auth_bookmarks, Color::LightGreen);
|
||||
@@ -243,4 +286,48 @@ mod test {
|
||||
assert_eq!(theme.transfer_status_sorting, Color::LightYellow);
|
||||
assert_eq!(theme.transfer_status_sync_browsing, Color::LightGreen);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_accept_legacy_progress_bar_fields() {
|
||||
let toml = r#"
|
||||
auth_protocol = "Yellow"
|
||||
transfer_progress_bar_full = "Green"
|
||||
"#;
|
||||
let theme: Theme = toml::from_str(toml).expect("theme should load");
|
||||
assert_eq!(theme.auth_protocol, Color::Yellow);
|
||||
assert_eq!(theme.transfer_progress_bar, Color::Green);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_ignore_duplicated_legacy_progress_bar_fields() {
|
||||
let toml = r#"
|
||||
auth_protocol = "Yellow"
|
||||
transfer_progress_bar_full = "Green"
|
||||
transfer_progress_bar_partial = "Red"
|
||||
"#;
|
||||
let theme: Theme = toml::from_str(toml).expect("theme should load");
|
||||
assert_eq!(theme.auth_protocol, Color::Yellow);
|
||||
// `_full` wins because `transfer_progress_bar` and `_full` are checked first.
|
||||
assert_eq!(theme.transfer_progress_bar, Color::Green);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fall_back_to_defaults_on_invalid_values() {
|
||||
// Invalid color value should make *that* field fall back to default, without
|
||||
// breaking the rest of the theme.
|
||||
let toml = r##"
|
||||
auth_protocol = "not-a-color"
|
||||
auth_username = "#ca9ee6"
|
||||
"##;
|
||||
let theme: Theme = toml::from_str(toml).expect("theme should load");
|
||||
assert_eq!(theme.auth_protocol, Theme::default().auth_protocol);
|
||||
assert_eq!(theme.auth_username, Color::Rgb(202, 158, 230));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_fall_back_to_defaults_on_missing_fields() {
|
||||
// Empty file should still produce a theme equal to default.
|
||||
let theme: Theme = toml::from_str("").expect("theme should load");
|
||||
assert_eq!(theme, Theme::default());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ mod view;
|
||||
// Includes
|
||||
use std::time::Duration;
|
||||
|
||||
use tuirealm::application::PollStrategy;
|
||||
use tuirealm::application::{Application, PollStrategy};
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::listener::EventListenerCfg;
|
||||
use tuirealm::{Application, NoUserEvent, Update};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{Activity, CROSSTERM_MAX_POLL, Context, ExitReason};
|
||||
use crate::config::themes::Theme;
|
||||
@@ -254,9 +255,7 @@ impl AuthActivity {
|
||||
pub fn new(ticks: Duration) -> AuthActivity {
|
||||
AuthActivity {
|
||||
app: Application::init(
|
||||
EventListenerCfg::default()
|
||||
.crossterm_input_listener(ticks, CROSSTERM_MAX_POLL)
|
||||
.poll_timeout(ticks),
|
||||
EventListenerCfg::default().crossterm_input_listener(ticks, CROSSTERM_MAX_POLL),
|
||||
),
|
||||
context: None,
|
||||
bookmarks_list: Vec::new(),
|
||||
@@ -369,7 +368,10 @@ impl Activity for AuthActivity {
|
||||
return;
|
||||
}
|
||||
// Tick
|
||||
match self.app.tick(PollStrategy::UpTo(3)) {
|
||||
match self
|
||||
.app
|
||||
.tick(PollStrategy::UpTo(3, std::time::Duration::from_millis(10)))
|
||||
{
|
||||
Ok(messages) => {
|
||||
for msg in messages.into_iter() {
|
||||
let mut msg = Some(msg);
|
||||
|
||||
@@ -28,19 +28,19 @@ pub use popup::{
|
||||
WindowSizeError,
|
||||
};
|
||||
pub use text::{HelpFooter, NewVersionDisclaimer, Subtitle, Title};
|
||||
use tui_realm_stdlib::Phantom;
|
||||
use tui_realm_stdlib::components::Phantom;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
// -- global listener
|
||||
|
||||
#[derive(Default, MockComponent)]
|
||||
#[derive(Default, Component)]
|
||||
pub struct GlobalListener {
|
||||
component: Phantom,
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GlobalListener {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for GlobalListener {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Function(10),
|
||||
|
||||
@@ -2,18 +2,22 @@
|
||||
//!
|
||||
//! auth activity bookmarks components
|
||||
|
||||
use tui_realm_stdlib::{Input, List, Radio};
|
||||
use tui_realm_stdlib::components::{Input, List, Radio};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderSides, BorderType, Borders, Color, HorizontalAlignment, InputType, SpanStatic, Style,
|
||||
TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::{FormMsg, Msg, UiMsg};
|
||||
use crate::ui::activities::auth::FormTab;
|
||||
|
||||
// -- bookmark list
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct BookmarksList {
|
||||
component: List,
|
||||
}
|
||||
@@ -23,23 +27,23 @@ impl BookmarksList {
|
||||
Self {
|
||||
component: List::default()
|
||||
.borders(Borders::default().color(color).modifiers(BorderType::Plain))
|
||||
.highlighted_color(color)
|
||||
.highlight_style(Style::default().fg(color))
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.title("Bookmarks", Alignment::Left)
|
||||
.title(Title::from("Bookmarks").alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
bookmarks
|
||||
.iter()
|
||||
.map(|x| vec![TextSpan::from(x.as_str())])
|
||||
.collect(),
|
||||
.map(|x| vec![SpanStatic::from(x.clone())])
|
||||
.collect::<Vec<Vec<SpanStatic>>>(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for BookmarksList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for BookmarksList {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -77,7 +81,7 @@ impl Component<Msg, NoUserEvent> for BookmarksList {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::Usize(choice)) => {
|
||||
State::Single(StateValue::Usize(choice)) => {
|
||||
Some(Msg::Form(FormMsg::LoadBookmark(choice)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
@@ -98,7 +102,7 @@ impl Component<Msg, NoUserEvent> for BookmarksList {
|
||||
|
||||
// -- recents list
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct RecentsList {
|
||||
component: List,
|
||||
}
|
||||
@@ -108,23 +112,23 @@ impl RecentsList {
|
||||
Self {
|
||||
component: List::default()
|
||||
.borders(Borders::default().color(color).modifiers(BorderType::Plain))
|
||||
.highlighted_color(color)
|
||||
.highlight_style(Style::default().fg(color))
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.title("Recent connections", Alignment::Left)
|
||||
.title(Title::from("Recent connections").alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
bookmarks
|
||||
.iter()
|
||||
.map(|x| vec![TextSpan::from(x.as_str())])
|
||||
.collect(),
|
||||
.map(|x| vec![SpanStatic::from(x.clone())])
|
||||
.collect::<Vec<Vec<SpanStatic>>>(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for RecentsList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for RecentsList {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -162,7 +166,7 @@ impl Component<Msg, NoUserEvent> for RecentsList {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::Usize(choice)) => {
|
||||
State::Single(StateValue::Usize(choice)) => {
|
||||
Some(Msg::Form(FormMsg::LoadRecent(choice)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
@@ -183,7 +187,7 @@ impl Component<Msg, NoUserEvent> for RecentsList {
|
||||
|
||||
// -- delete bookmark
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct DeleteBookmarkPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -192,6 +196,11 @@ impl DeleteBookmarkPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
@@ -200,14 +209,15 @@ impl DeleteBookmarkPopup {
|
||||
.choices(["Yes", "No"])
|
||||
.value(1)
|
||||
.rewind(true)
|
||||
.foreground(color)
|
||||
.title("Delete selected bookmark?", Alignment::Center),
|
||||
.title(
|
||||
Title::from("Delete selected bookmark?").alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for DeleteBookmarkPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseDeleteBookmark))
|
||||
@@ -237,7 +247,7 @@ impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Form(FormMsg::DeleteBookmark))
|
||||
} else {
|
||||
@@ -251,7 +261,7 @@ impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
|
||||
|
||||
// -- delete recent
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct DeleteRecentPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -260,6 +270,11 @@ impl DeleteRecentPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
@@ -268,14 +283,16 @@ impl DeleteRecentPopup {
|
||||
.choices(["Yes", "No"])
|
||||
.value(1)
|
||||
.rewind(true)
|
||||
.foreground(color)
|
||||
.title("Delete selected recent host?", Alignment::Center),
|
||||
.title(
|
||||
Title::from("Delete selected recent host?")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for DeleteRecentPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseDeleteRecent))
|
||||
@@ -305,7 +322,7 @@ impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Form(FormMsg::DeleteRecent))
|
||||
} else {
|
||||
@@ -321,7 +338,7 @@ impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
|
||||
|
||||
// -- save password
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct BookmarkSavePassword {
|
||||
component: Radio,
|
||||
form_tab: FormTab,
|
||||
@@ -331,6 +348,11 @@ impl BookmarkSavePassword {
|
||||
pub fn new(form_tab: FormTab, color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Reset)
|
||||
@@ -340,15 +362,14 @@ impl BookmarkSavePassword {
|
||||
.choices(["Yes", "No"])
|
||||
.value(0)
|
||||
.rewind(true)
|
||||
.foreground(color)
|
||||
.title("Save secrets?", Alignment::Center),
|
||||
.title(Title::from("Save secrets?").alignment(HorizontalAlignment::Center)),
|
||||
form_tab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for BookmarkSavePassword {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for BookmarkSavePassword {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseSaveBookmark))
|
||||
@@ -378,7 +399,7 @@ impl Component<Msg, NoUserEvent> for BookmarkSavePassword {
|
||||
|
||||
// -- new bookmark name
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct BookmarkName {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -395,15 +416,15 @@ impl BookmarkName {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Bookmark name", Alignment::Left)
|
||||
.title(Title::from("Bookmark name").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text),
|
||||
form_tab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for BookmarkName {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for BookmarkName {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseSaveBookmark))
|
||||
@@ -447,7 +468,7 @@ impl Component<Msg, NoUserEvent> for BookmarkName {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
//!
|
||||
//! auth activity components for file transfer params form
|
||||
|
||||
use tui_realm_stdlib::{Input, Radio};
|
||||
use tuirealm::Component;
|
||||
use tui_realm_stdlib::components::{Input, Radio};
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::component::AppComponent;
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, InputType, Style, TextModifiers, Title,
|
||||
};
|
||||
|
||||
use super::{FileTransferProtocol, FormMsg, Msg, UiMsg};
|
||||
use crate::ui::activities::auth::{
|
||||
@@ -51,8 +53,8 @@ pub use smb::InputSmbWorkgroup;
|
||||
pub use webdav::InputWebDAVUri;
|
||||
|
||||
fn handle_input_ev(
|
||||
component: &mut dyn Component<Msg, NoUserEvent>,
|
||||
ev: Event<NoUserEvent>,
|
||||
component: &mut dyn AppComponent<Msg, NoUserEvent>,
|
||||
ev: &Event<NoUserEvent>,
|
||||
on_key_down: Msg,
|
||||
on_key_up: Msg,
|
||||
form_tab: FormTab,
|
||||
@@ -104,7 +106,7 @@ fn handle_input_ev(
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
component.perform(Cmd::Type(ch));
|
||||
component.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputAddress {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -19,8 +19,11 @@ impl InputAddress {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("127.0.0.1", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Remote host", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"127.0.0.1",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Remote host").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
form_tab,
|
||||
@@ -28,8 +31,8 @@ impl InputAddress {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputAddress {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputAddress {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::AddressBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::AddressBlurDown)),
|
||||
@@ -44,7 +47,7 @@ impl Component<Msg, NoUserEvent> for InputAddress {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputPort {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -60,18 +63,21 @@ impl InputPort {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("22", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"22",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.input_type(InputType::UnsignedInteger)
|
||||
.input_len(5)
|
||||
.title("Port number", Alignment::Left)
|
||||
.title(Title::from("Port number").alignment(HorizontalAlignment::Left))
|
||||
.value(port.to_string()),
|
||||
form_tab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputPort {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputPort {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::PortBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::PortBlurDown)),
|
||||
@@ -86,7 +92,7 @@ impl Component<Msg, NoUserEvent> for InputPort {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputUsername {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -102,8 +108,11 @@ impl InputUsername {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("root", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Username", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"root",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Username").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(username),
|
||||
form_tab,
|
||||
@@ -111,8 +120,8 @@ impl InputUsername {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputUsername {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputUsername {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::UsernameBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::UsernameBlurDown)),
|
||||
@@ -127,7 +136,7 @@ impl Component<Msg, NoUserEvent> for InputUsername {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputPassword {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -143,7 +152,7 @@ impl InputPassword {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Password", Alignment::Left)
|
||||
.title(Title::from("Password").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Password('*'))
|
||||
.value(password),
|
||||
form_tab,
|
||||
@@ -151,8 +160,8 @@ impl InputPassword {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputPassword {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputPassword {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::PasswordBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::PasswordBlurDown)),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputKubeNamespace {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -19,8 +19,11 @@ impl InputKubeNamespace {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("namespace", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Pod namespace (optional)", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"namespace",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Pod namespace (optional)").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(bucket),
|
||||
form_tab,
|
||||
@@ -28,8 +31,8 @@ impl InputKubeNamespace {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputKubeNamespace {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputKubeNamespace {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeNamespaceBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::KubeNamespaceBlurDown)),
|
||||
@@ -44,7 +47,7 @@ impl Component<Msg, NoUserEvent> for InputKubeNamespace {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputKubeClusterUrl {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -60,11 +63,13 @@ impl InputKubeClusterUrl {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"cluster url",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("Kube cluster url (optional)").alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.title("Kube cluster url (optional)", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(bucket),
|
||||
form_tab,
|
||||
@@ -72,8 +77,8 @@ impl InputKubeClusterUrl {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputKubeClusterUrl {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputKubeClusterUrl {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeClusterUrlBlurDown)),
|
||||
FormTab::HostBridge => {
|
||||
@@ -90,7 +95,7 @@ impl Component<Msg, NoUserEvent> for InputKubeClusterUrl {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputKubeUsername {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -106,8 +111,11 @@ impl InputKubeUsername {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("username", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Kube username (optional)", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"username",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Kube username (optional)").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(bucket),
|
||||
form_tab,
|
||||
@@ -115,8 +123,8 @@ impl InputKubeUsername {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputKubeUsername {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputKubeUsername {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeUsernameBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::KubeUsernameBlurDown)),
|
||||
@@ -131,7 +139,7 @@ impl Component<Msg, NoUserEvent> for InputKubeUsername {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputKubeClientCert {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -147,11 +155,14 @@ impl InputKubeClientCert {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"/home/user/.kube/client.crt",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("Kube client cert path (optional)")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.title("Kube client cert path (optional)", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(bucket),
|
||||
form_tab,
|
||||
@@ -159,8 +170,8 @@ impl InputKubeClientCert {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputKubeClientCert {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputKubeClientCert {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeClientCertBlurDown)),
|
||||
FormTab::HostBridge => {
|
||||
@@ -177,7 +188,7 @@ impl Component<Msg, NoUserEvent> for InputKubeClientCert {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputKubeClientKey {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -193,11 +204,14 @@ impl InputKubeClientKey {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"/home/user/.kube/client.key",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("Kube client key path (optional)")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.title("Kube client key path (optional)", Alignment::Left)
|
||||
.input_type(InputType::Text)
|
||||
.value(bucket),
|
||||
form_tab,
|
||||
@@ -205,8 +219,8 @@ impl InputKubeClientKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputKubeClientKey {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputKubeClientKey {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::KubeClientKeyBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::KubeClientKeyBlurDown)),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputRemoteDirectory {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -19,8 +19,14 @@ impl InputRemoteDirectory {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("/home/foo", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Default remote working directory", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"/home/foo",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("Default remote working directory")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.input_type(InputType::Text)
|
||||
.value(remote_dir),
|
||||
form_tab,
|
||||
@@ -28,8 +34,8 @@ impl InputRemoteDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputRemoteDirectory {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputRemoteDirectory {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::RemoteDirectoryBlurDown)),
|
||||
FormTab::HostBridge => {
|
||||
@@ -46,7 +52,7 @@ impl Component<Msg, NoUserEvent> for InputRemoteDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputLocalDirectory {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -62,8 +68,14 @@ impl InputLocalDirectory {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("/home/foo", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Default local working directory", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"/home/foo",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("Default local working directory")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.input_type(InputType::Text)
|
||||
.value(local_dir),
|
||||
form_tab,
|
||||
@@ -71,8 +83,8 @@ impl InputLocalDirectory {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputLocalDirectory {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputLocalDirectory {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::LocalDirectoryBlurDown)),
|
||||
FormTab::HostBridge => {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color};
|
||||
use tuirealm::{Component, MockComponent, State, StateValue};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct RemoteProtocolRadio {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -14,6 +17,11 @@ impl RemoteProtocolRadio {
|
||||
pub fn new(default_protocol: FileTransferProtocol, color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
@@ -24,9 +32,8 @@ impl RemoteProtocolRadio {
|
||||
} else {
|
||||
vec!["SFTP", "SCP", "FTP", "FTPS", "S3", "Kube", "WebDAV"].into_iter()
|
||||
})
|
||||
.foreground(color)
|
||||
.rewind(true)
|
||||
.title("Protocol", Alignment::Left)
|
||||
.title(Title::from("Protocol").alignment(HorizontalAlignment::Left))
|
||||
.value(Self::protocol_enum_to_opt(default_protocol)),
|
||||
}
|
||||
}
|
||||
@@ -58,8 +65,8 @@ impl RemoteProtocolRadio {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for RemoteProtocolRadio {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for RemoteProtocolRadio {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let result = match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -86,7 +93,7 @@ impl Component<Msg, NoUserEvent> for RemoteProtocolRadio {
|
||||
};
|
||||
|
||||
match result {
|
||||
CmdResult::Changed(State::One(StateValue::Usize(choice))) => Some(Msg::Form(
|
||||
CmdResult::Changed(State::Single(StateValue::Usize(choice))) => Some(Msg::Form(
|
||||
FormMsg::RemoteProtocolChanged(Self::protocol_opt_to_enum(choice)),
|
||||
)),
|
||||
_ => Some(Msg::None),
|
||||
@@ -94,7 +101,7 @@ impl Component<Msg, NoUserEvent> for RemoteProtocolRadio {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct HostBridgeProtocolRadio {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -103,6 +110,11 @@ impl HostBridgeProtocolRadio {
|
||||
pub fn new(protocol: HostBridgeProtocol, color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
@@ -134,9 +146,8 @@ impl HostBridgeProtocolRadio {
|
||||
]
|
||||
.into_iter()
|
||||
})
|
||||
.foreground(color)
|
||||
.rewind(true)
|
||||
.title("Host type", Alignment::Left)
|
||||
.title(Title::from("Host type").alignment(HorizontalAlignment::Left))
|
||||
.value(Self::protocol_to_opt(protocol)),
|
||||
}
|
||||
}
|
||||
@@ -195,8 +206,8 @@ impl HostBridgeProtocolRadio {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for HostBridgeProtocolRadio {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for HostBridgeProtocolRadio {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let result = match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -223,7 +234,7 @@ impl Component<Msg, NoUserEvent> for HostBridgeProtocolRadio {
|
||||
};
|
||||
|
||||
match result {
|
||||
CmdResult::Changed(State::One(StateValue::Usize(choice))) => Some(Msg::Form(
|
||||
CmdResult::Changed(State::Single(StateValue::Usize(choice))) => Some(Msg::Form(
|
||||
FormMsg::HostBridgeProtocolChanged(Self::protocol_opt_to_enum(choice)),
|
||||
)),
|
||||
_ => Some(Msg::None),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use tuirealm::command::{Cmd, Direction};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3Bucket {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -20,8 +20,11 @@ impl InputS3Bucket {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("my-bucket", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Bucket name", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"my-bucket",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Bucket name").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(bucket),
|
||||
form_tab,
|
||||
@@ -29,8 +32,8 @@ impl InputS3Bucket {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3Bucket {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3Bucket {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3BucketBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3BucketBlurDown)),
|
||||
@@ -45,7 +48,7 @@ impl Component<Msg, NoUserEvent> for InputS3Bucket {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3Region {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -61,8 +64,11 @@ impl InputS3Region {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("eu-west-1", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Region", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"eu-west-1",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Region").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(region),
|
||||
form_tab,
|
||||
@@ -70,8 +76,8 @@ impl InputS3Region {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3Region {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3Region {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3RegionBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3RegionBlurDown)),
|
||||
@@ -86,7 +92,7 @@ impl Component<Msg, NoUserEvent> for InputS3Region {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3Endpoint {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -102,11 +108,11 @@ impl InputS3Endpoint {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"http://localhost:9000",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Endpoint", Alignment::Left)
|
||||
))
|
||||
.title(Title::from("Endpoint").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(endpoint),
|
||||
form_tab,
|
||||
@@ -114,8 +120,8 @@ impl InputS3Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3Endpoint {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3Endpoint {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3EndpointBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3EndpointBlurDown)),
|
||||
@@ -130,7 +136,7 @@ impl Component<Msg, NoUserEvent> for InputS3Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct RadioS3NewPathStyle {
|
||||
component: Radio,
|
||||
form_tab: FormTab,
|
||||
@@ -140,23 +146,27 @@ impl RadioS3NewPathStyle {
|
||||
pub fn new(new_path_style: bool, form_tab: FormTab, color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(["Yes", "No"])
|
||||
.foreground(color)
|
||||
.rewind(true)
|
||||
.title("New path style", Alignment::Left)
|
||||
.title(Title::from("New path style").alignment(HorizontalAlignment::Left))
|
||||
.value(usize::from(!new_path_style)),
|
||||
form_tab,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for RadioS3NewPathStyle {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for RadioS3NewPathStyle {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -199,7 +209,7 @@ impl Component<Msg, NoUserEvent> for RadioS3NewPathStyle {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3Profile {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -215,8 +225,11 @@ impl InputS3Profile {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("default", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Profile", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"default",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Profile").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(profile),
|
||||
form_tab,
|
||||
@@ -224,8 +237,8 @@ impl InputS3Profile {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3Profile {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3Profile {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3ProfileBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3ProfileBlurDown)),
|
||||
@@ -240,7 +253,7 @@ impl Component<Msg, NoUserEvent> for InputS3Profile {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3AccessKey {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -256,8 +269,11 @@ impl InputS3AccessKey {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder("AKIA...", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Access key", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"AKIA...",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Access key").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(access_key),
|
||||
form_tab,
|
||||
@@ -265,8 +281,8 @@ impl InputS3AccessKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3AccessKey {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3AccessKey {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3AccessKeyBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::S3AccessKeyBlurDown)),
|
||||
@@ -281,7 +297,7 @@ impl Component<Msg, NoUserEvent> for InputS3AccessKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3SecretAccessKey {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -297,7 +313,7 @@ impl InputS3SecretAccessKey {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Secret access key", Alignment::Left)
|
||||
.title(Title::from("Secret access key").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Password('*'))
|
||||
.value(secret_access_key),
|
||||
form_tab,
|
||||
@@ -305,8 +321,8 @@ impl InputS3SecretAccessKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3SecretAccessKey {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3SecretAccessKey {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3SecretAccessKeyBlurDown)),
|
||||
FormTab::HostBridge => {
|
||||
@@ -325,7 +341,7 @@ impl Component<Msg, NoUserEvent> for InputS3SecretAccessKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3SecurityToken {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -341,7 +357,7 @@ impl InputS3SecurityToken {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Security token", Alignment::Left)
|
||||
.title(Title::from("Security token").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Password('*'))
|
||||
.value(security_token),
|
||||
form_tab,
|
||||
@@ -349,8 +365,8 @@ impl InputS3SecurityToken {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3SecurityToken {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3SecurityToken {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3SecurityTokenBlurDown)),
|
||||
FormTab::HostBridge => {
|
||||
@@ -367,7 +383,7 @@ impl Component<Msg, NoUserEvent> for InputS3SecurityToken {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputS3SessionToken {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -383,7 +399,7 @@ impl InputS3SessionToken {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Session token", Alignment::Left)
|
||||
.title(Title::from("Session token").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Password('*'))
|
||||
.value(session_token),
|
||||
form_tab,
|
||||
@@ -391,8 +407,8 @@ impl InputS3SessionToken {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputS3SessionToken {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputS3SessionToken {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::S3SessionTokenBlurDown)),
|
||||
FormTab::HostBridge => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputSmbShare {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -19,7 +19,7 @@ impl InputSmbShare {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Share", Alignment::Left)
|
||||
.title(Title::from("Share").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
form_tab,
|
||||
@@ -27,8 +27,8 @@ impl InputSmbShare {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputSmbShare {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputSmbShare {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::SmbShareBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::SmbShareBlurDown)),
|
||||
@@ -44,7 +44,7 @@ impl Component<Msg, NoUserEvent> for InputSmbShare {
|
||||
}
|
||||
|
||||
#[cfg(posix)]
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputSmbWorkgroup {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -61,7 +61,7 @@ impl InputSmbWorkgroup {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Workgroup", Alignment::Left)
|
||||
.title(Title::from("Workgroup").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
form_tab,
|
||||
@@ -70,8 +70,8 @@ impl InputSmbWorkgroup {
|
||||
}
|
||||
|
||||
#[cfg(posix)]
|
||||
impl Component<Msg, NoUserEvent> for InputSmbWorkgroup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputSmbWorkgroup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::SmbWorkgroupDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::SmbWorkgroupDown)),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InputWebDAVUri {
|
||||
component: Input,
|
||||
form_tab: FormTab,
|
||||
@@ -19,11 +19,11 @@ impl InputWebDAVUri {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"http://localhost:8080",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("HTTP url", Alignment::Left)
|
||||
))
|
||||
.title(Title::from("HTTP url").alignment(HorizontalAlignment::Left))
|
||||
.input_type(InputType::Text)
|
||||
.value(host),
|
||||
form_tab,
|
||||
@@ -31,8 +31,8 @@ impl InputWebDAVUri {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputWebDAVUri {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputWebDAVUri {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let on_key_down = match self.form_tab {
|
||||
FormTab::Remote => Msg::Ui(UiMsg::Remote(UiAuthFormMsg::WebDAVUriBlurDown)),
|
||||
FormTab::HostBridge => Msg::Ui(UiMsg::HostBridge(UiAuthFormMsg::WebDAVUriBlurDown)),
|
||||
|
||||
@@ -2,17 +2,23 @@
|
||||
//!
|
||||
//! auth activity popups
|
||||
|
||||
use tui_realm_stdlib::{List, Paragraph, Radio, Textarea};
|
||||
use tui_realm_stdlib::components::{List, Paragraph, Radio, Textarea};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, SpanStatic, Style, TableBuilder,
|
||||
TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
use tuirealm::ratatui::text::Text;
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::{FormMsg, Msg, UiMsg};
|
||||
|
||||
// -- error popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ErrorPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -21,21 +27,23 @@ impl ErrorPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
text.as_ref().to_string(),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ErrorPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ErrorPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
@@ -48,7 +56,7 @@ impl Component<Msg, NoUserEvent> for ErrorPopup {
|
||||
|
||||
// -- info popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InfoPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -57,21 +65,23 @@ impl InfoPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
text.as_ref().to_string(),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InfoPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InfoPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
@@ -84,7 +94,7 @@ impl Component<Msg, NoUserEvent> for InfoPopup {
|
||||
|
||||
// -- wait popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct WaitPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -93,28 +103,30 @@ impl WaitPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
text.as_ref().to_string(),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WaitPopup {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for WaitPopup {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- window size error
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct WindowSizeError {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -123,30 +135,30 @@ impl WindowSizeError {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([TextSpan::from(
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
"termscp requires at least 24 lines of height to run",
|
||||
)])
|
||||
.wrap(true),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WindowSizeError {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for WindowSizeError {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- quit popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct QuitPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -155,21 +167,25 @@ impl QuitPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Quit termscp?", Alignment::Center)
|
||||
.title(Title::from("Quit termscp?").alignment(HorizontalAlignment::Center))
|
||||
.rewind(true)
|
||||
.choices(["Yes", "No"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for QuitPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseQuitPopup))
|
||||
@@ -199,7 +215,7 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Form(FormMsg::Quit))
|
||||
} else {
|
||||
@@ -213,7 +229,7 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
|
||||
// -- install update popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct InstallUpdatePopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -222,21 +238,25 @@ impl InstallUpdatePopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Install update?", Alignment::Center)
|
||||
.title(Title::from("Install update?").alignment(HorizontalAlignment::Center))
|
||||
.rewind(true)
|
||||
.choices(["Yes", "No"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InstallUpdatePopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseInstallUpdatePopup))
|
||||
@@ -266,7 +286,7 @@ impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Form(FormMsg::InstallUpdate))
|
||||
} else {
|
||||
@@ -280,7 +300,7 @@ impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
|
||||
|
||||
// -- release notes popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ReleaseNotes {
|
||||
component: Textarea,
|
||||
}
|
||||
@@ -295,14 +315,14 @@ impl ReleaseNotes {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.title("Release notes", Alignment::Center)
|
||||
.text_rows(notes.lines().map(TextSpan::from)),
|
||||
.title(Title::from("Release notes").alignment(HorizontalAlignment::Center))
|
||||
.text_rows(notes.lines().map(|l| SpanStatic::from(l.to_string()))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ReleaseNotes {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ReleaseNotes {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
@@ -348,7 +368,7 @@ impl Component<Msg, NoUserEvent> for ReleaseNotes {
|
||||
|
||||
// -- keybindings popup
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct Keybindings {
|
||||
component: List,
|
||||
}
|
||||
@@ -362,43 +382,47 @@ impl Keybindings {
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.highlighted_str("? ")
|
||||
.title("Keybindings", Alignment::Center)
|
||||
.highlight_str("? ")
|
||||
.title(Title::from("Keybindings").alignment(HorizontalAlignment::Center))
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.rows(
|
||||
TableBuilder::default()
|
||||
.add_col(TextSpan::new("<ESC>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Quit termscp"))
|
||||
.add_col(SpanStatic::raw("<ESC>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(" Quit termscp"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<TAB>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Switch from form and bookmarks"))
|
||||
.add_col(SpanStatic::raw("<TAB>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Switch from form and bookmarks",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Switch bookmark tab"))
|
||||
.add_col(SpanStatic::raw("<RIGHT/LEFT>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(" Switch bookmark tab"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Move up/down in current tab"))
|
||||
.add_col(SpanStatic::raw("<UP/DOWN>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(" Move up/down in current tab"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<ENTER>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Connect/Load bookmark"))
|
||||
.add_col(SpanStatic::raw("<ENTER>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(" Connect/Load bookmark"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<DEL|E>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Delete selected bookmark"))
|
||||
.add_col(SpanStatic::raw("<DEL|E>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(" Delete selected bookmark"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+C>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Enter setup"))
|
||||
.add_col(SpanStatic::raw("<CTRL+C>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(" Enter setup"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+S>").bold().fg(color))
|
||||
.add_col(TextSpan::from(" Save bookmark"))
|
||||
.build(),
|
||||
.add_col(SpanStatic::raw("<CTRL+S>").bold().fg(color))
|
||||
.add_col(SpanStatic::from(" Save bookmark"))
|
||||
.build()
|
||||
.into_iter()
|
||||
.map(|row| row.into_iter().flat_map(|l| l.spans).collect::<Vec<_>>()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Keybindings {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Keybindings {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
//!
|
||||
//! auth activity texts
|
||||
|
||||
use tui_realm_stdlib::{Label, Span};
|
||||
use tuirealm::props::{Color, TextModifiers, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::{Label, Span};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, NoUserEvent};
|
||||
use tuirealm::props::{Color, SpanStatic, TextModifiers};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
|
||||
use super::Msg;
|
||||
|
||||
// -- Title
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct Title {
|
||||
component: Label,
|
||||
}
|
||||
@@ -25,15 +27,15 @@ impl Default for Title {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Title {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Title {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- subtitle
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct Subtitle {
|
||||
component: Label,
|
||||
}
|
||||
@@ -48,15 +50,15 @@ impl Default for Subtitle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Subtitle {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Subtitle {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- new version disclaimer
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct NewVersionDisclaimer {
|
||||
component: Span,
|
||||
}
|
||||
@@ -65,9 +67,9 @@ impl NewVersionDisclaimer {
|
||||
pub fn new(new_version: &str, color: Color) -> Self {
|
||||
Self {
|
||||
component: Span::default().foreground(color).spans([
|
||||
TextSpan::from("termscp "),
|
||||
TextSpan::new(new_version).underlined().bold(),
|
||||
TextSpan::from(
|
||||
SpanStatic::from("termscp "),
|
||||
SpanStatic::raw(new_version.to_string()).underlined().bold(),
|
||||
SpanStatic::from(
|
||||
" is NOW available! Install update and view release notes with <CTRL+R>",
|
||||
),
|
||||
]),
|
||||
@@ -75,15 +77,15 @@ impl NewVersionDisclaimer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for NewVersionDisclaimer {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for NewVersionDisclaimer {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// -- HelpFooter
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct HelpFooter {
|
||||
component: Span,
|
||||
}
|
||||
@@ -92,27 +94,27 @@ impl HelpFooter {
|
||||
pub fn new(key_color: Color) -> Self {
|
||||
Self {
|
||||
component: Span::default().spans([
|
||||
TextSpan::from("<F1|CTRL+H>").bold().fg(key_color),
|
||||
TextSpan::from(" Help "),
|
||||
TextSpan::from("<CTRL+C>").bold().fg(key_color),
|
||||
TextSpan::from(" Enter setup "),
|
||||
TextSpan::from("<UP/DOWN>").bold().fg(key_color),
|
||||
TextSpan::from(" Change field "),
|
||||
TextSpan::from("<TAB>").bold().fg(key_color),
|
||||
TextSpan::from(" Switch tab "),
|
||||
TextSpan::from("<BACKTAB>").bold().fg(key_color),
|
||||
TextSpan::from(" Switch form "),
|
||||
TextSpan::from("<ENTER>").bold().fg(key_color),
|
||||
TextSpan::from(" Submit form "),
|
||||
TextSpan::from("<F10|ESC>").bold().fg(key_color),
|
||||
TextSpan::from(" Quit "),
|
||||
SpanStatic::from("<F1|CTRL+H>").bold().fg(key_color),
|
||||
SpanStatic::from(" Help "),
|
||||
SpanStatic::from("<CTRL+C>").bold().fg(key_color),
|
||||
SpanStatic::from(" Enter setup "),
|
||||
SpanStatic::from("<UP/DOWN>").bold().fg(key_color),
|
||||
SpanStatic::from(" Change field "),
|
||||
SpanStatic::from("<TAB>").bold().fg(key_color),
|
||||
SpanStatic::from(" Switch tab "),
|
||||
SpanStatic::from("<BACKTAB>").bold().fg(key_color),
|
||||
SpanStatic::from(" Switch form "),
|
||||
SpanStatic::from("<ENTER>").bold().fg(key_color),
|
||||
SpanStatic::from(" Submit form "),
|
||||
SpanStatic::from("<F10|ESC>").bold().fg(key_color),
|
||||
SpanStatic::from(" Quit "),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for HelpFooter {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for HelpFooter {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
//!
|
||||
//! Update impl
|
||||
|
||||
use tuirealm::{State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::{
|
||||
AuthActivity, AuthFormId, ExitReason, FormMsg, FormTab, HostBridgeProtocol, Id, InputMask, Msg,
|
||||
UiAuthFormMsg, UiMsg, Update,
|
||||
UiAuthFormMsg, UiMsg,
|
||||
};
|
||||
|
||||
impl Update<Msg> for AuthActivity {
|
||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
impl AuthActivity {
|
||||
pub(super) fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
self.redraw = true;
|
||||
match msg.unwrap_or(Msg::None) {
|
||||
Msg::Form(msg) => self.update_form(msg),
|
||||
@@ -49,7 +49,9 @@ impl AuthActivity {
|
||||
self.exit_reason = Some(super::ExitReason::Connect);
|
||||
}
|
||||
FormMsg::DeleteBookmark => {
|
||||
if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::BookmarksList) {
|
||||
if let Ok(State::Single(StateValue::Usize(idx))) =
|
||||
self.app.state(&Id::BookmarksList)
|
||||
{
|
||||
// Umount dialog
|
||||
self.umount_bookmark_del_dialog();
|
||||
// Delete bookmark
|
||||
@@ -59,7 +61,8 @@ impl AuthActivity {
|
||||
}
|
||||
}
|
||||
FormMsg::DeleteRecent => {
|
||||
if let Ok(State::One(StateValue::Usize(idx))) = self.app.state(&Id::RecentsList) {
|
||||
if let Ok(State::Single(StateValue::Usize(idx))) = self.app.state(&Id::RecentsList)
|
||||
{
|
||||
// Umount dialog
|
||||
self.umount_recent_del_dialog();
|
||||
// Delete recent
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
use tuirealm::props::Color;
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::ratatui::widgets::Clear;
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{
|
||||
AuthActivity, AuthFormId, Context, FileTransferProtocol, FormTab, HostBridgeProtocol, Id,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use tuirealm::{State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::*;
|
||||
use crate::filetransfer::FileTransferParams;
|
||||
@@ -123,7 +123,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::RemoteDirectory))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => {
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => {
|
||||
Some(PathBuf::from(x.as_str()))
|
||||
}
|
||||
_ => None,
|
||||
@@ -138,7 +138,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::LocalDirectory))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => {
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => {
|
||||
Some(PathBuf::from(x.as_str()))
|
||||
}
|
||||
_ => None,
|
||||
@@ -150,7 +150,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::WebDAVUri))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) => x,
|
||||
Ok(State::Single(StateValue::String(x))) => x,
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::Address))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) => x,
|
||||
Ok(State::Single(StateValue::String(x))) => x,
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,9 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::Port))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) => u16::from_str(x.as_str()).unwrap_or_default(),
|
||||
Ok(State::Single(StateValue::String(x))) => {
|
||||
u16::from_str(x.as_str()).unwrap_or_default()
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
@@ -183,7 +185,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::Username))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -196,7 +198,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::Password))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -206,7 +208,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3Bucket))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) => x,
|
||||
Ok(State::Single(StateValue::String(x))) => x,
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
@@ -219,7 +221,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3Region))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -232,7 +234,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3Endpoint))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -245,7 +247,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3Profile))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -258,7 +260,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3AccessKey))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -271,7 +273,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3SecretAccessKey))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -284,7 +286,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3SecurityToken))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -297,7 +299,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3SessionToken))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -309,7 +311,7 @@ impl AuthActivity {
|
||||
matches!(
|
||||
self.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::S3NewPathStyle)),
|
||||
Ok(State::One(StateValue::Usize(0)))
|
||||
Ok(State::Single(StateValue::Usize(0)))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -321,7 +323,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::KubeNamespace))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -334,7 +336,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::KubeClusterUrl))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -347,7 +349,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::KubeUsername))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -360,7 +362,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::KubeClientCert))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -373,7 +375,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::KubeClientKey))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) if !x.is_empty() => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -383,7 +385,7 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::SmbShare))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) => x,
|
||||
Ok(State::Single(StateValue::String(x))) => x,
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
@@ -397,19 +399,19 @@ impl AuthActivity {
|
||||
.app
|
||||
.state(&Self::form_tab_id(form_tab, AuthFormId::SmbWorkgroup))
|
||||
{
|
||||
Ok(State::One(StateValue::String(x))) => Some(x),
|
||||
Ok(State::Single(StateValue::String(x))) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::ui::activities::auth) fn get_new_bookmark(&self) -> (String, bool) {
|
||||
let name = match self.app.state(&Id::BookmarkName) {
|
||||
Ok(State::One(StateValue::String(name))) => name,
|
||||
Ok(State::Single(StateValue::String(name))) => name,
|
||||
_ => String::default(),
|
||||
};
|
||||
if matches!(
|
||||
self.app.state(&Id::BookmarkSavePassword),
|
||||
Ok(State::One(StateValue::Usize(0)))
|
||||
Ok(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
(name, true)
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use tuirealm::{Sub, SubClause, SubEventClause};
|
||||
use tuirealm::subscription::{EventClause, Sub, SubClause};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -435,55 +435,55 @@ impl AuthActivity {
|
||||
Box::<components::GlobalListener>::default(),
|
||||
vec![
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Esc,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Function(10),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('c'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('h'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Function(1),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('r'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('s'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(SubEventClause::WindowResize, SubClause::Always),
|
||||
Sub::new(EventClause::WindowResize, SubClause::Always),
|
||||
],
|
||||
) {
|
||||
error!("Failed to mount component: {err}");
|
||||
|
||||
@@ -26,7 +26,10 @@ use lib::transfer::{TransferOpts, TransferStates};
|
||||
use lib::walkdir::WalkdirStates;
|
||||
use session::TransferPayload;
|
||||
use tempfile::TempDir;
|
||||
use tuirealm::{Application, EventListenerCfg, NoUserEvent};
|
||||
use tuirealm::application::Application;
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::listener::EventListenerCfg;
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{Activity, CROSSTERM_MAX_POLL, Context, ExitReason};
|
||||
use crate::config::themes::Theme;
|
||||
@@ -286,9 +289,7 @@ impl FileTransferActivity {
|
||||
exit_reason: None,
|
||||
context: None,
|
||||
app: Application::init(
|
||||
EventListenerCfg::default()
|
||||
.poll_timeout(ticks)
|
||||
.crossterm_input_listener(ticks, CROSSTERM_MAX_POLL),
|
||||
EventListenerCfg::default().crossterm_input_listener(ticks, CROSSTERM_MAX_POLL),
|
||||
),
|
||||
redraw: true,
|
||||
browser: Browser::new(local_pane, remote_pane),
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use remotefs::File;
|
||||
use remotefs::fs::UnixPex;
|
||||
use tuirealm::{State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::browser::FileExplorerTab;
|
||||
use super::lib::browser::FoundExplorerTab;
|
||||
@@ -135,7 +135,7 @@ impl FileTransferActivity {
|
||||
|
||||
fn get_selected_index(&self, id: &Id) -> SelectedFileIndex {
|
||||
match self.app.state(id) {
|
||||
Ok(State::One(StateValue::Usize(idx))) => SelectedFileIndex::One(idx),
|
||||
Ok(State::Single(StateValue::Usize(idx))) => SelectedFileIndex::One(idx),
|
||||
_ => SelectedFileIndex::None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use std::time::SystemTime;
|
||||
|
||||
use remotefs::File;
|
||||
use remotefs::fs::Metadata;
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::super::ui_result;
|
||||
use super::{FileTransferActivity, LogLevel, SelectedFile, TransferPayload};
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
// ext
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{File, FileTransferActivity, LogLevel, TransferPayload};
|
||||
|
||||
impl FileTransferActivity {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! this little module exposes the routine to create a pending action on the file transfer activity.
|
||||
//! A pending action is an action which blocks the execution of the application in await of a certain `Msg`.
|
||||
|
||||
use tuirealm::{PollStrategy, Update};
|
||||
use tuirealm::application::PollStrategy;
|
||||
|
||||
use super::{FileTransferActivity, Msg};
|
||||
|
||||
@@ -18,7 +18,10 @@ impl FileTransferActivity {
|
||||
self.redraw = true;
|
||||
loop {
|
||||
// Poll
|
||||
match self.app.tick(PollStrategy::Once) {
|
||||
match self
|
||||
.app
|
||||
.tick(PollStrategy::Once(std::time::Duration::from_millis(10)))
|
||||
{
|
||||
Ok(mut messages) => {
|
||||
if !messages.is_empty() {
|
||||
self.redraw = true;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
//!
|
||||
//! file transfer activity components
|
||||
|
||||
use tui_realm_stdlib::Phantom;
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::{Component, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::Phantom;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
|
||||
use super::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
@@ -30,13 +30,13 @@ pub use self::log::Log;
|
||||
pub use self::selected_files::SelectedFilesList;
|
||||
pub use self::terminal::Terminal;
|
||||
|
||||
#[derive(Default, MockComponent)]
|
||||
#[derive(Default, Component)]
|
||||
pub struct GlobalListener {
|
||||
component: Phantom,
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GlobalListener {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for GlobalListener {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
//! log tab component
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Style, Table};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
AttrValue, Attribute, Borders, Color, HorizontalAlignment, Props, QueryResult, Style, Table,
|
||||
Title,
|
||||
};
|
||||
use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::{Msg, UiMsg};
|
||||
|
||||
@@ -31,38 +35,47 @@ impl Log {
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for Log {
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||
let width: usize = area.width as usize - 4;
|
||||
impl Component for Log {
|
||||
fn view(
|
||||
&mut self,
|
||||
frame: &mut tuirealm::ratatui::Frame,
|
||||
area: tuirealm::ratatui::layout::Rect,
|
||||
) {
|
||||
let _width: usize = area.width as usize - 4;
|
||||
let focus = self
|
||||
.props
|
||||
.get_or(Attribute::Focus, AttrValue::Flag(false))
|
||||
.unwrap_flag();
|
||||
.get(Attribute::Focus)
|
||||
.and_then(AttrValue::as_flag)
|
||||
.unwrap_or(false);
|
||||
let borders = self
|
||||
.props
|
||||
.get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
|
||||
.unwrap_borders();
|
||||
.get(Attribute::Borders)
|
||||
.and_then(AttrValue::as_borders)
|
||||
.unwrap_or_default();
|
||||
let bg = self
|
||||
.props
|
||||
.get_or(Attribute::Background, AttrValue::Color(Color::Reset))
|
||||
.unwrap_color();
|
||||
.get(Attribute::Background)
|
||||
.and_then(AttrValue::as_color)
|
||||
.unwrap_or(Color::Reset);
|
||||
// Make list
|
||||
let list_items: Vec<ListItem> = self
|
||||
.props
|
||||
.get(Attribute::Content)
|
||||
.unwrap()
|
||||
.unwrap_table()
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let row_refs = row.iter().collect::<Vec<_>>();
|
||||
ListItem::new(tui_realm_stdlib::utils::wrap_spans(
|
||||
row_refs.as_slice(),
|
||||
width,
|
||||
&self.props,
|
||||
))
|
||||
.and_then(AttrValue::as_table)
|
||||
.map(|table| {
|
||||
table
|
||||
.iter()
|
||||
.map(|row| {
|
||||
let spans = row
|
||||
.iter()
|
||||
.flat_map(|line| line.spans.iter().cloned())
|
||||
.collect::<Vec<_>>();
|
||||
ListItem::new(tuirealm::ratatui::text::Line::from(spans))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let title = ("Log".to_string(), Alignment::Left);
|
||||
.unwrap_or_default();
|
||||
let title = Title::from("Log".to_string()).alignment(HorizontalAlignment::Left);
|
||||
let w = TuiList::new(list_items)
|
||||
.block(tui_realm_stdlib::utils::get_block(
|
||||
borders,
|
||||
@@ -79,29 +92,26 @@ impl MockComponent for Log {
|
||||
frame.render_stateful_widget(w, area, &mut state);
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
self.props.get(attr)
|
||||
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
|
||||
self.props.get_for_query(attr)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
self.props.set(attr, value);
|
||||
if matches!(attr, Attribute::Content) {
|
||||
self.states.set_list_len(
|
||||
match self
|
||||
.props
|
||||
.get(Attribute::Content)
|
||||
.map(AttrValue::unwrap_table)
|
||||
{
|
||||
Some(spans) => spans.len(),
|
||||
_ => 0,
|
||||
},
|
||||
);
|
||||
let len = self
|
||||
.props
|
||||
.get(Attribute::Content)
|
||||
.and_then(AttrValue::as_table)
|
||||
.map(std::vec::Vec::len)
|
||||
.unwrap_or(0);
|
||||
self.states.set_list_len(len);
|
||||
self.states.reset_list_index();
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::One(StateValue::Usize(self.states.get_list_index()))
|
||||
State::Single(StateValue::Usize(self.states.get_list_index()))
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
@@ -112,7 +122,7 @@ impl MockComponent for Log {
|
||||
if prev != self.states.get_list_index() {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::Move(Direction::Up) => {
|
||||
@@ -121,7 +131,7 @@ impl MockComponent for Log {
|
||||
if prev != self.states.get_list_index() {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::Scroll(Direction::Down) => {
|
||||
@@ -130,7 +140,7 @@ impl MockComponent for Log {
|
||||
if prev != self.states.get_list_index() {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::Scroll(Direction::Up) => {
|
||||
@@ -139,7 +149,7 @@ impl MockComponent for Log {
|
||||
if prev != self.states.get_list_index() {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::GoTo(Position::Begin) => {
|
||||
@@ -148,7 +158,7 @@ impl MockComponent for Log {
|
||||
if prev != self.states.get_list_index() {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::GoTo(Position::End) => {
|
||||
@@ -157,16 +167,16 @@ impl MockComponent for Log {
|
||||
if prev != self.states.get_list_index() {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
_ => CmdResult::None,
|
||||
_ => CmdResult::NoChange,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Log {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Log {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Up, .. }) => {
|
||||
self.perform(Cmd::Move(Direction::Down));
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
//!
|
||||
//! file transfer activity components
|
||||
|
||||
use tui_realm_stdlib::Span;
|
||||
use tuirealm::props::{Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::Span;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, NoUserEvent};
|
||||
use tuirealm::props::{Color, SpanStatic};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
|
||||
use super::Msg;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct FooterBar {
|
||||
component: Span,
|
||||
}
|
||||
@@ -17,37 +19,37 @@ impl FooterBar {
|
||||
pub fn new(key_color: Color) -> Self {
|
||||
Self {
|
||||
component: Span::default().spans([
|
||||
TextSpan::from("<F1|H>").bold().fg(key_color),
|
||||
TextSpan::from(" Help "),
|
||||
TextSpan::from("<TAB>").bold().fg(key_color),
|
||||
TextSpan::from(" Change tab "),
|
||||
TextSpan::from("<SPACE>").bold().fg(key_color),
|
||||
TextSpan::from(" Transfer "),
|
||||
TextSpan::from("<ENTER>").bold().fg(key_color),
|
||||
TextSpan::from(" Enter dir "),
|
||||
TextSpan::from("<F2|S>").bold().fg(key_color),
|
||||
TextSpan::from(" Save as "),
|
||||
TextSpan::from("<F3|V>").bold().fg(key_color),
|
||||
TextSpan::from(" View "),
|
||||
TextSpan::from("<F4|O>").bold().fg(key_color),
|
||||
TextSpan::from(" Edit "),
|
||||
TextSpan::from("<F5|C>").bold().fg(key_color),
|
||||
TextSpan::from(" Copy "),
|
||||
TextSpan::from("<F6|R>").bold().fg(key_color),
|
||||
TextSpan::from(" Rename "),
|
||||
TextSpan::from("<F7|D>").bold().fg(key_color),
|
||||
TextSpan::from(" Make dir "),
|
||||
TextSpan::from("<F8|DEL>").bold().fg(key_color),
|
||||
TextSpan::from(" Delete "),
|
||||
TextSpan::from("<F10|Q>").bold().fg(key_color),
|
||||
TextSpan::from(" Quit "),
|
||||
SpanStatic::from("<F1|H>").bold().fg(key_color),
|
||||
SpanStatic::from(" Help "),
|
||||
SpanStatic::from("<TAB>").bold().fg(key_color),
|
||||
SpanStatic::from(" Change tab "),
|
||||
SpanStatic::from("<SPACE>").bold().fg(key_color),
|
||||
SpanStatic::from(" Transfer "),
|
||||
SpanStatic::from("<ENTER>").bold().fg(key_color),
|
||||
SpanStatic::from(" Enter dir "),
|
||||
SpanStatic::from("<F2|S>").bold().fg(key_color),
|
||||
SpanStatic::from(" Save as "),
|
||||
SpanStatic::from("<F3|V>").bold().fg(key_color),
|
||||
SpanStatic::from(" View "),
|
||||
SpanStatic::from("<F4|O>").bold().fg(key_color),
|
||||
SpanStatic::from(" Edit "),
|
||||
SpanStatic::from("<F5|C>").bold().fg(key_color),
|
||||
SpanStatic::from(" Copy "),
|
||||
SpanStatic::from("<F6|R>").bold().fg(key_color),
|
||||
SpanStatic::from(" Rename "),
|
||||
SpanStatic::from("<F7|D>").bold().fg(key_color),
|
||||
SpanStatic::from(" Make dir "),
|
||||
SpanStatic::from("<F8|DEL>").bold().fg(key_color),
|
||||
SpanStatic::from(" Delete "),
|
||||
SpanStatic::from("<F10|Q>").bold().fg(key_color),
|
||||
SpanStatic::from(" Quit "),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for FooterBar {
|
||||
fn on(&mut self, _: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for FooterBar {
|
||||
fn on(&mut self, _: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use remotefs::fs::{UnixPex, UnixPexClass};
|
||||
use tui_realm_stdlib::Checkbox;
|
||||
use tui_realm_stdlib::components::Checkbox;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, AttrValue, Attribute, BorderSides, Borders, Color};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
AttrValue, Attribute, BorderSides, Borders, Color, HorizontalAlignment, Props, Title,
|
||||
};
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction as LayoutDirection, Layout};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, Props, State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
@@ -60,21 +63,21 @@ impl ChmodPopup {
|
||||
user: Checkbox::default()
|
||||
.foreground(color)
|
||||
.choices(["Read", "Write", "Execute"])
|
||||
.title("User", Alignment::Left)
|
||||
.title(Title::from("User").alignment(HorizontalAlignment::Left))
|
||||
.borders(Borders::default().sides(BorderSides::NONE))
|
||||
.values(&make_pex_values(pex.user()))
|
||||
.rewind(true),
|
||||
group: Checkbox::default()
|
||||
.foreground(color)
|
||||
.choices(["Read", "Write", "Execute"])
|
||||
.title("Group", Alignment::Left)
|
||||
.title(Title::from("Group").alignment(HorizontalAlignment::Left))
|
||||
.borders(Borders::default().sides(BorderSides::NONE))
|
||||
.values(&make_pex_values(pex.group()))
|
||||
.rewind(true),
|
||||
others: Checkbox::default()
|
||||
.foreground(color)
|
||||
.choices(["Read", "Write", "Execute"])
|
||||
.title("Others", Alignment::Left)
|
||||
.title(Title::from("Others").alignment(HorizontalAlignment::Left))
|
||||
.borders(Borders::default().sides(BorderSides::NONE))
|
||||
.values(&make_pex_values(pex.others()))
|
||||
.rewind(true),
|
||||
@@ -146,8 +149,8 @@ impl ChmodPopup {
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for ChmodPopup {
|
||||
fn attr(&mut self, attr: tuirealm::Attribute, value: AttrValue) {
|
||||
impl Component for ChmodPopup {
|
||||
fn attr(&mut self, attr: tuirealm::props::Attribute, value: AttrValue) {
|
||||
self.props.set(attr, value.clone());
|
||||
|
||||
if attr == Attribute::Focus {
|
||||
@@ -166,28 +169,40 @@ impl MockComponent for ChmodPopup {
|
||||
}
|
||||
Cmd::Move(Direction::Up) => {
|
||||
self.active_checkbox_up();
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Move(Direction::Down) => {
|
||||
self.active_checkbox_down();
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Toggle => self.get_active_checkbox().perform(cmd),
|
||||
Cmd::Submit => CmdResult::Submit(self.state()),
|
||||
_ => CmdResult::None,
|
||||
_ => CmdResult::NoChange,
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, attr: tuirealm::Attribute) -> Option<AttrValue> {
|
||||
self.props.get(attr)
|
||||
fn query<'a>(
|
||||
&'a self,
|
||||
attr: tuirealm::props::Attribute,
|
||||
) -> Option<tuirealm::props::QueryResult<'a>> {
|
||||
self.props.get_for_query(attr)
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::One(StateValue::U32(self.get_mode().into()))
|
||||
State::Single(StateValue::U32(self.get_mode().into()))
|
||||
}
|
||||
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||
if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) != AttrValue::Flag(true) {
|
||||
fn view(
|
||||
&mut self,
|
||||
frame: &mut tuirealm::ratatui::Frame,
|
||||
area: tuirealm::ratatui::layout::Rect,
|
||||
) {
|
||||
if !self
|
||||
.props
|
||||
.get(Attribute::Display)
|
||||
.and_then(AttrValue::as_flag)
|
||||
.unwrap_or(true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let chunks = Layout::default()
|
||||
@@ -205,10 +220,11 @@ impl MockComponent for ChmodPopup {
|
||||
|
||||
let focus = self
|
||||
.props
|
||||
.get_or(Attribute::Focus, AttrValue::Flag(false))
|
||||
.unwrap_flag();
|
||||
.get(Attribute::Focus)
|
||||
.and_then(AttrValue::as_flag)
|
||||
.unwrap_or(false);
|
||||
|
||||
let div_title = (self.title.clone(), Alignment::Center);
|
||||
let div_title = Title::from(self.title.clone()).alignment(HorizontalAlignment::Center);
|
||||
|
||||
let div = tui_realm_stdlib::utils::get_block(
|
||||
Borders::default().color(self.color),
|
||||
@@ -225,8 +241,8 @@ impl MockComponent for ChmodPopup {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ChmodPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ChmodPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseChmodPopup))
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct CopyPopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,17 +23,19 @@ impl CopyPopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"destination",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Copy file(s) to\u{2026}", Alignment::Center),
|
||||
))
|
||||
.title(
|
||||
Title::from("Copy file(s) to\u{2026}").alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for CopyPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for CopyPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -73,13 +76,13 @@ impl Component<Msg, NoUserEvent> for CopyPopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => {
|
||||
State::Single(StateValue::String(i)) => {
|
||||
Some(Msg::Transfer(TransferMsg::CopyFileTo(i)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tui_realm_stdlib::Radio;
|
||||
use tui_realm_stdlib::components::Radio;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct DeletePopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -15,21 +18,25 @@ impl DeletePopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.choices(["Yes", "No"])
|
||||
.value(1)
|
||||
.title("Delete file(s)?", Alignment::Center),
|
||||
.title(Title::from("Delete file(s)?").alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DeletePopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for DeletePopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -59,7 +66,7 @@ impl Component<Msg, NoUserEvent> for DeletePopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Transfer(TransferMsg::DeleteFile))
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tui_realm_stdlib::Radio;
|
||||
use tui_realm_stdlib::components::Radio;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct DisconnectPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -15,20 +18,27 @@ impl DisconnectPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.choices(["Yes", "No"])
|
||||
.title("Are you sure you want to disconnect?", Alignment::Center),
|
||||
.title(
|
||||
Title::from("Are you sure you want to disconnect?")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DisconnectPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for DisconnectPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -58,7 +68,7 @@ impl Component<Msg, NoUserEvent> for DisconnectPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Ui(UiMsg::Disconnect))
|
||||
} else {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use tui_realm_stdlib::Paragraph;
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::Paragraph;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, SpanStatic};
|
||||
use tuirealm::ratatui::text::Text;
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ErrorPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -14,21 +15,23 @@ impl ErrorPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
text.as_ref().to_string(),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ErrorPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ErrorPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
@@ -39,7 +42,7 @@ impl Component<Msg, NoUserEvent> for ErrorPopup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct FatalPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -48,21 +51,23 @@ impl FatalPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
text.as_ref().to_string(),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for FatalPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for FatalPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
|
||||
@@ -2,17 +2,20 @@ use std::time::UNIX_EPOCH;
|
||||
|
||||
use bytesize::ByteSize;
|
||||
use remotefs::File;
|
||||
use tui_realm_stdlib::List;
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::List;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, SpanStatic, TableBuilder, Title,
|
||||
};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
#[cfg(posix)]
|
||||
use uzers::{get_group_by_gid, get_user_by_uid};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, UiMsg};
|
||||
use crate::utils::fmt::fmt_time;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct FileInfoPopup {
|
||||
component: List,
|
||||
}
|
||||
@@ -28,23 +31,23 @@ impl FileInfoPopup {
|
||||
};
|
||||
// Make texts
|
||||
texts
|
||||
.add_col(TextSpan::from("Path: "))
|
||||
.add_col(TextSpan::new(path.as_str()).fg(Color::Yellow));
|
||||
.add_col(SpanStatic::from("Path: "))
|
||||
.add_col(SpanStatic::raw(path.clone()).fg(Color::Yellow));
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("Name: "))
|
||||
.add_col(TextSpan::new(file.name()).fg(Color::Yellow));
|
||||
.add_col(SpanStatic::from("Name: "))
|
||||
.add_col(SpanStatic::raw(file.name().clone()).fg(Color::Yellow));
|
||||
if let Some(filetype) = file.extension() {
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("File type: "))
|
||||
.add_col(TextSpan::new(filetype).fg(Color::LightGreen));
|
||||
.add_col(SpanStatic::from("File type: "))
|
||||
.add_col(SpanStatic::raw(filetype.clone()).fg(Color::LightGreen));
|
||||
}
|
||||
let (bsize, size): (ByteSize, u64) = (ByteSize(file.metadata().size), file.metadata().size);
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("Size: "))
|
||||
.add_col(TextSpan::new(format!("{bsize} ({size})").as_str()).fg(Color::Cyan));
|
||||
.add_col(SpanStatic::from("Size: "))
|
||||
.add_col(SpanStatic::raw(format!("{bsize} ({size})")).fg(Color::Cyan));
|
||||
let atime: String = fmt_time(
|
||||
file.metadata().accessed.unwrap_or(UNIX_EPOCH),
|
||||
"%b %d %Y %H:%M:%S",
|
||||
@@ -59,16 +62,16 @@ impl FileInfoPopup {
|
||||
);
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("Creation time: "))
|
||||
.add_col(TextSpan::new(ctime.as_str()).fg(Color::LightGreen));
|
||||
.add_col(SpanStatic::from("Creation time: "))
|
||||
.add_col(SpanStatic::raw(ctime.clone()).fg(Color::LightGreen));
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("Last modified time: "))
|
||||
.add_col(TextSpan::new(mtime.as_str()).fg(Color::LightBlue));
|
||||
.add_col(SpanStatic::from("Last modified time: "))
|
||||
.add_col(SpanStatic::raw(mtime.clone()).fg(Color::LightBlue));
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("Last access time: "))
|
||||
.add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed));
|
||||
.add_col(SpanStatic::from("Last access time: "))
|
||||
.add_col(SpanStatic::raw(atime.clone()).fg(Color::LightRed));
|
||||
// User
|
||||
#[cfg(posix)]
|
||||
let username: String = match file.metadata().uid {
|
||||
@@ -93,24 +96,29 @@ impl FileInfoPopup {
|
||||
let group: String = format!("{}", file.metadata().gid.unwrap_or(0));
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("User: "))
|
||||
.add_col(TextSpan::new(username.as_str()).fg(Color::LightYellow));
|
||||
.add_col(SpanStatic::from("User: "))
|
||||
.add_col(SpanStatic::raw(username.clone()).fg(Color::LightYellow));
|
||||
texts
|
||||
.add_row()
|
||||
.add_col(TextSpan::from("Group: "))
|
||||
.add_col(TextSpan::new(group.as_str()).fg(Color::Blue));
|
||||
.add_col(SpanStatic::from("Group: "))
|
||||
.add_col(SpanStatic::raw(group.clone()).fg(Color::Blue));
|
||||
Self {
|
||||
component: List::default()
|
||||
.borders(Borders::default().modifiers(BorderType::Rounded))
|
||||
.scroll(false)
|
||||
.title(file.name(), Alignment::Left)
|
||||
.rows(texts.build()),
|
||||
.title(Title::from(file.name().clone()).alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
texts
|
||||
.build()
|
||||
.into_iter()
|
||||
.map(|row| row.into_iter().flat_map(|l| l.spans).collect::<Vec<_>>()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for FileInfoPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for FileInfoPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct FilterPopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,20 +23,20 @@ impl FilterPopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"regex or wildmatch",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
))
|
||||
.title(
|
||||
"Filter files by regex or wildmatch in the current directory",
|
||||
Alignment::Center,
|
||||
Title::from("Filter files by regex or wildmatch in the current directory")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for FilterPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for FilterPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -76,13 +77,15 @@ impl Component<Msg, NoUserEvent> for FilterPopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(filter)) => Some(Msg::Ui(UiMsg::FilterFiles(filter))),
|
||||
State::Single(StateValue::String(filter)) => {
|
||||
Some(Msg::Ui(UiMsg::FilterFiles(filter)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, PropValue, Style};
|
||||
use tuirealm::{
|
||||
AttrValue, Attribute, Component, Event, MockComponent, NoUserEvent, State, StateValue,
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
AttrValue, Attribute, BorderType, Borders, Color, HorizontalAlignment, InputType, PropValue,
|
||||
Style, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
@@ -51,14 +53,15 @@ impl From<CmdResult> for Suggestion {
|
||||
fn from(value: CmdResult) -> Self {
|
||||
match value {
|
||||
CmdResult::Batch(v) if v.len() == 1 => {
|
||||
if let CmdResult::Submit(State::One(StateValue::String(s))) = v.first().unwrap() {
|
||||
if let CmdResult::Submit(State::Single(StateValue::String(s))) = v.first().unwrap()
|
||||
{
|
||||
Suggestion::Suggest(s.clone())
|
||||
} else {
|
||||
Suggestion::None
|
||||
}
|
||||
}
|
||||
CmdResult::Batch(v) if v.len() == 2 => {
|
||||
if let CmdResult::Submit(State::One(StateValue::String(s))) = v.get(1).unwrap() {
|
||||
if let CmdResult::Submit(State::Single(StateValue::String(s))) = v.get(1).unwrap() {
|
||||
Suggestion::Rescan(PathBuf::from(s))
|
||||
} else {
|
||||
Suggestion::None
|
||||
@@ -72,13 +75,13 @@ impl From<CmdResult> for Suggestion {
|
||||
impl From<Suggestion> for CmdResult {
|
||||
fn from(value: Suggestion) -> Self {
|
||||
match value {
|
||||
Suggestion::None => CmdResult::None,
|
||||
Suggestion::Suggest(s) => {
|
||||
CmdResult::Batch(vec![CmdResult::Submit(State::One(StateValue::String(s)))])
|
||||
}
|
||||
Suggestion::None => CmdResult::NoChange,
|
||||
Suggestion::Suggest(s) => CmdResult::Batch(vec![CmdResult::Submit(State::Single(
|
||||
StateValue::String(s),
|
||||
))]),
|
||||
Suggestion::Rescan(p) => CmdResult::Batch(vec![
|
||||
CmdResult::None,
|
||||
CmdResult::Submit(State::One(StateValue::String(
|
||||
CmdResult::NoChange,
|
||||
CmdResult::Submit(State::Single(StateValue::String(
|
||||
p.to_string_lossy().to_string(),
|
||||
))),
|
||||
]),
|
||||
@@ -187,18 +190,25 @@ impl GotoPopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"/foo/bar/buzz",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Go to… (Press <TAB> for autocompletion)", Alignment::Center),
|
||||
))
|
||||
.title(
|
||||
Title::from("Go to… (Press <TAB> for autocompletion)")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
states,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for GotoPopup {
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::prelude::Rect) {
|
||||
impl Component for GotoPopup {
|
||||
fn view(
|
||||
&mut self,
|
||||
frame: &mut tuirealm::ratatui::Frame,
|
||||
area: tuirealm::ratatui::prelude::Rect,
|
||||
) {
|
||||
self.input.view(frame, area);
|
||||
}
|
||||
|
||||
@@ -220,12 +230,12 @@ impl MockComponent for GotoPopup {
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
fn query<'a>(&'a self, attr: Attribute) -> Option<tuirealm::props::QueryResult<'a>> {
|
||||
self.input.query(attr)
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::One(StateValue::String(self.states.computed_search()))
|
||||
State::Single(StateValue::String(self.states.computed_search()))
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
@@ -236,7 +246,7 @@ impl MockComponent for GotoPopup {
|
||||
.search
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| self.input.state().unwrap_one().unwrap_string());
|
||||
.unwrap_or_else(|| self.input.state().unwrap_single().unwrap_string());
|
||||
let suggest = self.states.suggest(&input);
|
||||
if let Suggestion::Suggest(suggestion) = suggest.clone() {
|
||||
self.input
|
||||
@@ -247,7 +257,7 @@ impl MockComponent for GotoPopup {
|
||||
}
|
||||
cmd => {
|
||||
let res = self.input.perform(cmd);
|
||||
if let CmdResult::Changed(State::One(StateValue::String(new_text))) = &res {
|
||||
if let CmdResult::Changed(State::Single(StateValue::String(new_text))) = &res {
|
||||
self.states.search = Some(new_text.clone());
|
||||
}
|
||||
res
|
||||
@@ -256,8 +266,8 @@ impl MockComponent for GotoPopup {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GotoPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for GotoPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -298,7 +308,7 @@ impl Component<Msg, NoUserEvent> for GotoPopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => {
|
||||
@@ -311,7 +321,7 @@ impl Component<Msg, NoUserEvent> for GotoPopup {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::GoTo(i))),
|
||||
State::Single(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::GoTo(i))),
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tui_realm_stdlib::List;
|
||||
use tui_realm_stdlib::components::List;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, SpanStatic, TableBuilder, Title,
|
||||
};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct KeybindingsPopup {
|
||||
component: List,
|
||||
}
|
||||
@@ -18,148 +21,152 @@ impl KeybindingsPopup {
|
||||
.borders(Borders::default().modifiers(BorderType::Rounded))
|
||||
.scroll(true)
|
||||
.step(8)
|
||||
.highlighted_str("? ")
|
||||
.title("Keybindings", Alignment::Center)
|
||||
.highlight_str("? ")
|
||||
.title(Title::from("Keybindings").alignment(HorizontalAlignment::Center))
|
||||
.rewind(true)
|
||||
.rows(
|
||||
TableBuilder::default()
|
||||
.add_col(TextSpan::new("<ESC>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Disconnect"))
|
||||
.add_col(SpanStatic::raw("<ESC>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Disconnect"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<BACKSPACE>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Go to previous directory"))
|
||||
.add_col(SpanStatic::raw("<BACKSPACE>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Go to previous directory"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<TAB|RIGHT|LEFT>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Change explorer tab"))
|
||||
.add_col(SpanStatic::raw("<TAB|RIGHT|LEFT>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Change explorer tab"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Move up/down in list"))
|
||||
.add_col(SpanStatic::raw("<UP/DOWN>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Move up/down in list"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<ENTER>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Enter directory"))
|
||||
.add_col(SpanStatic::raw("<ENTER>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Enter directory"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<SPACE>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Upload/Download file"))
|
||||
.add_col(SpanStatic::raw("<SPACE>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Upload/Download file"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<BACKTAB>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<BACKTAB>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Switch between explorer and log window",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<A>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Toggle hidden files"))
|
||||
.add_col(SpanStatic::raw("<A>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Toggle hidden files"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<B>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Change file sorting mode"))
|
||||
.add_col(SpanStatic::raw("<B>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Change file sorting mode"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<C|F5>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Copy"))
|
||||
.add_col(SpanStatic::raw("<C|F5>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Copy"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<D|F7>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Make directory"))
|
||||
.add_col(SpanStatic::raw("<D|F7>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Make directory"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<F>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Search files"))
|
||||
.add_col(SpanStatic::raw("<F>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Search files"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<G>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Go to path"))
|
||||
.add_col(SpanStatic::raw("<G>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Go to path"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<H|F1>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Show help"))
|
||||
.add_col(SpanStatic::raw("<H|F1>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Show help"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<I>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<I>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Show info about selected file",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<K>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<K>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Create symlink pointing to the current selected entry",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<L>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Reload directory content"))
|
||||
.add_col(SpanStatic::raw("<L>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Reload directory content"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<M>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Select file"))
|
||||
.add_col(SpanStatic::raw("<M>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Select file"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<N>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Create new file"))
|
||||
.add_col(SpanStatic::raw("<N>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Create new file"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<O|F4>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<O|F4>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Open text file with preferred editor",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<P>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Toggle bottom panel"))
|
||||
.add_col(SpanStatic::raw("<P>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Toggle bottom panel"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<Q|F10>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Quit termscp"))
|
||||
.add_col(SpanStatic::raw("<Q|F10>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Quit termscp"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<R|F6>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Rename file"))
|
||||
.add_col(SpanStatic::raw("<R|F6>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Rename file"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<S|F2>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Save file as"))
|
||||
.add_col(SpanStatic::raw("<S|F2>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Save file as"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<T>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Watch/unwatch file changes"))
|
||||
.add_col(SpanStatic::raw("<T>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Watch/unwatch file changes",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<U>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Go to parent directory"))
|
||||
.add_col(SpanStatic::raw("<U>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Go to parent directory"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<V|F3>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<V|F3>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Open file with default application for file type",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<W>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<W>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Open file with specified application",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<X>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Execute shell command"))
|
||||
.add_col(SpanStatic::raw("<X>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Execute shell command"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<Y>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<Y>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Toggle synchronized browsing",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<Z>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Change file permissions"))
|
||||
.add_col(SpanStatic::raw("<Z>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Change file permissions"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("</>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Filter files"))
|
||||
.add_col(SpanStatic::raw("</>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Filter files"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<DEL|F8|E>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Delete selected file"))
|
||||
.add_col(SpanStatic::raw("<DEL|F8|E>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Delete selected file"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+A>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Select all files"))
|
||||
.add_col(SpanStatic::raw("<CTRL+A>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Select all files"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<ALT+A>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Deselect all files"))
|
||||
.add_col(SpanStatic::raw("<ALT+A>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Deselect all files"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+C>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Interrupt file transfer"))
|
||||
.add_col(SpanStatic::raw("<CTRL+C>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Interrupt file transfer"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+S>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(
|
||||
.add_col(SpanStatic::raw("<CTRL+S>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(
|
||||
" Get total path size of selected files",
|
||||
))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+T>").bold().fg(key_color))
|
||||
.add_col(TextSpan::from(" Show watched paths"))
|
||||
.build(),
|
||||
.add_col(SpanStatic::raw("<CTRL+T>").bold().fg(key_color))
|
||||
.add_col(SpanStatic::from(" Show watched paths"))
|
||||
.build()
|
||||
.into_iter()
|
||||
.map(|row| row.into_iter().flat_map(|l| l.spans).collect::<Vec<_>>()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for KeybindingsPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for KeybindingsPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tui_realm_stdlib::{Input, Radio};
|
||||
use tui_realm_stdlib::components::{Input, Radio};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, InputType, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, PendingActionMsg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MkdirPopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,17 +25,17 @@ impl MkdirPopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"New directory name",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("directory-name", Alignment::Center),
|
||||
))
|
||||
.title(Title::from("directory-name").alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MkdirPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MkdirPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -73,13 +76,13 @@ impl Component<Msg, NoUserEvent> for MkdirPopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::Mkdir(i))),
|
||||
State::Single(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::Mkdir(i))),
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
@@ -90,7 +93,7 @@ impl Component<Msg, NoUserEvent> for MkdirPopup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SyncBrowsingMkdirPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -99,25 +102,25 @@ impl SyncBrowsingMkdirPopup {
|
||||
pub fn new(color: Color, dir_name: &str) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(Style::default().fg(color).add_modifier(TextModifiers::REVERSED))
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.choices(["Yes", "No"])
|
||||
.choices(["Yes", "No"])
|
||||
.title(
|
||||
format!(
|
||||
Title::from(format!(
|
||||
r#"Sync browsing: directory "{dir_name}" doesn't exist. Do you want to create it?"#
|
||||
),
|
||||
Alignment::Center,
|
||||
))
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -149,7 +152,7 @@ impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::PendingAction(PendingActionMsg::MakePendingDirectory))
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct NewfilePopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,17 +23,17 @@ impl NewfilePopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"New file name",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("file.txt", Alignment::Center),
|
||||
))
|
||||
.title(Title::from("file.txt").alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for NewfilePopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for NewfilePopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -73,13 +74,15 @@ impl Component<Msg, NoUserEvent> for NewfilePopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => Some(Msg::Transfer(TransferMsg::NewFile(i))),
|
||||
State::Single(StateValue::String(i)) => {
|
||||
Some(Msg::Transfer(TransferMsg::NewFile(i)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct OpenWithPopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,17 +23,20 @@ impl OpenWithPopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"Open file with\u{2026}",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Type the program to open the file with", Alignment::Center),
|
||||
))
|
||||
.title(
|
||||
Title::from("Type the program to open the file with")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for OpenWithPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for OpenWithPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -73,13 +77,13 @@ impl Component<Msg, NoUserEvent> for OpenWithPopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => {
|
||||
State::Single(StateValue::String(i)) => {
|
||||
Some(Msg::Transfer(TransferMsg::OpenFileWith(i)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
use tui_realm_stdlib::ProgressBar;
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::Gauge;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, Title};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct TransferProgressBar {
|
||||
component: ProgressBar,
|
||||
component: Gauge,
|
||||
}
|
||||
|
||||
impl TransferProgressBar {
|
||||
pub fn new<S: Into<String>>(prog: f64, label: S, title: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: ProgressBar::default()
|
||||
component: Gauge::default()
|
||||
.borders(Borders::default().modifiers(BorderType::Rounded))
|
||||
.foreground(color)
|
||||
.label(label)
|
||||
.progress(prog)
|
||||
.title(title, Alignment::Center),
|
||||
.title(Title::from(title.into()).alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for TransferProgressBar {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for TransferProgressBar {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
if matches!(
|
||||
ev,
|
||||
Event::Keyboard(KeyEvent {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tui_realm_stdlib::Radio;
|
||||
use tui_realm_stdlib::components::Radio;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct QuitPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -15,20 +18,27 @@ impl QuitPopup {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.choices(["Yes", "No"])
|
||||
.title("Are you sure you want to quit termscp?", Alignment::Center),
|
||||
.title(
|
||||
Title::from("Are you sure you want to quit termscp?")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for QuitPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -58,7 +68,7 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Ui(UiMsg::Quit))
|
||||
} else {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct RenamePopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,17 +23,19 @@ impl RenamePopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"/foo/bar/buzz.txt",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Move file(s) to\u{2026}", Alignment::Center),
|
||||
))
|
||||
.title(
|
||||
Title::from("Move file(s) to\u{2026}").alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for RenamePopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for RenamePopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -73,13 +76,13 @@ impl Component<Msg, NoUserEvent> for RenamePopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => {
|
||||
State::Single(StateValue::String(i)) => {
|
||||
Some(Msg::Transfer(TransferMsg::RenameFile(i)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tui_realm_stdlib::Radio;
|
||||
use tui_realm_stdlib::components::Radio;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, PendingActionMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ReplacePopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -19,20 +22,24 @@ impl ReplacePopup {
|
||||
};
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.choices(["Replace", "Skip", "Replace All", "Skip All", "Cancel"])
|
||||
.title(text, Alignment::Center),
|
||||
.title(Title::from(text).alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ReplacePopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ReplacePopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -60,19 +67,19 @@ impl Component<Msg, NoUserEvent> for ReplacePopup {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.perform(Cmd::Submit) {
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0))) => {
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0))) => {
|
||||
Some(Msg::PendingAction(PendingActionMsg::ReplaceOverwrite))
|
||||
}
|
||||
CmdResult::Submit(State::One(StateValue::Usize(1))) => {
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(1))) => {
|
||||
Some(Msg::PendingAction(PendingActionMsg::ReplaceSkip))
|
||||
}
|
||||
CmdResult::Submit(State::One(StateValue::Usize(2))) => {
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(2))) => {
|
||||
Some(Msg::PendingAction(PendingActionMsg::ReplaceOverwriteAll))
|
||||
}
|
||||
CmdResult::Submit(State::One(StateValue::Usize(3))) => {
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(3))) => {
|
||||
Some(Msg::PendingAction(PendingActionMsg::ReplaceSkipAll))
|
||||
}
|
||||
CmdResult::Submit(State::One(StateValue::Usize(4))) => {
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(4))) => {
|
||||
Some(Msg::PendingAction(PendingActionMsg::ReplaceCancel))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SaveAsPopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,17 +23,17 @@ impl SaveAsPopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"/foo/bar/buzz.txt",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Save as\u{2026}", Alignment::Center),
|
||||
))
|
||||
.title(Title::from("Save as\u{2026}").alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SaveAsPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SaveAsPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -73,13 +74,13 @@ impl Component<Msg, NoUserEvent> for SaveAsPopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => {
|
||||
State::Single(StateValue::String(i)) => {
|
||||
Some(Msg::Transfer(TransferMsg::SaveFileAs(i)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
use tui_realm_stdlib::Radio;
|
||||
use tui_realm_stdlib::components::Radio;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::explorer::FileSorting;
|
||||
use crate::ui::activities::filetransfer::{Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SortingPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -16,14 +19,18 @@ impl SortingPopup {
|
||||
pub fn new(value: FileSorting, color: Color) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.choices(["Name", "Modify time", "Creation time", "Size"])
|
||||
.title("Sort files by\u{2026}", Alignment::Center)
|
||||
.title(Title::from("Sort files by\u{2026}").alignment(HorizontalAlignment::Center))
|
||||
.value(match value {
|
||||
FileSorting::CreationTime => 2,
|
||||
FileSorting::ModifyTime => 1,
|
||||
@@ -35,8 +42,8 @@ impl SortingPopup {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SortingPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SortingPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
let result = match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -50,7 +57,7 @@ impl Component<Msg, NoUserEvent> for SortingPopup {
|
||||
}) => return Some(Msg::Ui(UiMsg::CloseFileSortingPopup)),
|
||||
_ => return None,
|
||||
};
|
||||
if let CmdResult::Changed(State::One(StateValue::Usize(i))) = result {
|
||||
if let CmdResult::Changed(State::Single(StateValue::Usize(i))) = result {
|
||||
Some(Msg::Ui(UiMsg::ChangeFileSorting(match i {
|
||||
0 => FileSorting::Name,
|
||||
1 => FileSorting::ModifyTime,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use tui_realm_stdlib::Span;
|
||||
use tuirealm::props::{Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::Span;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, NoUserEvent};
|
||||
use tuirealm::props::{Color, SpanStatic};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
|
||||
use crate::explorer::FileSorting;
|
||||
use crate::ui::activities::filetransfer::Msg;
|
||||
use crate::ui::activities::filetransfer::lib::browser::Browser;
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct StatusBarLocal {
|
||||
component: Span,
|
||||
}
|
||||
@@ -17,22 +19,22 @@ impl StatusBarLocal {
|
||||
let hidden_files = hidden_files_label(browser.host_bridge().hidden_files_visible());
|
||||
Self {
|
||||
component: Span::default().spans([
|
||||
TextSpan::new("File sorting: ").fg(sorting_color),
|
||||
TextSpan::new(file_sorting).fg(sorting_color).reversed(),
|
||||
TextSpan::new(" Hidden files: ").fg(hidden_color),
|
||||
TextSpan::new(hidden_files).fg(hidden_color).reversed(),
|
||||
SpanStatic::raw("File sorting: ").fg(sorting_color),
|
||||
SpanStatic::raw(file_sorting).fg(sorting_color).reversed(),
|
||||
SpanStatic::raw(" Hidden files: ").fg(hidden_color),
|
||||
SpanStatic::raw(hidden_files).fg(hidden_color).reversed(),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for StatusBarLocal {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for StatusBarLocal {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct StatusBarRemote {
|
||||
component: Span,
|
||||
}
|
||||
@@ -52,19 +54,19 @@ impl StatusBarRemote {
|
||||
};
|
||||
Self {
|
||||
component: Span::default().spans([
|
||||
TextSpan::new("File sorting: ").fg(sorting_color),
|
||||
TextSpan::new(file_sorting).fg(sorting_color).reversed(),
|
||||
TextSpan::new(" Hidden files: ").fg(hidden_color),
|
||||
TextSpan::new(hidden_files).fg(hidden_color).reversed(),
|
||||
TextSpan::new(" Sync browsing: ").fg(sync_color),
|
||||
TextSpan::new(sync_browsing).fg(sync_color).reversed(),
|
||||
SpanStatic::raw("File sorting: ").fg(sorting_color),
|
||||
SpanStatic::raw(file_sorting).fg(sorting_color).reversed(),
|
||||
SpanStatic::raw(" Hidden files: ").fg(hidden_color),
|
||||
SpanStatic::raw(hidden_files).fg(hidden_color).reversed(),
|
||||
SpanStatic::raw(" Sync browsing: ").fg(sync_color),
|
||||
SpanStatic::raw(sync_browsing).fg(sync_color).reversed(),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for StatusBarRemote {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for StatusBarRemote {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, InputType, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SymlinkPopup {
|
||||
component: Input,
|
||||
}
|
||||
@@ -22,20 +23,20 @@ impl SymlinkPopup {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"Symlink name",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
))
|
||||
.title(
|
||||
"Create a symlink pointing to the selected entry",
|
||||
Alignment::Center,
|
||||
Title::from("Create a symlink pointing to the selected entry")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SymlinkPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SymlinkPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -76,13 +77,13 @@ impl Component<Msg, NoUserEvent> for SymlinkPopup {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::String(i)) => {
|
||||
State::Single(StateValue::String(i)) => {
|
||||
Some(Msg::Transfer(TransferMsg::CreateSymlink(i)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use tui_realm_stdlib::Paragraph;
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tui_realm_stdlib::components::Paragraph;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, SpanStatic};
|
||||
use tuirealm::ratatui::text::Text;
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct WaitPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -14,26 +15,28 @@ impl WaitPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
text.as_ref().to_string(),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WaitPopup {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for WaitPopup {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct WalkdirWaitPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -42,24 +45,24 @@ impl WalkdirWaitPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S, color: Color) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.text([
|
||||
TextSpan::from(text.as_ref()),
|
||||
TextSpan::from("Press 'CTRL+C' to abort"),
|
||||
])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([
|
||||
SpanStatic::from(text.as_ref().to_string()),
|
||||
SpanStatic::from("Press 'CTRL+C' to abort"),
|
||||
]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WalkdirWaitPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for WalkdirWaitPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
if matches!(
|
||||
ev,
|
||||
Event::Keyboard(KeyEvent {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use tui_realm_stdlib::{List, Radio};
|
||||
use tui_realm_stdlib::components::{List, Radio};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, SpanStatic, Style, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct WatchedPathsList {
|
||||
component: List,
|
||||
}
|
||||
@@ -23,24 +26,24 @@ impl WatchedPathsList {
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.highlighted_color(color)
|
||||
.highlighted_str("\u{27a4} ")
|
||||
.highlight_style(Style::default().fg(color))
|
||||
.highlight_str("\u{27a4} ")
|
||||
.title(
|
||||
"These files are currently synched with the remote host",
|
||||
Alignment::Center,
|
||||
Title::from("These files are currently synched with the remote host")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
.rows(
|
||||
paths
|
||||
.iter()
|
||||
.map(|x| vec![TextSpan::from(x.to_string_lossy().to_string())])
|
||||
.collect(),
|
||||
.map(|x| vec![SpanStatic::from(x.to_string_lossy().to_string())])
|
||||
.collect::<Vec<Vec<SpanStatic>>>(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WatchedPathsList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for WatchedPathsList {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseWatchedPathsList))
|
||||
@@ -82,7 +85,7 @@ impl Component<Msg, NoUserEvent> for WatchedPathsList {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
// get state
|
||||
if let State::One(StateValue::Usize(idx)) = self.component.state() {
|
||||
if let State::Single(StateValue::Usize(idx)) = self.component.state() {
|
||||
Some(Msg::Transfer(TransferMsg::ToggleWatchFor(idx)))
|
||||
} else {
|
||||
Some(Msg::None)
|
||||
@@ -93,7 +96,7 @@ impl Component<Msg, NoUserEvent> for WatchedPathsList {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct WatcherPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -106,20 +109,24 @@ impl WatcherPopup {
|
||||
};
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(color)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(color)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(color)
|
||||
.choices(["Yes", "No"])
|
||||
.title(text, Alignment::Center),
|
||||
.title(Title::from(text).alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for WatcherPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for WatcherPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -149,7 +156,7 @@ impl Component<Msg, NoUserEvent> for WatcherPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Transfer(TransferMsg::ToggleWatch))
|
||||
} else {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use tui_realm_stdlib::List;
|
||||
use tui_realm_stdlib::components::List;
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{BorderType, Borders, Color, HorizontalAlignment, SpanStatic, Style, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use crate::ui::activities::filetransfer::{MarkQueue, Msg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SelectedFilesList {
|
||||
component: List,
|
||||
paths: Vec<PathBuf>,
|
||||
@@ -35,20 +36,20 @@ impl SelectedFilesList {
|
||||
.rewind(true)
|
||||
.scroll(true)
|
||||
.step(4)
|
||||
.highlighted_color(color)
|
||||
.highlighted_str("➤ ")
|
||||
.title(title, Alignment::Left)
|
||||
.highlight_style(Style::default().fg(color))
|
||||
.highlight_str("➤ ")
|
||||
.title(Title::from(title.to_string()).alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
paths
|
||||
.iter()
|
||||
.map(|(src, dest)| {
|
||||
vec![
|
||||
TextSpan::from(Self::filename(src)),
|
||||
TextSpan::from(" -> "),
|
||||
TextSpan::from(Self::filename(dest)),
|
||||
SpanStatic::from(Self::filename(src)),
|
||||
SpanStatic::from(" -> "),
|
||||
SpanStatic::from(Self::filename(dest)),
|
||||
]
|
||||
})
|
||||
.collect(),
|
||||
.collect::<Vec<Vec<SpanStatic>>>(),
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -61,8 +62,8 @@ impl SelectedFilesList {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SelectedFilesList {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SelectedFilesList {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -112,7 +113,7 @@ impl Component<Msg, NoUserEvent> for SelectedFilesList {
|
||||
..
|
||||
}) => {
|
||||
// unmark the selected file
|
||||
let State::One(StateValue::Usize(idx)) = self.state() else {
|
||||
let State::Single(StateValue::Usize(idx)) = self.state() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@ mod history;
|
||||
mod line;
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent};
|
||||
use tuirealm::props::Color;
|
||||
use tuirealm::{AttrValue, Attribute, Component, Event, MockComponent, NoUserEvent};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, NoUserEvent};
|
||||
use tuirealm::props::{AttrValue, Attribute, Color};
|
||||
|
||||
use self::component::TerminalComponent;
|
||||
use self::line::Line;
|
||||
use super::Msg;
|
||||
use crate::ui::activities::filetransfer::{TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent, Default)]
|
||||
#[derive(Component, Default)]
|
||||
pub struct Terminal {
|
||||
component: TerminalComponent,
|
||||
}
|
||||
@@ -45,8 +45,8 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Terminal {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Terminal {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ui(UiMsg::CloseExecPopup))
|
||||
@@ -55,7 +55,7 @@ impl Component<Msg, NoUserEvent> for Terminal {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.component.perform(Cmd::Submit) {
|
||||
CmdResult::Submit(state) => {
|
||||
let cmd = state.unwrap_one().unwrap_string();
|
||||
let cmd = state.unwrap_single().unwrap_string();
|
||||
Some(Msg::Transfer(TransferMsg::ExecuteCmd(cmd)))
|
||||
}
|
||||
_ => None,
|
||||
@@ -127,7 +127,7 @@ impl Component<Msg, NoUserEvent> for Terminal {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(c), ..
|
||||
}) => {
|
||||
self.component.perform(Cmd::Type(c));
|
||||
self.component.perform(Cmd::Type(*c));
|
||||
Some(Msg::None)
|
||||
}
|
||||
_ => None,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use tui_term::vt100::Parser;
|
||||
use tui_term::widget::PseudoTerminal;
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::props::{BorderSides, BorderType, Style};
|
||||
use tuirealm::component::Component;
|
||||
use tuirealm::props::{AttrValue, Attribute, BorderSides, BorderType, Props, QueryResult, Style};
|
||||
use tuirealm::ratatui::layout::Rect;
|
||||
use tuirealm::ratatui::widgets::Block;
|
||||
use tuirealm::{AttrValue, Attribute, MockComponent, Props, State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::Line;
|
||||
use super::history::History;
|
||||
@@ -45,8 +46,7 @@ impl TerminalComponent {
|
||||
}
|
||||
|
||||
pub fn write_prompt(&mut self) {
|
||||
if let Some(value) = self.query(Attribute::Content) {
|
||||
let prompt = value.unwrap_string();
|
||||
if let Some(AttrValue::String(prompt)) = self.props.get(Attribute::Content).cloned() {
|
||||
self.parser.process(prompt.as_bytes());
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,8 @@ impl TerminalComponent {
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for TerminalComponent {
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: Rect) {
|
||||
impl Component for TerminalComponent {
|
||||
fn view(&mut self, frame: &mut tuirealm::ratatui::Frame, area: Rect) {
|
||||
let width = area.width.saturating_sub(2);
|
||||
let height = area.height.saturating_sub(2);
|
||||
|
||||
@@ -95,23 +95,30 @@ impl MockComponent for TerminalComponent {
|
||||
}
|
||||
|
||||
let title = self
|
||||
.query(Attribute::Title)
|
||||
.map(AttrValue::unwrap_string)
|
||||
.props
|
||||
.get(Attribute::Title)
|
||||
.and_then(|v| match v {
|
||||
AttrValue::String(s) => Some(s.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_else(|| "Terminal".to_string());
|
||||
|
||||
let fg = self
|
||||
.query(Attribute::Foreground)
|
||||
.map(AttrValue::unwrap_color)
|
||||
.props
|
||||
.get(Attribute::Foreground)
|
||||
.and_then(AttrValue::as_color)
|
||||
.unwrap_or(tuirealm::ratatui::style::Color::Reset);
|
||||
|
||||
let bg = self
|
||||
.query(Attribute::Background)
|
||||
.map(AttrValue::unwrap_color)
|
||||
.props
|
||||
.get(Attribute::Background)
|
||||
.and_then(AttrValue::as_color)
|
||||
.unwrap_or(tuirealm::ratatui::style::Color::Reset);
|
||||
|
||||
let border_color = self
|
||||
.query(Attribute::Borders)
|
||||
.map(AttrValue::unwrap_color)
|
||||
.props
|
||||
.get(Attribute::Borders)
|
||||
.and_then(AttrValue::as_color)
|
||||
.unwrap_or(tuirealm::ratatui::style::Color::Reset);
|
||||
|
||||
let terminal = PseudoTerminal::new(self.parser.screen())
|
||||
@@ -128,13 +135,13 @@ impl MockComponent for TerminalComponent {
|
||||
frame.render_widget(terminal, area);
|
||||
}
|
||||
|
||||
fn query(&self, attr: tuirealm::Attribute) -> Option<tuirealm::AttrValue> {
|
||||
self.props.get(attr)
|
||||
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
|
||||
self.props.get_for_query(attr)
|
||||
}
|
||||
|
||||
fn attr(&mut self, attr: tuirealm::Attribute, value: AttrValue) {
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
if attr == Attribute::Text {
|
||||
if let tuirealm::AttrValue::String(s) = value {
|
||||
if let AttrValue::String(s) = value {
|
||||
self.parser.process(b"\r");
|
||||
self.parser.process(s.as_bytes());
|
||||
self.parser.process(b"\r");
|
||||
@@ -146,14 +153,14 @@ impl MockComponent for TerminalComponent {
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
State::One(StateValue::String(self.line.content().to_string()))
|
||||
State::Single(StateValue::String(self.line.content().to_string()))
|
||||
}
|
||||
|
||||
fn perform(&mut self, cmd: Cmd) -> CmdResult {
|
||||
match cmd {
|
||||
Cmd::Type(s) => {
|
||||
if !s.is_ascii() || self.scroll > 0 {
|
||||
return CmdResult::None; // Ignore non-ASCII characters or if scrolled
|
||||
return CmdResult::NoChange; // Ignore non-ASCII characters or if scrolled
|
||||
}
|
||||
self.parser.process(&[s as u8]);
|
||||
self.line.push(s);
|
||||
@@ -161,46 +168,46 @@ impl MockComponent for TerminalComponent {
|
||||
}
|
||||
Cmd::Move(Direction::Down) => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move down if not scrolled
|
||||
return CmdResult::NoChange; // Cannot move down if not scrolled
|
||||
}
|
||||
|
||||
self.history_next();
|
||||
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Move(Direction::Left) => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move up if not scrolled
|
||||
return CmdResult::NoChange; // Cannot move up if not scrolled
|
||||
}
|
||||
|
||||
if self.line.left() {
|
||||
self.parser.process(&[27, 91, 68]);
|
||||
}
|
||||
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Move(Direction::Right) => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move up if not scrolled
|
||||
return CmdResult::NoChange; // Cannot move up if not scrolled
|
||||
}
|
||||
|
||||
if self.line.right() {
|
||||
self.parser.process(&[27, 91, 67]);
|
||||
}
|
||||
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Move(Direction::Up) => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move up if not scrolled
|
||||
return CmdResult::NoChange; // Cannot move up if not scrolled
|
||||
}
|
||||
|
||||
self.history_prev();
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Cancel => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move to the beginning if scrolled
|
||||
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
|
||||
}
|
||||
|
||||
if !self.line.is_empty() {
|
||||
@@ -215,7 +222,7 @@ impl MockComponent for TerminalComponent {
|
||||
}
|
||||
Cmd::Delete => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move to the beginning if scrolled
|
||||
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
|
||||
}
|
||||
|
||||
if !self.line.is_empty() {
|
||||
@@ -231,40 +238,40 @@ impl MockComponent for TerminalComponent {
|
||||
self.scroll = self.scroll.saturating_sub(8);
|
||||
self.parser.set_scrollback(self.scroll);
|
||||
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Scroll(Direction::Up) => {
|
||||
self.parser.set_scrollback(self.scroll.saturating_add(8));
|
||||
let scrollback = self.parser.screen().scrollback();
|
||||
self.scroll = scrollback;
|
||||
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Toggle => {
|
||||
// insert
|
||||
self.parser.process(&[27, 91, 50, 126]); // Toggle insert mode
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::GoTo(Position::Begin) => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move to the beginning if scrolled
|
||||
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
|
||||
}
|
||||
|
||||
for _ in 0..self.line.begin() {
|
||||
self.parser.process(&[27, 91, 68]); // Move cursor to the left
|
||||
}
|
||||
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::GoTo(Position::End) => {
|
||||
if self.scroll > 0 {
|
||||
return CmdResult::None; // Cannot move to the beginning if scrolled
|
||||
return CmdResult::NoChange; // Cannot move to the beginning if scrolled
|
||||
}
|
||||
|
||||
for _ in 0..self.line.end() {
|
||||
self.parser.process(&[27, 91, 67]); // Move cursor to the right
|
||||
}
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
Cmd::Submit => {
|
||||
self.scroll = 0; // Reset scroll on submit
|
||||
@@ -281,9 +288,9 @@ impl MockComponent for TerminalComponent {
|
||||
self.history.push(&line);
|
||||
}
|
||||
|
||||
CmdResult::Submit(State::One(StateValue::String(line)))
|
||||
CmdResult::Submit(State::Single(StateValue::String(line)))
|
||||
}
|
||||
_ => CmdResult::None,
|
||||
_ => CmdResult::NoChange,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,16 @@ mod file_list;
|
||||
mod file_list_with_search;
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, Borders, Color, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{Borders, Color, HorizontalAlignment, SpanStatic, Title};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use self::file_list::FileList;
|
||||
use self::file_list_with_search::FileListWithSearch;
|
||||
use super::{Msg, TransferMsg, UiMsg};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerFuzzy {
|
||||
component: FileListWithSearch,
|
||||
}
|
||||
@@ -26,13 +27,22 @@ impl ExplorerFuzzy {
|
||||
.background(bg)
|
||||
.borders(Borders::default().color(hg))
|
||||
.foreground(fg)
|
||||
.highlighted_color(hg)
|
||||
.title(title, Alignment::Left)
|
||||
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect()),
|
||||
.highlight_color(hg)
|
||||
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
files
|
||||
.iter()
|
||||
.map(|x| {
|
||||
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
|
||||
(*x).to_string(),
|
||||
))]
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_search(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
fn on_search(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -79,8 +89,8 @@ impl ExplorerFuzzy {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => match self.perform(Cmd::Type(ch)) {
|
||||
CmdResult::Changed(State::One(StateValue::String(search))) => {
|
||||
}) => match self.perform(Cmd::Type(*ch)) {
|
||||
CmdResult::Changed(State::Single(StateValue::String(search))) => {
|
||||
Some(Msg::Ui(UiMsg::FuzzySearch(search)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
@@ -92,7 +102,7 @@ impl ExplorerFuzzy {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_file_list(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
fn on_file_list(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -145,7 +155,7 @@ impl ExplorerFuzzy {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
@@ -217,8 +227,8 @@ impl ExplorerFuzzy {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerFuzzy {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerFuzzy {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match self.component.focus() {
|
||||
file_list_with_search::Focus::List => self.on_file_list(ev),
|
||||
file_list_with_search::Focus::Search => self.on_search(ev),
|
||||
@@ -226,7 +236,7 @@ impl Component<Msg, NoUserEvent> for ExplorerFuzzy {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerFind {
|
||||
component: FileList,
|
||||
}
|
||||
@@ -238,15 +248,24 @@ impl ExplorerFind {
|
||||
.background(bg)
|
||||
.borders(Borders::default().color(hg))
|
||||
.foreground(fg)
|
||||
.highlighted_color(hg)
|
||||
.title(title, Alignment::Left)
|
||||
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect()),
|
||||
.highlight_color(hg)
|
||||
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
files
|
||||
.iter()
|
||||
.map(|x| {
|
||||
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
|
||||
(*x).to_string(),
|
||||
))]
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerFind {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerFind {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -299,7 +318,7 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
@@ -367,7 +386,7 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerLocal {
|
||||
component: FileList,
|
||||
}
|
||||
@@ -379,16 +398,25 @@ impl ExplorerLocal {
|
||||
.background(bg)
|
||||
.borders(Borders::default().color(hg))
|
||||
.foreground(fg)
|
||||
.highlighted_color(hg)
|
||||
.title(title, Alignment::Left)
|
||||
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect())
|
||||
.highlight_color(hg)
|
||||
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
files
|
||||
.iter()
|
||||
.map(|x| {
|
||||
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
|
||||
(*x).to_string(),
|
||||
))]
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.dot_dot(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerLocal {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -441,7 +469,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
@@ -464,7 +492,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
|
||||
Some(Msg::Transfer(TransferMsg::GoToParentDirectory))
|
||||
} else {
|
||||
Some(Msg::Transfer(TransferMsg::EnterDirectory))
|
||||
@@ -474,7 +502,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
code: Key::Char(' '),
|
||||
..
|
||||
}) => {
|
||||
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
|
||||
Some(Msg::None)
|
||||
} else {
|
||||
Some(Msg::Transfer(TransferMsg::TransferFile))
|
||||
@@ -585,7 +613,7 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerRemote {
|
||||
component: FileList,
|
||||
}
|
||||
@@ -597,16 +625,25 @@ impl ExplorerRemote {
|
||||
.background(bg)
|
||||
.borders(Borders::default().color(hg))
|
||||
.foreground(fg)
|
||||
.highlighted_color(hg)
|
||||
.title(title, Alignment::Left)
|
||||
.rows(files.iter().map(|x| vec![TextSpan::from(*x)]).collect())
|
||||
.highlight_color(hg)
|
||||
.title(Title::from(title.as_ref().to_string()).alignment(HorizontalAlignment::Left))
|
||||
.rows(
|
||||
files
|
||||
.iter()
|
||||
.map(|x| {
|
||||
vec![tuirealm::props::LineStatic::from(SpanStatic::from(
|
||||
(*x).to_string(),
|
||||
))]
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
.dot_dot(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerRemote {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -659,7 +696,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
code: Key::Char('m'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}) => {
|
||||
let CmdResult::Changed(State::One(StateValue::Usize(index))) =
|
||||
let CmdResult::Changed(State::Single(StateValue::Usize(index))) =
|
||||
self.perform(Cmd::Toggle)
|
||||
else {
|
||||
return Some(Msg::None);
|
||||
@@ -682,7 +719,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => {
|
||||
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
|
||||
Some(Msg::Transfer(TransferMsg::GoToParentDirectory))
|
||||
} else {
|
||||
Some(Msg::Transfer(TransferMsg::EnterDirectory))
|
||||
@@ -692,7 +729,7 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
||||
code: Key::Char(' '),
|
||||
..
|
||||
}) => {
|
||||
if matches!(self.component.state(), State::One(StateValue::String(_))) {
|
||||
if matches!(self.component.state(), State::Single(StateValue::String(_))) {
|
||||
Some(Msg::None)
|
||||
} else {
|
||||
Some(Msg::Transfer(TransferMsg::TransferFile))
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
//! `FileList` component renders a file list tab
|
||||
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::component::Component;
|
||||
use tuirealm::props::{
|
||||
Alignment, AttrValue, Attribute, Borders, Color, Style, Table, TextModifiers, TextSpan,
|
||||
AttrValue, Attribute, Borders, Color, HorizontalAlignment, Props, QueryResult, SpanStatic,
|
||||
Style, Table, TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::ratatui::text::{Line, Span};
|
||||
use tuirealm::ratatui::widgets::{List as TuiList, ListDirection, ListItem, ListState};
|
||||
use tuirealm::{MockComponent, Props, State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
pub const FILE_LIST_CMD_SELECT_ALL: &str = "A";
|
||||
pub const FILE_LIST_CMD_DESELECT_ALL: &str = "D";
|
||||
@@ -108,16 +110,16 @@ impl FileList {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn title<S: AsRef<str>>(mut self, t: S, a: Alignment) -> Self {
|
||||
self.attr(
|
||||
Attribute::Title,
|
||||
AttrValue::Title((t.as_ref().to_string(), a)),
|
||||
);
|
||||
pub fn title(mut self, t: Title) -> Self {
|
||||
self.attr(Attribute::Title, AttrValue::Title(t));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn highlighted_color(mut self, c: Color) -> Self {
|
||||
self.attr(Attribute::HighlightedColor, AttrValue::Color(c));
|
||||
pub fn highlight_color(mut self, c: Color) -> Self {
|
||||
self.attr(
|
||||
Attribute::HighlightStyle,
|
||||
AttrValue::Style(Style::default().fg(c).add_modifier(TextModifiers::REVERSED)),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -136,40 +138,57 @@ impl FileList {
|
||||
fn has_dot_dot(&self) -> bool {
|
||||
self.props
|
||||
.get(Attribute::Custom(PROP_DOT_DOT))
|
||||
.map(AttrValue::unwrap_flag)
|
||||
.and_then(AttrValue::as_flag)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for FileList {
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||
impl Component for FileList {
|
||||
fn view(
|
||||
&mut self,
|
||||
frame: &mut tuirealm::ratatui::Frame,
|
||||
area: tuirealm::ratatui::layout::Rect,
|
||||
) {
|
||||
let title = self
|
||||
.props
|
||||
.get_or(
|
||||
Attribute::Title,
|
||||
AttrValue::Title((String::default(), Alignment::Left)),
|
||||
)
|
||||
.unwrap_title();
|
||||
.get(Attribute::Title)
|
||||
.and_then(AttrValue::as_title)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| Title::from(String::default()).alignment(HorizontalAlignment::Left));
|
||||
let borders = self
|
||||
.props
|
||||
.get_or(Attribute::Borders, AttrValue::Borders(Borders::default()))
|
||||
.unwrap_borders();
|
||||
.get(Attribute::Borders)
|
||||
.and_then(AttrValue::as_borders)
|
||||
.unwrap_or_default();
|
||||
let focus = self
|
||||
.props
|
||||
.get_or(Attribute::Focus, AttrValue::Flag(false))
|
||||
.unwrap_flag();
|
||||
.get(Attribute::Focus)
|
||||
.and_then(AttrValue::as_flag)
|
||||
.unwrap_or(false);
|
||||
let div = tui_realm_stdlib::utils::get_block(borders, Some(&title), focus, None);
|
||||
// Make list entries
|
||||
let init_table_iter = if self.has_dot_dot() {
|
||||
vec![vec![TextSpan::from("..")]]
|
||||
vec![vec![tuirealm::props::LineStatic::from(SpanStatic::from(
|
||||
"..",
|
||||
))]]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
let foreground = self
|
||||
.props
|
||||
.get(Attribute::Foreground)
|
||||
.and_then(AttrValue::as_color)
|
||||
.unwrap_or(Color::Reset);
|
||||
let background = self
|
||||
.props
|
||||
.get(Attribute::Background)
|
||||
.and_then(AttrValue::as_color)
|
||||
.unwrap_or(Color::Reset);
|
||||
let list_items: Vec<ListItem> = match self
|
||||
.props
|
||||
.get(Attribute::Content)
|
||||
.map(AttrValue::unwrap_table)
|
||||
.and_then(AttrValue::as_table)
|
||||
{
|
||||
Some(table) => init_table_iter
|
||||
.iter()
|
||||
@@ -177,39 +196,44 @@ impl MockComponent for FileList {
|
||||
.map(|row| {
|
||||
let columns: Vec<Span> = row
|
||||
.iter()
|
||||
.map(|col| {
|
||||
let (fg, bg, modifiers) =
|
||||
tui_realm_stdlib::utils::use_or_default_styles(&self.props, col);
|
||||
|
||||
Span::styled(
|
||||
col.content.clone(),
|
||||
Style::default().add_modifier(modifiers).fg(fg).bg(bg),
|
||||
)
|
||||
.flat_map(|line| line.spans.iter())
|
||||
.map(|span| {
|
||||
let mut style = span.style;
|
||||
if style.fg.is_none() {
|
||||
style.fg = Some(foreground);
|
||||
}
|
||||
if style.bg.is_none() {
|
||||
style.bg = Some(background);
|
||||
}
|
||||
Span::styled(span.content.clone(), style)
|
||||
})
|
||||
.collect();
|
||||
ListItem::new(Line::from(columns))
|
||||
})
|
||||
.collect(), // Make List item from TextSpan
|
||||
.collect(),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let highlighted_color = self
|
||||
let highlight_style = self
|
||||
.props
|
||||
.get(Attribute::HighlightedColor)
|
||||
.map(AttrValue::unwrap_color);
|
||||
let modifiers = match focus {
|
||||
true => TextModifiers::REVERSED,
|
||||
false => TextModifiers::empty(),
|
||||
};
|
||||
.get(Attribute::HighlightStyle)
|
||||
.and_then(AttrValue::as_style);
|
||||
// Make list
|
||||
let mut list = TuiList::new(list_items)
|
||||
.block(div)
|
||||
.direction(ListDirection::TopToBottom);
|
||||
if let Some(highlighted_color) = highlighted_color {
|
||||
list = list.highlight_style(
|
||||
Style::default()
|
||||
.fg(highlighted_color)
|
||||
.add_modifier(modifiers),
|
||||
);
|
||||
if let Some(style) = highlight_style {
|
||||
let applied = if focus {
|
||||
style
|
||||
} else {
|
||||
// On blur keep the highlight color on the foreground but drop the
|
||||
// REVERSED modifier so the selected row just stands out via fg only.
|
||||
let mut s = Style::default();
|
||||
if let Some(fg) = style.fg {
|
||||
s = s.fg(fg);
|
||||
}
|
||||
s
|
||||
};
|
||||
list = list.highlight_style(applied);
|
||||
}
|
||||
let mut state: ListState = ListState::default();
|
||||
state.select(Some(self.states.list_index));
|
||||
@@ -219,31 +243,27 @@ impl MockComponent for FileList {
|
||||
fn attr(&mut self, attr: Attribute, value: AttrValue) {
|
||||
self.props.set(attr, value);
|
||||
if matches!(attr, Attribute::Content) {
|
||||
self.states.init_list_states(
|
||||
match self
|
||||
.props
|
||||
.get(Attribute::Content)
|
||||
.map(AttrValue::unwrap_table)
|
||||
{
|
||||
Some(line) => line.len(),
|
||||
_ => 0,
|
||||
},
|
||||
self.has_dot_dot(),
|
||||
);
|
||||
let len = self
|
||||
.props
|
||||
.get(Attribute::Content)
|
||||
.and_then(AttrValue::as_table)
|
||||
.map(std::vec::Vec::len)
|
||||
.unwrap_or(0);
|
||||
self.states.init_list_states(len, self.has_dot_dot());
|
||||
self.states.fix_list_index();
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
self.props.get(attr)
|
||||
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
|
||||
self.props.get_for_query(attr)
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
if self.has_dot_dot() && self.states.list_index == 0 {
|
||||
return State::One(StateValue::String("..".to_string()));
|
||||
return State::Single(StateValue::String("..".to_string()));
|
||||
}
|
||||
|
||||
State::One(StateValue::Usize(if self.has_dot_dot() {
|
||||
State::Single(StateValue::Usize(if self.has_dot_dot() {
|
||||
self.states.list_index.saturating_sub(1)
|
||||
} else {
|
||||
self.states.list_index
|
||||
@@ -258,7 +278,7 @@ impl MockComponent for FileList {
|
||||
if prev != self.states.list_index {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::Move(Direction::Up) => {
|
||||
@@ -267,7 +287,7 @@ impl MockComponent for FileList {
|
||||
if prev != self.states.list_index {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::Scroll(Direction::Down) => {
|
||||
@@ -276,7 +296,7 @@ impl MockComponent for FileList {
|
||||
if prev != self.states.list_index {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::Scroll(Direction::Up) => {
|
||||
@@ -285,7 +305,7 @@ impl MockComponent for FileList {
|
||||
if prev != self.states.list_index {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::GoTo(Position::Begin) => {
|
||||
@@ -294,7 +314,7 @@ impl MockComponent for FileList {
|
||||
if prev != self.states.list_index {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::GoTo(Position::End) => {
|
||||
@@ -303,12 +323,12 @@ impl MockComponent for FileList {
|
||||
if prev != self.states.list_index {
|
||||
CmdResult::Changed(self.state())
|
||||
} else {
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
}
|
||||
Cmd::Toggle => {
|
||||
if self.states.list_index == 0 && self.has_dot_dot() {
|
||||
return CmdResult::None;
|
||||
return CmdResult::NoChange;
|
||||
}
|
||||
|
||||
let index = self.states.real_index();
|
||||
@@ -317,9 +337,9 @@ impl MockComponent for FileList {
|
||||
.list_index
|
||||
.saturating_add(1)
|
||||
.min(self.states.list_len.saturating_sub(1));
|
||||
CmdResult::Changed(State::One(StateValue::Usize(index)))
|
||||
CmdResult::Changed(State::Single(StateValue::Usize(index)))
|
||||
}
|
||||
_ => CmdResult::None,
|
||||
_ => CmdResult::NoChange,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use tui_realm_stdlib::Input;
|
||||
use tui_realm_stdlib::components::Input;
|
||||
use tuirealm::command::{Cmd, CmdResult};
|
||||
use tuirealm::props::{Alignment, AttrValue, Attribute, Borders, Color, Table};
|
||||
use tuirealm::component::Component;
|
||||
use tuirealm::props::{
|
||||
AttrValue, Attribute, Borders, Color, HorizontalAlignment, QueryResult, Style, Table,
|
||||
TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::{MockComponent, State};
|
||||
use tuirealm::state::State;
|
||||
|
||||
use super::file_list::FileList;
|
||||
|
||||
@@ -62,21 +66,21 @@ impl FileListWithSearch {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn title<S: AsRef<str>>(mut self, t: S, a: Alignment) -> Self {
|
||||
self.file_list.attr(
|
||||
Attribute::Title,
|
||||
AttrValue::Title((t.as_ref().to_string(), a)),
|
||||
);
|
||||
pub fn title(mut self, t: Title) -> Self {
|
||||
let align = t.content.alignment.unwrap_or(HorizontalAlignment::Left);
|
||||
self.file_list.attr(Attribute::Title, AttrValue::Title(t));
|
||||
self.search.attr(
|
||||
Attribute::Title,
|
||||
AttrValue::Title(("Fuzzy search".to_string(), a)),
|
||||
AttrValue::Title(Title::from("Fuzzy search".to_string()).alignment(align)),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn highlighted_color(mut self, c: Color) -> Self {
|
||||
self.file_list
|
||||
.attr(Attribute::HighlightedColor, AttrValue::Color(c));
|
||||
pub fn highlight_color(mut self, c: Color) -> Self {
|
||||
self.file_list.attr(
|
||||
Attribute::HighlightStyle,
|
||||
AttrValue::Style(Style::default().fg(c).add_modifier(TextModifiers::REVERSED)),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -87,8 +91,12 @@ impl FileListWithSearch {
|
||||
}
|
||||
}
|
||||
|
||||
impl MockComponent for FileListWithSearch {
|
||||
fn view(&mut self, frame: &mut tuirealm::Frame, area: tuirealm::ratatui::layout::Rect) {
|
||||
impl Component for FileListWithSearch {
|
||||
fn view(
|
||||
&mut self,
|
||||
frame: &mut tuirealm::ratatui::Frame,
|
||||
area: tuirealm::ratatui::layout::Rect,
|
||||
) {
|
||||
// split the area in two
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
@@ -107,7 +115,7 @@ impl MockComponent for FileListWithSearch {
|
||||
self.file_list.view(frame, chunks[1]);
|
||||
}
|
||||
|
||||
fn query(&self, attr: Attribute) -> Option<AttrValue> {
|
||||
fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
|
||||
self.file_list.query(attr)
|
||||
}
|
||||
|
||||
@@ -151,7 +159,7 @@ impl MockComponent for FileListWithSearch {
|
||||
AttrValue::Flag(self.states.focus == Focus::List),
|
||||
);
|
||||
|
||||
CmdResult::None
|
||||
CmdResult::NoChange
|
||||
}
|
||||
cmd if self.states.focus == Focus::Search => self.search.perform(cmd),
|
||||
cmd => self.file_list.perform(cmd),
|
||||
|
||||
@@ -3,14 +3,17 @@ mod host;
|
||||
mod log;
|
||||
mod notify;
|
||||
|
||||
use tuirealm::{PollStrategy, Update};
|
||||
use tuirealm::application::PollStrategy;
|
||||
|
||||
use super::FileTransferActivity;
|
||||
|
||||
impl FileTransferActivity {
|
||||
/// Call `Application::tick()` and process messages in `Update`
|
||||
pub(super) fn tick(&mut self) {
|
||||
match self.app.tick(PollStrategy::UpTo(1)) {
|
||||
match self
|
||||
.app
|
||||
.tick(PollStrategy::UpTo(1, std::time::Duration::from_millis(10)))
|
||||
{
|
||||
Ok(messages) => {
|
||||
if !messages.is_empty() {
|
||||
self.redraw = true;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use tuirealm::props::{
|
||||
Alignment, AttrValue, Attribute, PropPayload, PropValue, TextModifiers, TextSpan,
|
||||
AttrValue, Attribute, HorizontalAlignment, LineStatic, PropPayload, PropValue, SpanStatic,
|
||||
TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::super::browser::FileExplorerTab;
|
||||
use super::super::{FileTransferActivity, Id, ui_result};
|
||||
@@ -33,17 +35,18 @@ impl FileTransferActivity {
|
||||
hostname.len() + 3
|
||||
) // 3 because of '/…/'
|
||||
);
|
||||
let files: Vec<Vec<TextSpan>> = self
|
||||
let files: Vec<Vec<LineStatic>> = self
|
||||
.host_bridge()
|
||||
.iter_files()
|
||||
.map(|x| {
|
||||
let mut span = TextSpan::from(self.host_bridge().fmt_file(x));
|
||||
let mut span = SpanStatic::from(self.host_bridge().fmt_file(x));
|
||||
if self.host_bridge().enqueued().contains_key(x.path()) {
|
||||
span.modifiers |=
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
|
||||
span.style = span.style.add_modifier(
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC,
|
||||
);
|
||||
}
|
||||
|
||||
vec![span]
|
||||
vec![LineStatic::from(span)]
|
||||
})
|
||||
.collect();
|
||||
// Update content and title
|
||||
@@ -55,7 +58,7 @@ impl FileTransferActivity {
|
||||
ui_result(self.app.attr(
|
||||
&Id::ExplorerHostBridge,
|
||||
Attribute::Title,
|
||||
AttrValue::Title((hostname, Alignment::Left)),
|
||||
AttrValue::Title(Title::from(hostname).alignment(HorizontalAlignment::Left)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -147,17 +150,18 @@ impl FileTransferActivity {
|
||||
hostname.len() + 3 // 3 because of '/…/'
|
||||
)
|
||||
);
|
||||
let files: Vec<Vec<TextSpan>> = self
|
||||
let files: Vec<Vec<LineStatic>> = self
|
||||
.remote()
|
||||
.iter_files()
|
||||
.map(|x| {
|
||||
let mut span = TextSpan::from(self.remote().fmt_file(x));
|
||||
let mut span = SpanStatic::from(self.remote().fmt_file(x));
|
||||
if self.remote().enqueued().contains_key(x.path()) {
|
||||
span.modifiers |=
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
|
||||
span.style = span.style.add_modifier(
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC,
|
||||
);
|
||||
}
|
||||
|
||||
vec![span]
|
||||
vec![LineStatic::from(span)]
|
||||
})
|
||||
.collect();
|
||||
// Update content and title
|
||||
@@ -169,7 +173,7 @@ impl FileTransferActivity {
|
||||
ui_result(self.app.attr(
|
||||
&Id::ExplorerRemote,
|
||||
Attribute::Title,
|
||||
AttrValue::Title((hostname, Alignment::Left)),
|
||||
AttrValue::Title(Title::from(hostname).alignment(HorizontalAlignment::Left)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -185,7 +189,7 @@ impl FileTransferActivity {
|
||||
ui_result(self.app.attr(
|
||||
&Id::TransferProgressBar,
|
||||
Attribute::Value,
|
||||
AttrValue::Payload(PropPayload::One(PropValue::F64(
|
||||
AttrValue::Payload(PropPayload::Single(PropValue::F64(
|
||||
self.transfer.progress.calc_progress(),
|
||||
))),
|
||||
));
|
||||
@@ -201,7 +205,7 @@ impl FileTransferActivity {
|
||||
ui_result(self.app.attr(
|
||||
&Id::TransferProgressBar,
|
||||
Attribute::Title,
|
||||
AttrValue::Title((title, Alignment::Center)),
|
||||
AttrValue::Title(Title::from(title).alignment(HorizontalAlignment::Center)),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -231,17 +235,18 @@ impl FileTransferActivity {
|
||||
}
|
||||
|
||||
pub(in crate::ui::activities::filetransfer) fn update_find_list(&mut self) {
|
||||
let files: Vec<Vec<TextSpan>> = self
|
||||
let files: Vec<Vec<LineStatic>> = self
|
||||
.found()
|
||||
.unwrap()
|
||||
.iter_files()
|
||||
.map(|x| {
|
||||
let mut span = TextSpan::from(self.found().unwrap().fmt_file(x));
|
||||
let mut span = SpanStatic::from(self.found().unwrap().fmt_file(x));
|
||||
if self.found().unwrap().enqueued().contains_key(x.path()) {
|
||||
span.modifiers |=
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC;
|
||||
span.style = span.style.add_modifier(
|
||||
TextModifiers::REVERSED | TextModifiers::UNDERLINED | TextModifiers::ITALIC,
|
||||
);
|
||||
}
|
||||
vec![span]
|
||||
vec![LineStatic::from(span)]
|
||||
})
|
||||
.collect();
|
||||
ui_result(self.app.attr(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use tuirealm::props::{AttrValue, Attribute, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::props::{AttrValue, Attribute, Color, SpanStatic, TableBuilder};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
|
||||
use super::super::{FileTransferActivity, Id, LogLevel, LogRecord, ui_result};
|
||||
|
||||
@@ -52,28 +53,20 @@ impl FileTransferActivity {
|
||||
LogLevel::Warn => Color::Yellow,
|
||||
LogLevel::Info => Color::Green,
|
||||
};
|
||||
let level_label = match record.level {
|
||||
LogLevel::Error => "ERROR",
|
||||
LogLevel::Warn => "WARN",
|
||||
LogLevel::Info => "INFO",
|
||||
};
|
||||
table
|
||||
.add_col(TextSpan::from(format!(
|
||||
.add_col(SpanStatic::from(format!(
|
||||
"{}",
|
||||
record.time.format("%Y-%m-%dT%H:%M:%S%Z")
|
||||
)))
|
||||
.add_col(TextSpan::from(" ["))
|
||||
.add_col(
|
||||
TextSpan::new(
|
||||
format!(
|
||||
"{:5}",
|
||||
match record.level {
|
||||
LogLevel::Error => "ERROR",
|
||||
LogLevel::Warn => "WARN",
|
||||
LogLevel::Info => "INFO",
|
||||
}
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.fg(fg),
|
||||
)
|
||||
.add_col(TextSpan::from("]: "))
|
||||
.add_col(TextSpan::from(record.msg.as_str()));
|
||||
.add_col(SpanStatic::from(" ["))
|
||||
.add_col(SpanStatic::raw(format!("{level_label:5}")).fg(fg))
|
||||
.add_col(SpanStatic::from("]: "))
|
||||
.add_col(SpanStatic::from(record.msg.clone()));
|
||||
}
|
||||
ui_result(self.app.attr(
|
||||
&Id::Log,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// locals
|
||||
// externals
|
||||
use remotefs::fs::File;
|
||||
use tuirealm::{State, StateValue, Update};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::actions::SelectedFile;
|
||||
use super::actions::walkdir::WalkdirError;
|
||||
@@ -15,8 +15,8 @@ use super::{
|
||||
ui_result,
|
||||
};
|
||||
|
||||
impl Update<Msg> for FileTransferActivity {
|
||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
impl FileTransferActivity {
|
||||
pub(super) fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
match msg.unwrap_or(Msg::None) {
|
||||
Msg::None => None,
|
||||
Msg::PendingAction(_) => {
|
||||
@@ -78,7 +78,7 @@ impl FileTransferActivity {
|
||||
self.action_find_delete();
|
||||
// Remove deleted entries from the find-result list
|
||||
match self.app.state(&Id::ExplorerFind) {
|
||||
Ok(State::One(StateValue::Usize(idx))) => {
|
||||
Ok(State::Single(StateValue::Usize(idx))) => {
|
||||
self.found_mut().unwrap().del_entry(idx);
|
||||
}
|
||||
Ok(State::Vec(values)) => {
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
//! `filetransfer_activity` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::Attribute;
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::ratatui::widgets::Clear;
|
||||
use tuirealm::{Attribute, Sub, SubClause, SubEventClause};
|
||||
use tuirealm::subscription::{EventClause, Sub, SubClause};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use crate::ui::activities::filetransfer::browser::FoundExplorerTab;
|
||||
@@ -215,7 +217,12 @@ impl FileTransferActivity {
|
||||
let lines = self
|
||||
.app
|
||||
.query(&Id::WaitPopup, Attribute::Text)
|
||||
.map(|x| x.map(|x| x.unwrap_payload().unwrap_vec().len()))
|
||||
.map(|x| {
|
||||
x.map(|x| match x.into_attr() {
|
||||
tuirealm::props::AttrValue::Text(t) => t.lines.len(),
|
||||
_ => 1,
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
.unwrap_or(1) as u16;
|
||||
let popup =
|
||||
@@ -270,21 +277,19 @@ impl FileTransferActivity {
|
||||
// Get current text width
|
||||
let text_width = self
|
||||
.app
|
||||
.query(&id, tuirealm::Attribute::Text)
|
||||
.query(&id, tuirealm::props::Attribute::Text)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|x| {
|
||||
if x.as_payload().is_none() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
x.unwrap_payload()
|
||||
.unwrap_vec()
|
||||
.into_iter()
|
||||
.map(|x| x.unwrap_text_span().content)
|
||||
.collect::<Vec<String>>()
|
||||
.map(|x| match x.into_attr() {
|
||||
tuirealm::props::AttrValue::Text(t) => t
|
||||
.lines
|
||||
.iter()
|
||||
.flat_map(|line| line.spans.iter())
|
||||
.map(|span| span.content.as_ref())
|
||||
.collect::<Vec<&str>>()
|
||||
.join("")
|
||||
.width() as u16
|
||||
.width() as u16,
|
||||
_ => 0,
|
||||
})
|
||||
.unwrap_or(0);
|
||||
// Calc real width of a row in the popup
|
||||
@@ -305,41 +310,41 @@ impl FileTransferActivity {
|
||||
Box::<components::GlobalListener>::default(),
|
||||
vec![
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Esc,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('h'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Function(1),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Function(10),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('q'),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(SubEventClause::WindowResize, SubClause::Always),
|
||||
Sub::new(EventClause::WindowResize, SubClause::Always),
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
//! `filetransfer_activity` is the module which implements the Filetransfer activity, which is the main activity afterall
|
||||
|
||||
use remotefs::fs::{File, UnixPex};
|
||||
use tuirealm::props::{PropPayload, PropValue, TextSpan};
|
||||
use tuirealm::{AttrValue, Attribute};
|
||||
use tuirealm::props::{AttrValue, Attribute, PropPayload, PropValue, SpanStatic};
|
||||
|
||||
use crate::explorer::FileSorting;
|
||||
use crate::ui::activities::filetransfer::browser::FileExplorerTab;
|
||||
@@ -93,9 +92,9 @@ impl FileTransferActivity {
|
||||
let _ = self.app.attr(
|
||||
&Id::WaitPopup,
|
||||
Attribute::Text,
|
||||
AttrValue::Payload(PropPayload::Vec(vec![
|
||||
PropValue::TextSpan(TextSpan::from(text)),
|
||||
PropValue::TextSpan(TextSpan::from("Press 'CTRL+C' to abort")),
|
||||
AttrValue::Text(tuirealm::ratatui::text::Text::from_iter([
|
||||
SpanStatic::from(text),
|
||||
SpanStatic::from("Press 'CTRL+C' to abort"),
|
||||
])),
|
||||
);
|
||||
|
||||
|
||||
@@ -14,10 +14,11 @@ mod view;
|
||||
// Ext
|
||||
use std::time::Duration;
|
||||
|
||||
use tuirealm::application::PollStrategy;
|
||||
use tuirealm::application::{Application, PollStrategy};
|
||||
use tuirealm::event::NoUserEvent;
|
||||
use tuirealm::listener::EventListenerCfg;
|
||||
use tuirealm::props::Color;
|
||||
use tuirealm::{Application, NoUserEvent, Update};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{Activity, CROSSTERM_MAX_POLL, Context, ExitReason};
|
||||
use crate::config::themes::Theme;
|
||||
@@ -258,9 +259,7 @@ impl SetupActivity {
|
||||
pub fn new(ticks: Duration) -> Self {
|
||||
Self {
|
||||
app: Application::init(
|
||||
EventListenerCfg::default()
|
||||
.crossterm_input_listener(ticks, CROSSTERM_MAX_POLL)
|
||||
.poll_timeout(ticks),
|
||||
EventListenerCfg::default().crossterm_input_listener(ticks, CROSSTERM_MAX_POLL),
|
||||
),
|
||||
exit_reason: None,
|
||||
context: None,
|
||||
@@ -347,7 +346,10 @@ impl Activity for SetupActivity {
|
||||
if self.context.is_none() {
|
||||
return;
|
||||
}
|
||||
match self.app.tick(PollStrategy::UpTo(3)) {
|
||||
match self
|
||||
.app
|
||||
.tick(PollStrategy::UpTo(3, std::time::Duration::from_millis(10)))
|
||||
{
|
||||
Ok(messages) => {
|
||||
if !messages.is_empty() {
|
||||
self.redraw = true;
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
use std::env;
|
||||
|
||||
use tuirealm::ratatui::style::Color;
|
||||
use tuirealm::{State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{Id, IdSsh, IdTheme, SetupActivity, ViewLayout};
|
||||
// Ext
|
||||
@@ -96,7 +97,7 @@ impl SetupActivity {
|
||||
// Get key
|
||||
// get index
|
||||
let idx: Option<usize> = match self.app.state(&Id::Ssh(IdSsh::SshKeys)) {
|
||||
Ok(State::One(StateValue::Usize(idx))) => Some(idx),
|
||||
Ok(State::Single(StateValue::Usize(idx))) => Some(idx),
|
||||
_ => None,
|
||||
};
|
||||
// get ssh key and delete it
|
||||
@@ -114,11 +115,11 @@ impl SetupActivity {
|
||||
pub(super) fn action_new_ssh_key(&mut self) -> Result<(), String> {
|
||||
// get parameters
|
||||
let host: String = match self.app.state(&Id::Ssh(IdSsh::SshHost)) {
|
||||
Ok(State::One(StateValue::String(host))) => host,
|
||||
Ok(State::Single(StateValue::String(host))) => host,
|
||||
_ => String::new(),
|
||||
};
|
||||
let username: String = match self.app.state(&Id::Ssh(IdSsh::SshUsername)) {
|
||||
Ok(State::One(StateValue::String(user))) => user,
|
||||
Ok(State::Single(StateValue::String(user))) => user,
|
||||
_ => String::new(),
|
||||
};
|
||||
// Prepare text editor
|
||||
@@ -382,7 +383,7 @@ impl SetupActivity {
|
||||
/// Get color from component
|
||||
fn get_color(&self, component: &Id) -> Result<Color, ()> {
|
||||
match self.app.state(component) {
|
||||
Ok(State::One(StateValue::String(color))) => {
|
||||
Ok(State::Single(StateValue::String(color))) => {
|
||||
match crate::utils::parser::parse_color(color.as_str()) {
|
||||
Some(c) => Ok(c),
|
||||
None => Err(()),
|
||||
|
||||
@@ -16,19 +16,19 @@ pub(super) use config::{
|
||||
};
|
||||
pub(super) use ssh::{DelSshKeyPopup, SshHost, SshKeys, SshUsername};
|
||||
pub(super) use theme::*;
|
||||
use tui_realm_stdlib::Phantom;
|
||||
use tui_realm_stdlib::components::Phantom;
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::{Component, MockComponent};
|
||||
|
||||
// -- global listener
|
||||
|
||||
#[derive(Default, MockComponent)]
|
||||
#[derive(Default, Component)]
|
||||
pub struct GlobalListener {
|
||||
component: Phantom,
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GlobalListener {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for GlobalListener {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Function(10),
|
||||
|
||||
@@ -2,15 +2,21 @@
|
||||
//!
|
||||
//! config tab components
|
||||
|
||||
use tui_realm_stdlib::{List, Paragraph, Radio, Span};
|
||||
use tui_realm_stdlib::components::{List, Paragraph, Radio, Span};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderSides, BorderType, Borders, Color, HorizontalAlignment, SpanStatic, Style, TableBuilder,
|
||||
TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::ratatui::style::Stylize;
|
||||
use tuirealm::ratatui::text::Text;
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::{CommonMsg, Msg, ViewLayout};
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ErrorPopup {
|
||||
component: Paragraph,
|
||||
}
|
||||
@@ -19,21 +25,23 @@ impl ErrorPopup {
|
||||
pub fn new<S: AsRef<str>>(text: S) -> Self {
|
||||
Self {
|
||||
component: Paragraph::default()
|
||||
.alignment(Alignment::Center)
|
||||
.alignment_horizontal(HorizontalAlignment::Center)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Red)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(Color::Red)
|
||||
.text([TextSpan::from(text.as_ref())])
|
||||
.wrap(true),
|
||||
.text(Text::from_iter([SpanStatic::from(
|
||||
text.as_ref().to_string(),
|
||||
)]))
|
||||
.wrap_trim(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ErrorPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ErrorPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
@@ -44,7 +52,7 @@ impl Component<Msg, NoUserEvent> for ErrorPopup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct Footer {
|
||||
component: Span,
|
||||
}
|
||||
@@ -53,28 +61,28 @@ impl Default for Footer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component: Span::default().spans([
|
||||
TextSpan::new("<F1|CTRL+H>").bold().fg(Color::Cyan),
|
||||
TextSpan::new(" Help "),
|
||||
TextSpan::new("<F4|CTRL+S>").bold().fg(Color::Cyan),
|
||||
TextSpan::new(" Save parameters "),
|
||||
TextSpan::new("<F10|ESC>").bold().fg(Color::Cyan),
|
||||
TextSpan::new(" Exit "),
|
||||
TextSpan::new("<TAB>").bold().fg(Color::Cyan),
|
||||
TextSpan::new(" Change panel "),
|
||||
TextSpan::new("<UP/DOWN>").bold().fg(Color::Cyan),
|
||||
TextSpan::new(" Change field "),
|
||||
SpanStatic::raw("<F1|CTRL+H>").bold().fg(Color::Cyan),
|
||||
SpanStatic::raw(" Help "),
|
||||
SpanStatic::raw("<F4|CTRL+S>").bold().fg(Color::Cyan),
|
||||
SpanStatic::raw(" Save parameters "),
|
||||
SpanStatic::raw("<F10|ESC>").bold().fg(Color::Cyan),
|
||||
SpanStatic::raw(" Exit "),
|
||||
SpanStatic::raw("<TAB>").bold().fg(Color::Cyan),
|
||||
SpanStatic::raw(" Change panel "),
|
||||
SpanStatic::raw("<UP/DOWN>").bold().fg(Color::Cyan),
|
||||
SpanStatic::raw(" Change field "),
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Footer {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Footer {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct Header {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -83,13 +91,17 @@ impl Header {
|
||||
pub fn new(layout: ViewLayout) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Yellow)
|
||||
.sides(BorderSides::BOTTOM),
|
||||
)
|
||||
.choices(["Configuration parameters", "SSH Keys", "Theme"])
|
||||
.foreground(Color::Yellow)
|
||||
.value(match layout {
|
||||
ViewLayout::SetupForm => 0,
|
||||
ViewLayout::SshKeys => 1,
|
||||
@@ -99,13 +111,13 @@ impl Header {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Header {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Header {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct Keybindings {
|
||||
component: List,
|
||||
}
|
||||
@@ -115,45 +127,47 @@ impl Default for Keybindings {
|
||||
Self {
|
||||
component: List::default()
|
||||
.borders(Borders::default().modifiers(BorderType::Rounded))
|
||||
.title("Keybindings", Alignment::Center)
|
||||
.title(Title::from("Keybindings").alignment(HorizontalAlignment::Center))
|
||||
.scroll(true)
|
||||
.highlighted_str("? ")
|
||||
.highlight_str("? ")
|
||||
.rows(
|
||||
TableBuilder::default()
|
||||
.add_col(TextSpan::new("<ESC>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Exit setup"))
|
||||
.add_col(SpanStatic::raw("<ESC>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Exit setup"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<TAB>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Change setup page"))
|
||||
.add_col(SpanStatic::raw("<TAB>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Change setup page"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Change cursor"))
|
||||
.add_col(SpanStatic::raw("<RIGHT/LEFT>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Change cursor"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Change input field"))
|
||||
.add_col(SpanStatic::raw("<UP/DOWN>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Change input field"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<ENTER>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Select / Dismiss popup"))
|
||||
.add_col(SpanStatic::raw("<ENTER>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Select / Dismiss popup"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<DEL|E>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Delete SSH key"))
|
||||
.add_col(SpanStatic::raw("<DEL|E>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Delete SSH key"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+N>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" New SSH key"))
|
||||
.add_col(SpanStatic::raw("<CTRL+N>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" New SSH key"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+R>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Revert changes"))
|
||||
.add_col(SpanStatic::raw("<CTRL+R>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Revert changes"))
|
||||
.add_row()
|
||||
.add_col(TextSpan::new("<CTRL+S>").bold().fg(Color::Cyan))
|
||||
.add_col(TextSpan::from(" Save configuration"))
|
||||
.build(),
|
||||
.add_col(SpanStatic::raw("<CTRL+S>").bold().fg(Color::Cyan))
|
||||
.add_col(SpanStatic::from(" Save configuration"))
|
||||
.build()
|
||||
.into_iter()
|
||||
.map(|row| row.into_iter().flat_map(|l| l.spans).collect::<Vec<_>>()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for Keybindings {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for Keybindings {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Esc | Key::Enter,
|
||||
@@ -197,7 +211,7 @@ impl Component<Msg, NoUserEvent> for Keybindings {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct QuitPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -206,15 +220,19 @@ impl Default for QuitPopup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::Red)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Red)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(Color::Red)
|
||||
.title(
|
||||
"There are unsaved changes! Save changes before leaving?",
|
||||
Alignment::Center,
|
||||
Title::from("There are unsaved changes! Save changes before leaving?")
|
||||
.alignment(HorizontalAlignment::Center),
|
||||
)
|
||||
.rewind(true)
|
||||
.choices(["Save", "Don't save", "Cancel"]),
|
||||
@@ -222,8 +240,8 @@ impl Default for QuitPopup {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for QuitPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Common(CommonMsg::CloseQuitPopup))
|
||||
@@ -243,10 +261,10 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.perform(Cmd::Submit) {
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0))) => {
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0))) => {
|
||||
Some(Msg::Common(CommonMsg::SaveAndQuit))
|
||||
}
|
||||
CmdResult::Submit(State::One(StateValue::Usize(1))) => {
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(1))) => {
|
||||
Some(Msg::Common(CommonMsg::Quit))
|
||||
}
|
||||
_ => Some(Msg::Common(CommonMsg::CloseQuitPopup)),
|
||||
@@ -256,7 +274,7 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SavePopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -265,21 +283,25 @@ impl Default for SavePopup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::Yellow)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Yellow)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(Color::Yellow)
|
||||
.title("Save changes?", Alignment::Center)
|
||||
.title(Title::from("Save changes?").alignment(HorizontalAlignment::Center))
|
||||
.rewind(true)
|
||||
.choices(["Yes", "No"]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SavePopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SavePopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Common(CommonMsg::CloseSavePopup))
|
||||
@@ -309,7 +331,7 @@ impl Component<Msg, NoUserEvent> for SavePopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Common(CommonMsg::SaveConfig))
|
||||
} else {
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
//!
|
||||
//! config tab components
|
||||
|
||||
use tui_realm_stdlib::{Input, Radio};
|
||||
use tui_realm_stdlib::components::{Input, Radio};
|
||||
use tuirealm::command::{Cmd, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
BorderType, Borders, Color, HorizontalAlignment, InputType, Style, TextModifiers, Title,
|
||||
};
|
||||
|
||||
use super::{ConfigMsg, Msg};
|
||||
use crate::explorer::GroupDirs as GroupDirsEnum;
|
||||
@@ -19,7 +21,7 @@ use crate::utils::parser::parse_bytesize;
|
||||
|
||||
// -- components
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct CheckUpdates {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -28,22 +30,26 @@ impl CheckUpdates {
|
||||
pub fn new(enabled: bool) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::LightYellow)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::LightYellow)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(["Yes", "No"])
|
||||
.foreground(Color::LightYellow)
|
||||
.rewind(true)
|
||||
.title("Check for updates?", Alignment::Left)
|
||||
.title(Title::from("Check for updates?").alignment(HorizontalAlignment::Left))
|
||||
.value(usize::from(!enabled)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for CheckUpdates {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for CheckUpdates {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_radio_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -53,7 +59,7 @@ impl Component<Msg, NoUserEvent> for CheckUpdates {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct DefaultProtocol {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -70,7 +76,7 @@ impl DefaultProtocol {
|
||||
.choices(["SFTP", "SCP", "FTP", "FTPS", "Kube", "S3", "SMB", "WebDAV"])
|
||||
.foreground(Color::Cyan)
|
||||
.rewind(true)
|
||||
.title("Default protocol", Alignment::Left)
|
||||
.title(Title::from("Default protocol").alignment(HorizontalAlignment::Left))
|
||||
.value(match protocol {
|
||||
FileTransferProtocol::Sftp => RADIO_PROTOCOL_SFTP,
|
||||
FileTransferProtocol::Scp => RADIO_PROTOCOL_SCP,
|
||||
@@ -85,8 +91,8 @@ impl DefaultProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DefaultProtocol {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for DefaultProtocol {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_radio_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -96,7 +102,7 @@ impl Component<Msg, NoUserEvent> for DefaultProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct GroupDirs {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -105,15 +111,19 @@ impl GroupDirs {
|
||||
pub fn new(opt: Option<GroupDirsEnum>) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::LightMagenta)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::LightMagenta)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(["Display first", "Display last", "No"])
|
||||
.foreground(Color::LightMagenta)
|
||||
.rewind(true)
|
||||
.title("Group directories", Alignment::Left)
|
||||
.title(Title::from("Group directories").alignment(HorizontalAlignment::Left))
|
||||
.value(match opt {
|
||||
Some(GroupDirsEnum::First) => 0,
|
||||
Some(GroupDirsEnum::Last) => 1,
|
||||
@@ -123,8 +133,8 @@ impl GroupDirs {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for GroupDirs {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for GroupDirs {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_radio_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -134,7 +144,7 @@ impl Component<Msg, NoUserEvent> for GroupDirs {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct HiddenFiles {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -151,14 +161,17 @@ impl HiddenFiles {
|
||||
.choices(["Yes", "No"])
|
||||
.foreground(Color::LightRed)
|
||||
.rewind(true)
|
||||
.title("Show hidden files? (by default)", Alignment::Left)
|
||||
.title(
|
||||
Title::from("Show hidden files? (by default)")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.value(usize::from(!enabled)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for HiddenFiles {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for HiddenFiles {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_radio_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -168,7 +181,7 @@ impl Component<Msg, NoUserEvent> for HiddenFiles {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct NotificationsEnabled {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -177,22 +190,26 @@ impl NotificationsEnabled {
|
||||
pub fn new(enabled: bool) -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::LightRed)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::LightRed)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(["Yes", "No"])
|
||||
.foreground(Color::LightRed)
|
||||
.rewind(true)
|
||||
.title("Enable notifications?", Alignment::Left)
|
||||
.title(Title::from("Enable notifications?").alignment(HorizontalAlignment::Left))
|
||||
.value(usize::from(!enabled)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for NotificationsEnabled {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for NotificationsEnabled {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_radio_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -202,7 +219,7 @@ impl Component<Msg, NoUserEvent> for NotificationsEnabled {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct PromptOnFileReplace {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -219,14 +236,17 @@ impl PromptOnFileReplace {
|
||||
.choices(["Yes", "No"])
|
||||
.foreground(Color::LightBlue)
|
||||
.rewind(true)
|
||||
.title("Prompt when replacing existing files?", Alignment::Left)
|
||||
.title(
|
||||
Title::from("Prompt when replacing existing files?")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.value(usize::from(!enabled)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for PromptOnFileReplace {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for PromptOnFileReplace {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_radio_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -236,7 +256,7 @@ impl Component<Msg, NoUserEvent> for PromptOnFileReplace {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct LocalFileFmt {
|
||||
component: Input,
|
||||
}
|
||||
@@ -252,18 +272,21 @@ impl LocalFileFmt {
|
||||
)
|
||||
.foreground(Color::LightGreen)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"{NAME:36} {PEX} {SIZE} {MTIME:17:%b %d %Y %H:%M}",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("File formatter syntax (local)")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.title("File formatter syntax (local)", Alignment::Left)
|
||||
.value(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for LocalFileFmt {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for LocalFileFmt {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -273,7 +296,7 @@ impl Component<Msg, NoUserEvent> for LocalFileFmt {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct NotificationsThreshold {
|
||||
component: Input,
|
||||
}
|
||||
@@ -297,15 +320,21 @@ impl NotificationsThreshold {
|
||||
.foreground(Color::LightYellow)
|
||||
.invalid_style(Style::default().fg(Color::Red))
|
||||
.input_type(InputType::Custom(validate, char_valid))
|
||||
.placeholder("64 MB", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Notifications: minimum transfer size", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"64 MB",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("Notifications: minimum transfer size")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.value(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for NotificationsThreshold {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for NotificationsThreshold {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -315,7 +344,7 @@ impl Component<Msg, NoUserEvent> for NotificationsThreshold {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct RemoteFileFmt {
|
||||
component: Input,
|
||||
}
|
||||
@@ -331,18 +360,21 @@ impl RemoteFileFmt {
|
||||
)
|
||||
.foreground(Color::Cyan)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"{NAME:36} {PEX} {SIZE} {MTIME:17:%b %d %Y %H:%M}",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(
|
||||
Title::from("File formatter syntax (remote)")
|
||||
.alignment(HorizontalAlignment::Left),
|
||||
)
|
||||
.title("File formatter syntax (remote)", Alignment::Left)
|
||||
.value(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for RemoteFileFmt {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for RemoteFileFmt {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -352,7 +384,7 @@ impl Component<Msg, NoUserEvent> for RemoteFileFmt {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SshConfig {
|
||||
component: Input,
|
||||
}
|
||||
@@ -368,18 +400,18 @@ impl SshConfig {
|
||||
)
|
||||
.foreground(Color::LightBlue)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"~/.ssh/config",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("SSH configuration path", Alignment::Left)
|
||||
))
|
||||
.title(Title::from("SSH configuration path").alignment(HorizontalAlignment::Left))
|
||||
.value(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SshConfig {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SshConfig {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -389,7 +421,7 @@ impl Component<Msg, NoUserEvent> for SshConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct TextEditor {
|
||||
component: Input,
|
||||
}
|
||||
@@ -405,15 +437,18 @@ impl TextEditor {
|
||||
)
|
||||
.foreground(Color::LightGreen)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder("vim", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Text editor", Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"vim",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Text editor").alignment(HorizontalAlignment::Left))
|
||||
.value(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for TextEditor {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for TextEditor {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
handle_input_ev(
|
||||
self,
|
||||
ev,
|
||||
@@ -426,8 +461,8 @@ impl Component<Msg, NoUserEvent> for TextEditor {
|
||||
// -- event handler
|
||||
|
||||
fn handle_input_ev(
|
||||
component: &mut dyn Component<Msg, NoUserEvent>,
|
||||
ev: Event<NoUserEvent>,
|
||||
component: &mut dyn AppComponent<Msg, NoUserEvent>,
|
||||
ev: &Event<NoUserEvent>,
|
||||
on_key_down: Msg,
|
||||
on_key_up: Msg,
|
||||
) -> Option<Msg> {
|
||||
@@ -476,7 +511,7 @@ fn handle_input_ev(
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
component.perform(Cmd::Type(ch));
|
||||
component.perform(Cmd::Type(*ch));
|
||||
Some(Msg::Config(ConfigMsg::ConfigChanged))
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
@@ -488,8 +523,8 @@ fn handle_input_ev(
|
||||
}
|
||||
|
||||
fn handle_radio_ev(
|
||||
component: &mut dyn Component<Msg, NoUserEvent>,
|
||||
ev: Event<NoUserEvent>,
|
||||
component: &mut dyn AppComponent<Msg, NoUserEvent>,
|
||||
ev: &Event<NoUserEvent>,
|
||||
on_key_down: Msg,
|
||||
on_key_up: Msg,
|
||||
) -> Option<Msg> {
|
||||
|
||||
@@ -2,13 +2,15 @@
|
||||
//!
|
||||
//! ssh components
|
||||
|
||||
use tui_realm_stdlib::{Input, List, Radio};
|
||||
use tui_realm_stdlib::components::{Input, List, Radio};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
Alignment, BorderSides, BorderType, Borders, Color, InputType, Style, TextSpan,
|
||||
BorderSides, BorderType, Borders, Color, HorizontalAlignment, InputType, SpanStatic, Style,
|
||||
TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::{Msg, SshMsg};
|
||||
|
||||
@@ -17,7 +19,7 @@ SshHost,
|
||||
SshKeys,
|
||||
SshUsername, */
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct DelSshKeyPopup {
|
||||
component: Radio,
|
||||
}
|
||||
@@ -26,22 +28,26 @@ impl Default for DelSshKeyPopup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
component: Radio::default()
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.fg(Color::Red)
|
||||
.add_modifier(TextModifiers::REVERSED),
|
||||
)
|
||||
.borders(
|
||||
Borders::default()
|
||||
.color(Color::Red)
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.choices(["Yes", "No"])
|
||||
.foreground(Color::Red)
|
||||
.rewind(true)
|
||||
.title("Delete key?", Alignment::Center)
|
||||
.title(Title::from("Delete key?").alignment(HorizontalAlignment::Center))
|
||||
.value(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for DelSshKeyPopup {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for DelSshKeyPopup {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||
Some(Msg::Ssh(SshMsg::CloseDelSshKeyPopup))
|
||||
@@ -71,7 +77,7 @@ impl Component<Msg, NoUserEvent> for DelSshKeyPopup {
|
||||
}) => {
|
||||
if matches!(
|
||||
self.perform(Cmd::Submit),
|
||||
CmdResult::Submit(State::One(StateValue::Usize(0)))
|
||||
CmdResult::Submit(State::Single(StateValue::Usize(0)))
|
||||
) {
|
||||
Some(Msg::Ssh(SshMsg::DeleteSshKey))
|
||||
} else {
|
||||
@@ -83,7 +89,7 @@ impl Component<Msg, NoUserEvent> for DelSshKeyPopup {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SshKeys {
|
||||
component: List,
|
||||
}
|
||||
@@ -98,18 +104,22 @@ impl SshKeys {
|
||||
.modifiers(BorderType::Rounded),
|
||||
)
|
||||
.foreground(Color::LightGreen)
|
||||
.highlighted_color(Color::LightGreen)
|
||||
.highlight_style(Style::default().fg(Color::LightGreen))
|
||||
.rewind(true)
|
||||
.rows(keys.iter().map(|x| vec![TextSpan::from(x)]).collect())
|
||||
.rows(
|
||||
keys.iter()
|
||||
.map(|x| vec![SpanStatic::from(x.clone())])
|
||||
.collect::<Vec<Vec<SpanStatic>>>(),
|
||||
)
|
||||
.step(4)
|
||||
.scroll(true)
|
||||
.title("SSH Keys", Alignment::Left),
|
||||
.title(Title::from("SSH Keys").alignment(HorizontalAlignment::Left)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SshKeys {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SshKeys {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Down, ..
|
||||
@@ -147,7 +157,9 @@ impl Component<Msg, NoUserEvent> for SshKeys {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Enter, ..
|
||||
}) => match self.state() {
|
||||
State::One(StateValue::Usize(choice)) => Some(Msg::Ssh(SshMsg::EditSshKey(choice))),
|
||||
State::Single(StateValue::Usize(choice)) => {
|
||||
Some(Msg::Ssh(SshMsg::EditSshKey(choice)))
|
||||
}
|
||||
_ => Some(Msg::None),
|
||||
},
|
||||
Event::Keyboard(KeyEvent {
|
||||
@@ -163,7 +175,7 @@ impl Component<Msg, NoUserEvent> for SshKeys {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SshHost {
|
||||
component: Input,
|
||||
}
|
||||
@@ -177,17 +189,17 @@ impl Default for SshHost {
|
||||
.sides(BorderSides::TOP | BorderSides::RIGHT | BorderSides::LEFT),
|
||||
)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder(
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"192.168.1.2",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
)
|
||||
.title("Hostname or address", Alignment::Center),
|
||||
))
|
||||
.title(Title::from("Hostname or address").alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SshHost {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SshHost {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -233,7 +245,7 @@ impl Component<Msg, NoUserEvent> for SshHost {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
@@ -250,7 +262,7 @@ impl Component<Msg, NoUserEvent> for SshHost {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct SshUsername {
|
||||
component: Input,
|
||||
}
|
||||
@@ -264,14 +276,17 @@ impl Default for SshUsername {
|
||||
.sides(BorderSides::BOTTOM | BorderSides::RIGHT | BorderSides::LEFT),
|
||||
)
|
||||
.input_type(InputType::Text)
|
||||
.placeholder("root", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title("Username", Alignment::Center),
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"root",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from("Username").alignment(HorizontalAlignment::Center)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for SshUsername {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for SshUsername {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -312,7 +327,7 @@ impl Component<Msg, NoUserEvent> for SshUsername {
|
||||
code: Key::Char(ch),
|
||||
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
|
||||
}) => {
|
||||
self.perform(Cmd::Type(ch));
|
||||
self.perform(Cmd::Type(*ch));
|
||||
Some(Msg::None)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
|
||||
@@ -2,20 +2,22 @@
|
||||
//!
|
||||
//! theme tab components
|
||||
|
||||
use tui_realm_stdlib::{Input, Label};
|
||||
use tui_realm_stdlib::components::{Input, Label};
|
||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, InputType, Style, TextModifiers};
|
||||
use tuirealm::{
|
||||
AttrValue, Attribute, Component, Event, MockComponent, NoUserEvent, State, StateValue,
|
||||
use tuirealm::component::{AppComponent, Component};
|
||||
use tuirealm::event::{Event, Key, KeyEvent, KeyModifiers, NoUserEvent};
|
||||
use tuirealm::props::{
|
||||
AttrValue, Attribute, BorderType, Borders, Color, HorizontalAlignment, InputType, Style,
|
||||
TextModifiers, Title,
|
||||
};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
|
||||
use super::{Msg, ThemeMsg};
|
||||
use crate::ui::activities::setup::IdTheme;
|
||||
|
||||
// -- components
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthTitle {
|
||||
component: Label,
|
||||
}
|
||||
@@ -30,13 +32,13 @@ impl Default for AuthTitle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthTitle {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthTitle {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscTitle {
|
||||
component: Label,
|
||||
}
|
||||
@@ -51,13 +53,13 @@ impl Default for MiscTitle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscTitle {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscTitle {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct TransferTitle {
|
||||
component: Label,
|
||||
}
|
||||
@@ -72,13 +74,13 @@ impl Default for TransferTitle {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for TransferTitle {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for TransferTitle {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct TransferTitle2 {
|
||||
component: Label,
|
||||
}
|
||||
@@ -93,13 +95,13 @@ impl Default for TransferTitle2 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for TransferTitle2 {
|
||||
fn on(&mut self, _ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for TransferTitle2 {
|
||||
fn on(&mut self, _ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthAddress {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -118,13 +120,13 @@ impl AuthAddress {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthAddress {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthAddress {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthBookmarks {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -143,13 +145,13 @@ impl AuthBookmarks {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthBookmarks {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthBookmarks {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthPassword {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -168,13 +170,13 @@ impl AuthPassword {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthPassword {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthPassword {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthPort {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -193,13 +195,13 @@ impl AuthPort {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthPort {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthPort {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthProtocol {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -218,13 +220,13 @@ impl AuthProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthProtocol {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthProtocol {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthRecentHosts {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -243,12 +245,12 @@ impl AuthRecentHosts {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthRecentHosts {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthRecentHosts {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct AuthUsername {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -267,13 +269,13 @@ impl AuthUsername {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for AuthUsername {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for AuthUsername {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerLocalBg {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -292,13 +294,13 @@ impl ExplorerLocalBg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerLocalBg {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerLocalBg {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerLocalFg {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -317,13 +319,13 @@ impl ExplorerLocalFg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerLocalFg {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerLocalFg {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerLocalHg {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -342,13 +344,13 @@ impl ExplorerLocalHg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerLocalHg {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerLocalHg {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerRemoteBg {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -367,13 +369,13 @@ impl ExplorerRemoteBg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerRemoteBg {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerRemoteBg {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerRemoteFg {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -392,13 +394,13 @@ impl ExplorerRemoteFg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerRemoteFg {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerRemoteFg {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ExplorerRemoteHg {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -417,13 +419,13 @@ impl ExplorerRemoteHg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ExplorerRemoteHg {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ExplorerRemoteHg {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct LogBg {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -442,13 +444,13 @@ impl LogBg {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for LogBg {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for LogBg {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct LogWindow {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -467,13 +469,13 @@ impl LogWindow {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for LogWindow {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for LogWindow {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscError {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -492,13 +494,13 @@ impl MiscError {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscError {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscError {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscInfo {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -517,13 +519,13 @@ impl MiscInfo {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscInfo {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscInfo {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscInput {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -542,13 +544,13 @@ impl MiscInput {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscInput {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscInput {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscKeys {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -567,13 +569,13 @@ impl MiscKeys {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscKeys {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscKeys {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscQuit {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -592,13 +594,13 @@ impl MiscQuit {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscQuit {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscQuit {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscSave {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -617,13 +619,13 @@ impl MiscSave {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscSave {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscSave {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct MiscWarn {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -642,13 +644,13 @@ impl MiscWarn {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for MiscWarn {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for MiscWarn {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct ProgBar {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -667,13 +669,13 @@ impl ProgBar {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for ProgBar {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for ProgBar {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct StatusHidden {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -692,13 +694,13 @@ impl StatusHidden {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for StatusHidden {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for StatusHidden {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct StatusSorting {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -717,13 +719,13 @@ impl StatusSorting {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for StatusSorting {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for StatusSorting {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
pub struct StatusSync {
|
||||
component: InputColor,
|
||||
}
|
||||
@@ -742,15 +744,15 @@ impl StatusSync {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for StatusSync {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for StatusSync {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
self.component.on(ev)
|
||||
}
|
||||
}
|
||||
|
||||
// -- input color
|
||||
|
||||
#[derive(MockComponent)]
|
||||
#[derive(Component)]
|
||||
struct InputColor {
|
||||
component: Input,
|
||||
id: IdTheme,
|
||||
@@ -770,8 +772,11 @@ impl InputColor {
|
||||
)
|
||||
.foreground(color)
|
||||
.input_type(InputType::Color)
|
||||
.placeholder("#aa33ee", Style::default().fg(Color::Rgb(128, 128, 128)))
|
||||
.title(name, Alignment::Left)
|
||||
.placeholder(tuirealm::props::SpanStatic::styled(
|
||||
"#aa33ee",
|
||||
Style::default().fg(Color::Rgb(128, 128, 128)),
|
||||
))
|
||||
.title(Title::from(name.to_string()).alignment(HorizontalAlignment::Left))
|
||||
.value(value),
|
||||
id,
|
||||
on_key_down,
|
||||
@@ -780,7 +785,7 @@ impl InputColor {
|
||||
}
|
||||
|
||||
fn update_color(&mut self, result: CmdResult) -> Option<Msg> {
|
||||
if let CmdResult::Changed(State::One(StateValue::String(color))) = result {
|
||||
if let CmdResult::Changed(State::Single(StateValue::String(color))) = result {
|
||||
let color = tuirealm::utils::parser::parse_color(&color).unwrap();
|
||||
self.attr(Attribute::Foreground, AttrValue::Color(color));
|
||||
self.attr(
|
||||
@@ -807,8 +812,8 @@ impl InputColor {
|
||||
}
|
||||
}
|
||||
|
||||
impl Component<Msg, NoUserEvent> for InputColor {
|
||||
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
|
||||
impl AppComponent<Msg, NoUserEvent> for InputColor {
|
||||
fn on(&mut self, ev: &Event<NoUserEvent>) -> Option<Msg> {
|
||||
match ev {
|
||||
Event::Keyboard(KeyEvent {
|
||||
code: Key::Left, ..
|
||||
@@ -854,7 +859,7 @@ impl Component<Msg, NoUserEvent> for InputColor {
|
||||
code: Key::Char(ch),
|
||||
..
|
||||
}) => {
|
||||
let result = self.perform(Cmd::Type(ch));
|
||||
let result = self.perform(Cmd::Type(*ch));
|
||||
self.update_color(result)
|
||||
}
|
||||
Event::Keyboard(KeyEvent {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// Ext
|
||||
use std::env;
|
||||
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::SetupActivity;
|
||||
|
||||
impl SetupActivity {
|
||||
|
||||
@@ -5,17 +5,16 @@
|
||||
|
||||
// locals
|
||||
// ext
|
||||
use tuirealm::Update;
|
||||
|
||||
use super::{
|
||||
CommonMsg, ConfigMsg, Id, IdConfig, IdSsh, IdTheme, Msg, SetupActivity, SshMsg, ThemeMsg,
|
||||
ViewLayout,
|
||||
};
|
||||
|
||||
impl Update<Msg> for SetupActivity {
|
||||
impl SetupActivity {
|
||||
/// Update auth activity model based on msg
|
||||
/// The function exits when returns None
|
||||
fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
pub(super) fn update(&mut self, msg: Option<Msg>) -> Option<Msg> {
|
||||
match msg.unwrap_or(Msg::None) {
|
||||
Msg::Common(msg) => self.common_update(msg),
|
||||
Msg::Config(msg) => self.config_update(msg),
|
||||
|
||||
@@ -8,8 +8,9 @@ pub mod ssh_keys;
|
||||
pub mod theme;
|
||||
|
||||
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||
use tuirealm::ratatui::Frame;
|
||||
use tuirealm::ratatui::widgets::Clear;
|
||||
use tuirealm::{Frame, Sub, SubClause, SubEventClause};
|
||||
use tuirealm::subscription::{EventClause, Sub, SubClause};
|
||||
|
||||
use super::*;
|
||||
use crate::utils::ui::{Popup, Size};
|
||||
@@ -172,62 +173,62 @@ impl SetupActivity {
|
||||
Box::<components::GlobalListener>::default(),
|
||||
vec![
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Esc,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Function(10),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Tab,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('h'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Function(1),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('r'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Char('s'),
|
||||
modifiers: KeyModifiers::CONTROL,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(
|
||||
SubEventClause::Keyboard(KeyEvent {
|
||||
EventClause::Keyboard(KeyEvent {
|
||||
code: Key::Function(4),
|
||||
modifiers: KeyModifiers::NONE,
|
||||
}),
|
||||
Self::no_popup_mounted_clause(),
|
||||
),
|
||||
Sub::new(SubEventClause::WindowResize, SubClause::Always),
|
||||
Sub::new(EventClause::WindowResize, SubClause::Always),
|
||||
],
|
||||
) {
|
||||
error!("Failed to mount component: {err}");
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::{State, StateValue};
|
||||
use tuirealm::state::{State, StateValue};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{
|
||||
Context, Id, IdCommon, IdConfig, RADIO_PROTOCOL_KUBE, RADIO_PROTOCOL_WEBDAV, SetupActivity,
|
||||
@@ -256,13 +257,13 @@ impl SetupActivity {
|
||||
|
||||
/// Collect values from input and put them into the configuration
|
||||
pub(crate) fn collect_input_values(&mut self) {
|
||||
if let Ok(State::One(StateValue::String(editor))) =
|
||||
if let Ok(State::Single(StateValue::String(editor))) =
|
||||
self.app.state(&Id::Config(IdConfig::TextEditor))
|
||||
{
|
||||
self.config_mut()
|
||||
.set_text_editor(PathBuf::from(editor.as_str()));
|
||||
}
|
||||
if let Ok(State::One(StateValue::Usize(protocol))) =
|
||||
if let Ok(State::Single(StateValue::Usize(protocol))) =
|
||||
self.app.state(&Id::Config(IdConfig::DefaultProtocol))
|
||||
{
|
||||
let protocol: FileTransferProtocol = match protocol {
|
||||
@@ -277,35 +278,35 @@ impl SetupActivity {
|
||||
};
|
||||
self.config_mut().set_default_protocol(protocol);
|
||||
}
|
||||
if let Ok(State::One(StateValue::Usize(opt))) =
|
||||
if let Ok(State::Single(StateValue::Usize(opt))) =
|
||||
self.app.state(&Id::Config(IdConfig::HiddenFiles))
|
||||
{
|
||||
let show: bool = matches!(opt, 0);
|
||||
self.config_mut().set_show_hidden_files(show);
|
||||
}
|
||||
if let Ok(State::One(StateValue::Usize(opt))) =
|
||||
if let Ok(State::Single(StateValue::Usize(opt))) =
|
||||
self.app.state(&Id::Config(IdConfig::CheckUpdates))
|
||||
{
|
||||
let check: bool = matches!(opt, 0);
|
||||
self.config_mut().set_check_for_updates(check);
|
||||
}
|
||||
if let Ok(State::One(StateValue::Usize(opt))) =
|
||||
if let Ok(State::Single(StateValue::Usize(opt))) =
|
||||
self.app.state(&Id::Config(IdConfig::PromptOnFileReplace))
|
||||
{
|
||||
let check: bool = matches!(opt, 0);
|
||||
self.config_mut().set_prompt_on_file_replace(check);
|
||||
}
|
||||
if let Ok(State::One(StateValue::String(fmt))) =
|
||||
if let Ok(State::Single(StateValue::String(fmt))) =
|
||||
self.app.state(&Id::Config(IdConfig::LocalFileFmt))
|
||||
{
|
||||
self.config_mut().set_local_file_fmt(fmt);
|
||||
}
|
||||
if let Ok(State::One(StateValue::String(fmt))) =
|
||||
if let Ok(State::Single(StateValue::String(fmt))) =
|
||||
self.app.state(&Id::Config(IdConfig::RemoteFileFmt))
|
||||
{
|
||||
self.config_mut().set_remote_file_fmt(fmt);
|
||||
}
|
||||
if let Ok(State::One(StateValue::Usize(opt))) =
|
||||
if let Ok(State::Single(StateValue::Usize(opt))) =
|
||||
self.app.state(&Id::Config(IdConfig::GroupDirs))
|
||||
{
|
||||
let dirs: Option<GroupDirs> = match opt {
|
||||
@@ -315,18 +316,18 @@ impl SetupActivity {
|
||||
};
|
||||
self.config_mut().set_group_dirs(dirs);
|
||||
}
|
||||
if let Ok(State::One(StateValue::Usize(opt))) =
|
||||
if let Ok(State::Single(StateValue::Usize(opt))) =
|
||||
self.app.state(&Id::Config(IdConfig::NotificationsEnabled))
|
||||
{
|
||||
self.config_mut().set_notifications(opt == 0);
|
||||
}
|
||||
if let Ok(State::One(StateValue::U64(bytes))) = self
|
||||
if let Ok(State::Single(StateValue::U64(bytes))) = self
|
||||
.app
|
||||
.state(&Id::Config(IdConfig::NotificationsThreshold))
|
||||
{
|
||||
self.config_mut().set_notification_threshold(bytes);
|
||||
}
|
||||
if let Ok(State::One(StateValue::String(mut path))) =
|
||||
if let Ok(State::Single(StateValue::String(mut path))) =
|
||||
self.app.state(&Id::Config(IdConfig::SshConfig))
|
||||
{
|
||||
if path.is_empty() {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// Ext
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::ratatui::widgets::Clear;
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{Context, Id, IdCommon, IdSsh, SetupActivity, ViewLayout, components};
|
||||
use crate::utils::ui::{Popup, Size};
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// Locals
|
||||
// Ext
|
||||
use tuirealm::ratatui::layout::{Constraint, Direction, Layout};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
use super::{Context, Id, IdCommon, IdTheme, SetupActivity, Theme, ViewLayout, components};
|
||||
|
||||
|
||||
+10
-4
@@ -3,7 +3,7 @@
|
||||
//! `Context` is the module which provides all the functionalities related to the UI data holder, called Context
|
||||
|
||||
// Locals
|
||||
use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalBridge};
|
||||
use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalAdapter};
|
||||
|
||||
use super::store::Store;
|
||||
use crate::filetransfer::{FileTransferParams, HostBridgeParams};
|
||||
@@ -18,7 +18,7 @@ pub struct Context {
|
||||
bookmarks_client: Option<BookmarksClient>,
|
||||
config_client: ConfigClient,
|
||||
pub(crate) store: Store,
|
||||
pub(crate) terminal: TerminalBridge<CrosstermTerminalAdapter>,
|
||||
pub(crate) terminal: CrosstermTerminalAdapter,
|
||||
theme_provider: ThemeProvider,
|
||||
error: Option<String>,
|
||||
}
|
||||
@@ -31,7 +31,13 @@ impl Context {
|
||||
theme_provider: ThemeProvider,
|
||||
error: Option<String>,
|
||||
) -> Context {
|
||||
let mut terminal = TerminalBridge::init_crossterm().expect("Could not initialize terminal");
|
||||
let mut terminal = CrosstermTerminalAdapter::new().expect("Could not initialize terminal");
|
||||
terminal
|
||||
.enable_raw_mode()
|
||||
.expect("Could not enable terminal raw mode");
|
||||
terminal
|
||||
.enter_alternate_screen()
|
||||
.expect("Could not enter alternate screen");
|
||||
let _ = terminal.disable_mouse_capture();
|
||||
|
||||
Context {
|
||||
@@ -88,7 +94,7 @@ impl Context {
|
||||
&mut self.theme_provider
|
||||
}
|
||||
|
||||
pub fn terminal(&mut self) -> &mut TerminalBridge<CrosstermTerminalAdapter> {
|
||||
pub fn terminal(&mut self) -> &mut CrosstermTerminalAdapter {
|
||||
&mut self.terminal
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -2,26 +2,26 @@
|
||||
//!
|
||||
//! `Utils` implements utilities functions to work with layouts
|
||||
|
||||
use tuirealm::terminal::{TerminalAdapter, TerminalBridge};
|
||||
use tuirealm::terminal::TerminalAdapter;
|
||||
|
||||
/// Read a secret from tty with customisable prompt
|
||||
pub fn read_secret_from_tty<T>(
|
||||
terminal_bridge: &mut TerminalBridge<T>,
|
||||
terminal: &mut T,
|
||||
prompt: impl ToString,
|
||||
) -> std::io::Result<Option<String>>
|
||||
where
|
||||
T: TerminalAdapter,
|
||||
{
|
||||
let _ = terminal_bridge.disable_raw_mode();
|
||||
let _ = terminal_bridge.leave_alternate_screen();
|
||||
let _ = terminal.disable_raw_mode();
|
||||
let _ = terminal.leave_alternate_screen();
|
||||
let res = match rpassword::prompt_password(prompt) {
|
||||
Ok(p) if p.is_empty() => Ok(None),
|
||||
Ok(p) => Ok(Some(p)),
|
||||
Err(err) => Err(err),
|
||||
};
|
||||
|
||||
let _ = terminal_bridge.enter_alternate_screen();
|
||||
let _ = terminal_bridge.enable_raw_mode();
|
||||
let _ = terminal.enter_alternate_screen();
|
||||
let _ = terminal.enable_raw_mode();
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user