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:
Christian Visintin
2026-04-18 15:26:30 +02:00
parent 2dddc88eda
commit 7fde801045
78 changed files with 2127 additions and 1588 deletions
Generated
+96 -73
View File
@@ -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
View File
@@ -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"
+3 -1
View File
@@ -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
View File
@@ -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());
}
}
+8 -6
View File
@@ -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);
+5 -5
View File
@@ -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),
+63 -42
View File
@@ -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 {
+8 -6
View File
@@ -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)),
+40 -26
View File
@@ -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)),
+23 -11
View File
@@ -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),
+60 -44
View File
@@ -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)),
+92 -68
View File
@@ -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,
+34 -32
View File
@@ -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
}
}
+9 -6
View File
@@ -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
+1
View File
@@ -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,
+28 -26
View File
@@ -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 {
+9 -9
View File
@@ -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}");
+5 -4
View File
@@ -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),
+2 -2
View File
@@ -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;
+6 -6
View File
@@ -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),
+5 -2
View File
@@ -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;
+25 -20
View File
@@ -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(
+12 -19
View File
@@ -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,
+4 -4
View File
@@ -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)) => {
+25 -20
View File
@@ -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"),
])),
);
+8 -6
View File
@@ -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;
+6 -5
View File
@@ -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(()),
+5 -5
View File
@@ -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),
+87 -65
View File
@@ -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 {
+97 -62
View File
@@ -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> {
+45 -30
View File
@@ -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 {
+107 -102
View File
@@ -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 {
+2
View File
@@ -7,6 +7,8 @@
// Ext
use std::env;
use tuirealm::terminal::TerminalAdapter;
use super::SetupActivity;
impl SetupActivity {
+2 -3
View File
@@ -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),
+11 -10
View File
@@ -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}");
+13 -12
View File
@@ -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() {
+1
View File
@@ -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};
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
}