show_hidden_files and group_dirs in termscp configuration; instantiate FileExplorer based on current configuration in FileTransferActivity

This commit is contained in:
ChristianVisintin
2020-12-27 10:31:33 +01:00
parent 99fd0b199d
commit 09bc8a92a2
12 changed files with 363 additions and 74 deletions

View File

@@ -28,6 +28,8 @@ FIXME: Released on
- Windows: `C:\Users\Alice\AppData\Roaming\termscp\config.toml` - Windows: `C:\Users\Alice\AppData\Roaming\termscp\config.toml`
- Added Text editor to configuration - Added Text editor to configuration
- Added Default File transfer protocol to configuration - Added Default File transfer protocol to configuration
- Added "Show hidden files` to configuration
- Added "Group directories" to configuration
- Added SSH keys to configuration; SSH keys will be stored at - Added SSH keys to configuration; SSH keys will be stored at
- Linux: `/home/alice/.config/termscp/.ssh/` - Linux: `/home/alice/.config/termscp/.ssh/`
- MacOS: `/Users/Alice/Library/Application Support/termscp/.ssh/` - MacOS: `/Users/Alice/Library/Application Support/termscp/.ssh/`

View File

@@ -259,6 +259,8 @@ These parameters can be changed:
- **Default Protocol**: the default protocol is the default value for the file transfer protocol to be used in termscp. This applies for the login page and for the address CLI argument. - **Default Protocol**: the default protocol is the default value for the file transfer protocol to be used in termscp. This applies for the login page and for the address CLI argument.
- **Text Editor**: the text editor to use. By default termscp will find the default editor for you; with this option you can force an editor to be used (e.g. `vim`). **Also GUI editors are supported**, unless they `nohup` from the parent process so if you ask: yes, you can use `notepad.exe`, and no: **Visual Studio Code doesn't work**. - **Text Editor**: the text editor to use. By default termscp will find the default editor for you; with this option you can force an editor to be used (e.g. `vim`). **Also GUI editors are supported**, unless they `nohup` from the parent process so if you ask: yes, you can use `notepad.exe`, and no: **Visual Studio Code doesn't work**.
- **Show Hidden Files**: select whether hidden files shall be displayed by default. You will be able to decide whether to show or not hidden files at runtime pressing `A` anyway.
- **Group Dirs**: select whether directories should be groupped or not in file explorers. If `Display first` is selected, directories will be sorted using the configured method but displayed before files, viceversa if `Display last` is selected.
### SSH Key Storage 🔐 ### SSH Key Storage 🔐

View File

@@ -54,6 +54,8 @@ pub struct UserConfig {
pub struct UserInterfaceConfig { pub struct UserInterfaceConfig {
pub text_editor: PathBuf, pub text_editor: PathBuf,
pub default_protocol: String, pub default_protocol: String,
pub show_hidden_files: bool,
pub group_dirs: Option<String>,
} }
#[derive(Deserialize, Serialize, std::fmt::Debug)] #[derive(Deserialize, Serialize, std::fmt::Debug)]
@@ -81,6 +83,8 @@ impl Default for UserInterfaceConfig {
Err(_) => PathBuf::from("nano"), // Default to nano Err(_) => PathBuf::from("nano"), // Default to nano
}, },
default_protocol: FileTransferProtocol::Sftp.to_string(), default_protocol: FileTransferProtocol::Sftp.to_string(),
show_hidden_files: false,
group_dirs: None,
} }
} }
} }
@@ -165,14 +169,15 @@ mod tests {
let ui: UserInterfaceConfig = UserInterfaceConfig { let ui: UserInterfaceConfig = UserInterfaceConfig {
default_protocol: String::from("SFTP"), default_protocol: String::from("SFTP"),
text_editor: PathBuf::from("nano"), text_editor: PathBuf::from("nano"),
show_hidden_files: true,
group_dirs: Some(String::from("first")),
}; };
let cfg: UserConfig = UserConfig { let cfg: UserConfig = UserConfig {
user_interface: ui, user_interface: ui,
remote: remote, remote: remote,
}; };
assert_eq!( assert_eq!(
*cfg *cfg.remote
.remote
.ssh_keys .ssh_keys
.get(&String::from("192.168.1.31")) .get(&String::from("192.168.1.31"))
.unwrap(), .unwrap(),
@@ -180,6 +185,8 @@ mod tests {
); );
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP")); assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano")); assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
assert_eq!(cfg.user_interface.show_hidden_files, true);
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("first")));
} }
#[test] #[test]

View File

@@ -106,6 +106,43 @@ mod tests {
// Verify ui // Verify ui
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP")); assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim")); assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
assert_eq!(cfg.user_interface.show_hidden_files, true);
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("last")));
// Verify keys
assert_eq!(
*cfg.remote
.ssh_keys
.get(&String::from("192.168.1.31"))
.unwrap(),
PathBuf::from("/home/omar/.ssh/raspberry.key")
);
assert_eq!(
*cfg.remote
.ssh_keys
.get(&String::from("192.168.1.32"))
.unwrap(),
PathBuf::from("/home/omar/.ssh/beaglebone.key")
);
assert!(cfg.remote.ssh_keys.get(&String::from("1.1.1.1")).is_none());
}
#[test]
fn test_config_serializer_deserialize_ok_no_opts() {
let toml_file: tempfile::NamedTempFile = create_good_toml_no_opts();
toml_file.as_file().sync_all().unwrap();
toml_file.as_file().seek(SeekFrom::Start(0)).unwrap();
// Parse
let deserializer: ConfigSerializer = ConfigSerializer {};
let cfg = deserializer.deserialize(Box::new(toml_file));
println!("{:?}", cfg);
assert!(cfg.is_ok());
let cfg: UserConfig = cfg.ok().unwrap();
// Verify configuration
// Verify ui
assert_eq!(cfg.user_interface.default_protocol, String::from("SCP"));
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("vim"));
assert_eq!(cfg.user_interface.show_hidden_files, true);
assert_eq!(cfg.user_interface.group_dirs, None);
// Verify keys // Verify keys
assert_eq!( assert_eq!(
*cfg.remote *cfg.remote
@@ -160,6 +197,25 @@ mod tests {
[user_interface] [user_interface]
default_protocol = "SCP" default_protocol = "SCP"
text_editor = "vim" text_editor = "vim"
show_hidden_files = true
group_dirs = "last"
[remote.ssh_keys]
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"
"#;
tmpfile.write_all(file_content.as_bytes()).unwrap();
tmpfile
}
fn create_good_toml_no_opts() -> tempfile::NamedTempFile {
// Write
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
let file_content: &str = r#"
[user_interface]
default_protocol = "SCP"
text_editor = "vim"
show_hidden_files = true
[remote.ssh_keys] [remote.ssh_keys]
"192.168.1.31" = "/home/omar/.ssh/raspberry.key" "192.168.1.31" = "/home/omar/.ssh/raspberry.key"

View File

@@ -55,9 +55,12 @@ impl FileExplorerBuilder {
/// ### with_hidden_files /// ### with_hidden_files
/// ///
/// Enable HIDDEN_FILES option /// Enable HIDDEN_FILES option
pub fn with_hidden_files(&mut self) -> &mut FileExplorerBuilder { pub fn with_hidden_files(&mut self, val: bool) -> &mut FileExplorerBuilder {
if let Some(e) = self.explorer.as_mut() { if let Some(e) = self.explorer.as_mut() {
e.opts.insert(ExplorerOpts::SHOW_HIDDEN_FILES); match val {
true => e.opts.insert(ExplorerOpts::SHOW_HIDDEN_FILES),
false => e.opts.remove(ExplorerOpts::SHOW_HIDDEN_FILES),
}
} }
self self
} }
@@ -114,7 +117,7 @@ mod tests {
let explorer: FileExplorer = FileExplorerBuilder::new() let explorer: FileExplorer = FileExplorerBuilder::new()
.with_file_sorting(FileSorting::ByModifyTime) .with_file_sorting(FileSorting::ByModifyTime)
.with_group_dirs(Some(GroupDirs::First)) .with_group_dirs(Some(GroupDirs::First))
.with_hidden_files() .with_hidden_files(true)
.with_stack_size(24) .with_stack_size(24)
.build(); .build();
// Verify // Verify

View File

@@ -29,6 +29,7 @@ extern crate rand;
use crate::config::serializer::ConfigSerializer; use crate::config::serializer::ConfigSerializer;
use crate::config::{SerializerError, SerializerErrorKind, UserConfig}; use crate::config::{SerializerError, SerializerErrorKind, UserConfig};
use crate::filetransfer::FileTransferProtocol; use crate::filetransfer::FileTransferProtocol;
use crate::fs::explorer::GroupDirs;
// Ext // Ext
use std::fs::{create_dir, remove_file, File, OpenOptions}; use std::fs::{create_dir, remove_file, File, OpenOptions};
use std::io::Write; use std::io::Write;
@@ -123,6 +124,45 @@ impl ConfigClient {
self.config.user_interface.default_protocol = proto.to_string(); self.config.user_interface.default_protocol = proto.to_string();
} }
/// ### get_show_hidden_files
///
/// Get value of `show_hidden_files`
pub fn get_show_hidden_files(&self) -> bool {
self.config.user_interface.show_hidden_files
}
/// ### set_show_hidden_files
///
/// Set new value for `show_hidden_files`
pub fn set_show_hidden_files(&mut self, value: bool) {
self.config.user_interface.show_hidden_files = value;
}
/// ### get_group_dirs
///
/// Get GroupDirs value from configuration (will be converted from string)
pub fn get_group_dirs(&self) -> Option<GroupDirs> {
// Convert string to `GroupDirs`
match &self.config.user_interface.group_dirs {
None => None,
Some(val) => match GroupDirs::from_str(val.as_str()) {
Ok(val) => Some(val),
Err(_) => None,
},
}
}
/// ### set_group_dirs
///
/// Set value for group_dir in configuration.
/// Provided value, if `Some` will be converted to `GroupDirs`
pub fn set_group_dirs(&mut self, val: Option<GroupDirs>) {
self.config.user_interface.group_dirs = match val {
None => None,
Some(val) => Some(val.to_string()),
};
}
// SSH Keys // SSH Keys
/// ### save_ssh_key /// ### save_ssh_key
@@ -387,6 +427,30 @@ mod tests {
); );
} }
#[test]
fn test_system_config_show_hidden_files() {
let tmp_dir: tempfile::TempDir = create_tmp_dir();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
.ok()
.unwrap();
client.set_show_hidden_files(true);
assert_eq!(client.get_show_hidden_files(), true);
}
#[test]
fn test_system_config_group_dirs() {
let tmp_dir: tempfile::TempDir = create_tmp_dir();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
.ok()
.unwrap();
client.set_group_dirs(Some(GroupDirs::First));
assert_eq!(client.get_group_dirs(), Some(GroupDirs::First),);
client.set_group_dirs(None);
assert_eq!(client.get_group_dirs(), None,);
}
#[test] #[test]
fn test_system_config_ssh_keys() { fn test_system_config_ssh_keys() {
let tmp_dir: tempfile::TempDir = create_tmp_dir(); let tmp_dir: tempfile::TempDir = create_tmp_dir();

View File

@@ -53,7 +53,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_form /// ### handle_input_event_mode_form
/// ///
/// Handler for input event when in form mode /// Handler for input event when in form mode
pub(super) fn handle_input_event_mode_form(&mut self, ev: &InputEvent) { fn handle_input_event_mode_form(&mut self, ev: &InputEvent) {
match self.input_form { match self.input_form {
InputForm::AuthCredentials => self.handle_input_event_mode_form_auth(ev), InputForm::AuthCredentials => self.handle_input_event_mode_form_auth(ev),
InputForm::Bookmarks => self.handle_input_event_mode_form_bookmarks(ev), InputForm::Bookmarks => self.handle_input_event_mode_form_bookmarks(ev),
@@ -64,7 +64,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_form_auth /// ### handle_input_event_mode_form_auth
/// ///
/// Handle input event when input mode is Form and Tab is Auth /// Handle input event when input mode is Form and Tab is Auth
pub(super) fn handle_input_event_mode_form_auth(&mut self, ev: &InputEvent) { fn handle_input_event_mode_form_auth(&mut self, ev: &InputEvent) {
if let InputEvent::Key(key) = ev { if let InputEvent::Key(key) = ev {
match key.code { match key.code {
KeyCode::Esc => { KeyCode::Esc => {
@@ -220,7 +220,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_form_bookmarks /// ### handle_input_event_mode_form_bookmarks
/// ///
/// Handle input event when input mode is Form and Tab is Bookmarks /// Handle input event when input mode is Form and Tab is Bookmarks
pub(super) fn handle_input_event_mode_form_bookmarks(&mut self, ev: &InputEvent) { fn handle_input_event_mode_form_bookmarks(&mut self, ev: &InputEvent) {
if let InputEvent::Key(key) = ev { if let InputEvent::Key(key) = ev {
match key.code { match key.code {
KeyCode::Esc => { KeyCode::Esc => {
@@ -304,7 +304,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_form_recents /// ### handle_input_event_mode_form_recents
/// ///
/// Handle input event when input mode is Form and Tab is Recents /// Handle input event when input mode is Form and Tab is Recents
pub(super) fn handle_input_event_mode_form_recents(&mut self, ev: &InputEvent) { fn handle_input_event_mode_form_recents(&mut self, ev: &InputEvent) {
if let InputEvent::Key(key) = ev { if let InputEvent::Key(key) = ev {
match key.code { match key.code {
KeyCode::Esc => { KeyCode::Esc => {
@@ -388,7 +388,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_text /// ### handle_input_event_mode_text
/// ///
/// Handler for input event when in popup mode /// Handler for input event when in popup mode
pub(super) fn handle_input_event_mode_popup(&mut self, ev: &InputEvent, ptype: PopupType) { fn handle_input_event_mode_popup(&mut self, ev: &InputEvent, ptype: PopupType) {
match ptype { match ptype {
PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev), PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
PopupType::Help => self.handle_input_event_mode_popup_help(ev), PopupType::Help => self.handle_input_event_mode_popup_help(ev),
@@ -402,7 +402,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_popup_alert /// ### handle_input_event_mode_popup_alert
/// ///
/// Handle input event when the input mode is popup, and popup type is alert /// Handle input event when the input mode is popup, and popup type is alert
pub(super) fn handle_input_event_mode_popup_alert(&mut self, ev: &InputEvent) { fn handle_input_event_mode_popup_alert(&mut self, ev: &InputEvent) {
// Only enter should be allowed here // Only enter should be allowed here
if let InputEvent::Key(key) = ev { if let InputEvent::Key(key) = ev {
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) { if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
@@ -414,7 +414,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_popup_help /// ### handle_input_event_mode_popup_help
/// ///
/// Input event handler for popup help /// Input event handler for popup help
pub(super) fn handle_input_event_mode_popup_help(&mut self, ev: &InputEvent) { fn handle_input_event_mode_popup_help(&mut self, ev: &InputEvent) {
// If enter, close popup // If enter, close popup
if let InputEvent::Key(key) = ev { if let InputEvent::Key(key) = ev {
if matches!(key.code, KeyCode::Esc | KeyCode::Enter) { if matches!(key.code, KeyCode::Esc | KeyCode::Enter) {
@@ -427,7 +427,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_popup_save_bookmark /// ### handle_input_event_mode_popup_save_bookmark
/// ///
/// Input event handler for SaveBookmark popup /// Input event handler for SaveBookmark popup
pub(super) fn handle_input_event_mode_popup_save_bookmark(&mut self, ev: &InputEvent) { fn handle_input_event_mode_popup_save_bookmark(&mut self, ev: &InputEvent) {
// If enter, close popup, otherwise push chars to input // If enter, close popup, otherwise push chars to input
if let InputEvent::Key(key) = ev { if let InputEvent::Key(key) = ev {
match key.code { match key.code {
@@ -466,7 +466,7 @@ impl AuthActivity {
/// ### handle_input_event_mode_popup_yesno /// ### handle_input_event_mode_popup_yesno
/// ///
/// Input event handler for popup alert /// Input event handler for popup alert
pub(super) fn handle_input_event_mode_popup_yesno( fn handle_input_event_mode_popup_yesno(
&mut self, &mut self,
ev: &InputEvent, ev: &InputEvent,
yes_cb: DialogCallback, yes_cb: DialogCallback,

View File

@@ -318,7 +318,7 @@ impl AuthActivity {
/// ### draw_local_explorer /// ### draw_local_explorer
/// ///
/// Draw local explorer list /// Draw local explorer list
pub(super) fn draw_bookmarks_tab(&self) -> Option<List> { fn draw_bookmarks_tab(&self) -> Option<List> {
self.bookmarks_client.as_ref()?; self.bookmarks_client.as_ref()?;
let hosts: Vec<ListItem> = self let hosts: Vec<ListItem> = self
.bookmarks_client .bookmarks_client
@@ -366,7 +366,7 @@ impl AuthActivity {
/// ### draw_local_explorer /// ### draw_local_explorer
/// ///
/// Draw local explorer list /// Draw local explorer list
pub(super) fn draw_recents_tab(&self) -> Option<List> { fn draw_recents_tab(&self) -> Option<List> {
self.bookmarks_client.as_ref()?; self.bookmarks_client.as_ref()?;
let hosts: Vec<ListItem> = self let hosts: Vec<ListItem> = self
.bookmarks_client .bookmarks_client
@@ -463,7 +463,7 @@ impl AuthActivity {
/// ### draw_popup_input /// ### draw_popup_input
/// ///
/// Draw input popup /// Draw input popup
pub(super) fn draw_popup_save_bookmark(&self) -> (Paragraph, Tabs) { fn draw_popup_save_bookmark(&self) -> (Paragraph, Tabs) {
let input: Paragraph = Paragraph::new(self.input_txt.as_ref()) let input: Paragraph = Paragraph::new(self.input_txt.as_ref())
.style(Style::default().fg(Color::White)) .style(Style::default().fg(Color::White))
.block( .block(
@@ -497,7 +497,7 @@ impl AuthActivity {
/// ### draw_popup_yesno /// ### draw_popup_yesno
/// ///
/// Draw yes/no select popup /// Draw yes/no select popup
pub(super) fn draw_popup_yesno(&self, text: String) -> Tabs { fn draw_popup_yesno(&self, text: String) -> Tabs {
let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")]; let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
let index: usize = match self.choice_opt { let index: usize = match self.choice_opt {
DialogYesNoOption::Yes => 0, DialogYesNoOption::Yes => 0,
@@ -522,7 +522,7 @@ impl AuthActivity {
/// ### draw_popup_help /// ### draw_popup_help
/// ///
/// Draw authentication page help popup /// Draw authentication page help popup
pub(super) fn draw_popup_help(&self) -> List { fn draw_popup_help(&self) -> List {
// Write header // Write header
let cmds: Vec<ListItem> = vec![ let cmds: Vec<ListItem> = vec![
ListItem::new(Spans::from(vec![ ListItem::new(Spans::from(vec![

View File

@@ -130,11 +130,19 @@ impl FileTransferActivity {
/// ///
/// Build explorer reading configuration from `ConfigClient` /// Build explorer reading configuration from `ConfigClient`
pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer { pub(super) fn build_explorer(cli: Option<&ConfigClient>) -> FileExplorer {
FileExplorerBuilder::new() match &cli {
.with_file_sorting(FileSorting::ByName) Some(cli) => FileExplorerBuilder::new() // Build according to current configuration
.with_group_dirs(Some(GroupDirs::First)) .with_file_sorting(FileSorting::ByName)
.with_stack_size(16) .with_group_dirs(cli.get_group_dirs())
.build() .with_hidden_files(cli.get_show_hidden_files())
.with_stack_size(16)
.build(),
None => FileExplorerBuilder::new() // Build default
.with_file_sorting(FileSorting::ByName)
.with_group_dirs(Some(GroupDirs::First))
.with_stack_size(16)
.build(),
}
} }
/// ### setup_text_editor /// ### setup_text_editor

View File

@@ -30,6 +30,7 @@ use super::{
UserInterfaceInputField, YesNoDialogOption, UserInterfaceInputField, YesNoDialogOption,
}; };
use crate::filetransfer::FileTransferProtocol; use crate::filetransfer::FileTransferProtocol;
use crate::fs::explorer::GroupDirs;
// Ext // Ext
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use std::path::PathBuf; use std::path::PathBuf;
@@ -195,59 +196,105 @@ impl SetupActivity {
KeyCode::Left => { KeyCode::Left => {
// Move left on fields which are tabs // Move left on fields which are tabs
if let Some(config_cli) = self.config_cli.as_mut() { if let Some(config_cli) = self.config_cli.as_mut() {
if matches!(field, UserInterfaceInputField::DefaultProtocol) { match field {
// Move left UserInterfaceInputField::DefaultProtocol => {
config_cli.set_default_protocol( // Move left
match config_cli.get_default_protocol() { config_cli.set_default_protocol(
FileTransferProtocol::Ftp(secure) => match secure { match config_cli.get_default_protocol() {
true => FileTransferProtocol::Ftp(false), FileTransferProtocol::Ftp(secure) => match secure {
false => FileTransferProtocol::Scp, true => FileTransferProtocol::Ftp(false),
false => FileTransferProtocol::Scp,
},
FileTransferProtocol::Scp => FileTransferProtocol::Sftp,
FileTransferProtocol::Sftp => {
FileTransferProtocol::Ftp(true)
} // Wrap
}, },
FileTransferProtocol::Scp => FileTransferProtocol::Sftp, );
FileTransferProtocol::Sftp => FileTransferProtocol::Ftp(true), // Wrap }
}, UserInterfaceInputField::GroupDirs => {
); // Move left
config_cli.set_group_dirs(match config_cli.get_group_dirs() {
None => Some(GroupDirs::Last),
Some(val) => match val {
GroupDirs::Last => Some(GroupDirs::First),
GroupDirs::First => None,
},
});
}
UserInterfaceInputField::ShowHiddenFiles => {
// Move left
config_cli.set_show_hidden_files(true);
}
_ => { /* Not a tab field */ }
} }
} }
} }
KeyCode::Right => { KeyCode::Right => {
// Move right on fields which are tabs // Move right on fields which are tabs
if let Some(config_cli) = self.config_cli.as_mut() { if let Some(config_cli) = self.config_cli.as_mut() {
if matches!(field, UserInterfaceInputField::DefaultProtocol) { match field {
// Move left UserInterfaceInputField::DefaultProtocol => {
config_cli.set_default_protocol( // Move left
match config_cli.get_default_protocol() { config_cli.set_default_protocol(
FileTransferProtocol::Sftp => FileTransferProtocol::Scp, match config_cli.get_default_protocol() {
FileTransferProtocol::Scp => FileTransferProtocol::Ftp(false), FileTransferProtocol::Sftp => FileTransferProtocol::Scp,
FileTransferProtocol::Ftp(secure) => match secure { FileTransferProtocol::Scp => {
false => FileTransferProtocol::Ftp(true), FileTransferProtocol::Ftp(false)
true => FileTransferProtocol::Sftp, // Wrap }
FileTransferProtocol::Ftp(secure) => match secure {
false => FileTransferProtocol::Ftp(true),
true => FileTransferProtocol::Sftp, // Wrap
},
}, },
}, );
); }
UserInterfaceInputField::GroupDirs => {
// Move right
config_cli.set_group_dirs(match config_cli.get_group_dirs() {
Some(val) => match val {
GroupDirs::First => Some(GroupDirs::Last),
GroupDirs::Last => None,
},
None => Some(GroupDirs::First),
});
}
UserInterfaceInputField::ShowHiddenFiles => {
// Move right
config_cli.set_show_hidden_files(false);
}
_ => { /* Not a tab field */ }
} }
} }
} }
KeyCode::Up => { KeyCode::Up => {
// Change selected field // Change selected field
self.tab = SetupTab::UserInterface(match field { self.tab = SetupTab::UserInterface(match field {
UserInterfaceInputField::TextEditor => { UserInterfaceInputField::GroupDirs => {
UserInterfaceInputField::ShowHiddenFiles
}
UserInterfaceInputField::ShowHiddenFiles => {
UserInterfaceInputField::DefaultProtocol UserInterfaceInputField::DefaultProtocol
} }
UserInterfaceInputField::DefaultProtocol => { UserInterfaceInputField::DefaultProtocol => {
UserInterfaceInputField::TextEditor UserInterfaceInputField::TextEditor
} // Wrap }
UserInterfaceInputField::TextEditor => UserInterfaceInputField::GroupDirs, // Wrap
}); });
} }
KeyCode::Down => { KeyCode::Down => {
// Change selected field // Change selected field
self.tab = SetupTab::UserInterface(match field { self.tab = SetupTab::UserInterface(match field {
UserInterfaceInputField::DefaultProtocol => {
UserInterfaceInputField::TextEditor
}
UserInterfaceInputField::TextEditor => { UserInterfaceInputField::TextEditor => {
UserInterfaceInputField::DefaultProtocol UserInterfaceInputField::DefaultProtocol
} // Wrap }
UserInterfaceInputField::DefaultProtocol => {
UserInterfaceInputField::ShowHiddenFiles
}
UserInterfaceInputField::ShowHiddenFiles => {
UserInterfaceInputField::GroupDirs
}
UserInterfaceInputField::GroupDirs => UserInterfaceInputField::TextEditor, // Wrap
}); });
} }
KeyCode::Char(ch) => { KeyCode::Char(ch) => {
@@ -450,7 +497,7 @@ impl SetupActivity {
/// ### handle_input_event_mode_popup_yesno /// ### handle_input_event_mode_popup_yesno
/// ///
/// Input event handler for popup alert /// Input event handler for popup alert
pub(super) fn handle_input_event_mode_popup_yesno( fn handle_input_event_mode_popup_yesno(
&mut self, &mut self,
ev: &InputEvent, ev: &InputEvent,
yes_cb: OnChoiceCallback, yes_cb: OnChoiceCallback,

View File

@@ -29,8 +29,9 @@ use super::{
YesNoDialogOption, YesNoDialogOption,
}; };
use crate::filetransfer::FileTransferProtocol; use crate::filetransfer::FileTransferProtocol;
use crate::fs::explorer::GroupDirs;
use crate::utils::fmt::align_text_center; use crate::utils::fmt::align_text_center;
// Ext
use tui::{ use tui::{
layout::{Constraint, Corner, Direction, Layout, Rect}, layout::{Constraint, Corner, Direction, Layout, Rect},
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
@@ -84,6 +85,8 @@ impl SetupActivity {
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints( .constraints(
[ [
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(1), Constraint::Length(1),
@@ -98,6 +101,12 @@ impl SetupActivity {
if let Some(tab) = self.draw_default_protocol_tab() { if let Some(tab) = self.draw_default_protocol_tab() {
f.render_widget(tab, ui_cfg_chunks[1]); f.render_widget(tab, ui_cfg_chunks[1]);
} }
if let Some(tab) = self.draw_hidden_files_tab() {
f.render_widget(tab, ui_cfg_chunks[2]);
}
if let Some(tab) = self.draw_default_group_dirs_tab() {
f.render_widget(tab, ui_cfg_chunks[3]);
}
// Set cursor // Set cursor
if let Some(cli) = &self.config_cli { if let Some(cli) = &self.config_cli {
if matches!(form_field, UserInterfaceInputField::TextEditor) { if matches!(form_field, UserInterfaceInputField::TextEditor) {
@@ -217,6 +226,33 @@ impl SetupActivity {
Paragraph::new(footer_text) Paragraph::new(footer_text)
} }
/// ### draw_text_editor_input
///
/// Draw input text field for text editor parameter
fn draw_text_editor_input(&self) -> Option<Paragraph> {
match &self.config_cli {
Some(cli) => Some(
Paragraph::new(String::from(
cli.get_text_editor().as_path().to_string_lossy(),
))
.style(Style::default().fg(match &self.tab {
SetupTab::SshConfig => Color::White,
SetupTab::UserInterface(field) => match field {
UserInterfaceInputField::TextEditor => Color::LightGreen,
_ => Color::White,
},
}))
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.title("Text Editor"),
),
),
None => None,
}
}
/// ### draw_default_protocol_tab /// ### draw_default_protocol_tab
/// ///
/// Draw default protocol input tab /// Draw default protocol input tab
@@ -267,29 +303,91 @@ impl SetupActivity {
} }
} }
/// ### draw_text_editor_input /// ### draw_default_protocol_tab
/// ///
/// Draw input text field for text editor parameter /// Draw default protocol input tab
fn draw_text_editor_input(&self) -> Option<Paragraph> { fn draw_hidden_files_tab(&self) -> Option<Tabs> {
// Check if config client is some
match &self.config_cli { match &self.config_cli {
Some(cli) => Some( Some(cli) => {
Paragraph::new(String::from( let choices: Vec<Spans> = vec![Spans::from("Yes"), Spans::from("No")];
cli.get_text_editor().as_path().to_string_lossy(), let index: usize = match cli.get_show_hidden_files() {
)) true => 0,
.style(Style::default().fg(match &self.tab { false => 1,
SetupTab::SshConfig => Color::White, };
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
SetupTab::UserInterface(field) => match field { SetupTab::UserInterface(field) => match field {
UserInterfaceInputField::TextEditor => Color::LightGreen, UserInterfaceInputField::ShowHiddenFiles => {
_ => Color::White, (Color::LightRed, Color::Black, Color::LightRed)
}
_ => (Color::Reset, Color::LightRed, Color::Reset),
}, },
})) _ => (Color::Reset, Color::Reset, Color::Reset),
.block( };
Block::default() Some(
.borders(Borders::ALL) Tabs::new(choices)
.border_type(BorderType::Rounded) .block(
.title("Text Editor"), Block::default()
), .borders(Borders::ALL)
), .border_type(BorderType::Rounded)
.style(Style::default().fg(block_fg))
.title("Show hidden files (by default)"),
)
.select(index)
.style(Style::default())
.highlight_style(
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
),
)
}
None => None,
}
}
/// ### draw_default_group_dirs_tab
///
/// Draw group dirs input tab
fn draw_default_group_dirs_tab(&self) -> Option<Tabs> {
// Check if config client is some
match &self.config_cli {
Some(cli) => {
let choices: Vec<Spans> = vec![
Spans::from("Display First"),
Spans::from("Display Last"),
Spans::from("No"),
];
let index: usize = match cli.get_group_dirs() {
None => 2,
Some(val) => match val {
GroupDirs::First => 0,
GroupDirs::Last => 1,
},
};
let (bg, fg, block_fg): (Color, Color, Color) = match &self.tab {
SetupTab::UserInterface(field) => match field {
UserInterfaceInputField::GroupDirs => {
(Color::LightMagenta, Color::Black, Color::LightMagenta)
}
_ => (Color::Reset, Color::LightMagenta, Color::Reset),
},
_ => (Color::Reset, Color::Reset, Color::Reset),
};
Some(
Tabs::new(choices)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.style(Style::default().fg(block_fg))
.title("Group directories"),
)
.select(index)
.style(Style::default())
.highlight_style(
Style::default().add_modifier(Modifier::BOLD).fg(fg).bg(bg),
),
)
}
None => None, None => None,
} }
} }
@@ -481,7 +579,7 @@ impl SetupActivity {
/// ### draw_popup_help /// ### draw_popup_help
/// ///
/// Draw authentication page help popup /// Draw authentication page help popup
pub(super) fn draw_popup_help(&self) -> List { fn draw_popup_help(&self) -> List {
// Write header // Write header
let cmds: Vec<ListItem> = vec![ let cmds: Vec<ListItem> = vec![
ListItem::new(Spans::from(vec![ ListItem::new(Spans::from(vec![

View File

@@ -53,6 +53,8 @@ type OnChoiceCallback = fn(&mut SetupActivity);
enum UserInterfaceInputField { enum UserInterfaceInputField {
DefaultProtocol, DefaultProtocol,
TextEditor, TextEditor,
ShowHiddenFiles,
GroupDirs,
} }
/// ### SetupTab /// ### SetupTab