From 5bc46dd720481a5b1bbb7a3955e0df77c761d9f7 Mon Sep 17 00:00:00 2001 From: veeso Date: Sun, 14 Mar 2021 15:31:49 +0100 Subject: [PATCH] Something is working, but it is still unusable --- src/ui/activities/auth_activity/bookmarks.rs | 163 +++++++++---------- src/ui/activities/auth_activity/mod.rs | 91 ++--------- src/ui/activities/auth_activity/update.rs | 95 ++++------- src/ui/activities/auth_activity/view.rs | 137 +++++++++++++++- src/ui/layout/props.rs | 3 + 5 files changed, 262 insertions(+), 227 deletions(-) diff --git a/src/ui/activities/auth_activity/bookmarks.rs b/src/ui/activities/auth_activity/bookmarks.rs index aecca8c..40d2c33 100644 --- a/src/ui/activities/auth_activity/bookmarks.rs +++ b/src/ui/activities/auth_activity/bookmarks.rs @@ -27,9 +27,11 @@ extern crate dirs; // Locals -use super::{AuthActivity, Color, DialogYesNoOption, Popup}; +use super::{AuthActivity, FileTransferProtocol}; use crate::system::bookmarks_client::BookmarksClient; use crate::system::environment; +use crate::ui::layout::props::PropValue; +use crate::ui::layout::Payload; // Ext use std::path::PathBuf; @@ -60,14 +62,10 @@ impl AuthActivity { // Iterate over bookmarks if let Some(key) = self.bookmarks_list.get(idx) { if let Some(bookmark) = bookmarks_cli.get_bookmark(&key) { - // Load parameters - self.address = bookmark.0; - self.port = bookmark.1.to_string(); - self.protocol = bookmark.2; - self.username = bookmark.3; - if let Some(password) = bookmark.4 { - self.password = password; - } + // Load parameters into components + self.load_bookmark_into_gui( + bookmark.0, bookmark.1, bookmark.2, bookmark.3, bookmark.4, + ); } } } @@ -76,41 +74,24 @@ impl AuthActivity { /// ### save_bookmark /// /// Save current input fields as a bookmark - pub(super) fn save_bookmark(&mut self, name: String) { - // Check port - let port: u16 = match self.port.parse::() { - Ok(val) => { - if val > 65535 { - self.popup = Some(Popup::Alert( - Color::Red, - String::from("Specified port must be in range 0-65535"), - )); - return; - } - val as u16 - } - Err(_) => { - self.popup = Some(Popup::Alert( - Color::Red, - String::from("Specified port is not a number"), - )); - return; - } - }; + pub(super) fn save_bookmark(&mut self, name: String, save_password: bool) { + let (address, port, protocol, username, password) = self.get_input(); if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() { // Check if password must be saved - let password: Option = match self.choice_opt { - DialogYesNoOption::Yes => Some(self.password.clone()), - DialogYesNoOption::No => None, + let password: Option = match save_password { + true => match self + .view + .get_value(super::COMPONENT_RADIO_BOOKMARK_SAVE_PWD) + { + Some(Payload::Unsigned(choice)) => match choice { + 0 => Some(password), // Yes + _ => None, // No + }, + _ => None, // No such component + }, + false => None, }; - bookmarks_cli.add_bookmark( - name.clone(), - self.address.clone(), - port, - self.protocol, - self.username.clone(), - password, - ); + bookmarks_cli.add_bookmark(name.clone(), address, port, protocol, username, password); // Save bookmarks self.write_bookmarks(); // Push bookmark to list and sort @@ -143,10 +124,9 @@ impl AuthActivity { if let Some(key) = self.recents_list.get(idx) { if let Some(bookmark) = client.get_recent(key) { // Load parameters - self.address = bookmark.0; - self.port = bookmark.1.to_string(); - self.protocol = bookmark.2; - self.username = bookmark.3; + self.load_bookmark_into_gui( + bookmark.0, bookmark.1, bookmark.2, bookmark.3, None, + ); } } } @@ -156,33 +136,9 @@ impl AuthActivity { /// /// Save current input fields as a "recent" pub(super) fn save_recent(&mut self) { - // Check port - let port: u16 = match self.port.parse::() { - Ok(val) => { - if val > 65535 { - self.popup = Some(Popup::Alert( - Color::Red, - String::from("Specified port must be in range 0-65535"), - )); - return; - } - val as u16 - } - Err(_) => { - self.popup = Some(Popup::Alert( - Color::Red, - String::from("Specified port is not a number"), - )); - return; - } - }; + let (address, port, protocol, username, _password) = self.get_input(); if let Some(bookmarks_cli) = self.bookmarks_client.as_mut() { - bookmarks_cli.add_recent( - self.address.clone(), - port, - self.protocol, - self.username.clone(), - ); + bookmarks_cli.add_recent(address, port, protocol, username); // Save bookmarks self.write_bookmarks(); } @@ -194,10 +150,7 @@ impl AuthActivity { fn write_bookmarks(&mut self) { if let Some(bookmarks_cli) = self.bookmarks_client.as_ref() { if let Err(err) = bookmarks_cli.write_bookmarks() { - self.popup = Some(Popup::Alert( - Color::Red, - format!("Could not write bookmarks: {}", err), - )); + self.mount_error(format!("Could not write bookmarks: {}", err).as_str()); } } } @@ -240,28 +193,29 @@ impl AuthActivity { self.sort_recents(); } Err(err) => { - self.popup = Some(Popup::Alert( - Color::Red, + self.mount_error( format!( "Could not initialize bookmarks (at \"{}\", \"{}\"): {}", bookmarks_file.display(), config_dir_path.display(), err - ), - )) + ) + .as_str(), + ); } } } } Err(err) => { - self.popup = Some(Popup::Alert( - Color::Red, - format!("Could not initialize configuration directory: {}", err), - )) + self.mount_error( + format!("Could not initialize configuration directory: {}", err).as_str(), + ); } } } + // -- privates + /// ### sort_bookmarks /// /// Sort bookmarks in list @@ -278,4 +232,47 @@ impl AuthActivity { // Reverse order self.recents_list.sort_by(|a, b| b.cmp(a)); } + + /// ### load_bookmark_into_gui + /// + /// Load bookmark data into the gui components + fn load_bookmark_into_gui( + &mut self, + addr: String, + port: u16, + protocol: FileTransferProtocol, + username: String, + password: Option, + ) { + // Load parameters into components + if let Some(mut props) = self.view.get_props(super::COMPONENT_INPUT_ADDR) { + let props = props.with_value(PropValue::Str(addr)).build(); + self.view.update(super::COMPONENT_INPUT_ADDR, props); + } + if let Some(mut props) = self.view.get_props(super::COMPONENT_INPUT_PORT) { + let props = props.with_value(PropValue::Unsigned(port as usize)).build(); + self.view.update(super::COMPONENT_INPUT_PORT, props); + } + if let Some(mut props) = self.view.get_props(super::COMPONENT_RADIO_PROTOCOL) { + let props = props + .with_value(PropValue::Unsigned(match protocol { + FileTransferProtocol::Sftp => 0, + FileTransferProtocol::Scp => 1, + FileTransferProtocol::Ftp(false) => 2, + FileTransferProtocol::Ftp(true) => 3, + })) + .build(); + self.view.update(super::COMPONENT_RADIO_PROTOCOL, props); + } + if let Some(mut props) = self.view.get_props(super::COMPONENT_INPUT_USERNAME) { + let props = props.with_value(PropValue::Str(username)).build(); + self.view.update(super::COMPONENT_INPUT_USERNAME, props); + } + if let Some(password) = password { + if let Some(mut props) = self.view.get_props(super::COMPONENT_INPUT_PASSWORD) { + let props = props.with_value(PropValue::Str(password)).build(); + self.view.update(super::COMPONENT_INPUT_PASSWORD, props); + } + } + } } diff --git a/src/ui/activities/auth_activity/mod.rs b/src/ui/activities/auth_activity/mod.rs index 2958fca..c14110b 100644 --- a/src/ui/activities/auth_activity/mod.rs +++ b/src/ui/activities/auth_activity/mod.rs @@ -45,10 +45,6 @@ use crate::utils::git; // Includes use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use std::path::PathBuf; -use tui::style::Color; - -// Types -type DialogCallback = fn(&mut AuthActivity); // -- components const COMPONENT_TEXT_HEADER: &str = "TEXT_HEADER"; @@ -68,48 +64,6 @@ const COMPONENT_RADIO_BOOKMARK_SAVE_PWD: &str = "RADIO_SAVE_PASSWORD"; const COMPONENT_BOOKMARKS_LIST: &str = "BOOKMARKS_LIST"; const COMPONENT_RECENTS_LIST: &str = "RECENTS_LIST"; -/// ### InputField -/// -/// InputField describes the current input field to edit -#[derive(std::cmp::PartialEq)] -enum InputField { - Address, - Port, - Protocol, - Username, - Password, -} - -/// ### DialogYesNoOption -/// -/// Current yes/no dialog option -#[derive(std::cmp::PartialEq, Clone)] -enum DialogYesNoOption { - Yes, - No, -} - -/// ### Popup -/// -/// Popup describes the type of the popup displayed -#[derive(Clone)] -enum Popup { - Alert(Color, String), // Show a message displaying text with the provided color - Help, // Help page - SaveBookmark, - YesNo(String, DialogCallback, DialogCallback), // Yes, no callback -} - -#[derive(std::cmp::PartialEq)] -/// ### InputForm -/// -/// InputForm describes the selected input form -enum InputForm { - AuthCredentials, - Bookmarks, - Recents, -} - /// ### AuthActivity /// /// AuthActivity is the data holder for the authentication activity @@ -126,17 +80,9 @@ pub struct AuthActivity { view: View, bookmarks_client: Option, config_client: Option, - selected_field: InputField, // Selected field in AuthCredentials Form - popup: Option, - input_form: InputForm, - password_placeholder: String, - redraw: bool, // Should ui actually be redrawned? - input_txt: String, // Input text - choice_opt: DialogYesNoOption, // Dialog popup selected option - bookmarks_idx: usize, // Index of selected bookmark - bookmarks_list: Vec, // List of bookmarks - recents_idx: usize, // Index of selected recent - recents_list: Vec, // list of recents + redraw: bool, // Should ui actually be redrawned? + bookmarks_list: Vec, // List of bookmarks + recents_list: Vec, // list of recents // misc new_version: Option, // Contains new version of termscp } @@ -165,16 +111,8 @@ impl AuthActivity { view: View::init(), bookmarks_client: None, config_client: None, - selected_field: InputField::Address, - popup: None, - input_form: InputForm::AuthCredentials, - password_placeholder: String::new(), redraw: true, // True at startup - input_txt: String::new(), - choice_opt: DialogYesNoOption::Yes, - bookmarks_idx: 0, bookmarks_list: Vec::new(), - recents_idx: 0, recents_list: Vec::new(), new_version: None, } @@ -199,19 +137,18 @@ impl AuthActivity { self.config_client = Some(cli); } Err(err) => { - self.popup = Some(Popup::Alert( - Color::Red, - format!("Could not initialize user configuration: {}", err), - )) + self.mount_error( + format!("Could not initialize user configuration: {}", err) + .as_str(), + ); } } } } Err(err) => { - self.popup = Some(Popup::Alert( - Color::Red, - format!("Could not initialize configuration directory: {}", err), - )) + self.mount_error( + format!("Could not initialize configuration directory: {}", err).as_str(), + ); } } } @@ -227,10 +164,9 @@ impl AuthActivity { Ok(version) => self.new_version = version, Err(err) => { // Report error - self.popup = Some(Popup::Alert( - Color::Red, - format!("Could not check for new updates: {}", err), - )) + self.mount_error( + format!("Could not check for new updates: {}", err).as_str(), + ); } } } @@ -251,7 +187,6 @@ impl Activity for AuthActivity { self.context.as_mut().unwrap().clear_screen(); // Put raw mode on enabled let _ = enable_raw_mode(); - self.popup = None; // Init bookmarks client if self.bookmarks_client.is_none() { self.init_bookmarks_client(); diff --git a/src/ui/activities/auth_activity/update.rs b/src/ui/activities/auth_activity/update.rs index 8524f3b..13a2577 100644 --- a/src/ui/activities/auth_activity/update.rs +++ b/src/ui/activities/auth_activity/update.rs @@ -25,16 +25,13 @@ // locals use super::{ - AuthActivity, FileTransferProtocol, COMPONENT_BOOKMARKS_LIST, COMPONENT_INPUT_ADDR, - COMPONENT_INPUT_BOOKMARK_NAME, COMPONENT_INPUT_PASSWORD, COMPONENT_INPUT_PORT, - COMPONENT_INPUT_USERNAME, COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, - COMPONENT_RADIO_BOOKMARK_DEL_RECENT, COMPONENT_RADIO_BOOKMARK_SAVE_PWD, - COMPONENT_RADIO_PROTOCOL, COMPONENT_RECENTS_LIST, COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP, -}; -use crate::ui::layout::{ - props::{TextParts, TextSpan}, - Msg, Payload, + AuthActivity, COMPONENT_BOOKMARKS_LIST, COMPONENT_INPUT_ADDR, COMPONENT_INPUT_BOOKMARK_NAME, + COMPONENT_INPUT_PASSWORD, COMPONENT_INPUT_PORT, COMPONENT_INPUT_USERNAME, + COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, COMPONENT_RADIO_BOOKMARK_DEL_RECENT, + COMPONENT_RADIO_BOOKMARK_SAVE_PWD, COMPONENT_RADIO_PROTOCOL, COMPONENT_RECENTS_LIST, + COMPONENT_TEXT_ERROR, COMPONENT_TEXT_HELP, }; +use crate::ui::layout::{props::TextParts, Msg, Payload}; // ext use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; @@ -193,6 +190,15 @@ impl AuthActivity { self.mount_recent_del_dialog(); None } + // Enter + (COMPONENT_BOOKMARKS_LIST, Msg::OnSubmit(Payload::Unsigned(idx))) => { + self.load_bookmark(*idx); + None + } + (COMPONENT_RECENTS_LIST, Msg::OnSubmit(Payload::Unsigned(idx))) => { + self.load_recent(*idx); + None + } // Bookmark radio // Del bookmarks ( @@ -209,16 +215,19 @@ impl AuthActivity { Some(Payload::Unsigned(index)) => { // Delete bookmark self.del_bookmark(index); - // TODO: view bookmarks // Update bookmarks match self.view.get_props(COMPONENT_BOOKMARKS_LIST).as_mut() { None => None, Some(props) => { - let msg = self - .view - .update(COMPONENT_BOOKMARKS_LIST, props.with_texts( - TextParts::new(Some(String::from("Bookmarks")), Some(self.view_bookmarks())) - ).build()); // TODO: set rows + let msg = self.view.update( + COMPONENT_BOOKMARKS_LIST, + props + .with_texts(TextParts::new( + Some(String::from("Bookmarks")), + Some(self.view_bookmarks()), + )) + .build(), + ); self.update(msg) } } @@ -240,16 +249,19 @@ impl AuthActivity { Some(Payload::Unsigned(index)) => { // Delete recent self.del_recent(index); - // TODO: view recents // Update bookmarks match self.view.get_props(COMPONENT_RECENTS_LIST).as_mut() { None => None, Some(props) => { - let msg = self - .view - .update(COMPONENT_RECENTS_LIST, props.with_texts( - TextParts::new(Some(String::from("Recent connections")), Some(self.view_recent_connections())) - ).build()); // TODO: set rows + let msg = self.view.update( + COMPONENT_RECENTS_LIST, + props + .with_texts(TextParts::new( + Some(String::from("Recent connections")), + Some(self.view_recent_connections()), + )) + .build(), + ); self.update(msg) } } @@ -344,7 +356,8 @@ impl AuthActivity { }, _ => false, }; - // TODO: save bookmark + // Save bookmark + self.save_bookmark(bookmark_name, save_pwd); // Umount popup self.umount_bookmark_save_dialog(); None @@ -365,44 +378,8 @@ impl AuthActivity { // On submit on any unhandled (connect) (_, Msg::OnSubmit(_)) | (_, &MSG_KEY_ENTER) => { // Match key for all other components - // Collect values from inputs - let address: String = match self.view.get_value(COMPONENT_INPUT_ADDR) { - Some(Payload::Text(addr)) => addr, - _ => { - // Show error - self.mount_error("Invalid address!"); - return None; - } - }; - let port: u16 = match self.view.get_value(COMPONENT_INPUT_PORT) { - Some(Payload::Unsigned(p)) => p as u16, - _ => { - // Show error - self.mount_error("Invalid port number!"); - // Return None - return None; - } - }; - let username: String = match self.view.get_value(COMPONENT_INPUT_USERNAME) { - Some(Payload::Text(u)) => u, - _ => String::new(), - }; - let password: String = match self.view.get_value(COMPONENT_INPUT_PASSWORD) { - Some(Payload::Text(p)) => p, - _ => String::new(), - }; - let protocol: FileTransferProtocol = - match self.view.get_value(COMPONENT_RADIO_PROTOCOL) { - Some(Payload::Unsigned(choice)) => match choice { - 1 => FileTransferProtocol::Scp, - 2 => FileTransferProtocol::Ftp(false), - 3 => FileTransferProtocol::Ftp(true), - _ => FileTransferProtocol::Sftp, - }, - _ => FileTransferProtocol::Sftp, - }; - // FIXME: save recent, pass attributes self.save_recent(); + let (address, port, protocol, username, password) = self.get_input(); // TOREM: remove this after removing states self.address = address; self.port = port.to_string(); diff --git a/src/ui/activities/auth_activity/view.rs b/src/ui/activities/auth_activity/view.rs index afd31be..1e7b0dd 100644 --- a/src/ui/activities/auth_activity/view.rs +++ b/src/ui/activities/auth_activity/view.rs @@ -24,17 +24,19 @@ */ // Locals -use super::{AuthActivity, Context}; +use super::{AuthActivity, Context, FileTransferProtocol}; use crate::ui::layout::components::{ - bookmark_list::BookmarkList, input::Input, radio_group::RadioGroup, table::Table, text::Text, + bookmark_list::BookmarkList, ctext::CText, input::Input, radio_group::RadioGroup, table::Table, + text::Text, }; use crate::ui::layout::props::{ - InputType, PropValue, Props, PropsBuilder, TableBuilder, TextParts, TextSpan, TextSpanBuilder, + InputType, PropValue, PropsBuilder, TableBuilder, TextParts, TextSpan, TextSpanBuilder, }; -use crate::utils::fmt::align_text_center; +use crate::ui::layout::utils::draw_area_in; +use crate::ui::layout::Payload; // Ext use tui::{ - layout::{Constraint, Corner, Direction, Layout}, + layout::{Constraint, Direction, Layout}, style::Color, }; @@ -153,6 +155,34 @@ impl AuthActivity { )) ); } + // Bookmarks + self.view.mount( + super::COMPONENT_BOOKMARKS_LIST, + Box::new(BookmarkList::new( + PropsBuilder::default() + .with_foreground(Color::LightGreen) + .with_texts(TextParts::new( + Some(String::from("Bookmarks")), + Some(self.view_bookmarks()), + )) + .build(), + )), + ); + // Recents + self.view.mount( + super::COMPONENT_RECENTS_LIST, + Box::new(BookmarkList::new( + PropsBuilder::default() + .with_foreground(Color::LightBlue) + .with_texts(TextParts::new( + Some(String::from("Recent connections")), + Some(self.view_recent_connections()), + )) + .build(), + )), + ); + // Active address + self.view.active(super::COMPONENT_INPUT_ADDR); } /// ### view @@ -218,8 +248,68 @@ impl AuthActivity { .render(super::COMPONENT_BOOKMARKS_LIST, f, bookmark_chunks[0]); self.view .render(super::COMPONENT_RECENTS_LIST, f, bookmark_chunks[1]); - // Popup + // Popups + if let Some(mut props) = self.view.get_props(super::COMPONENT_TEXT_ERROR) { + if props.build().visible { + // make popup + self.view.render( + super::COMPONENT_TEXT_ERROR, + f, + draw_area_in(f.size(), 50, 10), + ); + } + } + if let Some(mut props) = self + .view + .get_props(super::COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK) + { + if props.build().visible { + // make popup + self.view.render( + super::COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK, + f, + draw_area_in(f.size(), 30, 10), + ); + } + } + if let Some(mut props) = self + .view + .get_props(super::COMPONENT_RADIO_BOOKMARK_DEL_RECENT) + { + if props.build().visible { + // make popup + self.view.render( + super::COMPONENT_RADIO_BOOKMARK_DEL_RECENT, + f, + draw_area_in(f.size(), 30, 10), + ); + } + } + if let Some(mut props) = self.view.get_props(super::COMPONENT_TEXT_HELP) { + if props.build().visible { + // make popup + self.view.render( + super::COMPONENT_TEXT_HELP, + f, + draw_area_in(f.size(), 50, 70), + ); + } + } + if let Some(mut props) = self + .view + .get_props(super::COMPONENT_RADIO_BOOKMARK_SAVE_PWD) + { + if props.build().visible { + // make popup + let popup = draw_area_in(f.size(), 20, 20); + self.view + .render(super::COMPONENT_INPUT_BOOKMARK_NAME, f, popup); + self.view + .render(super::COMPONENT_RADIO_BOOKMARK_SAVE_PWD, f, popup); + } + } }); + self.context = Some(ctx); } // -- partials @@ -253,7 +343,7 @@ impl AuthActivity { // Mount self.view.mount( super::COMPONENT_TEXT_ERROR, - Box::new(Text::new( + Box::new(CText::new( PropsBuilder::default() .with_foreground(Color::Red) .bold() @@ -459,4 +549,37 @@ impl AuthActivity { pub(super) fn umount_help(&mut self) { self.view.umount(super::COMPONENT_TEXT_HELP); } + + /// ### get_input + /// + /// Collect input values from view + pub(super) fn get_input(&self) -> (String, u16, FileTransferProtocol, String, String) { + let addr: String = match self.view.get_value(super::COMPONENT_INPUT_ADDR) { + Some(Payload::Text(a)) => a, + _ => String::new(), + }; + let port: u16 = match self.view.get_value(super::COMPONENT_INPUT_PORT) { + Some(Payload::Unsigned(p)) => p as u16, + _ => 0, + }; + let protocol: FileTransferProtocol = + match self.view.get_value(super::COMPONENT_RADIO_PROTOCOL) { + Some(Payload::Unsigned(p)) => match p { + 1 => FileTransferProtocol::Scp, + 2 => FileTransferProtocol::Ftp(false), + 3 => FileTransferProtocol::Ftp(true), + _ => FileTransferProtocol::Sftp, + }, + _ => FileTransferProtocol::Sftp, + }; + let username: String = match self.view.get_value(super::COMPONENT_INPUT_USERNAME) { + Some(Payload::Text(a)) => a, + _ => String::new(), + }; + let password: String = match self.view.get_value(super::COMPONENT_INPUT_PASSWORD) { + Some(Payload::Text(a)) => a, + _ => String::new(), + }; + (addr, port, protocol, username, password) + } } diff --git a/src/ui/layout/props.rs b/src/ui/layout/props.rs index 1860991..64e9471 100644 --- a/src/ui/layout/props.rs +++ b/src/ui/layout/props.rs @@ -94,6 +94,7 @@ pub struct PropsBuilder { props: Option, } +#[allow(dead_code)] impl PropsBuilder { /// ### build /// @@ -382,6 +383,7 @@ pub struct TextSpanBuilder { text: Option, } +#[allow(dead_code)] impl TextSpanBuilder { /// ### new /// @@ -456,6 +458,7 @@ impl TextSpanBuilder { /// /// PropValue describes a property initial value #[derive(Clone, PartialEq, std::fmt::Debug)] +#[allow(dead_code)] pub enum PropValue { Str(String), Unsigned(usize),