Merged 0.6.1; added new DirectoryAlreadyExists; return new variant for SFTP/SCP

This commit is contained in:
veeso
2021-08-18 09:33:27 +02:00
40 changed files with 1034 additions and 1549 deletions

View File

@@ -188,7 +188,7 @@ impl ActivityManager {
};
// If ft params is None, return None
let ft_params: &FileTransferParams = match ctx.ft_params() {
Some(ft_params) => &ft_params,
Some(ft_params) => ft_params,
None => {
error!("Failed to start FileTransferActivity: file transfer params is None");
return None;

View File

@@ -44,13 +44,13 @@ pub struct SerializerError {
#[derive(Error, Debug)]
pub enum SerializerErrorKind {
#[error("Operation failed")]
GenericError,
Generic,
#[error("IO error")]
IoError,
Io,
#[error("Serialization error")]
SerializationError,
Serialization,
#[error("Syntax error")]
SyntaxError,
Syntax,
}
impl SerializerError {
@@ -92,7 +92,7 @@ where
Ok(dt) => dt,
Err(err) => {
return Err(SerializerError::new_ex(
SerializerErrorKind::SerializationError,
SerializerErrorKind::Serialization,
err.to_string(),
))
}
@@ -102,7 +102,7 @@ where
match writable.write_all(data.as_bytes()) {
Ok(_) => Ok(()),
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
)),
}
@@ -119,7 +119,7 @@ where
let mut data: String = String::new();
if let Err(err) = readable.read_to_string(&mut data) {
return Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
));
}
@@ -131,7 +131,7 @@ where
Ok(deserialized)
}
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::SyntaxError,
SerializerErrorKind::Syntax,
err.to_string(),
)),
}
@@ -154,11 +154,11 @@ mod tests {
#[test]
fn test_config_serialization_errors() {
let error: SerializerError = SerializerError::new(SerializerErrorKind::SyntaxError);
let error: SerializerError = SerializerError::new(SerializerErrorKind::Syntax);
assert!(error.msg.is_none());
assert_eq!(format!("{}", error), String::from("Syntax error"));
let error: SerializerError =
SerializerError::new_ex(SerializerErrorKind::SyntaxError, String::from("bad syntax"));
SerializerError::new_ex(SerializerErrorKind::Syntax, String::from("bad syntax"));
assert!(error.msg.is_some());
assert_eq!(
format!("{}", error),
@@ -166,20 +166,17 @@ mod tests {
);
// Fmt
assert_eq!(
format!(
"{}",
SerializerError::new(SerializerErrorKind::GenericError)
),
format!("{}", SerializerError::new(SerializerErrorKind::Generic)),
String::from("Operation failed")
);
assert_eq!(
format!("{}", SerializerError::new(SerializerErrorKind::IoError)),
format!("{}", SerializerError::new(SerializerErrorKind::Io)),
String::from("IO error")
);
assert_eq!(
format!(
"{}",
SerializerError::new(SerializerErrorKind::SerializationError)
SerializerError::new(SerializerErrorKind::Serialization)
),
String::from("Serialization error")
);

View File

@@ -189,7 +189,7 @@ impl FtpFileTransfer {
FsEntry::Directory(FsDirectory {
name: p
.file_name()
.unwrap_or(&std::ffi::OsStr::new(""))
.unwrap_or_else(|| std::ffi::OsStr::new(""))
.to_string_lossy()
.to_string(),
abs_path: p.clone(),
@@ -206,7 +206,7 @@ impl FtpFileTransfer {
false => FsEntry::File(FsFile {
name: p
.file_name()
.unwrap_or(&std::ffi::OsStr::new(""))
.unwrap_or_else(|| std::ffi::OsStr::new(""))
.to_string_lossy()
.to_string(),
abs_path: p.clone(),
@@ -659,7 +659,7 @@ impl FileTransfer for FtpFileTransfer {
// Remove recursively files
debug!("Removing {} entries from directory...", files.len());
for file in files.iter() {
if let Err(err) = self.remove(&file) {
if let Err(err) = self.remove(file) {
return Err(FileTransferError::new_ex(
FileTransferErrorType::PexError,
err.to_string(),

View File

@@ -44,7 +44,7 @@ pub use params::FileTransferParams;
///
/// This enum defines the different transfer protocol available in termscp
#[derive(PartialEq, std::fmt::Debug, std::clone::Clone, Copy)]
#[derive(PartialEq, Debug, std::clone::Clone, Copy)]
pub enum FileTransferProtocol {
Sftp,
Scp,
@@ -54,7 +54,7 @@ pub enum FileTransferProtocol {
/// ## FileTransferError
///
/// FileTransferError defines the possible errors available for a file transfer
#[derive(std::fmt::Debug)]
#[derive(Debug)]
pub struct FileTransferError {
code: FileTransferErrorType,
msg: Option<String>,
@@ -84,6 +84,8 @@ pub enum FileTransferErrorType {
SslError,
#[error("Could not stat directory")]
DirStatFailed,
#[error("Directory already exists")]
DirectoryAlreadyExists,
#[error("Failed to create file")]
FileCreateDenied,
#[error("No such file or directory")]
@@ -180,7 +182,7 @@ pub trait FileTransfer {
/// ### mkdir
///
/// Make directory
/// You must return error in case the directory already exists
/// It MUSTN'T return error in case the directory already exists
fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError>;
/// ### remove

View File

@@ -169,7 +169,7 @@ impl ScpFileTransfer {
// Get symlink; PATH mustn't be equal to filename
let symlink: Option<Box<FsEntry>> = match symlink_path {
None => None,
Some(p) => match p.file_name().unwrap_or(&std::ffi::OsStr::new(""))
Some(p) => match p.file_name().unwrap_or_else(|| std::ffi::OsStr::new(""))
== file_name.as_str()
{
// If name is equal, don't stat path; otherwise it would get stuck
@@ -339,7 +339,7 @@ impl FileTransfer for ScpFileTransfer {
// Try addresses
for socket_addr in socket_addresses.iter() {
debug!("Trying socket address {}", socket_addr);
match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) {
match TcpStream::connect_timeout(socket_addr, Duration::from_secs(30)) {
Ok(stream) => {
debug!("{} succeded", socket_addr);
tcp = Some(stream);
@@ -643,13 +643,20 @@ impl FileTransfer for ScpFileTransfer {
/// ### mkdir
///
/// Make directory
/// You must return error in case the directory already exists
/// It MUSTN'T return error in case the directory already exists
fn mkdir(&mut self, dir: &Path) -> Result<(), FileTransferError> {
match self.is_connected() {
true => {
let dir: PathBuf = Self::resolve(dir);
info!("Making directory {}", dir.display());
let p: PathBuf = self.wrkdir.clone();
// If directory already exists, return Err
if let Ok(_) = self.stat(dir.as_path()) {
error!("Directory {} already exists", dir.display());
return Err(FileTransferError::new(
FileTransferErrorType::DirectoryAlreadyExists,
));
}
// Mkdir dir && echo 0
match self.perform_shell_cmd_with_path(
p.as_path(),

View File

@@ -282,7 +282,7 @@ impl FileTransfer for SftpFileTransfer {
// Try addresses
for socket_addr in socket_addresses.iter() {
debug!("Trying socket address {}", socket_addr);
match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(30)) {
match TcpStream::connect_timeout(socket_addr, Duration::from_secs(30)) {
Ok(stream) => {
tcp = Some(stream);
break;
@@ -559,6 +559,13 @@ impl FileTransfer for SftpFileTransfer {
Some(sftp) => {
// Make directory
let path: PathBuf = self.get_abs_path(PathBuf::from(dir).as_path());
// If directory already exists, return Err
if let Ok(_) = sftp.stat(path.as_path()) {
error!("Directory {} already exists", path.display());
return Err(FileTransferError::new(
FileTransferErrorType::DirectoryAlreadyExists,
));
}
info!("Making directory {}", path.display());
match sftp.mkdir(path.as_path(), 0o775) {
Ok(_) => Ok(()),
@@ -602,7 +609,7 @@ impl FileTransfer for SftpFileTransfer {
// Get directory files
let directory_content: Vec<FsEntry> = self.list_dir(d.abs_path.as_path())?;
for entry in directory_content.iter() {
if let Err(err) = self.remove(&entry) {
if let Err(err) = self.remove(entry) {
return Err(err);
}
}

View File

@@ -124,7 +124,7 @@ mod tests {
let explorer: FileExplorer = FileExplorerBuilder::new().build();
// Verify
assert!(!explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES));
assert_eq!(explorer.file_sorting, FileSorting::ByName); // Default
assert_eq!(explorer.file_sorting, FileSorting::Name); // Default
assert_eq!(explorer.group_dirs, None);
assert_eq!(explorer.stack_size, 16);
}
@@ -132,7 +132,7 @@ mod tests {
#[test]
fn test_fs_explorer_builder_new_all() {
let explorer: FileExplorer = FileExplorerBuilder::new()
.with_file_sorting(FileSorting::ByModifyTime)
.with_file_sorting(FileSorting::ModifyTime)
.with_group_dirs(Some(GroupDirs::First))
.with_hidden_files(true)
.with_stack_size(24)
@@ -140,7 +140,7 @@ mod tests {
.build();
// Verify
assert!(explorer.opts.intersects(ExplorerOpts::SHOW_HIDDEN_FILES));
assert_eq!(explorer.file_sorting, FileSorting::ByModifyTime); // Default
assert_eq!(explorer.file_sorting, FileSorting::ModifyTime); // Default
assert_eq!(explorer.group_dirs, Some(GroupDirs::First));
assert_eq!(explorer.stack_size, 24);
}

View File

@@ -52,10 +52,10 @@ bitflags! {
/// FileSorting defines the criteria for sorting files
#[derive(Copy, Clone, PartialEq, std::fmt::Debug)]
pub enum FileSorting {
ByName,
ByModifyTime,
ByCreationTime,
BySize,
Name,
ModifyTime,
CreationTime,
Size,
}
/// ## GroupDirs
@@ -87,7 +87,7 @@ impl Default for FileExplorer {
wrkdir: PathBuf::from("/"),
dirstack: VecDeque::with_capacity(16),
stack_size: 16,
file_sorting: FileSorting::ByName,
file_sorting: FileSorting::Name,
group_dirs: None,
opts: ExplorerOpts::empty(),
fmt: Formatter::default(),
@@ -237,10 +237,10 @@ impl FileExplorer {
fn sort(&mut self) {
// Choose sorting method
match &self.file_sorting {
FileSorting::ByName => self.sort_files_by_name(),
FileSorting::ByCreationTime => self.sort_files_by_creation_time(),
FileSorting::ByModifyTime => self.sort_files_by_mtime(),
FileSorting::BySize => self.sort_files_by_size(),
FileSorting::Name => self.sort_files_by_name(),
FileSorting::CreationTime => self.sort_files_by_creation_time(),
FileSorting::ModifyTime => self.sort_files_by_mtime(),
FileSorting::Size => self.sort_files_by_size(),
}
// Directories first (NOTE: MUST COME AFTER OTHER SORTING)
// Group directories if necessary
@@ -318,10 +318,10 @@ impl FileExplorer {
impl ToString for FileSorting {
fn to_string(&self) -> String {
String::from(match self {
FileSorting::ByCreationTime => "by_creation_time",
FileSorting::ByModifyTime => "by_mtime",
FileSorting::ByName => "by_name",
FileSorting::BySize => "by_size",
FileSorting::CreationTime => "by_creation_time",
FileSorting::ModifyTime => "by_mtime",
FileSorting::Name => "by_name",
FileSorting::Size => "by_size",
})
}
}
@@ -330,10 +330,10 @@ impl FromStr for FileSorting {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"by_creation_time" => Ok(FileSorting::ByCreationTime),
"by_mtime" => Ok(FileSorting::ByModifyTime),
"by_name" => Ok(FileSorting::ByName),
"by_size" => Ok(FileSorting::BySize),
"by_creation_time" => Ok(FileSorting::CreationTime),
"by_mtime" => Ok(FileSorting::ModifyTime),
"by_name" => Ok(FileSorting::Name),
"by_size" => Ok(FileSorting::Size),
_ => Err(()),
}
}
@@ -380,8 +380,8 @@ mod tests {
assert_eq!(explorer.wrkdir, PathBuf::from("/"));
assert_eq!(explorer.stack_size, 16);
assert_eq!(explorer.group_dirs, None);
assert_eq!(explorer.file_sorting, FileSorting::ByName);
assert_eq!(explorer.get_file_sorting(), FileSorting::ByName);
assert_eq!(explorer.file_sorting, FileSorting::Name);
assert_eq!(explorer.get_file_sorting(), FileSorting::Name);
}
#[test]
@@ -459,7 +459,7 @@ mod tests {
make_fs_entry("Cargo.lock", false),
make_fs_entry("codecov.yml", false),
]);
explorer.sort_by(FileSorting::ByName);
explorer.sort_by(FileSorting::Name);
// First entry should be "Cargo.lock"
assert_eq!(explorer.files.get(0).unwrap().get_name(), "Cargo.lock");
// Last should be "src/"
@@ -475,7 +475,7 @@ mod tests {
let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false);
// Create files (files are then sorted by name)
explorer.set_files(vec![entry1, entry2]);
explorer.sort_by(FileSorting::ByModifyTime);
explorer.sort_by(FileSorting::ModifyTime);
// First entry should be "CODE_OF_CONDUCT.md"
assert_eq!(
explorer.files.get(0).unwrap().get_name(),
@@ -494,7 +494,7 @@ mod tests {
let entry2: FsEntry = make_fs_entry("CODE_OF_CONDUCT.md", false);
// Create files (files are then sorted by name)
explorer.set_files(vec![entry1, entry2]);
explorer.sort_by(FileSorting::ByCreationTime);
explorer.sort_by(FileSorting::CreationTime);
// First entry should be "CODE_OF_CONDUCT.md"
assert_eq!(
explorer.files.get(0).unwrap().get_name(),
@@ -513,7 +513,7 @@ mod tests {
make_fs_entry("src/", true),
make_fs_entry_with_size("CONTRIBUTING.md", false, 256),
]);
explorer.sort_by(FileSorting::BySize);
explorer.sort_by(FileSorting::Size);
// Directory has size 4096
assert_eq!(explorer.files.get(0).unwrap().get_name(), "src/");
assert_eq!(explorer.files.get(1).unwrap().get_name(), "README.md");
@@ -536,7 +536,7 @@ mod tests {
make_fs_entry("Cargo.lock", false),
make_fs_entry("codecov.yml", false),
]);
explorer.sort_by(FileSorting::ByName);
explorer.sort_by(FileSorting::Name);
explorer.group_dirs_by(Some(GroupDirs::First));
// First entry should be "docs"
assert_eq!(explorer.files.get(0).unwrap().get_name(), "docs/");
@@ -563,7 +563,7 @@ mod tests {
make_fs_entry("Cargo.lock", false),
make_fs_entry("codecov.yml", false),
]);
explorer.sort_by(FileSorting::ByName);
explorer.sort_by(FileSorting::Name);
explorer.group_dirs_by(Some(GroupDirs::Last));
// Last entry should be "src"
assert_eq!(explorer.files.get(8).unwrap().get_name(), "docs/");
@@ -614,25 +614,25 @@ mod tests {
#[test]
fn test_fs_explorer_to_string_from_str_traits() {
// File Sorting
assert_eq!(FileSorting::ByCreationTime.to_string(), "by_creation_time");
assert_eq!(FileSorting::ByModifyTime.to_string(), "by_mtime");
assert_eq!(FileSorting::ByName.to_string(), "by_name");
assert_eq!(FileSorting::BySize.to_string(), "by_size");
assert_eq!(FileSorting::CreationTime.to_string(), "by_creation_time");
assert_eq!(FileSorting::ModifyTime.to_string(), "by_mtime");
assert_eq!(FileSorting::Name.to_string(), "by_name");
assert_eq!(FileSorting::Size.to_string(), "by_size");
assert_eq!(
FileSorting::from_str("by_creation_time").ok().unwrap(),
FileSorting::ByCreationTime
FileSorting::CreationTime
);
assert_eq!(
FileSorting::from_str("by_mtime").ok().unwrap(),
FileSorting::ByModifyTime
FileSorting::ModifyTime
);
assert_eq!(
FileSorting::from_str("by_name").ok().unwrap(),
FileSorting::ByName
FileSorting::Name
);
assert_eq!(
FileSorting::from_str("by_size").ok().unwrap(),
FileSorting::BySize
FileSorting::Size
);
assert!(FileSorting::from_str("omar").is_err());
// Group dirs

View File

@@ -56,6 +56,7 @@ extern crate regex;
extern crate ssh2;
extern crate tempfile;
extern crate textwrap;
extern crate tui_realm_stdlib;
extern crate tuirealm;
extern crate ureq;
#[cfg(target_family = "unix")]

View File

@@ -115,7 +115,7 @@ impl BookmarksClient {
if let Err(e) = key_storage.set_key(service_id, key.as_str()) {
error!("Failed to set new key into storage: {}", e);
return Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
format!("Could not write key to storage: {}", e),
));
}
@@ -125,7 +125,7 @@ impl BookmarksClient {
_ => {
error!("Failed to get key from storage: {}", e);
return Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
format!("Could not get key from storage: {}", e),
));
}
@@ -328,7 +328,7 @@ impl BookmarksClient {
Err(err) => {
error!("Failed to write bookmarks: {}", err);
Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
))
}
@@ -358,7 +358,7 @@ impl BookmarksClient {
Err(err) => {
error!("Failed to read bookmarks: {}", err);
Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
))
}
@@ -407,7 +407,7 @@ impl BookmarksClient {
match crypto::aes128_b64_decrypt(self.key.as_str(), secret) {
Ok(txt) => Ok(txt),
Err(err) => Err(SerializerError::new_ex(
SerializerErrorKind::SyntaxError,
SerializerErrorKind::Syntax,
err.to_string(),
)),
}

View File

@@ -76,7 +76,7 @@ impl ConfigClient {
if let Err(err) = create_dir(ssh_key_dir) {
error!("Failed to create SSH key dir: {}", err);
return Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
format!(
"Could not create SSH key directory \"{}\": {}",
ssh_key_dir.display(),
@@ -252,7 +252,7 @@ impl ConfigClient {
) -> Result<(), SerializerError> {
if self.degraded {
return Err(SerializerError::new_ex(
SerializerErrorKind::GenericError,
SerializerErrorKind::Generic,
String::from("Configuration won't be saved, since in degraded mode"),
));
}
@@ -291,7 +291,7 @@ impl ConfigClient {
pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> {
if self.degraded {
return Err(SerializerError::new_ex(
SerializerErrorKind::GenericError,
SerializerErrorKind::Generic,
String::from("Configuration won't be saved, since in degraded mode"),
));
}
@@ -351,7 +351,7 @@ impl ConfigClient {
pub fn write_config(&self) -> Result<(), SerializerError> {
if self.degraded {
return Err(SerializerError::new_ex(
SerializerErrorKind::GenericError,
SerializerErrorKind::Generic,
String::from("Configuration won't be saved, since in degraded mode"),
));
}
@@ -366,7 +366,7 @@ impl ConfigClient {
Err(err) => {
error!("Failed to write configuration file: {}", err);
Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
))
}
@@ -379,7 +379,7 @@ impl ConfigClient {
pub fn read_config(&mut self) -> Result<(), SerializerError> {
if self.degraded {
return Err(SerializerError::new_ex(
SerializerErrorKind::GenericError,
SerializerErrorKind::Generic,
String::from("Configuration won't be loaded, since in degraded mode"),
));
}
@@ -401,7 +401,7 @@ impl ConfigClient {
Err(err) => {
error!("Failed to read configuration: {}", err);
Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
))
}
@@ -432,7 +432,7 @@ impl ConfigClient {
/// Make serializer error from `std::io::Error`
fn make_io_err(err: std::io::Error) -> Result<(), SerializerError> {
Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
))
}

View File

@@ -116,7 +116,7 @@ impl ThemeProvider {
warn!("Configuration won't be loaded, since degraded; reloading default...");
self.theme = Theme::default();
return Err(SerializerError::new_ex(
SerializerErrorKind::GenericError,
SerializerErrorKind::Generic,
String::from("Can't access theme file"),
));
}
@@ -139,7 +139,7 @@ impl ThemeProvider {
Err(err) => {
error!("Failed to read theme: {}", err);
Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
))
}
@@ -153,7 +153,7 @@ impl ThemeProvider {
if self.degraded {
warn!("Configuration won't be saved, since in degraded mode");
return Err(SerializerError::new_ex(
SerializerErrorKind::GenericError,
SerializerErrorKind::Generic,
String::from("Can't access theme file"),
));
}
@@ -169,7 +169,7 @@ impl ThemeProvider {
Err(err) => {
error!("Failed to write theme: {}", err);
Err(SerializerError::new_ex(
SerializerErrorKind::IoError,
SerializerErrorKind::Io,
err.to_string(),
))
}

View File

@@ -32,7 +32,7 @@ use crate::system::environment;
// Ext
use std::path::PathBuf;
use tuirealm::components::{input::InputPropsBuilder, radio::RadioPropsBuilder};
use tui_realm_stdlib::{input::InputPropsBuilder, radio::RadioPropsBuilder};
use tuirealm::{Payload, PropsBuilder, Value};
impl AuthActivity {
@@ -44,7 +44,7 @@ impl AuthActivity {
// Iterate over kyes
let name: Option<&String> = self.bookmarks_list.get(idx);
if let Some(name) = name {
bookmarks_cli.del_bookmark(&name);
bookmarks_cli.del_bookmark(name);
// Write bookmarks
self.write_bookmarks();
}
@@ -60,7 +60,7 @@ impl AuthActivity {
if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() {
// Iterate over bookmarks
if let Some(key) = self.bookmarks_list.get(idx) {
if let Some(bookmark) = bookmarks_cli.get_bookmark(&key) {
if let Some(bookmark) = bookmarks_cli.get_bookmark(key) {
// Load parameters into components
self.load_bookmark_into_gui(
bookmark.0, bookmark.1, bookmark.2, bookmark.3, bookmark.4,
@@ -104,7 +104,7 @@ impl AuthActivity {
if let Some(client) = self.bookmarks_client.as_mut() {
let name: Option<&String> = self.recents_list.get(idx);
if let Some(name) = name {
client.del_recent(&name);
client.del_recent(name);
// Write bookmarks
self.write_bookmarks();
}

View File

@@ -35,7 +35,7 @@ use super::{
COMPONENT_TEXT_HELP, COMPONENT_TEXT_NEW_VERSION_NOTES, COMPONENT_TEXT_SIZE_ERR,
};
use crate::ui::keymap::*;
use tuirealm::components::InputPropsBuilder;
use tui_realm_stdlib::InputPropsBuilder;
use tuirealm::{Msg, Payload, PropsBuilder, Update, Value};
// -- update
@@ -52,53 +52,53 @@ impl Update for AuthActivity {
None => None, // Exit after None
Some(msg) => match msg {
// Focus ( DOWN )
(COMPONENT_RADIO_PROTOCOL, &MSG_KEY_DOWN) => {
(COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_DOWN => {
// Give focus to port
self.view.active(COMPONENT_INPUT_ADDR);
None
}
(COMPONENT_INPUT_ADDR, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_DOWN => {
// Give focus to port
self.view.active(COMPONENT_INPUT_PORT);
None
}
(COMPONENT_INPUT_PORT, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_DOWN => {
// Give focus to port
self.view.active(COMPONENT_INPUT_USERNAME);
None
}
(COMPONENT_INPUT_USERNAME, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_DOWN => {
// Give focus to port
self.view.active(COMPONENT_INPUT_PASSWORD);
None
}
(COMPONENT_INPUT_PASSWORD, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_DOWN => {
// Give focus to port
self.view.active(COMPONENT_RADIO_PROTOCOL);
None
}
// Focus ( UP )
(COMPONENT_INPUT_PASSWORD, &MSG_KEY_UP) => {
(COMPONENT_INPUT_PASSWORD, key) if key == &MSG_KEY_UP => {
// Give focus to port
self.view.active(COMPONENT_INPUT_USERNAME);
None
}
(COMPONENT_INPUT_USERNAME, &MSG_KEY_UP) => {
(COMPONENT_INPUT_USERNAME, key) if key == &MSG_KEY_UP => {
// Give focus to port
self.view.active(COMPONENT_INPUT_PORT);
None
}
(COMPONENT_INPUT_PORT, &MSG_KEY_UP) => {
(COMPONENT_INPUT_PORT, key) if key == &MSG_KEY_UP => {
// Give focus to port
self.view.active(COMPONENT_INPUT_ADDR);
None
}
(COMPONENT_INPUT_ADDR, &MSG_KEY_UP) => {
(COMPONENT_INPUT_ADDR, key) if key == &MSG_KEY_UP => {
// Give focus to port
self.view.active(COMPONENT_RADIO_PROTOCOL);
None
}
(COMPONENT_RADIO_PROTOCOL, &MSG_KEY_UP) => {
(COMPONENT_RADIO_PROTOCOL, key) if key == &MSG_KEY_UP => {
// Give focus to port
self.view.active(COMPONENT_INPUT_PASSWORD);
None
@@ -118,25 +118,25 @@ impl Update for AuthActivity {
}
// Bookmarks commands
// <RIGHT> / <LEFT>
(COMPONENT_BOOKMARKS_LIST, &MSG_KEY_RIGHT) => {
(COMPONENT_BOOKMARKS_LIST, key) if key == &MSG_KEY_RIGHT => {
// Give focus to recents
self.view.active(COMPONENT_RECENTS_LIST);
None
}
(COMPONENT_RECENTS_LIST, &MSG_KEY_LEFT) => {
(COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_LEFT => {
// Give focus to bookmarks
self.view.active(COMPONENT_BOOKMARKS_LIST);
None
}
// <DEL | 'E'>
(COMPONENT_BOOKMARKS_LIST, &MSG_KEY_DEL)
| (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_CHAR_E) => {
(COMPONENT_BOOKMARKS_LIST, key)
if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E =>
{
// Show delete popup
self.mount_bookmark_del_dialog();
None
}
(COMPONENT_RECENTS_LIST, &MSG_KEY_DEL)
| (COMPONENT_RECENTS_LIST, &MSG_KEY_CHAR_E) => {
(COMPONENT_RECENTS_LIST, key) if key == &MSG_KEY_DEL || key == &MSG_KEY_CHAR_E => {
// Show delete popup
self.mount_recent_del_dialog();
None
@@ -203,67 +203,68 @@ impl Update for AuthActivity {
}
}
// <ESC> hide tab
(COMPONENT_RADIO_BOOKMARK_DEL_RECENT, &MSG_KEY_ESC) => {
(COMPONENT_RADIO_BOOKMARK_DEL_RECENT, key) if key == &MSG_KEY_ESC => {
self.umount_recent_del_dialog();
None
}
(COMPONENT_RADIO_BOOKMARK_DEL_RECENT, _) => None,
(COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, &MSG_KEY_ESC) => {
(COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, key) if key == &MSG_KEY_ESC => {
self.umount_bookmark_del_dialog();
None
}
(COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, _) => None,
// Error message
(COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_ERROR, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => {
// Umount text error
self.umount_error();
None
}
(COMPONENT_TEXT_ERROR, _) => None,
(COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ESC)
| (COMPONENT_TEXT_NEW_VERSION_NOTES, &MSG_KEY_ENTER) => {
(COMPONENT_TEXT_NEW_VERSION_NOTES, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
// Umount release notes
self.umount_release_notes();
None
}
(COMPONENT_TEXT_NEW_VERSION_NOTES, _) => None,
// Help
(_, &MSG_KEY_CTRL_H) => {
(_, key) if key == &MSG_KEY_CTRL_H => {
// Show help
self.mount_help();
None
}
// Release notes
(_, &MSG_KEY_CTRL_R) => {
(_, key) if key == &MSG_KEY_CTRL_R => {
// Show release notes
self.mount_release_notes();
None
}
(COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_HELP, key) if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER => {
// Hide text help
self.umount_help();
None
}
(COMPONENT_TEXT_HELP, _) => None,
// Enter setup
(_, &MSG_KEY_CTRL_C) => {
(_, key) if key == &MSG_KEY_CTRL_C => {
self.exit_reason = Some(super::ExitReason::EnterSetup);
None
}
// Save bookmark; show popup
(_, &MSG_KEY_CTRL_S) => {
(_, key) if key == &MSG_KEY_CTRL_S => {
// Show popup
self.mount_bookmark_save_dialog();
// Give focus to bookmark name
self.view.active(COMPONENT_INPUT_BOOKMARK_NAME);
None
}
(COMPONENT_INPUT_BOOKMARK_NAME, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_BOOKMARK_NAME, key) if key == &MSG_KEY_DOWN => {
// Give focus to pwd
self.view.active(COMPONENT_RADIO_BOOKMARK_SAVE_PWD);
None
}
(COMPONENT_RADIO_BOOKMARK_SAVE_PWD, &MSG_KEY_UP) => {
(COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key) if key == &MSG_KEY_UP => {
// Give focus to pwd
self.view.active(COMPONENT_INPUT_BOOKMARK_NAME);
None
@@ -291,8 +292,9 @@ impl Update for AuthActivity {
self.view_bookmarks()
}
// Hide save bookmark
(COMPONENT_INPUT_BOOKMARK_NAME, &MSG_KEY_ESC)
| (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_BOOKMARK_NAME, key) | (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, key)
if key == &MSG_KEY_ESC =>
{
// Umount popup
self.umount_bookmark_save_dialog();
None
@@ -307,45 +309,30 @@ impl Update for AuthActivity {
self.umount_quit();
None
}
(COMPONENT_RADIO_QUIT, &MSG_KEY_ESC) => {
(COMPONENT_RADIO_QUIT, key) if key == &MSG_KEY_ESC => {
self.umount_quit();
None
}
// -- text size error; block everything
(COMPONENT_TEXT_SIZE_ERR, _) => None,
// <TAB> bookmarks
(COMPONENT_BOOKMARKS_LIST, &MSG_KEY_TAB)
| (COMPONENT_RECENTS_LIST, &MSG_KEY_TAB) => {
(COMPONENT_BOOKMARKS_LIST, key) | (COMPONENT_RECENTS_LIST, key)
if key == &MSG_KEY_TAB =>
{
// Give focus to address
self.view.active(COMPONENT_INPUT_ADDR);
None
}
// Any <TAB>, go to bookmarks
(_, &MSG_KEY_TAB) => {
(_, key) if key == &MSG_KEY_TAB => {
self.view.active(COMPONENT_BOOKMARKS_LIST);
None
}
// On submit on any unhandled (connect)
(_, Msg::OnSubmit(_)) | (_, &MSG_KEY_ENTER) => {
// Validate fields
match self.collect_host_params() {
Err(err) => {
// mount error
self.mount_error(err);
}
Ok(params) => {
self.save_recent();
// Set file transfer params to context
self.context_mut().set_ftparams(params);
// Set exit reason
self.exit_reason = Some(super::ExitReason::Connect);
}
}
// Return None
None
}
(_, Msg::OnSubmit(_)) => self.on_unhandled_submit(),
(_, key) if key == &MSG_KEY_ENTER => self.on_unhandled_submit(),
// <ESC> => Quit
(_, &MSG_KEY_ESC) => {
(_, key) if key == &MSG_KEY_ESC => {
self.mount_quit();
None
}
@@ -367,4 +354,23 @@ impl AuthActivity {
}
}
}
fn on_unhandled_submit(&mut self) -> Option<(String, Msg)> {
// Validate fields
match self.collect_host_params() {
Err(err) => {
// mount error
self.mount_error(err);
}
Ok(params) => {
self.save_recent();
// Set file transfer params to context
self.context_mut().set_ftparams(params);
// Set exit reason
self.exit_reason = Some(super::ExitReason::Connect);
}
}
// Return None
None
}
}

View File

@@ -27,17 +27,15 @@
*/
// Locals
use super::{AuthActivity, Context, FileTransferProtocol};
use crate::ui::components::{
bookmark_list::{BookmarkList, BookmarkListPropsBuilder},
msgbox::{MsgBox, MsgBoxPropsBuilder},
};
use crate::ui::components::bookmark_list::{BookmarkList, BookmarkListPropsBuilder};
use crate::utils::ui::draw_area_in;
// Ext
use tuirealm::components::{
use tui_realm_stdlib::{
input::{Input, InputPropsBuilder},
label::{Label, LabelPropsBuilder},
list::{List, ListPropsBuilder},
paragraph::{Paragraph, ParagraphPropsBuilder},
radio::{Radio, RadioPropsBuilder},
scrolltable::{ScrollTablePropsBuilder, Scrolltable},
span::{Span, SpanPropsBuilder},
textarea::{Textarea, TextareaPropsBuilder},
};
@@ -47,7 +45,7 @@ use tuirealm::tui::{
widgets::{BorderType, Borders, Clear},
};
use tuirealm::{
props::{InputType, PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder},
props::{Alignment, InputType, PropsBuilder, TableBuilder, TextSpan},
Msg, Payload, Value,
};
@@ -91,19 +89,11 @@ impl AuthActivity {
Box::new(Span::new(
SpanPropsBuilder::default()
.with_spans(vec![
TextSpanBuilder::new("Press ").bold().build(),
TextSpanBuilder::new("<CTRL+H>")
.bold()
.with_foreground(key_color)
.build(),
TextSpanBuilder::new(" to show keybindings; ")
.bold()
.build(),
TextSpanBuilder::new("<CTRL+C>")
.bold()
.with_foreground(key_color)
.build(),
TextSpanBuilder::new(" to enter setup").bold().build(),
TextSpan::new("Press ").bold(),
TextSpan::new("<CTRL+H>").bold().fg(key_color),
TextSpan::new(" to show keybindings; ").bold(),
TextSpan::new("<CTRL+C>").bold().fg(key_color),
TextSpan::new(" to enter setup").bold(),
])
.build(),
)),
@@ -118,16 +108,10 @@ impl AuthActivity {
.with_color(protocol_color)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, protocol_color)
.with_options(
Some(String::from("Protocol")),
vec![
String::from("SFTP"),
String::from("SCP"),
String::from("FTP"),
String::from("FTPS"),
],
)
.with_title("Protocol", Alignment::Left)
.with_options(&["SFTP", "SCP", "FTP", "FTPS"])
.with_value(Self::protocol_enum_to_opt(default_protocol))
.rewind(true)
.build(),
)),
);
@@ -138,7 +122,7 @@ impl AuthActivity {
InputPropsBuilder::default()
.with_foreground(addr_color)
.with_borders(Borders::ALL, BorderType::Rounded, addr_color)
.with_label(String::from("Remote host"))
.with_label("Remote host", Alignment::Left)
.build(),
)),
);
@@ -149,7 +133,7 @@ impl AuthActivity {
InputPropsBuilder::default()
.with_foreground(port_color)
.with_borders(Borders::ALL, BorderType::Rounded, port_color)
.with_label(String::from("Port number"))
.with_label("Port number", Alignment::Left)
.with_input(InputType::Number)
.with_input_len(5)
.with_value(Self::get_default_port_for_protocol(default_protocol).to_string())
@@ -163,7 +147,7 @@ impl AuthActivity {
InputPropsBuilder::default()
.with_foreground(username_color)
.with_borders(Borders::ALL, BorderType::Rounded, username_color)
.with_label(String::from("Username"))
.with_label("Username", Alignment::Left)
.build(),
)),
);
@@ -174,7 +158,7 @@ impl AuthActivity {
InputPropsBuilder::default()
.with_foreground(password_color)
.with_borders(Borders::ALL, BorderType::Rounded, password_color)
.with_label(String::from("Password"))
.with_label("Password", Alignment::Left)
.with_input(InputType::Password)
.build(),
)),
@@ -193,7 +177,7 @@ impl AuthActivity {
.with_foreground(Color::Yellow)
.with_spans(vec![
TextSpan::from("termscp "),
TextSpanBuilder::new(version.as_str()).underlined().bold().build(),
TextSpan::new(version.as_str()).underlined().bold(),
TextSpan::from(" is NOW available! Get it from <https://veeso.github.io/termscp/>; view release notes with <CTRL+R>"),
])
.build(),
@@ -208,7 +192,7 @@ impl AuthActivity {
.with_background(bookmarks_color)
.with_foreground(Color::Black)
.with_borders(Borders::ALL, BorderType::Plain, bookmarks_color)
.with_bookmarks(Some(String::from("Bookmarks")), vec![])
.with_title("Bookmarks", Alignment::Left)
.build(),
)),
);
@@ -220,7 +204,7 @@ impl AuthActivity {
.with_background(recents_color)
.with_foreground(Color::Black)
.with_borders(Borders::ALL, BorderType::Plain, recents_color)
.with_bookmarks(Some(String::from("Recent connections")), vec![])
.with_title("Recent connections", Alignment::Left)
.build(),
)),
);
@@ -426,7 +410,7 @@ impl AuthActivity {
let msg = self.view.update(
super::COMPONENT_BOOKMARKS_LIST,
BookmarkListPropsBuilder::from(props)
.with_bookmarks(Some(String::from("Bookmarks")), bookmarks)
.with_bookmarks(bookmarks)
.build(),
);
msg
@@ -464,7 +448,7 @@ impl AuthActivity {
let msg = self.view.update(
super::COMPONENT_RECENTS_LIST,
BookmarkListPropsBuilder::from(props)
.with_bookmarks(Some(String::from("Recent connections")), bookmarks)
.with_bookmarks(bookmarks)
.build(),
);
msg
@@ -482,12 +466,13 @@ impl AuthActivity {
let err_color = self.theme().misc_error_dialog;
self.view.mount(
super::COMPONENT_TEXT_ERROR,
Box::new(MsgBox::new(
MsgBoxPropsBuilder::default()
Box::new(Paragraph::new(
ParagraphPropsBuilder::default()
.with_foreground(err_color)
.with_borders(Borders::ALL, BorderType::Thick, err_color)
.bold()
.with_texts(None, vec![TextSpan::from(text)])
.with_text_alignment(Alignment::Center)
.with_texts(vec![TextSpan::from(text)])
.build(),
)),
);
@@ -510,17 +495,15 @@ impl AuthActivity {
let err_color = self.theme().misc_error_dialog;
self.view.mount(
super::COMPONENT_TEXT_SIZE_ERR,
Box::new(MsgBox::new(
MsgBoxPropsBuilder::default()
Box::new(Paragraph::new(
ParagraphPropsBuilder::default()
.with_foreground(err_color)
.with_borders(Borders::ALL, BorderType::Thick, err_color)
.bold()
.with_texts(
None,
vec![TextSpan::from(
"termscp requires at least 24 lines of height to run",
)],
)
.with_texts(vec![TextSpan::from(
"termscp requires at least 24 lines of height to run",
)])
.with_text_alignment(Alignment::Center)
.build(),
)),
);
@@ -548,10 +531,9 @@ impl AuthActivity {
.with_color(quit_color)
.with_borders(Borders::ALL, BorderType::Rounded, quit_color)
.with_inverted_color(Color::Black)
.with_options(
Some(String::from("Quit termscp?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Quit termscp?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.rewind(true)
.build(),
)),
);
@@ -577,11 +559,10 @@ impl AuthActivity {
.with_color(warn_color)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, warn_color)
.with_options(
Some(String::from("Delete bookmark?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Delete bookmark?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.with_value(1)
.rewind(true)
.build(),
)),
);
@@ -610,11 +591,10 @@ impl AuthActivity {
.with_color(warn_color)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, warn_color)
.with_options(
Some(String::from("Delete bookmark?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Delete bookmark?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.with_value(1)
.rewind(true)
.build(),
)),
);
@@ -640,7 +620,7 @@ impl AuthActivity {
Box::new(Input::new(
InputPropsBuilder::default()
.with_foreground(save_color)
.with_label(String::from("Save bookmark as…"))
.with_label("Save bookmark as…", Alignment::Center)
.with_borders(
Borders::TOP | Borders::RIGHT | Borders::LEFT,
BorderType::Rounded,
@@ -659,10 +639,9 @@ impl AuthActivity {
BorderType::Rounded,
Color::Reset,
)
.with_options(
Some(String::from("Save password?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Save password?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.rewind(true)
.build(),
)),
);
@@ -685,77 +664,38 @@ impl AuthActivity {
let key_color = self.theme().misc_keys;
self.view.mount(
super::COMPONENT_TEXT_HELP,
Box::new(Scrolltable::new(
ScrollTablePropsBuilder::default()
Box::new(List::new(
ListPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
.with_highlighted_str(Some("?"))
.with_max_scroll_step(8)
.scrollable(true)
.bold()
.with_table(
Some(String::from("Help")),
.with_title("Help", Alignment::Center)
.with_rows(
TableBuilder::default()
.add_col(
TextSpanBuilder::new("<ESC>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<ESC>").bold().fg(key_color))
.add_col(TextSpan::from(" Quit termscp"))
.add_row()
.add_col(
TextSpanBuilder::new("<TAB>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<TAB>").bold().fg(key_color))
.add_col(TextSpan::from(" Switch from form and bookmarks"))
.add_row()
.add_col(
TextSpanBuilder::new("<RIGHT/LEFT>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(key_color))
.add_col(TextSpan::from(" Switch bookmark tab"))
.add_row()
.add_col(
TextSpanBuilder::new("<UP/DOWN>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(key_color))
.add_col(TextSpan::from(" Move up/down in current tab"))
.add_row()
.add_col(
TextSpanBuilder::new("<ENTER>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<ENTER>").bold().fg(key_color))
.add_col(TextSpan::from(" Connect/Load bookmark"))
.add_row()
.add_col(
TextSpanBuilder::new("<DEL|E>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<DEL|E>").bold().fg(key_color))
.add_col(TextSpan::from(" Delete selected bookmark"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+C>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<CTRL+C>").bold().fg(key_color))
.add_col(TextSpan::from(" Enter setup"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+S>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<CTRL+S>").bold().fg(key_color))
.add_col(TextSpan::from(" Save bookmark"))
.build(),
)
@@ -786,7 +726,8 @@ impl AuthActivity {
Box::new(Textarea::new(
TextareaPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow)
.with_texts(Some(String::from("Release notes")), spans)
.with_title("Release notes", Alignment::Center)
.with_texts(spans)
.build(),
)),
);

View File

@@ -72,7 +72,7 @@ impl FileTransferActivity {
}
pub(crate) fn local_remove_file(&mut self, entry: &FsEntry) {
match self.host.remove(&entry) {
match self.host.remove(entry) {
Ok(_) => {
// Log
self.log(
@@ -94,7 +94,7 @@ impl FileTransferActivity {
}
pub(crate) fn remote_remove_file(&mut self, entry: &FsEntry) {
match self.client.remove(&entry) {
match self.client.remove(entry) {
Ok(_) => {
self.log(
LogLevel::Info,

View File

@@ -142,7 +142,7 @@ impl Browser {
let mut builder: FileExplorerBuilder = FileExplorerBuilder::new();
// Set common keys
builder
.with_file_sorting(FileSorting::ByName)
.with_file_sorting(FileSorting::Name)
.with_stack_size(16)
.with_group_dirs(cli.get_group_dirs())
.with_hidden_files(cli.get_show_hidden_files());
@@ -154,7 +154,7 @@ impl Browser {
/// Build explorer reading from `ConfigClient`, for found result (has some differences)
fn build_found_explorer() -> FileExplorer {
FileExplorerBuilder::new()
.with_file_sorting(FileSorting::ByName)
.with_file_sorting(FileSorting::Name)
.with_group_dirs(Some(GroupDirs::First))
.with_hidden_files(true)
.with_stack_size(0)

View File

@@ -228,7 +228,7 @@ impl FileTransferActivity {
///
/// Returns config client reference
fn config(&self) -> &ConfigClient {
&self.context().config()
self.context().config()
}
/// ### theme

View File

@@ -27,7 +27,7 @@
*/
// Locals
use super::{FileTransferActivity, LogLevel};
use crate::filetransfer::FileTransferError;
use crate::filetransfer::{FileTransferError, FileTransferErrorType};
use crate::fs::{FsEntry, FsFile};
use crate::host::HostError;
use crate::utils::fmt::fmt_millis;
@@ -363,26 +363,33 @@ impl FileTransferActivity {
}
}
FsEntry::Directory(dir) => {
// Check whether should create directory
if self.client.list_dir(remote_path.as_path()).is_err() {
match self.client.mkdir(remote_path.as_path()) {
Ok(_) => {
self.log(
LogLevel::Info,
format!("Created directory \"{}\"", remote_path.display()),
);
}
Err(err) => {
self.log_and_alert(
LogLevel::Error,
format!(
"Failed to create directory \"{}\": {}",
remote_path.display(),
err
),
);
return;
}
// Create directory on remote first
match self.client.mkdir(remote_path.as_path()) {
Ok(_) => {
self.log(
LogLevel::Info,
format!("Created directory \"{}\"", remote_path.display()),
);
}
Err(err) if err.kind() == FileTransferErrorType::DirectoryAlreadyExists => {
self.log(
LogLevel::Info,
format!(
"Directory \"{}\" already exists on remote",
remote_path.display()
),
);
}
Err(err) => {
self.log_and_alert(
LogLevel::Error,
format!(
"Failed to create directory \"{}\": {}",
remote_path.display(),
err
),
);
return;
}
}
// Get files in dir
@@ -395,7 +402,7 @@ impl FileTransferActivity {
break;
}
// Send entry; name is always None after first call
self.filetransfer_send_recurse(&entry, remote_path.as_path(), None);
self.filetransfer_send_recurse(entry, remote_path.as_path(), None);
}
}
Err(err) => {
@@ -729,7 +736,7 @@ impl FileTransferActivity {
// Receive entry; name is always None after first call
// Local path becomes local_dir_path
self.filetransfer_recv_recurse(
&entry,
entry,
local_dir_path.as_path(),
None,
);

View File

@@ -42,9 +42,9 @@ use crate::ui::components::{file_list::FileListPropsBuilder, logbox::LogboxProps
use crate::ui::keymap::*;
use crate::utils::fmt::fmt_path_elide_ex;
// externals
use tui_realm_stdlib::progress_bar::ProgressBarPropsBuilder;
use tuirealm::{
components::progress_bar::ProgressBarPropsBuilder,
props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder},
props::{Alignment, PropsBuilder, TableBuilder, TextSpan},
tui::style::Color,
Msg, Payload, Update, Value,
};
@@ -63,13 +63,13 @@ impl Update for FileTransferActivity {
None => None, // Exit after None
Some(msg) => match msg {
// -- local tab
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_RIGHT) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_RIGHT => {
// Change tab
self.view.active(COMPONENT_EXPLORER_REMOTE);
self.browser.change_tab(FileExplorerTab::Remote);
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_BACKSPACE) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_BACKSPACE => {
// Go to previous directory
self.action_go_to_previous_local_dir(false);
if self.browser.sync_browsing {
@@ -98,11 +98,11 @@ impl Update for FileTransferActivity {
None
}
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_SPACE) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_SPACE => {
self.action_local_send();
self.update_remote_filelist()
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_A) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_A => {
// Toggle hidden files
self.local_mut().toggle_hidden_files();
// Update status bar
@@ -110,24 +110,24 @@ impl Update for FileTransferActivity {
// Reload file list component
self.update_local_filelist()
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_I) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_I => {
if let SelectedEntry::One(file) = self.get_local_selected_entries() {
self.mount_file_info(&file);
}
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_L) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_L => {
// Reload directory
self.reload_local_dir();
// Reload file list component
self.update_local_filelist()
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_O) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_O => {
self.action_edit_local_file();
// Reload file list component
self.update_local_filelist()
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_U) => {
(COMPONENT_EXPLORER_LOCAL, key) if key == &MSG_KEY_CHAR_U => {
self.action_go_to_local_upper_dir(false);
if self.browser.sync_browsing {
let _ = self.update_remote_filelist();
@@ -136,7 +136,7 @@ impl Update for FileTransferActivity {
self.update_local_filelist()
}
// -- remote tab
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_LEFT) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_LEFT => {
// Change tab
self.view.active(COMPONENT_EXPLORER_LOCAL);
self.browser.change_tab(FileExplorerTab::Local);
@@ -162,11 +162,11 @@ impl Update for FileTransferActivity {
None
}
}
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_SPACE) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_SPACE => {
self.action_remote_recv();
self.update_local_filelist()
}
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_BACKSPACE) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_BACKSPACE => {
// Go to previous directory
self.action_go_to_previous_remote_dir(false);
// If sync is enabled update local too
@@ -176,7 +176,7 @@ impl Update for FileTransferActivity {
// Reload file list component
self.update_remote_filelist()
}
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_A) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_A => {
// Toggle hidden files
self.remote_mut().toggle_hidden_files();
// Update status bar
@@ -184,25 +184,25 @@ impl Update for FileTransferActivity {
// Reload file list component
self.update_remote_filelist()
}
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_I) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_I => {
if let SelectedEntry::One(file) = self.get_remote_selected_entries() {
self.mount_file_info(&file);
}
None
}
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_L) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_L => {
// Reload directory
self.reload_remote_dir();
// Reload file list component
self.update_remote_filelist()
}
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_O) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_O => {
// Edit file
self.action_edit_remote_file();
// Reload file list component
self.update_remote_filelist()
}
(COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_U) => {
(COMPONENT_EXPLORER_REMOTE, key) if key == &MSG_KEY_CHAR_U => {
self.action_go_to_remote_upper_dir(false);
if self.browser.sync_browsing {
let _ = self.update_local_filelist();
@@ -211,64 +211,78 @@ impl Update for FileTransferActivity {
self.update_remote_filelist()
}
// -- common explorer keys
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_B)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_B) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_B =>
{
// Show sorting file
self.mount_file_sorting();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_C)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_C) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_C =>
{
self.mount_copy();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_D)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_D) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_D =>
{
self.mount_mkdir();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_F)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_F) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_F =>
{
self.mount_find_input();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_G)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_G) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_G =>
{
self.mount_goto();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_H)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_H) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_H =>
{
self.mount_help();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_N)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_N) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_N =>
{
self.mount_newfile();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_Q)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_Q)
| (COMPONENT_LOG_BOX, &MSG_KEY_CHAR_Q) => {
(COMPONENT_EXPLORER_LOCAL, key)
| (COMPONENT_EXPLORER_REMOTE, key)
| (COMPONENT_LOG_BOX, key)
if key == &MSG_KEY_CHAR_Q =>
{
self.mount_quit();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_R)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_R) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_R =>
{
// Mount rename
self.mount_rename();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_S)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_S)
| (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_S) => {
(COMPONENT_EXPLORER_LOCAL, key)
| (COMPONENT_EXPLORER_REMOTE, key)
| (COMPONENT_EXPLORER_FIND, key)
if key == &MSG_KEY_CHAR_S =>
{
// Mount save as
self.mount_saveas();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_V)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_V)
| (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_V) => {
(COMPONENT_EXPLORER_LOCAL, key)
| (COMPONENT_EXPLORER_REMOTE, key)
| (COMPONENT_EXPLORER_FIND, key)
if key == &MSG_KEY_CHAR_V =>
{
// View
match self.browser.tab() {
FileExplorerTab::Local => self.action_open_local(),
@@ -279,44 +293,49 @@ impl Update for FileTransferActivity {
}
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_W)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_W)
| (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_W) => {
(COMPONENT_EXPLORER_LOCAL, key)
| (COMPONENT_EXPLORER_REMOTE, key)
| (COMPONENT_EXPLORER_FIND, key)
if key == &MSG_KEY_CHAR_W =>
{
// Open with
self.mount_openwith();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_X)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_X) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_X =>
{
// Mount exec
self.mount_exec();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_Y)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_Y) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_CHAR_Y =>
{
// Toggle browser sync
self.browser.toggle_sync_browsing();
// Update status bar
self.refresh_remote_status_bar();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_ESC)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_ESC)
| (COMPONENT_LOG_BOX, &MSG_KEY_ESC) => {
(COMPONENT_EXPLORER_LOCAL, key)
| (COMPONENT_EXPLORER_REMOTE, key)
| (COMPONENT_LOG_BOX, key)
if key == &MSG_KEY_ESC =>
{
self.mount_disconnect();
None
}
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_DEL)
| (COMPONENT_EXPLORER_LOCAL, &MSG_KEY_CHAR_E)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_DEL)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_CHAR_E)
| (COMPONENT_EXPLORER_FIND, &MSG_KEY_DEL)
| (COMPONENT_EXPLORER_FIND, &MSG_KEY_CHAR_E) => {
(COMPONENT_EXPLORER_LOCAL, key)
| (COMPONENT_EXPLORER_REMOTE, key)
| (COMPONENT_EXPLORER_FIND, key)
if key == &MSG_KEY_CHAR_E || key == &MSG_KEY_DEL =>
{
self.mount_radio_delete();
None
}
// -- find result explorer
(COMPONENT_EXPLORER_FIND, &MSG_KEY_ESC) => {
(COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_ESC => {
// Umount find
self.umount_find();
// Finalize find
@@ -337,7 +356,7 @@ impl Update for FileTransferActivity {
_ => None,
}
}
(COMPONENT_EXPLORER_FIND, &MSG_KEY_SPACE) => {
(COMPONENT_EXPLORER_FIND, key) if key == &MSG_KEY_SPACE => {
// Get entry
self.action_find_transfer(None);
// Reload files
@@ -349,18 +368,19 @@ impl Update for FileTransferActivity {
}
}
// -- switch to log
(COMPONENT_EXPLORER_LOCAL, &MSG_KEY_TAB)
| (COMPONENT_EXPLORER_REMOTE, &MSG_KEY_TAB) => {
(COMPONENT_EXPLORER_LOCAL, key) | (COMPONENT_EXPLORER_REMOTE, key)
if key == &MSG_KEY_TAB =>
{
self.view.active(COMPONENT_LOG_BOX); // Active log box
None
}
// -- Log box
(COMPONENT_LOG_BOX, &MSG_KEY_TAB) => {
(COMPONENT_LOG_BOX, key) if key == &MSG_KEY_TAB => {
self.view.blur(); // Blur log box
None
}
// -- copy popup
(COMPONENT_INPUT_COPY, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_COPY, key) if key == &MSG_KEY_ESC => {
self.umount_copy();
None
}
@@ -383,7 +403,7 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_COPY, _) => None,
// -- exec popup
(COMPONENT_INPUT_EXEC, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_EXEC, key) if key == &MSG_KEY_ESC => {
self.umount_exec();
None
}
@@ -406,7 +426,7 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_EXEC, _) => None,
// -- find popup
(COMPONENT_INPUT_FIND, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_FIND, key) if key == &MSG_KEY_ESC => {
self.umount_find_input();
None
}
@@ -441,7 +461,7 @@ impl Update for FileTransferActivity {
None
}
// -- goto popup
(COMPONENT_INPUT_GOTO, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_GOTO, key) if key == &MSG_KEY_ESC => {
self.umount_goto();
None
}
@@ -474,7 +494,7 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_GOTO, _) => None,
// -- make directory
(COMPONENT_INPUT_MKDIR, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_MKDIR, key) if key == &MSG_KEY_ESC => {
self.umount_mkdir();
None
}
@@ -494,7 +514,7 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_MKDIR, _) => None,
// -- new file
(COMPONENT_INPUT_NEWFILE, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_NEWFILE, key) if key == &MSG_KEY_ESC => {
self.umount_newfile();
None
}
@@ -514,7 +534,7 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_NEWFILE, _) => None,
// -- open with
(COMPONENT_INPUT_OPEN_WITH, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_OPEN_WITH, key) if key == &MSG_KEY_ESC => {
self.umount_openwith();
None
}
@@ -531,7 +551,7 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_OPEN_WITH, _) => None,
// -- rename
(COMPONENT_INPUT_RENAME, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_RENAME, key) if key == &MSG_KEY_ESC => {
self.umount_rename();
None
}
@@ -553,7 +573,7 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_RENAME, _) => None,
// -- save as
(COMPONENT_INPUT_SAVEAS, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_SAVEAS, key) if key == &MSG_KEY_ESC => {
self.umount_saveas();
None
}
@@ -578,15 +598,18 @@ impl Update for FileTransferActivity {
}
(COMPONENT_INPUT_SAVEAS, _) => None,
// -- fileinfo
(COMPONENT_LIST_FILEINFO, &MSG_KEY_ENTER)
| (COMPONENT_LIST_FILEINFO, &MSG_KEY_ESC) => {
(COMPONENT_LIST_FILEINFO, key) | (COMPONENT_LIST_FILEINFO, key)
if key == &MSG_KEY_ENTER || key == &MSG_KEY_ESC =>
{
self.umount_file_info();
None
}
(COMPONENT_LIST_FILEINFO, _) => None,
// -- delete
(COMPONENT_RADIO_DELETE, &MSG_KEY_ESC)
| (COMPONENT_RADIO_DELETE, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => {
(COMPONENT_RADIO_DELETE, key)
if key == &MSG_KEY_ESC
|| key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) =>
{
self.umount_radio_delete();
None
}
@@ -631,8 +654,10 @@ impl Update for FileTransferActivity {
}
(COMPONENT_RADIO_DELETE, _) => None,
// -- disconnect
(COMPONENT_RADIO_DISCONNECT, &MSG_KEY_ESC)
| (COMPONENT_RADIO_DISCONNECT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => {
(COMPONENT_RADIO_DISCONNECT, key)
if key == &MSG_KEY_ESC
|| key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) =>
{
self.umount_disconnect();
None
}
@@ -643,8 +668,10 @@ impl Update for FileTransferActivity {
}
(COMPONENT_RADIO_DISCONNECT, _) => None,
// -- quit
(COMPONENT_RADIO_QUIT, &MSG_KEY_ESC)
| (COMPONENT_RADIO_QUIT, Msg::OnSubmit(Payload::One(Value::Usize(1)))) => {
(COMPONENT_RADIO_QUIT, key)
if key == &MSG_KEY_ESC
|| key == &Msg::OnSubmit(Payload::One(Value::Usize(1))) =>
{
self.umount_quit();
None
}
@@ -655,18 +682,21 @@ impl Update for FileTransferActivity {
}
(COMPONENT_RADIO_QUIT, _) => None,
// -- sorting
(COMPONENT_RADIO_SORTING, &MSG_KEY_ESC)
| (COMPONENT_RADIO_SORTING, Msg::OnSubmit(_)) => {
(COMPONENT_RADIO_SORTING, key) if key == &MSG_KEY_ESC => {
self.umount_file_sorting();
None
}
(COMPONENT_RADIO_SORTING, Msg::OnSubmit(_)) => {
self.umount_file_sorting();
None
}
(COMPONENT_RADIO_SORTING, Msg::OnChange(Payload::One(Value::Usize(mode)))) => {
// Get sorting mode
let sorting: FileSorting = match mode {
1 => FileSorting::ByModifyTime,
2 => FileSorting::ByCreationTime,
3 => FileSorting::BySize,
_ => FileSorting::ByName,
1 => FileSorting::ModifyTime,
2 => FileSorting::CreationTime,
3 => FileSorting::Size,
_ => FileSorting::Name,
};
match self.browser.tab() {
FileExplorerTab::Local => self.local_mut().sort_by(sorting),
@@ -688,25 +718,31 @@ impl Update for FileTransferActivity {
}
(COMPONENT_RADIO_SORTING, _) => None,
// -- error
(COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) => {
(COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
self.umount_error();
None
}
(COMPONENT_TEXT_ERROR, _) => None,
// -- fatal
(COMPONENT_TEXT_FATAL, &MSG_KEY_ESC) | (COMPONENT_TEXT_FATAL, &MSG_KEY_ENTER) => {
(COMPONENT_TEXT_FATAL, key) | (COMPONENT_TEXT_FATAL, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
self.exit_reason = Some(super::ExitReason::Disconnect);
None
}
(COMPONENT_TEXT_FATAL, _) => None,
// -- help
(COMPONENT_TEXT_HELP, &MSG_KEY_ESC) | (COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) => {
(COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
self.umount_help();
None
}
(COMPONENT_TEXT_HELP, _) => None,
// -- progress bar
(COMPONENT_PROGRESS_BAR_PARTIAL, &MSG_KEY_CTRL_C) => {
(COMPONENT_PROGRESS_BAR_PARTIAL, key) if key == &MSG_KEY_CTRL_C => {
// Set transfer aborted to True
self.transfer.abort();
None
@@ -752,7 +788,8 @@ impl FileTransferActivity {
.collect();
// Update
let props = FileListPropsBuilder::from(props)
.with_files(Some(hostname), files)
.with_files(files)
.with_title(hostname, Alignment::Left)
.build();
// Update
self.view.update(super::COMPONENT_EXPLORER_LOCAL, props)
@@ -790,7 +827,8 @@ impl FileTransferActivity {
.collect();
// Update
let props = FileListPropsBuilder::from(props)
.with_files(Some(hostname), files)
.with_files(files)
.with_title(hostname, Alignment::Left)
.build();
self.view.update(super::COMPONENT_EXPLORER_REMOTE, props)
}
@@ -823,7 +861,7 @@ impl FileTransferActivity {
)))
.add_col(TextSpan::from(" ["))
.add_col(
TextSpanBuilder::new(
TextSpan::new(
format!(
"{:5}",
match record.level {
@@ -834,16 +872,13 @@ impl FileTransferActivity {
)
.as_str(),
)
.with_foreground(fg)
.build(),
.fg(fg),
)
.add_col(TextSpan::from("]: "))
.add_col(TextSpan::from(record.msg.as_ref()));
}
let table = table.build();
let props = LogboxPropsBuilder::from(props)
.with_log(Some(String::from("Log")), table)
.build();
let props = LogboxPropsBuilder::from(props).with_log(table).build();
self.view.update(super::COMPONENT_LOG_BOX, props)
}
None => None,
@@ -852,9 +887,8 @@ impl FileTransferActivity {
pub(super) fn update_progress_bar(&mut self, filename: String) -> Option<(String, Msg)> {
if let Some(props) = self.view.get_props(COMPONENT_PROGRESS_BAR_FULL) {
let root_name: String = props.texts.title.as_deref().unwrap_or("").to_string();
let props = ProgressBarPropsBuilder::from(props)
.with_texts(Some(root_name), self.transfer.full.to_string())
.with_label(self.transfer.full.to_string())
.with_progress(self.transfer.full.calc_progress())
.build();
let _ = self.view.update(COMPONENT_PROGRESS_BAR_FULL, props);
@@ -862,7 +896,8 @@ impl FileTransferActivity {
match self.view.get_props(COMPONENT_PROGRESS_BAR_PARTIAL) {
Some(props) => {
let props = ProgressBarPropsBuilder::from(props)
.with_texts(Some(filename), self.transfer.partial.to_string())
.with_title(filename, Alignment::Center)
.with_label(self.transfer.partial.to_string())
.with_progress(self.transfer.partial.calc_progress())
.build();
self.view.update(COMPONENT_PROGRESS_BAR_PARTIAL, props)
@@ -889,7 +924,6 @@ impl FileTransferActivity {
match self.view.get_props(COMPONENT_EXPLORER_FIND) {
None => None,
Some(props) => {
let title: String = props.texts.title.clone().unwrap_or_default();
// Prepare files
let files: Vec<String> = self
.found()
@@ -897,9 +931,7 @@ impl FileTransferActivity {
.iter_files()
.map(|x: &FsEntry| self.found().unwrap().fmt_file(x))
.collect();
let props = FileListPropsBuilder::from(props)
.with_files(Some(title), files)
.build();
let props = FileListPropsBuilder::from(props).with_files(files).build();
self.view.update(COMPONENT_EXPLORER_FIND, props)
}
}

View File

@@ -32,7 +32,6 @@ use crate::fs::FsEntry;
use crate::ui::components::{
file_list::{FileList, FileListPropsBuilder},
logbox::{LogBox, LogboxPropsBuilder},
msgbox::{MsgBox, MsgBoxPropsBuilder},
};
use crate::ui::store::Store;
use crate::utils::fmt::fmt_time;
@@ -40,15 +39,16 @@ use crate::utils::ui::draw_area_in;
// Ext
use bytesize::ByteSize;
use std::path::PathBuf;
use tuirealm::components::{
use tui_realm_stdlib::{
input::{Input, InputPropsBuilder},
list::{List, ListPropsBuilder},
paragraph::{Paragraph, ParagraphPropsBuilder},
progress_bar::{ProgressBar, ProgressBarPropsBuilder},
radio::{Radio, RadioPropsBuilder},
scrolltable::{ScrollTablePropsBuilder, Scrolltable},
span::{Span, SpanPropsBuilder},
table::{Table, TablePropsBuilder},
};
use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder};
use tuirealm::props::{Alignment, PropsBuilder, TableBuilder, TextSpan};
use tuirealm::tui::{
layout::{Constraint, Direction, Layout},
style::Color,
@@ -101,6 +101,7 @@ impl FileTransferActivity {
super::COMPONENT_LOG_BOX,
Box::new(LogBox::new(
LogboxPropsBuilder::default()
.with_title("Log", Alignment::Left)
.with_background(log_background)
.with_borders(Borders::ALL, BorderType::Plain, log_panel)
.build(),
@@ -383,12 +384,13 @@ impl FileTransferActivity {
let error_color = self.theme().misc_error_dialog;
self.view.mount(
super::COMPONENT_TEXT_ERROR,
Box::new(MsgBox::new(
MsgBoxPropsBuilder::default()
Box::new(Paragraph::new(
ParagraphPropsBuilder::default()
.with_foreground(error_color)
.with_borders(Borders::ALL, BorderType::Rounded, error_color)
.bold()
.with_texts(None, vec![TextSpan::from(text)])
.with_text_alignment(Alignment::Center)
.with_texts(vec![TextSpan::from(text)])
.build(),
)),
);
@@ -408,12 +410,13 @@ impl FileTransferActivity {
let error_color = self.theme().misc_error_dialog;
self.view.mount(
super::COMPONENT_TEXT_FATAL,
Box::new(MsgBox::new(
MsgBoxPropsBuilder::default()
Box::new(Paragraph::new(
ParagraphPropsBuilder::default()
.with_foreground(error_color)
.with_borders(Borders::ALL, BorderType::Rounded, error_color)
.bold()
.with_texts(None, vec![TextSpan::from(text)])
.with_text_alignment(Alignment::Center)
.with_texts(vec![TextSpan::from(text)])
.build(),
)),
);
@@ -422,28 +425,26 @@ impl FileTransferActivity {
}
pub(super) fn mount_wait(&mut self, text: &str) {
self.mount_wait_ex(text, false, Color::Reset);
self.mount_wait_ex(text, Color::Reset);
}
pub(super) fn mount_blocking_wait(&mut self, text: &str) {
self.mount_wait_ex(text, true, Color::Reset);
self.mount_wait_ex(text, Color::Reset);
self.view();
}
fn mount_wait_ex(&mut self, text: &str, blink: bool, color: Color) {
fn mount_wait_ex(&mut self, text: &str, color: Color) {
// Mount
let mut builder: MsgBoxPropsBuilder = MsgBoxPropsBuilder::default();
let mut builder: ParagraphPropsBuilder = ParagraphPropsBuilder::default();
builder
.with_foreground(color)
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
.bold()
.with_texts(None, vec![TextSpan::from(text)]);
if blink {
builder.blink();
}
.with_text_alignment(Alignment::Center)
.with_texts(vec![TextSpan::from(text)]);
self.view.mount(
super::COMPONENT_TEXT_WAIT,
Box::new(MsgBox::new(builder.build())),
Box::new(Paragraph::new(builder.build())),
);
// Give focus to info
self.view.active(super::COMPONENT_TEXT_WAIT);
@@ -466,10 +467,9 @@ impl FileTransferActivity {
.with_color(quit_color)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, quit_color)
.with_options(
Some(String::from("Are you sure you want to quit?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Are you sure you want to quit?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.rewind(true)
.build(),
)),
);
@@ -496,10 +496,9 @@ impl FileTransferActivity {
.with_color(quit_color)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, quit_color)
.with_options(
Some(String::from("Are you sure you want to disconnect?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Are you sure you want to disconnect?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.rewind(true)
.build(),
)),
);
@@ -521,7 +520,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Copy file(s) to…"))
.with_label("Copy file(s) to…", Alignment::Center)
.build(),
)),
);
@@ -540,7 +539,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Execute command"))
.with_label("Execute command", Alignment::Center)
.build(),
)),
);
@@ -570,7 +569,10 @@ impl FileTransferActivity {
super::COMPONENT_EXPLORER_FIND,
Box::new(FileList::new(
FileListPropsBuilder::default()
.with_files(Some(format!("Search results for \"{}\"", search)), vec![])
.with_title(
format!("Search results for \"{}\"", search),
Alignment::Left,
)
.with_borders(Borders::ALL, BorderType::Plain, hg)
.with_highlight_color(hg)
.with_background(bg)
@@ -594,7 +596,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Search files by name"))
.with_label("Search files by name", Alignment::Center)
.build(),
)),
);
@@ -615,7 +617,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Change working directory"))
.with_label("Change working directory", Alignment::Center)
.build(),
)),
);
@@ -634,7 +636,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Insert directory name"))
.with_label("Insert directory name", Alignment::Center)
.build(),
)),
);
@@ -653,7 +655,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("New file name"))
.with_label("New file name", Alignment::Center)
.build(),
)),
);
@@ -672,7 +674,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Open file with…"))
.with_label("Open file with…", Alignment::Center)
.build(),
)),
);
@@ -691,7 +693,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Move file(s) to…"))
.with_label("Move file(s) to…", Alignment::Center)
.build(),
)),
);
@@ -710,7 +712,7 @@ impl FileTransferActivity {
InputPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, input_color)
.with_foreground(input_color)
.with_label(String::from("Save as…"))
.with_label("Save as…", Alignment::Center)
.build(),
)),
);
@@ -735,7 +737,7 @@ impl FileTransferActivity {
BorderType::Rounded,
Color::Reset,
)
.with_texts(Some(root_name), String::new())
.with_title(root_name, Alignment::Center)
.build(),
)),
);
@@ -750,7 +752,7 @@ impl FileTransferActivity {
BorderType::Rounded,
Color::Reset,
)
.with_texts(Some(String::from("Please wait")), String::new())
.with_title("Please wait", Alignment::Center)
.build(),
)),
);
@@ -770,10 +772,10 @@ impl FileTransferActivity {
_ => panic!("You can't mount file sorting when in found result"),
};
let index: usize = match sorting {
FileSorting::ByCreationTime => 2,
FileSorting::ByModifyTime => 1,
FileSorting::ByName => 0,
FileSorting::BySize => 3,
FileSorting::CreationTime => 2,
FileSorting::ModifyTime => 1,
FileSorting::Name => 0,
FileSorting::Size => 3,
};
self.view.mount(
super::COMPONENT_RADIO_SORTING,
@@ -782,15 +784,13 @@ impl FileTransferActivity {
.with_color(sorting_color)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, sorting_color)
.with_options(
Some(String::from("Sort files by")),
vec![
String::from("Name"),
String::from("Modify time"),
String::from("Creation time"),
String::from("Size"),
],
)
.with_title("Sort files by", Alignment::Center)
.with_options(&[
String::from("Name"),
String::from("Modify time"),
String::from("Creation time"),
String::from("Size"),
])
.with_value(index)
.build(),
)),
@@ -811,11 +811,10 @@ impl FileTransferActivity {
.with_color(warn_color)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Plain, warn_color)
.with_options(
Some(String::from("Delete file")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Delete file", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.with_value(1)
.rewind(true)
.build(),
)),
);
@@ -841,54 +840,35 @@ impl FileTransferActivity {
None => format!("{}", file.get_abs_path().display()),
};
// Make texts
texts.add_col(TextSpan::from("Path: ")).add_col(
TextSpanBuilder::new(path.as_str())
.with_foreground(Color::Yellow)
.build(),
);
texts
.add_col(TextSpan::from("Path: "))
.add_col(TextSpan::new(path.as_str()).fg(Color::Yellow));
if let Some(filetype) = file.get_ftype() {
texts
.add_row()
.add_col(TextSpan::from("File type: "))
.add_col(
TextSpanBuilder::new(filetype.as_str())
.with_foreground(Color::LightGreen)
.build(),
);
.add_col(TextSpan::new(filetype.as_str()).fg(Color::LightGreen));
}
let (bsize, size): (ByteSize, usize) = (ByteSize(file.get_size() as u64), file.get_size());
texts.add_row().add_col(TextSpan::from("Size: ")).add_col(
TextSpanBuilder::new(format!("{} ({})", bsize, size).as_str())
.with_foreground(Color::Cyan)
.build(),
);
texts
.add_row()
.add_col(TextSpan::from("Size: "))
.add_col(TextSpan::new(format!("{} ({})", bsize, size).as_str()).fg(Color::Cyan));
let ctime: String = fmt_time(file.get_creation_time(), "%b %d %Y %H:%M:%S");
let atime: String = fmt_time(file.get_last_access_time(), "%b %d %Y %H:%M:%S");
let mtime: String = fmt_time(file.get_creation_time(), "%b %d %Y %H:%M:%S");
texts
.add_row()
.add_col(TextSpan::from("Creation time: "))
.add_col(
TextSpanBuilder::new(ctime.as_str())
.with_foreground(Color::LightGreen)
.build(),
);
.add_col(TextSpan::new(ctime.as_str()).fg(Color::LightGreen));
texts
.add_row()
.add_col(TextSpan::from("Last modified time: "))
.add_col(
TextSpanBuilder::new(mtime.as_str())
.with_foreground(Color::LightBlue)
.build(),
);
.add_col(TextSpan::new(mtime.as_str()).fg(Color::LightBlue));
texts
.add_row()
.add_col(TextSpan::from("Last access time: "))
.add_col(
TextSpanBuilder::new(atime.as_str())
.with_foreground(Color::LightRed)
.build(),
);
.add_col(TextSpan::new(atime.as_str()).fg(Color::LightRed));
// User
#[cfg(target_family = "unix")]
let username: String = match file.get_user() {
@@ -911,22 +891,21 @@ impl FileTransferActivity {
};
#[cfg(target_os = "windows")]
let group: String = format!("{}", file.get_group().unwrap_or(0));
texts.add_row().add_col(TextSpan::from("User: ")).add_col(
TextSpanBuilder::new(username.as_str())
.with_foreground(Color::LightYellow)
.build(),
);
texts.add_row().add_col(TextSpan::from("Group: ")).add_col(
TextSpanBuilder::new(group.as_str())
.with_foreground(Color::Blue)
.build(),
);
texts
.add_row()
.add_col(TextSpan::from("User: "))
.add_col(TextSpan::new(username.as_str()).fg(Color::LightYellow));
texts
.add_row()
.add_col(TextSpan::from("Group: "))
.add_col(TextSpan::new(group.as_str()).fg(Color::Blue));
self.view.mount(
super::COMPONENT_LIST_FILEINFO,
Box::new(Table::new(
TablePropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
.with_table(Some(file.get_name().to_string()), texts.build())
.with_title(file.get_name(), Alignment::Left)
.with_table(texts.build())
.build(),
)),
);
@@ -941,22 +920,16 @@ impl FileTransferActivity {
let sorting_color = self.theme().transfer_status_sorting;
let hidden_color = self.theme().transfer_status_hidden;
let local_bar_spans: Vec<TextSpan> = vec![
TextSpanBuilder::new("File sorting: ")
.with_foreground(sorting_color)
.build(),
TextSpanBuilder::new(Self::get_file_sorting_str(self.local().get_file_sorting()))
.with_foreground(sorting_color)
.reversed()
.build(),
TextSpanBuilder::new(" Hidden files: ")
.with_foreground(hidden_color)
.build(),
TextSpanBuilder::new(Self::get_hidden_files_str(
TextSpan::new("File sorting: ").fg(sorting_color),
TextSpan::new(Self::get_file_sorting_str(self.local().get_file_sorting()))
.fg(sorting_color)
.reversed(),
TextSpan::new(" Hidden files: ").fg(hidden_color),
TextSpan::new(Self::get_hidden_files_str(
self.local().hidden_files_visible(),
))
.with_foreground(hidden_color)
.reversed()
.build(),
.fg(hidden_color)
.reversed(),
];
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_LOCAL) {
self.view.update(
@@ -973,32 +946,23 @@ impl FileTransferActivity {
let hidden_color = self.theme().transfer_status_hidden;
let sync_color = self.theme().transfer_status_sync_browsing;
let remote_bar_spans: Vec<TextSpan> = vec![
TextSpanBuilder::new("File sorting: ")
.with_foreground(sorting_color)
.build(),
TextSpanBuilder::new(Self::get_file_sorting_str(self.remote().get_file_sorting()))
.with_foreground(sorting_color)
.reversed()
.build(),
TextSpanBuilder::new(" Hidden files: ")
.with_foreground(hidden_color)
.build(),
TextSpanBuilder::new(Self::get_hidden_files_str(
TextSpan::new("File sorting: ").fg(sorting_color),
TextSpan::new(Self::get_file_sorting_str(self.remote().get_file_sorting()))
.fg(sorting_color)
.reversed(),
TextSpan::new(" Hidden files: ").fg(hidden_color),
TextSpan::new(Self::get_hidden_files_str(
self.remote().hidden_files_visible(),
))
.with_foreground(hidden_color)
.reversed()
.build(),
TextSpanBuilder::new(" Sync Browsing: ")
.with_foreground(sync_color)
.build(),
TextSpanBuilder::new(match self.browser.sync_browsing {
.fg(hidden_color)
.reversed(),
TextSpan::new(" Sync Browsing: ").fg(sync_color),
TextSpan::new(match self.browser.sync_browsing {
true => "ON ",
false => "OFF",
})
.with_foreground(sync_color)
.reversed()
.build(),
.fg(sync_color)
.reversed(),
];
if let Some(props) = self.view.get_props(super::COMPONENT_SPAN_STATUS_BAR_REMOTE) {
self.view.update(
@@ -1017,253 +981,109 @@ impl FileTransferActivity {
let key_color = self.theme().misc_keys;
self.view.mount(
super::COMPONENT_TEXT_HELP,
Box::new(Scrolltable::new(
ScrollTablePropsBuilder::default()
Box::new(List::new(
ListPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
.with_highlighted_str(Some("?"))
.with_max_scroll_step(8)
.bold()
.with_table(
Some(String::from("Help")),
.scrollable(true)
.with_title("Help", Alignment::Center)
.with_rows(
TableBuilder::default()
.add_col(
TextSpanBuilder::new("<ESC>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<ESC>").bold().fg(key_color))
.add_col(TextSpan::from(" Disconnect"))
.add_row()
.add_col(
TextSpanBuilder::new("<TAB>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<TAB>").bold().fg(key_color))
.add_col(TextSpan::from(
" Switch between explorer and logs",
))
.add_row()
.add_col(
TextSpanBuilder::new("<BACKSPACE>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<BACKSPACE>").bold().fg(key_color))
.add_col(TextSpan::from(" Go to previous directory"))
.add_row()
.add_col(
TextSpanBuilder::new("<RIGHT/LEFT>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(key_color))
.add_col(TextSpan::from(" Change explorer tab"))
.add_row()
.add_col(
TextSpanBuilder::new("<UP/DOWN>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(key_color))
.add_col(TextSpan::from(" Move up/down in list"))
.add_row()
.add_col(
TextSpanBuilder::new("<ENTER>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<ENTER>").bold().fg(key_color))
.add_col(TextSpan::from(" Enter directory"))
.add_row()
.add_col(
TextSpanBuilder::new("<SPACE>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<SPACE>").bold().fg(key_color))
.add_col(TextSpan::from(" Upload/Download file"))
.add_row()
.add_col(
TextSpanBuilder::new("<A>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<A>").bold().fg(key_color))
.add_col(TextSpan::from(" Toggle hidden files"))
.add_row()
.add_col(
TextSpanBuilder::new("<B>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<B>").bold().fg(key_color))
.add_col(TextSpan::from(" Change file sorting mode"))
.add_row()
.add_col(
TextSpanBuilder::new("<C>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<C>").bold().fg(key_color))
.add_col(TextSpan::from(" Copy"))
.add_row()
.add_col(
TextSpanBuilder::new("<D>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<D>").bold().fg(key_color))
.add_col(TextSpan::from(" Make directory"))
.add_row()
.add_col(
TextSpanBuilder::new("<G>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<G>").bold().fg(key_color))
.add_col(TextSpan::from(" Go to path"))
.add_row()
.add_col(
TextSpanBuilder::new("<H>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<H>").bold().fg(key_color))
.add_col(TextSpan::from(" Show help"))
.add_row()
.add_col(
TextSpanBuilder::new("<I>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<I>").bold().fg(key_color))
.add_col(TextSpan::from(" Show info about selected file"))
.add_row()
.add_col(
TextSpanBuilder::new("<L>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<L>").bold().fg(key_color))
.add_col(TextSpan::from(" Reload directory content"))
.add_row()
.add_col(
TextSpanBuilder::new("<M>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<M>").bold().fg(key_color))
.add_col(TextSpan::from(" Select file"))
.add_row()
.add_col(
TextSpanBuilder::new("<N>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<N>").bold().fg(key_color))
.add_col(TextSpan::from(" Create new file"))
.add_row()
.add_col(
TextSpanBuilder::new("<O>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<O>").bold().fg(key_color))
.add_col(TextSpan::from(
" Open text file with preferred editor",
))
.add_row()
.add_col(
TextSpanBuilder::new("<Q>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<Q>").bold().fg(key_color))
.add_col(TextSpan::from(" Quit termscp"))
.add_row()
.add_col(
TextSpanBuilder::new("<R>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<R>").bold().fg(key_color))
.add_col(TextSpan::from(" Rename file"))
.add_row()
.add_col(
TextSpanBuilder::new("<S>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<S>").bold().fg(key_color))
.add_col(TextSpan::from(" Save file as"))
.add_row()
.add_col(
TextSpanBuilder::new("<U>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<U>").bold().fg(key_color))
.add_col(TextSpan::from(" Go to parent directory"))
.add_row()
.add_col(
TextSpanBuilder::new("<V>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<V>").bold().fg(key_color))
.add_col(TextSpan::from(
" Open file with default application for file type",
))
.add_row()
.add_col(
TextSpanBuilder::new("<W>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<W>").bold().fg(key_color))
.add_col(TextSpan::from(
" Open file with specified application",
))
.add_row()
.add_col(
TextSpanBuilder::new("<X>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<X>").bold().fg(key_color))
.add_col(TextSpan::from(" Execute shell command"))
.add_row()
.add_col(
TextSpanBuilder::new("<Y>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<Y>").bold().fg(key_color))
.add_col(TextSpan::from(" Toggle synchronized browsing"))
.add_row()
.add_col(
TextSpanBuilder::new("<DEL|E>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<DEL|E>").bold().fg(key_color))
.add_col(TextSpan::from(" Delete selected file"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+A>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<CTRL+A>").bold().fg(key_color))
.add_col(TextSpan::from(" Select all files"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+C>")
.bold()
.with_foreground(key_color)
.build(),
)
.add_col(TextSpan::new("<CTRL+C>").bold().fg(key_color))
.add_col(TextSpan::from(" Interrupt file transfer"))
.build(),
)
@@ -1280,10 +1100,10 @@ impl FileTransferActivity {
fn get_file_sorting_str(mode: FileSorting) -> &'static str {
match mode {
FileSorting::ByName => "By name",
FileSorting::ByCreationTime => "By creation time",
FileSorting::ByModifyTime => "By modify time",
FileSorting::BySize => "By size",
FileSorting::Name => "By name",
FileSorting::CreationTime => "By creation time",
FileSorting::ModifyTime => "By modify time",
FileSorting::Size => "By size",
}
}

View File

@@ -152,7 +152,7 @@ impl SetupActivity {
}
fn config(&self) -> &ConfigClient {
&self.context().config()
self.context().config()
}
fn config_mut(&mut self) -> &mut ConfigClient {

View File

@@ -74,65 +74,67 @@ impl SetupActivity {
None => None,
Some(msg) => match msg {
// Input field <DOWN>
(COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_TEXT_EDITOR, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL);
None
}
(COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_DOWN) => {
(COMPONENT_RADIO_DEFAULT_PROTOCOL, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_RADIO_HIDDEN_FILES);
None
}
(COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_DOWN) => {
(COMPONENT_RADIO_HIDDEN_FILES, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_RADIO_UPDATES);
None
}
(COMPONENT_RADIO_UPDATES, &MSG_KEY_DOWN) => {
(COMPONENT_RADIO_UPDATES, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_RADIO_GROUP_DIRS);
None
}
(COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_DOWN) => {
(COMPONENT_RADIO_GROUP_DIRS, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT);
None
}
(COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_LOCAL_FILE_FMT, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT);
None
}
(COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_REMOTE_FILE_FMT, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_INPUT_TEXT_EDITOR);
None
}
// Input field <UP>
(COMPONENT_INPUT_REMOTE_FILE_FMT, &MSG_KEY_UP) => {
(COMPONENT_INPUT_REMOTE_FILE_FMT, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_INPUT_LOCAL_FILE_FMT);
None
}
(COMPONENT_INPUT_LOCAL_FILE_FMT, &MSG_KEY_UP) => {
(COMPONENT_INPUT_LOCAL_FILE_FMT, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_RADIO_GROUP_DIRS);
None
}
(COMPONENT_RADIO_GROUP_DIRS, &MSG_KEY_UP) => {
(COMPONENT_RADIO_GROUP_DIRS, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_RADIO_UPDATES);
None
}
(COMPONENT_RADIO_UPDATES, &MSG_KEY_UP) => {
(COMPONENT_RADIO_UPDATES, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_RADIO_HIDDEN_FILES);
None
}
(COMPONENT_RADIO_HIDDEN_FILES, &MSG_KEY_UP) => {
(COMPONENT_RADIO_HIDDEN_FILES, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_RADIO_DEFAULT_PROTOCOL);
None
}
(COMPONENT_RADIO_DEFAULT_PROTOCOL, &MSG_KEY_UP) => {
(COMPONENT_RADIO_DEFAULT_PROTOCOL, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_INPUT_TEXT_EDITOR);
None
}
(COMPONENT_INPUT_TEXT_EDITOR, &MSG_KEY_UP) => {
(COMPONENT_INPUT_TEXT_EDITOR, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_INPUT_REMOTE_FILE_FMT);
None
}
// Error <ENTER> or <ESC>
(COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
// Umount text error
self.umount_error();
None
@@ -161,7 +163,9 @@ impl SetupActivity {
}
(COMPONENT_RADIO_QUIT, _) => None,
// Close help
(COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
// Umount help
self.umount_help();
None
@@ -189,12 +193,12 @@ impl SetupActivity {
None
}
// <CTRL+H> Show help
(_, &MSG_KEY_CTRL_H) => {
(_, key) if key == &MSG_KEY_CTRL_H => {
// Show help
self.mount_help();
None
}
(_, &MSG_KEY_TAB) => {
(_, key) if key == &MSG_KEY_TAB => {
// Change view
if let Err(err) = self.action_change_tab(ViewLayout::SshKeys) {
self.mount_error(err.as_str());
@@ -202,7 +206,7 @@ impl SetupActivity {
None
}
// <CTRL+R> Revert changes
(_, &MSG_KEY_CTRL_R) => {
(_, key) if key == &MSG_KEY_CTRL_R => {
// Revert changes
if let Err(err) = self.action_reset_config() {
self.mount_error(err.as_str());
@@ -210,13 +214,13 @@ impl SetupActivity {
None
}
// <CTRL+S> Save
(_, &MSG_KEY_CTRL_S) => {
(_, key) if key == &MSG_KEY_CTRL_S => {
// Show save
self.mount_save_popup();
None
}
// <ESC>
(_, &MSG_KEY_ESC) => {
(_, key) if key == &MSG_KEY_ESC => {
self.action_on_esc();
None
}
@@ -232,7 +236,9 @@ impl SetupActivity {
None => None,
Some(msg) => match msg {
// Error <ENTER> or <ESC>
(COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
// Umount text error
self.umount_error();
None
@@ -261,7 +267,9 @@ impl SetupActivity {
}
(COMPONENT_RADIO_QUIT, _) => None,
// Close help
(COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
// Umount help
self.umount_help();
None
@@ -300,28 +308,30 @@ impl SetupActivity {
(COMPONENT_RADIO_SAVE, _) => None,
// Edit SSH Key
// <CTRL+H> Show help
(_, &MSG_KEY_CTRL_H) => {
(_, key) if key == &MSG_KEY_CTRL_H => {
// Show help
self.mount_help();
None
}
// New key <DOWN>
(COMPONENT_INPUT_SSH_HOST, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_SSH_HOST, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_INPUT_SSH_USERNAME);
None
}
(COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_DOWN) => {
(COMPONENT_INPUT_SSH_USERNAME, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_INPUT_SSH_HOST);
None
}
// New key <UP>
(COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_UP)
| (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_TAB) => {
(COMPONENT_INPUT_SSH_USERNAME, key) | (COMPONENT_INPUT_SSH_USERNAME, key)
if key == &MSG_KEY_UP || key == &MSG_KEY_TAB =>
{
self.view.active(COMPONENT_INPUT_SSH_HOST);
None
}
(COMPONENT_INPUT_SSH_HOST, &MSG_KEY_UP)
| (COMPONENT_INPUT_SSH_HOST, &MSG_KEY_TAB) => {
(COMPONENT_INPUT_SSH_HOST, key) | (COMPONENT_INPUT_SSH_HOST, key)
if key == &MSG_KEY_UP || key == &MSG_KEY_TAB =>
{
self.view.active(COMPONENT_INPUT_SSH_USERNAME);
None
}
@@ -335,14 +345,15 @@ impl SetupActivity {
None
}
// New key <ESC>
(COMPONENT_INPUT_SSH_HOST, &MSG_KEY_ESC)
| (COMPONENT_INPUT_SSH_USERNAME, &MSG_KEY_ESC) => {
(COMPONENT_INPUT_SSH_HOST, key) | (COMPONENT_INPUT_SSH_USERNAME, key)
if key == &MSG_KEY_ESC =>
{
// Umount new ssh key
self.umount_new_ssh_key();
None
}
// <CTRL+N> New key
(COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_N) => {
(COMPONENT_LIST_SSH_KEYS, key) if key == &MSG_KEY_CTRL_N => {
// Show new key popup
self.mount_new_ssh_key();
None
@@ -356,13 +367,14 @@ impl SetupActivity {
None
}
// <DEL | CTRL+E> Show delete
(COMPONENT_LIST_SSH_KEYS, &MSG_KEY_CTRL_E)
| (COMPONENT_LIST_SSH_KEYS, &MSG_KEY_DEL) => {
(COMPONENT_LIST_SSH_KEYS, key) | (COMPONENT_LIST_SSH_KEYS, key)
if key == &MSG_KEY_CTRL_E || key == &MSG_KEY_DEL =>
{
// Show delete key
self.mount_del_ssh_key();
None
}
(_, &MSG_KEY_TAB) => {
(_, key) if key == &MSG_KEY_TAB => {
// Change view
if let Err(err) = self.action_change_tab(ViewLayout::Theme) {
self.mount_error(err.as_str());
@@ -370,7 +382,7 @@ impl SetupActivity {
None
}
// <CTRL+R> Revert changes
(_, &MSG_KEY_CTRL_R) => {
(_, key) if key == &MSG_KEY_CTRL_R => {
// Revert changes
if let Err(err) = self.action_reset_config() {
self.mount_error(err.as_str());
@@ -378,13 +390,13 @@ impl SetupActivity {
None
}
// <CTRL+S> Save
(_, &MSG_KEY_CTRL_S) => {
(_, key) if key == &MSG_KEY_CTRL_S => {
// Show save
self.mount_save_popup();
None
}
// <ESC>
(_, &MSG_KEY_ESC) => {
(_, key) if key == &MSG_KEY_ESC => {
self.action_on_esc();
None
}
@@ -400,217 +412,217 @@ impl SetupActivity {
None => None,
Some(msg) => match msg {
// Input fields
(COMPONENT_COLOR_AUTH_PROTOCOL, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_AUTH_PROTOCOL, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_AUTH_ADDR);
None
}
(COMPONENT_COLOR_AUTH_ADDR, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_AUTH_ADDR, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_AUTH_PORT);
None
}
(COMPONENT_COLOR_AUTH_PORT, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_AUTH_PORT, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_AUTH_USERNAME);
None
}
(COMPONENT_COLOR_AUTH_USERNAME, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_AUTH_USERNAME, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_AUTH_PASSWORD);
None
}
(COMPONENT_COLOR_AUTH_PASSWORD, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_AUTH_PASSWORD, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_AUTH_BOOKMARKS);
None
}
(COMPONENT_COLOR_AUTH_BOOKMARKS, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_AUTH_BOOKMARKS, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_AUTH_RECENTS);
None
}
(COMPONENT_COLOR_AUTH_RECENTS, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_AUTH_RECENTS, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_MISC_ERROR);
None
}
(COMPONENT_COLOR_MISC_ERROR, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_MISC_ERROR, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_MISC_INPUT);
None
}
(COMPONENT_COLOR_MISC_INPUT, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_MISC_INPUT, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_MISC_KEYS);
None
}
(COMPONENT_COLOR_MISC_KEYS, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_MISC_KEYS, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_MISC_QUIT);
None
}
(COMPONENT_COLOR_MISC_QUIT, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_MISC_QUIT, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_MISC_SAVE);
None
}
(COMPONENT_COLOR_MISC_SAVE, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_MISC_SAVE, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_MISC_WARN);
None
}
(COMPONENT_COLOR_MISC_WARN, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_MISC_WARN, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, key) if key == &MSG_KEY_DOWN => {
self.view
.active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, key) if key == &MSG_KEY_DOWN => {
self.view
.active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, key) if key == &MSG_KEY_DOWN => {
self.view
.active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL);
None
}
(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL);
None
}
(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_LOG_BG);
None
}
(COMPONENT_COLOR_TRANSFER_LOG_BG, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_LOG_BG, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_LOG_WIN);
None
}
(COMPONENT_COLOR_TRANSFER_LOG_WIN, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_LOG_WIN, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SORTING);
None
}
(COMPONENT_COLOR_TRANSFER_STATUS_SORTING, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_STATUS_SORTING, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN);
None
}
(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SYNC);
None
}
(COMPONENT_COLOR_TRANSFER_STATUS_SYNC, &MSG_KEY_DOWN) => {
(COMPONENT_COLOR_TRANSFER_STATUS_SYNC, key) if key == &MSG_KEY_DOWN => {
self.view.active(COMPONENT_COLOR_AUTH_PROTOCOL);
None
}
(COMPONENT_COLOR_AUTH_PROTOCOL, &MSG_KEY_UP) => {
(COMPONENT_COLOR_AUTH_PROTOCOL, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SYNC);
None
}
(COMPONENT_COLOR_AUTH_ADDR, &MSG_KEY_UP) => {
(COMPONENT_COLOR_AUTH_ADDR, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_AUTH_PROTOCOL);
None
}
(COMPONENT_COLOR_AUTH_PORT, &MSG_KEY_UP) => {
(COMPONENT_COLOR_AUTH_PORT, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_AUTH_ADDR);
None
}
(COMPONENT_COLOR_AUTH_USERNAME, &MSG_KEY_UP) => {
(COMPONENT_COLOR_AUTH_USERNAME, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_AUTH_PORT);
None
}
(COMPONENT_COLOR_AUTH_PASSWORD, &MSG_KEY_UP) => {
(COMPONENT_COLOR_AUTH_PASSWORD, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_AUTH_USERNAME);
None
}
(COMPONENT_COLOR_AUTH_BOOKMARKS, &MSG_KEY_UP) => {
(COMPONENT_COLOR_AUTH_BOOKMARKS, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_AUTH_PASSWORD);
None
}
(COMPONENT_COLOR_AUTH_RECENTS, &MSG_KEY_UP) => {
(COMPONENT_COLOR_AUTH_RECENTS, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_AUTH_BOOKMARKS);
None
}
(COMPONENT_COLOR_MISC_ERROR, &MSG_KEY_UP) => {
(COMPONENT_COLOR_MISC_ERROR, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_AUTH_RECENTS);
None
}
(COMPONENT_COLOR_MISC_INPUT, &MSG_KEY_UP) => {
(COMPONENT_COLOR_MISC_INPUT, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_MISC_ERROR);
None
}
(COMPONENT_COLOR_MISC_KEYS, &MSG_KEY_UP) => {
(COMPONENT_COLOR_MISC_KEYS, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_MISC_INPUT);
None
}
(COMPONENT_COLOR_MISC_QUIT, &MSG_KEY_UP) => {
(COMPONENT_COLOR_MISC_QUIT, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_MISC_KEYS);
None
}
(COMPONENT_COLOR_MISC_SAVE, &MSG_KEY_UP) => {
(COMPONENT_COLOR_MISC_SAVE, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_MISC_QUIT);
None
}
(COMPONENT_COLOR_MISC_WARN, &MSG_KEY_UP) => {
(COMPONENT_COLOR_MISC_WARN, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_MISC_SAVE);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_MISC_WARN);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_BG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_FG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_EXPLORER_LOCAL_HG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG, key) if key == &MSG_KEY_UP => {
self.view
.active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_BG);
None
}
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG, key) if key == &MSG_KEY_UP => {
self.view
.active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_FG);
None
}
(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL, key) if key == &MSG_KEY_UP => {
self.view
.active(COMPONENT_COLOR_TRANSFER_EXPLORER_REMOTE_HG);
None
}
(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_FULL);
None
}
(COMPONENT_COLOR_TRANSFER_LOG_BG, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_LOG_BG, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_PROG_BAR_PARTIAL);
None
}
(COMPONENT_COLOR_TRANSFER_LOG_WIN, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_LOG_WIN, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_LOG_BG);
None
}
(COMPONENT_COLOR_TRANSFER_STATUS_SORTING, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_STATUS_SORTING, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_LOG_WIN);
None
}
(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_SORTING);
None
}
(COMPONENT_COLOR_TRANSFER_STATUS_SYNC, &MSG_KEY_UP) => {
(COMPONENT_COLOR_TRANSFER_STATUS_SYNC, key) if key == &MSG_KEY_UP => {
self.view.active(COMPONENT_COLOR_TRANSFER_STATUS_HIDDEN);
None
}
@@ -624,7 +636,9 @@ impl SetupActivity {
None
}
// Error <ENTER> or <ESC>
(COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) | (COMPONENT_TEXT_ERROR, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_ERROR, key) | (COMPONENT_TEXT_ERROR, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
// Umount text error
self.umount_error();
None
@@ -653,7 +667,9 @@ impl SetupActivity {
}
(COMPONENT_RADIO_QUIT, _) => None,
// Close help
(COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => {
(COMPONENT_TEXT_HELP, key) | (COMPONENT_TEXT_HELP, key)
if key == &MSG_KEY_ESC || key == &MSG_KEY_ENTER =>
{
// Umount help
self.umount_help();
None
@@ -676,12 +692,12 @@ impl SetupActivity {
(COMPONENT_RADIO_SAVE, _) => None,
// Edit SSH Key
// <CTRL+H> Show help
(_, &MSG_KEY_CTRL_H) => {
(_, key) if key == &MSG_KEY_CTRL_H => {
// Show help
self.mount_help();
None
}
(_, &MSG_KEY_TAB) => {
(_, key) if key == &MSG_KEY_TAB => {
// Change view
if let Err(err) = self.action_change_tab(ViewLayout::SetupForm) {
self.mount_error(err.as_str());
@@ -689,7 +705,7 @@ impl SetupActivity {
None
}
// <CTRL+R> Revert changes
(_, &MSG_KEY_CTRL_R) => {
(_, key) if key == &MSG_KEY_CTRL_R => {
// Revert changes
if let Err(err) = self.action_reset_theme() {
self.mount_error(err.as_str());
@@ -697,13 +713,13 @@ impl SetupActivity {
None
}
// <CTRL+S> Save
(_, &MSG_KEY_CTRL_S) => {
(_, key) if key == &MSG_KEY_CTRL_S => {
// Show save
self.mount_save_popup();
None
}
// <ESC>
(_, &MSG_KEY_ESC) => {
(_, key) if key == &MSG_KEY_ESC => {
self.action_on_esc();
None
}

View File

@@ -34,14 +34,14 @@ use super::*;
pub use setup::*;
pub use ssh_keys::*;
pub use theme::*;
// Locals
use crate::ui::components::msgbox::{MsgBox, MsgBoxPropsBuilder};
// Ext
use tuirealm::components::{
use tui_realm_stdlib::{
list::{List, ListPropsBuilder},
paragraph::{Paragraph, ParagraphPropsBuilder},
radio::{Radio, RadioPropsBuilder},
scrolltable::{ScrollTablePropsBuilder, Scrolltable},
span::{Span, SpanPropsBuilder},
};
use tuirealm::props::{PropsBuilder, TableBuilder, TextSpan, TextSpanBuilder};
use tuirealm::props::{Alignment, PropsBuilder, TableBuilder, TextSpan};
use tuirealm::tui::{
style::Color,
widgets::{BorderType, Borders},
@@ -79,12 +79,13 @@ impl SetupActivity {
// Mount
self.view.mount(
super::COMPONENT_TEXT_ERROR,
Box::new(MsgBox::new(
MsgBoxPropsBuilder::default()
Box::new(Paragraph::new(
ParagraphPropsBuilder::default()
.with_foreground(Color::Red)
.bold()
.with_borders(Borders::ALL, BorderType::Rounded, Color::Red)
.with_texts(None, vec![TextSpan::from(text)])
.with_texts(vec![TextSpan::from(text)])
.with_text_alignment(Alignment::Center)
.build(),
)),
);
@@ -110,16 +111,16 @@ impl SetupActivity {
.with_color(Color::LightRed)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed)
.with_options(
Some(String::from(
"There are unsaved changes! Save changes before leaving?",
)),
vec![
String::from("Save"),
String::from("Don't save"),
String::from("Cancel"),
],
.with_title(
"There are unsaved changes! Save changes before leaving?",
Alignment::Center,
)
.with_options(&[
String::from("Save"),
String::from("Don't save"),
String::from("Cancel"),
])
.rewind(true)
.build(),
)),
);
@@ -145,10 +146,9 @@ impl SetupActivity {
.with_color(Color::LightYellow)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow)
.with_options(
Some(String::from("Save changes?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Save changes?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.rewind(true)
.build(),
)),
);
@@ -163,91 +163,82 @@ impl SetupActivity {
self.view.umount(super::COMPONENT_RADIO_SAVE);
}
pub(self) fn mount_header_tab(&mut self, idx: usize) {
self.view.mount(
super::COMPONENT_RADIO_TAB,
Box::new(Radio::new(
RadioPropsBuilder::default()
.with_color(Color::LightYellow)
.with_inverted_color(Color::Black)
.with_borders(Borders::BOTTOM, BorderType::Thick, Color::White)
.with_options(&[
String::from("User Interface"),
String::from("SSH Keys"),
String::from("Theme"),
])
.with_value(idx)
.rewind(true)
.build(),
)),
);
}
pub(self) fn mount_footer(&mut self) {
self.view.mount(
super::COMPONENT_TEXT_FOOTER,
Box::new(Span::new(
SpanPropsBuilder::default()
.with_spans(vec![
TextSpan::new("Press ").bold(),
TextSpan::new("<CTRL+H>").bold().fg(Color::Cyan),
TextSpan::new(" to show keybindings").bold(),
])
.build(),
)),
);
}
/// ### mount_help
///
/// Mount help
pub(super) fn mount_help(&mut self) {
self.view.mount(
super::COMPONENT_TEXT_HELP,
Box::new(Scrolltable::new(
ScrollTablePropsBuilder::default()
Box::new(List::new(
ListPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, Color::White)
.with_highlighted_str(Some("?"))
.with_max_scroll_step(8)
.bold()
.with_table(
Some(String::from("Help")),
.with_title("Help", Alignment::Center)
.scrollable(true)
.with_rows(
TableBuilder::default()
.add_col(
TextSpanBuilder::new("<ESC>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<ESC>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Exit setup"))
.add_row()
.add_col(
TextSpanBuilder::new("<TAB>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<TAB>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Change setup page"))
.add_row()
.add_col(
TextSpanBuilder::new("<RIGHT/LEFT>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<RIGHT/LEFT>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Change cursor"))
.add_row()
.add_col(
TextSpanBuilder::new("<UP/DOWN>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<UP/DOWN>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Change input field"))
.add_row()
.add_col(
TextSpanBuilder::new("<ENTER>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<ENTER>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Select / Dismiss popup"))
.add_row()
.add_col(
TextSpanBuilder::new("<DEL|E>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<DEL|E>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Delete SSH key"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+N>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<CTRL+N>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" New SSH key"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+R>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<CTRL+R>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Revert changes"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+S>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::new("<CTRL+S>").bold().fg(Color::Cyan))
.add_col(TextSpan::from(" Save configuration"))
.build(),
)

View File

@@ -33,10 +33,9 @@ use crate::fs::explorer::GroupDirs;
use crate::utils::ui::draw_area_in;
// Ext
use std::path::PathBuf;
use tuirealm::components::{
use tui_realm_stdlib::{
input::{Input, InputPropsBuilder},
radio::{Radio, RadioPropsBuilder},
span::{Span, SpanPropsBuilder},
};
use tuirealm::tui::{
layout::{Constraint, Direction, Layout},
@@ -44,7 +43,7 @@ use tuirealm::tui::{
widgets::{BorderType, Borders, Clear},
};
use tuirealm::{
props::{PropsBuilder, TextSpanBuilder},
props::{Alignment, PropsBuilder},
Payload, Value, View,
};
@@ -59,41 +58,9 @@ impl SetupActivity {
self.view = View::init();
// Common stuff
// Radio tab
self.view.mount(
super::COMPONENT_RADIO_TAB,
Box::new(Radio::new(
RadioPropsBuilder::default()
.with_color(Color::LightYellow)
.with_inverted_color(Color::Black)
.with_borders(Borders::BOTTOM, BorderType::Thick, Color::White)
.with_options(
None,
vec![
String::from("User Interface"),
String::from("SSH Keys"),
String::from("Theme"),
],
)
.with_value(0)
.build(),
)),
);
self.mount_header_tab(0);
// Footer
self.view.mount(
super::COMPONENT_TEXT_FOOTER,
Box::new(Span::new(
SpanPropsBuilder::default()
.with_spans(vec![
TextSpanBuilder::new("Press ").bold().build(),
TextSpanBuilder::new("<CTRL+H>")
.bold()
.with_foreground(Color::Cyan)
.build(),
TextSpanBuilder::new(" to show keybindings").bold().build(),
])
.build(),
)),
);
self.mount_footer();
// Input fields
self.view.mount(
super::COMPONENT_INPUT_TEXT_EDITOR,
@@ -101,7 +68,7 @@ impl SetupActivity {
InputPropsBuilder::default()
.with_foreground(Color::LightGreen)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen)
.with_label(String::from("Text editor"))
.with_label("Text editor", Alignment::Left)
.build(),
)),
);
@@ -113,15 +80,14 @@ impl SetupActivity {
.with_color(Color::LightCyan)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightCyan)
.with_options(
Some(String::from("Default file transfer protocol")),
vec![
String::from("SFTP"),
String::from("SCP"),
String::from("FTP"),
String::from("FTPS"),
],
)
.with_title("Default file transfer protocol", Alignment::Left)
.with_options(&[
String::from("SFTP"),
String::from("SCP"),
String::from("FTP"),
String::from("FTPS"),
])
.rewind(true)
.build(),
)),
);
@@ -132,10 +98,9 @@ impl SetupActivity {
.with_color(Color::LightRed)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed)
.with_options(
Some(String::from("Show hidden files (by default)")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Show hidden files (by default)?", Alignment::Left)
.with_options(&[String::from("Yes"), String::from("No")])
.rewind(true)
.build(),
)),
);
@@ -146,10 +111,9 @@ impl SetupActivity {
.with_color(Color::LightYellow)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightYellow)
.with_options(
Some(String::from("Check for updates?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Check for updates?", Alignment::Left)
.with_options(&[String::from("Yes"), String::from("No")])
.rewind(true)
.build(),
)),
);
@@ -160,14 +124,13 @@ impl SetupActivity {
.with_color(Color::LightMagenta)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightMagenta)
.with_options(
Some(String::from("Group directories")),
vec![
String::from("Display first"),
String::from("Display Last"),
String::from("No"),
],
)
.with_title("Group directories", Alignment::Left)
.with_options(&[
String::from("Display first"),
String::from("Display Last"),
String::from("No"),
])
.rewind(true)
.build(),
)),
);
@@ -177,7 +140,7 @@ impl SetupActivity {
InputPropsBuilder::default()
.with_foreground(Color::LightBlue)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightBlue)
.with_label(String::from("File formatter syntax (local)"))
.with_label("File formatter syntax (local)", Alignment::Left)
.build(),
)),
);
@@ -187,7 +150,7 @@ impl SetupActivity {
InputPropsBuilder::default()
.with_foreground(Color::LightGreen)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightGreen)
.with_label(String::from("File formatter syntax (remote)"))
.with_label("File formatter syntax (remote)", Alignment::Left)
.build(),
)),
);

View File

@@ -31,10 +31,9 @@ use super::{Context, SetupActivity};
use crate::ui::components::bookmark_list::{BookmarkList, BookmarkListPropsBuilder};
use crate::utils::ui::draw_area_in;
// Ext
use tuirealm::components::{
use tui_realm_stdlib::{
input::{Input, InputPropsBuilder},
radio::{Radio, RadioPropsBuilder},
span::{Span, SpanPropsBuilder},
};
use tuirealm::tui::{
layout::{Constraint, Direction, Layout},
@@ -42,7 +41,7 @@ use tuirealm::tui::{
widgets::{BorderType, Borders, Clear},
};
use tuirealm::{
props::{PropsBuilder, TextSpanBuilder},
props::{Alignment, PropsBuilder},
View,
};
@@ -57,46 +56,15 @@ impl SetupActivity {
self.view = View::init();
// Common stuff
// Radio tab
self.view.mount(
super::COMPONENT_RADIO_TAB,
Box::new(Radio::new(
RadioPropsBuilder::default()
.with_color(Color::LightYellow)
.with_inverted_color(Color::Black)
.with_borders(Borders::BOTTOM, BorderType::Thick, Color::LightYellow)
.with_options(
None,
vec![
String::from("User Interface"),
String::from("SSH Keys"),
String::from("Theme"),
],
)
.with_value(1)
.build(),
)),
);
// Radio tab
self.mount_header_tab(1);
// Footer
self.view.mount(
super::COMPONENT_TEXT_FOOTER,
Box::new(Span::new(
SpanPropsBuilder::default()
.with_spans(vec![
TextSpanBuilder::new("Press ").bold().build(),
TextSpanBuilder::new("<CTRL+H>")
.bold()
.with_foreground(Color::Cyan)
.build(),
TextSpanBuilder::new(" to show keybindings").bold().build(),
])
.build(),
)),
);
self.mount_footer();
self.view.mount(
super::COMPONENT_LIST_SSH_KEYS,
Box::new(BookmarkList::new(
BookmarkListPropsBuilder::default()
.with_bookmarks(Some(String::from("SSH Keys")), vec![])
.with_title("SSH keys", Alignment::Left)
.with_borders(Borders::ALL, BorderType::Plain, Color::LightGreen)
.with_background(Color::LightGreen)
.with_foreground(Color::Black)
@@ -211,11 +179,10 @@ impl SetupActivity {
.with_color(Color::LightRed)
.with_inverted_color(Color::Black)
.with_borders(Borders::ALL, BorderType::Rounded, Color::LightRed)
.with_options(
Some(String::from("Delete key?")),
vec![String::from("Yes"), String::from("No")],
)
.with_title("Delete key?", Alignment::Center)
.with_options(&[String::from("Yes"), String::from("No")])
.with_value(1) // Default: No
.rewind(true)
.build(),
)),
);
@@ -238,7 +205,7 @@ impl SetupActivity {
super::COMPONENT_INPUT_SSH_HOST,
Box::new(Input::new(
InputPropsBuilder::default()
.with_label(String::from("Hostname or address"))
.with_label("Hostname or address", Alignment::Center)
.with_borders(
Borders::TOP | Borders::RIGHT | Borders::LEFT,
BorderType::Plain,
@@ -251,7 +218,7 @@ impl SetupActivity {
super::COMPONENT_INPUT_SSH_USERNAME,
Box::new(Input::new(
InputPropsBuilder::default()
.with_label(String::from("Username"))
.with_label("Username", Alignment::Center)
.with_borders(
Borders::BOTTOM | Borders::RIGHT | Borders::LEFT,
BorderType::Plain,
@@ -287,7 +254,7 @@ impl SetupActivity {
})
.collect();
let props = BookmarkListPropsBuilder::from(props)
.with_bookmarks(Some(String::from("SSH Keys")), keys)
.with_bookmarks(keys)
.build();
self.view.update(super::COMPONENT_LIST_SSH_KEYS, props);
}

View File

@@ -33,18 +33,14 @@ use crate::ui::components::color_picker::{ColorPicker, ColorPickerPropsBuilder};
use crate::utils::parser::parse_color;
use crate::utils::ui::draw_area_in;
// Ext
use tuirealm::components::{
label::{Label, LabelPropsBuilder},
radio::{Radio, RadioPropsBuilder},
span::{Span, SpanPropsBuilder},
};
use tui_realm_stdlib::label::{Label, LabelPropsBuilder};
use tuirealm::tui::{
layout::{Constraint, Direction, Layout},
style::Color,
widgets::{BorderType, Borders, Clear},
};
use tuirealm::{
props::{PropsBuilder, TextSpanBuilder},
props::{Alignment, PropsBuilder},
Payload, Value, View,
};
@@ -59,41 +55,9 @@ impl SetupActivity {
self.view = View::init();
// Common stuff
// Radio tab
self.view.mount(
super::COMPONENT_RADIO_TAB,
Box::new(Radio::new(
RadioPropsBuilder::default()
.with_color(Color::LightYellow)
.with_inverted_color(Color::Black)
.with_borders(Borders::BOTTOM, BorderType::Thick, Color::White)
.with_options(
None,
vec![
String::from("User Interface"),
String::from("SSH Keys"),
String::from("Theme"),
],
)
.with_value(2)
.build(),
)),
);
self.mount_header_tab(2);
// Footer
self.view.mount(
super::COMPONENT_TEXT_FOOTER,
Box::new(Span::new(
SpanPropsBuilder::default()
.with_spans(vec![
TextSpanBuilder::new("Press ").bold().build(),
TextSpanBuilder::new("<CTRL+H>")
.bold()
.with_foreground(Color::Cyan)
.build(),
TextSpanBuilder::new(" to show keybindings").bold().build(),
])
.build(),
)),
);
self.mount_footer();
// auth colors
self.mount_title(super::COMPONENT_COLOR_AUTH_TITLE, "Authentication styles");
self.mount_color_picker(super::COMPONENT_COLOR_AUTH_PROTOCOL, "Protocol");
@@ -653,7 +617,7 @@ impl SetupActivity {
Box::new(ColorPicker::new(
ColorPickerPropsBuilder::default()
.with_borders(Borders::ALL, BorderType::Rounded, Color::Reset)
.with_label(label.to_string())
.with_label(label.to_string(), Alignment::Left)
.build(),
)),
);

View File

@@ -26,18 +26,19 @@
* SOFTWARE.
*/
// ext
use tuirealm::components::utils::get_block;
use tui_realm_stdlib::utils::get_block;
use tuirealm::event::{Event, KeyCode};
use tuirealm::props::{BordersProps, Props, PropsBuilder, TextParts, TextSpan};
use tuirealm::props::{Alignment, BlockTitle, BordersProps, Props, PropsBuilder};
use tuirealm::tui::{
layout::{Corner, Rect},
style::{Color, Style},
text::Span,
widgets::{BorderType, Borders, List, ListItem, ListState},
};
use tuirealm::{Canvas, Component, Msg, Payload, Value};
use tuirealm::{Component, Frame, Msg, Payload, PropPayload, PropValue, Value};
// -- props
const PROP_BOOKMARKS: &str = "bookmarks";
pub struct BookmarkListPropsBuilder {
props: Option<Props>,
@@ -117,10 +118,19 @@ impl BookmarkListPropsBuilder {
self
}
pub fn with_bookmarks(&mut self, title: Option<String>, bookmarks: Vec<String>) -> &mut Self {
pub fn with_title<S: AsRef<str>>(&mut self, text: S, alignment: Alignment) -> &mut Self {
if let Some(props) = self.props.as_mut() {
let bookmarks: Vec<TextSpan> = bookmarks.into_iter().map(TextSpan::from).collect();
props.texts = TextParts::new(title, Some(bookmarks));
props.title = Some(BlockTitle::new(text, alignment));
}
self
}
pub fn with_bookmarks(&mut self, bookmarks: Vec<String>) -> &mut Self {
if let Some(props) = self.props.as_mut() {
let bookmarks: Vec<PropValue> = bookmarks.into_iter().map(PropValue::Str).collect();
props
.own
.insert(PROP_BOOKMARKS, PropPayload::Vec(bookmarks));
}
self
}
@@ -210,25 +220,30 @@ impl BookmarkList {
// Initialize states
let mut states: OwnStates = OwnStates::default();
// Set list length
states.set_list_len(match &props.texts.spans {
Some(tokens) => tokens.len(),
None => 0,
});
states.set_list_len(Self::bookmarks_len(&props));
BookmarkList { props, states }
}
fn bookmarks_len(props: &Props) -> usize {
match props.own.get(PROP_BOOKMARKS) {
None => 0,
Some(bookmarks) => bookmarks.unwrap_vec().len(),
}
}
}
impl Component for BookmarkList {
#[cfg(not(tarpaulin_include))]
fn render(&self, render: &mut Canvas, area: Rect) {
fn render(&self, render: &mut Frame, area: Rect) {
if self.props.visible {
// Make list
let list_item: Vec<ListItem> = match self.props.texts.spans.as_ref() {
None => vec![],
Some(lines) => lines
let list_item: Vec<ListItem> = match self.props.own.get(PROP_BOOKMARKS) {
Some(PropPayload::Vec(lines)) => lines
.iter()
.map(|line| ListItem::new(Span::from(line.content.to_string())))
.map(|x| x.unwrap_str())
.map(|x| ListItem::new(Span::from(x.to_string())))
.collect(),
_ => vec![],
};
let (fg, bg): (Color, Color) = match self.states.focus {
true => (self.props.foreground, self.props.background),
@@ -241,7 +256,7 @@ impl Component for BookmarkList {
List::new(list_item)
.block(get_block(
&self.props.borders,
&self.props.texts.title,
self.props.title.as_ref(),
self.states.focus,
))
.start_corner(Corner::TopLeft)
@@ -260,10 +275,7 @@ impl Component for BookmarkList {
fn update(&mut self, props: Props) -> Msg {
self.props = props;
// re-Set list length
self.states.set_list_len(match &self.props.texts.spans {
Some(tokens) => tokens.len(),
None => 0,
});
self.states.set_list_len(Self::bookmarks_len(&self.props));
// Reset list index
self.states.reset_list_index();
Msg::None
@@ -347,20 +359,24 @@ mod tests {
.with_foreground(Color::Red)
.with_background(Color::Blue)
.with_borders(Borders::ALL, BorderType::Double, Color::Red)
.with_bookmarks(
Some(String::from("filelist")),
vec![String::from("file1"), String::from("file2")],
)
.with_title("filelist", Alignment::Left)
.with_bookmarks(vec![String::from("file1"), String::from("file2")])
.build(),
);
assert_eq!(component.props.foreground, Color::Red);
assert_eq!(component.props.background, Color::Blue);
assert_eq!(component.props.visible, true);
assert_eq!(component.props.title.as_ref().unwrap().text(), "filelist");
assert_eq!(
component.props.texts.title.as_ref().unwrap().as_str(),
"filelist"
component
.props
.own
.get(PROP_BOOKMARKS)
.unwrap()
.unwrap_vec()
.len(),
2
);
assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 2);
// Verify states
assert_eq!(component.states.list_index, 0);
assert_eq!(component.states.list_len, 2);
@@ -384,14 +400,11 @@ mod tests {
// Update
component.update(
BookmarkListPropsBuilder::from(component.get_props())
.with_bookmarks(
Some(String::from("filelist")),
vec![
String::from("file1"),
String::from("file2"),
String::from("file3"),
],
)
.with_bookmarks(vec![
String::from("file1"),
String::from("file2"),
String::from("file3"),
])
.build(),
);
// Verify states

View File

@@ -30,15 +30,15 @@
use crate::utils::fmt::fmt_color;
use crate::utils::parser::parse_color;
// ext
use tuirealm::components::input::{Input, InputPropsBuilder};
use tui_realm_stdlib::input::{Input, InputPropsBuilder};
use tuirealm::event::Event;
use tuirealm::props::{Props, PropsBuilder};
use tuirealm::props::{Alignment, Props, PropsBuilder};
use tuirealm::tui::{
layout::Rect,
style::Color,
widgets::{BorderType, Borders},
};
use tuirealm::{Canvas, Component, Msg, Payload, Value};
use tuirealm::{Component, Frame, Msg, Payload, Value};
// -- props
@@ -98,8 +98,8 @@ impl ColorPickerPropsBuilder {
/// ### with_label
///
/// Set input label
pub fn with_label(&mut self, label: String) -> &mut Self {
self.puppet.with_label(label);
pub fn with_label<S: AsRef<str>>(&mut self, label: S, alignment: Alignment) -> &mut Self {
self.puppet.with_label(label, alignment);
self
}
@@ -149,7 +149,7 @@ impl Component for ColorPicker {
/// Based on the current properties and states, renders a widget using the provided render engine in the provided Area
/// If focused, cursor is also set (if supported by widget)
#[cfg(not(tarpaulin_include))]
fn render(&self, render: &mut Canvas, area: Rect) {
fn render(&self, render: &mut Frame, area: Rect) {
self.input.render(render, area);
}
@@ -260,6 +260,7 @@ mod test {
.visible()
.with_color(&Color::Rgb(204, 170, 0))
.with_borders(Borders::ALL, BorderType::Double, Color::Rgb(204, 170, 0))
.with_label("omar", Alignment::Left)
.build(),
);
// Focus

View File

@@ -26,10 +26,10 @@
* SOFTWARE.
*/
// ext
use tuirealm::components::utils::get_block;
use tui_realm_stdlib::utils::get_block;
use tuirealm::event::{Event, KeyCode, KeyModifiers};
use tuirealm::props::{
BordersProps, PropPayload, PropValue, Props, PropsBuilder, TextParts, TextSpan,
Alignment, BlockTitle, BordersProps, PropPayload, PropValue, Props, PropsBuilder,
};
use tuirealm::tui::{
layout::{Corner, Rect},
@@ -37,11 +37,12 @@ use tuirealm::tui::{
text::Span,
widgets::{BorderType, Borders, List, ListItem, ListState},
};
use tuirealm::{Canvas, Component, Msg, Payload, Value};
use tuirealm::{Component, Frame, Msg, Payload, Value};
// -- props
const PROP_HIGHLIGHT_COLOR: &str = "props-highlight-color";
const PROP_FILES: &str = "files";
const PALETTE_HIGHLIGHT_COLOR: &str = "props-highlight-color";
pub struct FileListPropsBuilder {
props: Option<Props>,
@@ -107,10 +108,7 @@ impl FileListPropsBuilder {
/// Set highlighted color
pub fn with_highlight_color(&mut self, color: Color) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.own.insert(
PROP_HIGHLIGHT_COLOR,
PropPayload::One(PropValue::Color(color)),
);
props.palette.insert(PALETTE_HIGHLIGHT_COLOR, color);
}
self
}
@@ -134,10 +132,17 @@ impl FileListPropsBuilder {
self
}
pub fn with_files(&mut self, title: Option<String>, files: Vec<String>) -> &mut Self {
pub fn with_title<S: AsRef<str>>(&mut self, text: S, alignment: Alignment) -> &mut Self {
if let Some(props) = self.props.as_mut() {
let files: Vec<TextSpan> = files.into_iter().map(TextSpan::from).collect();
props.texts = TextParts::new(title, Some(files));
props.title = Some(BlockTitle::new(text, alignment));
}
self
}
pub fn with_files(&mut self, files: Vec<String>) -> &mut Self {
if let Some(props) = self.props.as_mut() {
let files: Vec<PropValue> = files.into_iter().map(PropValue::Str).collect();
props.own.insert(PROP_FILES, PropPayload::Vec(files));
}
self
}
@@ -299,32 +304,39 @@ impl FileList {
// Initialize states
let mut states: OwnStates = OwnStates::default();
// Init list states
states.init_list_states(props.texts.spans.as_ref().map(|x| x.len()).unwrap_or(0));
states.init_list_states(Self::files_len(&props));
FileList { props, states }
}
fn files_len(props: &Props) -> usize {
match props.own.get(PROP_FILES) {
None => 0,
Some(files) => files.unwrap_vec().len(),
}
}
}
impl Component for FileList {
#[cfg(not(tarpaulin_include))]
fn render(&self, render: &mut Canvas, area: Rect) {
fn render(&self, render: &mut Frame, area: Rect) {
if self.props.visible {
// Make list
let list_item: Vec<ListItem> = match self.props.texts.spans.as_ref() {
None => vec![],
Some(lines) => lines
let list_item: Vec<ListItem> = match self.props.own.get(PROP_FILES) {
Some(PropPayload::Vec(lines)) => lines
.iter()
.enumerate()
.map(|(num, line)| {
let to_display: String = match self.states.is_selected(num) {
true => format!("*{}", line.content),
false => line.content.to_string(),
true => format!("*{}", line.unwrap_str()),
false => line.unwrap_str().to_string(),
};
ListItem::new(Span::from(to_display))
})
.collect(),
_ => vec![],
};
let highlighted_color: Color = match self.props.own.get(PROP_HIGHLIGHT_COLOR) {
Some(PropPayload::One(PropValue::Color(c))) => *c,
let highlighted_color: Color = match self.props.palette.get(PALETTE_HIGHLIGHT_COLOR) {
Some(c) => *c,
_ => Color::Reset,
};
let (h_fg, h_bg): (Color, Color) = match self.states.focus {
@@ -338,7 +350,7 @@ impl Component for FileList {
List::new(list_item)
.block(get_block(
&self.props.borders,
&self.props.texts.title,
self.props.title.as_ref(),
self.states.focus,
))
.start_corner(Corner::TopLeft)
@@ -362,14 +374,7 @@ impl Component for FileList {
fn update(&mut self, props: Props) -> Msg {
self.props = props;
// re-Set list states
self.states.init_list_states(
self.props
.texts
.spans
.as_ref()
.map(|x| x.len())
.unwrap_or(0),
);
self.states.init_list_states(Self::files_len(&self.props));
Msg::None
}
@@ -551,24 +556,33 @@ mod tests {
.with_background(Color::Blue)
.with_highlight_color(Color::LightRed)
.with_borders(Borders::ALL, BorderType::Double, Color::Red)
.with_files(
Some(String::from("files")),
vec![String::from("file1"), String::from("file2")],
)
.with_title("files", Alignment::Left)
.with_files(vec![String::from("file1"), String::from("file2")])
.build(),
);
assert_eq!(
*component.props.own.get(PROP_HIGHLIGHT_COLOR).unwrap(),
PropPayload::One(PropValue::Color(Color::LightRed))
*component
.props
.palette
.get(PALETTE_HIGHLIGHT_COLOR)
.unwrap(),
Color::LightRed
);
assert_eq!(component.props.foreground, Color::Red);
assert_eq!(component.props.background, Color::Blue);
assert_eq!(component.props.visible, true);
assert_eq!(component.props.title.as_ref().unwrap().text(), "files");
assert_eq!(
component.props.texts.title.as_ref().unwrap().as_str(),
"files"
component
.props
.own
.get(PROP_FILES)
.as_ref()
.unwrap()
.unwrap_vec()
.len(),
2
);
assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 2);
// Verify states
assert_eq!(component.states.list_index, 0);
assert_eq!(component.states.selected.len(), 0);
@@ -594,14 +608,11 @@ mod tests {
// Update
component.update(
FileListPropsBuilder::from(component.get_props())
.with_files(
Some(String::from("filelist")),
vec![
String::from("file1"),
String::from("file2"),
String::from("file3"),
],
)
.with_files(vec![
String::from("file1"),
String::from("file2"),
String::from("file3"),
])
.build(),
);
// Verify states
@@ -670,14 +681,11 @@ mod tests {
// Make component
let mut component: FileList = FileList::new(
FileListPropsBuilder::default()
.with_files(
Some(String::from("files")),
vec![
String::from("file1"),
String::from("file2"),
String::from("file3"),
],
)
.with_files(vec![
String::from("file1"),
String::from("file2"),
String::from("file3"),
])
.build(),
);
// Get state
@@ -735,10 +743,7 @@ mod tests {
// Update files
component.update(
FileListPropsBuilder::from(component.get_props())
.with_files(
Some(String::from("filelist")),
vec![String::from("file1"), String::from("file2")],
)
.with_files(vec![String::from("file1"), String::from("file2")])
.build(),
);
// Selection should now be empty

View File

@@ -26,18 +26,22 @@
* SOFTWARE.
*/
// ext
use tuirealm::components::utils::{get_block, wrap_spans};
use tui_realm_stdlib::utils::{get_block, wrap_spans};
use tuirealm::event::{Event, KeyCode};
use tuirealm::props::{BordersProps, Props, PropsBuilder, Table as TextTable, TextParts};
use tuirealm::props::{
Alignment, BlockTitle, BordersProps, Props, PropsBuilder, Table as TextTable,
};
use tuirealm::tui::{
layout::{Corner, Rect},
style::{Color, Style},
widgets::{BorderType, Borders, List, ListItem, ListState},
};
use tuirealm::{Canvas, Component, Msg, Payload, Value};
use tuirealm::{Component, Frame, Msg, Payload, PropPayload, PropValue, Value};
// -- props
const PROP_TABLE: &str = "table";
pub struct LogboxPropsBuilder {
props: Option<Props>,
}
@@ -106,9 +110,18 @@ impl LogboxPropsBuilder {
self
}
pub fn with_log(&mut self, title: Option<String>, table: TextTable) -> &mut Self {
pub fn with_title<S: AsRef<str>>(&mut self, text: S, alignment: Alignment) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.texts = TextParts::table(title, table);
props.title = Some(BlockTitle::new(text, alignment));
}
self
}
pub fn with_log(&mut self, table: TextTable) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props
.own
.insert(PROP_TABLE, PropPayload::One(PropValue::Table(table)));
}
self
}
@@ -198,33 +211,37 @@ impl LogBox {
// Initialize states
let mut states: OwnStates = OwnStates::default();
// Set list length
states.set_list_len(match &props.texts.table {
Some(rows) => rows.len(),
None => 0,
});
states.set_list_len(Self::table_len(&props));
// Reset list index
states.reset_list_index();
LogBox { props, states }
}
fn table_len(props: &Props) -> usize {
match props.own.get(PROP_TABLE) {
Some(PropPayload::One(PropValue::Table(table))) => table.len(),
_ => 0,
}
}
}
impl Component for LogBox {
#[cfg(not(tarpaulin_include))]
fn render(&self, render: &mut Canvas, area: Rect) {
fn render(&self, render: &mut Frame, area: Rect) {
if self.props.visible {
let width: usize = area.width as usize - 4;
// Make list
let list_items: Vec<ListItem> = match self.props.texts.table.as_ref() {
None => Vec::new(),
Some(table) => table
let list_items: Vec<ListItem> = match self.props.own.get(PROP_TABLE) {
Some(PropPayload::One(PropValue::Table(table))) => table
.iter()
.map(|row| ListItem::new(wrap_spans(row, width, &self.props)))
.collect(), // Make List item from TextSpan
_ => Vec::new(),
};
let w = List::new(list_items)
.block(get_block(
&self.props.borders,
&self.props.texts.title,
self.props.title.as_ref(),
self.states.focus,
))
.start_corner(Corner::BottomLeft)
@@ -240,10 +257,7 @@ impl Component for LogBox {
fn update(&mut self, props: Props) -> Msg {
self.props = props;
// re-Set list length
self.states.set_list_len(match &self.props.texts.table {
Some(rows) => rows.len(),
None => 0,
});
self.states.set_list_len(Self::table_len(&self.props));
// Reset list index
self.states.reset_list_index();
Msg::None
@@ -323,8 +337,8 @@ mod tests {
.visible()
.with_borders(Borders::ALL, BorderType::Double, Color::Red)
.with_background(Color::Blue)
.with_title("log", Alignment::Left)
.with_log(
Some(String::from("Log")),
TableBuilder::default()
.add_col(TextSpan::from("12:29"))
.add_col(TextSpan::from("system crashed"))
@@ -337,11 +351,7 @@ mod tests {
);
assert_eq!(component.props.visible, true);
assert_eq!(component.props.background, Color::Blue);
assert_eq!(
component.props.texts.title.as_ref().unwrap().as_str(),
"Log"
);
assert_eq!(component.props.texts.table.as_ref().unwrap().len(), 2);
assert_eq!(component.props.title.as_ref().unwrap().text(), "Log");
// Verify states
assert_eq!(component.states.list_index, 0);
assert_eq!(component.states.list_len, 2);
@@ -364,7 +374,6 @@ mod tests {
component.update(
LogboxPropsBuilder::from(component.get_props())
.with_log(
Some(String::from("Log")),
TableBuilder::default()
.add_col(TextSpan::from("12:29"))
.add_col(TextSpan::from("system crashed"))

View File

@@ -30,4 +30,3 @@ pub mod bookmark_list;
pub mod color_picker;
pub mod file_list;
pub mod logbox;
pub mod msgbox;

View File

@@ -1,268 +0,0 @@
//! ## MsgBox
//!
//! `MsgBox` component renders a simple readonly no event associated centered text
/**
* MIT License
*
* termscp - Copyright (c) 2021 Christian Visintin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// locals
use crate::utils::fmt::align_text_center;
// ext
use tuirealm::components::utils::{get_block, use_or_default_styles};
use tuirealm::event::Event;
use tuirealm::props::{BordersProps, Props, PropsBuilder, TextParts, TextSpan};
use tuirealm::tui::{
layout::{Corner, Rect},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{BorderType, Borders, List, ListItem},
};
use tuirealm::{Canvas, Component, Msg, Payload};
// -- Props
pub struct MsgBoxPropsBuilder {
props: Option<Props>,
}
impl Default for MsgBoxPropsBuilder {
fn default() -> Self {
MsgBoxPropsBuilder {
props: Some(Props::default()),
}
}
}
impl PropsBuilder for MsgBoxPropsBuilder {
fn build(&mut self) -> Props {
self.props.take().unwrap()
}
fn hidden(&mut self) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.visible = false;
}
self
}
fn visible(&mut self) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.visible = true;
}
self
}
}
impl From<Props> for MsgBoxPropsBuilder {
fn from(props: Props) -> Self {
MsgBoxPropsBuilder { props: Some(props) }
}
}
impl MsgBoxPropsBuilder {
pub fn with_foreground(&mut self, color: Color) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.foreground = color;
}
self
}
pub fn bold(&mut self) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.modifiers |= Modifier::BOLD;
}
self
}
pub fn blink(&mut self) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.modifiers |= Modifier::SLOW_BLINK;
}
self
}
pub fn with_borders(
&mut self,
borders: Borders,
variant: BorderType,
color: Color,
) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.borders = BordersProps {
borders,
variant,
color,
}
}
self
}
pub fn with_texts(&mut self, title: Option<String>, texts: Vec<TextSpan>) -> &mut Self {
if let Some(props) = self.props.as_mut() {
props.texts = TextParts::new(title, Some(texts));
}
self
}
}
// -- component
pub struct MsgBox {
props: Props,
}
impl MsgBox {
/// ### new
///
/// Instantiate a new Text component
pub fn new(props: Props) -> Self {
MsgBox { props }
}
}
impl Component for MsgBox {
#[cfg(not(tarpaulin_include))]
fn render(&self, render: &mut Canvas, area: Rect) {
// Make a Span
if self.props.visible {
let lines: Vec<ListItem> = match self.props.texts.spans.as_ref() {
None => Vec::new(),
Some(rows) => {
let mut lines: Vec<ListItem> = Vec::new();
for line in rows.iter() {
// Keep line color, or use default
let (fg, bg, modifiers) = use_or_default_styles(&self.props, line);
let message_row =
textwrap::wrap(line.content.as_str(), area.width as usize);
for msg in message_row.iter() {
lines.push(ListItem::new(Spans::from(vec![Span::styled(
align_text_center(msg, area.width),
Style::default().add_modifier(modifiers).fg(fg).bg(bg),
)])));
}
}
lines
}
};
render.render_widget(
List::new(lines)
.block(get_block(
&self.props.borders,
&self.props.texts.title,
true,
))
.start_corner(Corner::TopLeft)
.style(
Style::default()
.fg(self.props.foreground)
.bg(self.props.background),
),
area,
);
}
}
fn update(&mut self, props: Props) -> Msg {
self.props = props;
// Return None
Msg::None
}
fn get_props(&self) -> Props {
self.props.clone()
}
fn on(&mut self, ev: Event) -> Msg {
// Return key
if let Event::Key(key) = ev {
Msg::OnKey(key)
} else {
Msg::None
}
}
fn get_state(&self) -> Payload {
Payload::None
}
fn blur(&mut self) {}
fn active(&mut self) {}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use tuirealm::event::{KeyCode, KeyEvent};
use tuirealm::props::{TextSpan, TextSpanBuilder};
use tuirealm::tui::style::Color;
#[test]
fn test_ui_components_msgbox() {
let mut component: MsgBox = MsgBox::new(
MsgBoxPropsBuilder::default()
.hidden()
.visible()
.with_foreground(Color::Red)
.bold()
.blink()
.with_borders(Borders::ALL, BorderType::Double, Color::Red)
.with_texts(
None,
vec![
TextSpan::from("Press "),
TextSpanBuilder::new("<ESC>")
.with_foreground(Color::Cyan)
.bold()
.build(),
TextSpan::from(" to quit"),
],
)
.build(),
);
assert_eq!(component.props.foreground, Color::Red);
assert!(component.props.modifiers.intersects(Modifier::BOLD));
assert_eq!(component.props.visible, true);
assert_eq!(component.props.texts.spans.as_ref().unwrap().len(), 3);
component.active();
component.blur();
// Update
let props = MsgBoxPropsBuilder::from(component.get_props())
.hidden()
.with_foreground(Color::Yellow)
.build();
assert_eq!(component.update(props), Msg::None);
assert_eq!(component.props.visible, false);
assert_eq!(component.props.foreground, Color::Yellow);
// Get value
assert_eq!(component.get_state(), Payload::None);
// Event
assert_eq!(
component.on(Event::Key(KeyEvent::from(KeyCode::Delete))),
Msg::OnKey(KeyEvent::from(KeyCode::Delete))
);
}
}

View File

@@ -100,23 +100,6 @@ pub fn fmt_millis(duration: Duration) -> String {
format!("{}.{:0width$}", seconds, millis, width = 3)
}
/// align_text_center
///
/// Align text to center for a given width
pub fn align_text_center(text: &str, width: u16) -> String {
let indent_size: usize = match (width as usize) >= text.len() {
// NOTE: The check prevents underflow
true => (width as usize - text.len()) / 2,
false => 0,
};
textwrap::indent(
text,
(0..indent_size).map(|_| " ").collect::<String>().as_str(),
)
.trim_end()
.to_string()
}
/// ### elide_path
///
/// Elide a path if longer than width
@@ -362,18 +345,6 @@ mod tests {
);
}
#[test]
fn test_utils_align_text_center() {
assert_eq!(
align_text_center("hello world!", 24),
String::from(" hello world!")
);
// Bad case
assert_eq!(
align_text_center("hello world!", 8),
String::from("hello world!")
);
}
#[test]
fn test_utils_fmt_millis() {
assert_eq!(