mirror of
https://github.com/veeso/termscp.git
synced 2026-09-26 22:11:26 -07:00
build: migrate to tui-realm 4.0
Upgrade tuirealm (3.x -> 4.0.0), tui-realm-stdlib (3 -> 4), tui-term (0.2 -> 0.3). Apply all breaking changes from the 4.0 migration guide across the termscp UI. Key changes: - Root-level re-exports removed; imports moved to module-qualified paths (`tuirealm::application`, `::component`, `::event`, `::props`, `::state`, `::subscription`, `::listener`, `::ratatui`). Same for stdlib component types (`tui_realm_stdlib::components::*`). - `MockComponent` trait renamed to `Component`; old `Component` trait renamed to `AppComponent`. `#[derive(MockComponent)]` is now `#[derive(Component)]`. `Component::on` now takes `&Event<_>`. - `TextSpan` replaced with `SpanStatic`/`LineStatic`/`TextStatic` (ratatui-based); tuple `(String, Alignment)` titles replaced with the new `Title` builder; `Alignment` split into `HorizontalAlignment`/`VerticalAlignment`; stdlib components use `.alignment_horizontal` instead of `.alignment`. - `State::One`/`PropPayload::One` -> `Single`. `CmdResult::None` -> `NoChange`. `Props::get_or` removed; `Props::get` now returns a borrowed `Option<&AttrValue>` (call sites switched to `.and_then(AttrValue::as_*)`). `Component::query` returns `Option<QueryResult<'a>>`. - `Attribute::HighlightedColor` -> `HighlightStyle` (a full `Style`). `.highlighted_*` helpers renamed to `.highlight_*`. - `PollStrategy::UpTo(n)` now requires a `Duration`; tick timeout moved from `EventListenerCfg::poll_timeout` into `PollStrategy`. - `TerminalBridge` removed; `Context` now holds `CrosstermTerminalAdapter` directly and enables raw mode + alternate screen explicitly. The `TerminalAdapter` trait is imported where its methods are used. - `Update` trait removed; activity `update` methods are plain inherent functions. - `ProgressBar` replaced by stdlib `Gauge`. Paragraph `.wrap` renamed to `.wrap_trim`; `.text` now takes an `Into<Text>`. Stdlib `List` row items are now individual lines (`Vec<Span>` per row) rather than a `Table` of spans; custom `FileList`/`Log` convert between the two models. - Radio builders drop `.foreground(color)` so unselected items render with the terminal default foreground, and set `highlight_style(Style::default().fg(color).add_modifier(REVERSED))` so the selected entry is visibly highlighted only with the theme color. - Custom `FileList` keeps the selected row highlighted with the full highlight style when focused and falls back to a foreground-only style when unfocused. - Theme loading is now backwards compatible: `Theme` uses a custom `Deserialize` through an intermediate `ThemeFile` with optional fields, so missing keys, unknown values or legacy aliases (`transfer_progress_bar_full`/`_partial`) fall back to defaults on a per-field basis instead of failing the whole load.
This commit is contained in:
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user