Working on auth activity view

This commit is contained in:
veeso
2021-03-13 17:30:57 +01:00
parent 2d1af97590
commit 2e3dc7f7a5
3 changed files with 468 additions and 192 deletions

View File

@@ -54,6 +54,7 @@ type DialogCallback = fn(&mut AuthActivity);
// -- components
const COMPONENT_TEXT_HEADER: &str = "TEXT_HEADER";
const COMPONENT_TEXT_NEW_VERSION: &str = "TEXT_NEW_VERSION";
const COMPONENT_TEXT_FOOTER: &str = "TEXT_FOOTER";
const COMPONENT_TEXT_HELP: &str = "TEXT_HELP";
const COMPONENT_TEXT_ERROR: &str = "TEXT_ERROR";

View File

@@ -25,7 +25,7 @@
// locals
use super::{
AuthActivity, FileTransferProtocol, InputEvent, COMPONENT_BOOKMARKS_LIST, COMPONENT_INPUT_ADDR,
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,
@@ -91,7 +91,6 @@ const MSG_KEY_CTRL_S: Msg = Msg::OnKey(KeyEvent {
// -- update
impl AuthActivity {
/// ### update
///
/// Update auth activity model based on msg
@@ -182,39 +181,17 @@ impl AuthActivity {
None
}
// <DEL | 'E'>
(COMPONENT_BOOKMARKS_LIST, &MSG_KEY_DEL) | (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_CHAR_E) => {
(COMPONENT_BOOKMARKS_LIST, &MSG_KEY_DEL)
| (COMPONENT_BOOKMARKS_LIST, &MSG_KEY_CHAR_E) => {
// Show delete popup
match self
.view
.get_props(COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK)
.as_mut()
{
None => None,
Some(props) => {
let msg = self.view.update(
COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK,
props.visible().build(),
);
self.update(msg)
}
}
self.mount_bookmark_del_dialog();
None
}
(COMPONENT_RECENTS_LIST, &MSG_KEY_DEL) | (COMPONENT_RECENTS_LIST, &MSG_KEY_CHAR_E) => {
(COMPONENT_RECENTS_LIST, &MSG_KEY_DEL)
| (COMPONENT_RECENTS_LIST, &MSG_KEY_CHAR_E) => {
// Show delete popup
match self
.view
.get_props(COMPONENT_RADIO_BOOKMARK_DEL_RECENT)
.as_mut()
{
None => None,
Some(props) => {
let msg = self.view.update(
COMPONENT_RADIO_BOOKMARK_DEL_RECENT,
props.visible().build(),
);
self.update(msg)
}
}
self.mount_recent_del_dialog();
None
}
// Bookmark radio
// Del bookmarks
@@ -222,52 +199,65 @@ impl AuthActivity {
COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK,
Msg::OnSubmit(Payload::Unsigned(index)),
) => {
// Delete bookmark
self.del_bookmark(*index);
// TODO: view bookmarks
// Hide bookmark del
if let Some(props) = self
.view
.get_props(COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK)
.as_mut()
{
let msg = self.view.update(
COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK,
props.hidden().build(),
);
let _ = self.update(msg);
}
// 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.build()); // TODO: set rows
self.update(msg)
// hide bookmark delete
self.umount_bookmark_del_dialog();
// Index must be 0 => YES
match *index {
0 => {
// Get selected bookmark
match self.view.get_value(COMPONENT_BOOKMARKS_LIST) {
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
self.update(msg)
}
}
}
_ => None,
}
}
_ => None,
}
}
(COMPONENT_RADIO_BOOKMARK_DEL_RECENT, Msg::OnSubmit(Payload::Unsigned(index))) => {
// Delete recent
self.del_recent(*index);
// TODO: view bookmarks
// Hide bookmark del
if let Some(props) = self
.view
.get_props(COMPONENT_RADIO_BOOKMARK_DEL_RECENT)
.as_mut()
{
let msg = self
.view
.update(COMPONENT_RADIO_BOOKMARK_DEL_RECENT, props.hidden().build());
let _ = self.update(msg);
}
// 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.build()); // TODO: set rows
self.update(msg)
// hide bookmark delete
self.umount_recent_del_dialog();
// Index must be 0 => YES
match *index {
0 => {
// Get selected bookmark
match self.view.get_value(COMPONENT_RECENTS_LIST) {
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
self.update(msg)
}
}
}
_ => None,
}
}
_ => None,
}
}
// <ESC> hide tab
@@ -306,27 +296,13 @@ impl AuthActivity {
// Help
(_, &MSG_KEY_CTRL_H) => {
// Show help
match self.view.get_props(COMPONENT_TEXT_HELP).as_mut() {
Some(props) => {
let msg = self
.view
.update(COMPONENT_TEXT_HELP, props.visible().build());
self.update(msg)
}
None => None,
}
self.mount_help();
None
}
(COMPONENT_TEXT_HELP, &MSG_KEY_ENTER) | (COMPONENT_TEXT_HELP, &MSG_KEY_ESC) => {
// Hide text help
match self.view.get_props(COMPONENT_TEXT_HELP).as_mut() {
None => None,
Some(props) => {
let msg = self
.view
.update(COMPONENT_TEXT_HELP, props.hidden().build());
self.update(msg)
}
}
self.umount_help();
None
}
// Enter setup
(_, &MSG_KEY_CTRL_C) => {
@@ -336,23 +312,7 @@ impl AuthActivity {
// Save bookmark; show popup
(_, &MSG_KEY_CTRL_S) => {
// Show popup
if let Some(props) = self
.view
.get_props(COMPONENT_RADIO_BOOKMARK_SAVE_PWD)
.as_mut()
{
let msg = self
.view
.update(COMPONENT_RADIO_BOOKMARK_SAVE_PWD, props.visible().build());
let _ = self.update(msg);
}
if let Some(props) = self.view.get_props(COMPONENT_INPUT_BOOKMARK_NAME).as_mut()
{
let msg = self
.view
.update(COMPONENT_INPUT_BOOKMARK_NAME, props.visible().build());
let _ = self.update(msg);
}
self.mount_bookmark_save_dialog();
// Give focus to bookmark name
self.view.active(COMPONENT_INPUT_BOOKMARK_NAME);
None
@@ -385,47 +345,21 @@ impl AuthActivity {
_ => false,
};
// TODO: save bookmark
// Hide popup
if let Some(props) = self
.view
.get_props(COMPONENT_RADIO_BOOKMARK_SAVE_PWD)
.as_mut()
{
let msg = self
.view
.update(COMPONENT_RADIO_BOOKMARK_SAVE_PWD, props.hidden().build());
let _ = self.update(msg);
}
if let Some(props) = self.view.get_props(COMPONENT_INPUT_BOOKMARK_NAME).as_mut()
{
let msg = self
.view
.update(COMPONENT_INPUT_BOOKMARK_NAME, props.hidden().build());
let _ = self.update(msg);
}
// Umount popup
self.umount_bookmark_save_dialog();
None
}
// Hide save bookmark
(COMPONENT_INPUT_BOOKMARK_NAME, &MSG_KEY_ESC)
| (COMPONENT_RADIO_BOOKMARK_SAVE_PWD, &MSG_KEY_ESC) => {
// Hide popup
if let Some(props) = self
.view
.get_props(COMPONENT_RADIO_BOOKMARK_SAVE_PWD)
.as_mut()
{
let msg = self
.view
.update(COMPONENT_RADIO_BOOKMARK_SAVE_PWD, props.hidden().build());
let _ = self.update(msg);
}
if let Some(props) = self.view.get_props(COMPONENT_INPUT_BOOKMARK_NAME).as_mut()
{
let msg = self
.view
.update(COMPONENT_INPUT_BOOKMARK_NAME, props.hidden().build());
let _ = self.update(msg);
}
// Umount popup
self.umount_bookmark_save_dialog();
None
}
// Error message
(COMPONENT_TEXT_ERROR, &MSG_KEY_ENTER) => {
// Umount text error
self.umount_error();
None
}
// On submit on any unhandled (connect)
@@ -436,21 +370,7 @@ impl AuthActivity {
Some(Payload::Text(addr)) => addr,
_ => {
// Show error
if let Some(props) = self.view.get_props(COMPONENT_TEXT_ERROR).as_mut()
{
let msg = self.view.update(
COMPONENT_TEXT_ERROR,
props
.visible()
.with_texts(TextParts::new(
None,
Some(vec![TextSpan::from("Invalid address!")]),
))
.build(),
);
return self.update(msg);
}
// Return None
self.mount_error("Invalid address!");
return None;
}
};
@@ -458,20 +378,7 @@ impl AuthActivity {
Some(Payload::Unsigned(p)) => p as u16,
_ => {
// Show error
if let Some(props) = self.view.get_props(COMPONENT_TEXT_ERROR).as_mut()
{
let msg = self.view.update(
COMPONENT_TEXT_ERROR,
props
.visible()
.with_texts(TextParts::new(
None,
Some(vec![TextSpan::from("Invalid port number!")]),
))
.build(),
);
return self.update(msg);
}
self.mount_error("Invalid port number!");
// Return None
return None;
}

View File

@@ -29,7 +29,7 @@ use crate::ui::layout::components::{
bookmark_list::BookmarkList, input::Input, radio_group::RadioGroup, table::Table, text::Text,
};
use crate::ui::layout::props::{
PropValue, Props, PropsBuilder, TextParts, TextSpan, TextSpanBuilder,
InputType, PropValue, Props, PropsBuilder, TableBuilder, TextParts, TextSpan, TextSpanBuilder,
};
use crate::utils::fmt::align_text_center;
// Ext
@@ -37,7 +37,7 @@ use std::string::ToString;
use tui::{
layout::{Constraint, Corner, Direction, Layout, Rect},
style::{Color, Modifier, Style},
widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph, Tabs},
widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph, Tabs, Widget},
};
use unicode_width::UnicodeWidthStr;
@@ -45,7 +45,7 @@ impl AuthActivity {
/// ### init
///
/// Initialize view, mounting all startup components inside the view
pub fn init(&mut self) {
pub(super) fn init(&mut self) {
// Header
self.view.mount(super::COMPONENT_TEXT_HEADER, Box::new(
Text::new(
@@ -55,26 +55,394 @@ impl AuthActivity {
)
));
// Footer
self.view.mount(super::COMPONENT_TEXT_FOOTER, Box::new(
Text::new(
PropsBuilder::default().with_foreground(Color::White).with_texts(
TextParts::new(None, Some(vec![
TextSpanBuilder::new("Press ").bold().build(),
TextSpanBuilder::new("<CTRL+H>").bold().with_foreground(Color::Cyan).build(),
TextSpanBuilder::new(" to show keybindings; ").bold().build(),
TextSpanBuilder::new("<CTRL+C>").bold().with_foreground(Color::Cyan).build(),
TextSpanBuilder::new(" to enter setup").bold().build(),
]))
).build()
)
));
self.view.mount(
super::COMPONENT_TEXT_FOOTER,
Box::new(Text::new(
PropsBuilder::default()
.with_foreground(Color::White)
.with_texts(TextParts::new(
None,
Some(vec![
TextSpanBuilder::new("Press ").bold().build(),
TextSpanBuilder::new("<CTRL+H>")
.bold()
.with_foreground(Color::Cyan)
.build(),
TextSpanBuilder::new(" to show keybindings; ")
.bold()
.build(),
TextSpanBuilder::new("<CTRL+C>")
.bold()
.with_foreground(Color::Cyan)
.build(),
TextSpanBuilder::new(" to enter setup").bold().build(),
]),
))
.build(),
)),
);
// Address
self.view.mount(
super::COMPONENT_INPUT_ADDR,
Box::new(Input::new(
PropsBuilder::default()
.with_foreground(Color::Yellow)
.with_texts(TextParts::new(Some(String::from("Remote address")), None))
.build(),
)),
);
// Port
self.view.mount(
super::COMPONENT_INPUT_PORT,
Box::new(Input::new(
PropsBuilder::default()
.with_foreground(Color::LightCyan)
.with_texts(TextParts::new(Some(String::from("Port number")), None))
.with_input(InputType::Number)
.with_input_len(5)
.with_value(PropValue::Unsigned(22))
.build(),
)),
);
// Protocol
self.view.mount(
super::COMPONENT_RADIO_PROTOCOL,
Box::new(RadioGroup::new(
PropsBuilder::default()
.with_foreground(Color::LightGreen)
.with_texts(TextParts::new(
Some(String::from("Protocol")),
Some(vec![
TextSpan::from("SFTP"),
TextSpan::from("SCP"),
TextSpan::from("FTP"),
TextSpan::from("FTPS"),
]),
))
.build(),
)),
);
// Username
self.view.mount(
super::COMPONENT_INPUT_USERNAME,
Box::new(Input::new(
PropsBuilder::default()
.with_foreground(Color::LightMagenta)
.with_texts(TextParts::new(Some(String::from("Username")), None))
.build(),
)),
);
// Password
self.view.mount(
super::COMPONENT_INPUT_PASSWORD,
Box::new(Input::new(
PropsBuilder::default()
.with_foreground(Color::LightBlue)
.with_texts(TextParts::new(Some(String::from("Password")), None))
.with_input(InputType::Password)
.build(),
)),
);
// Version notice
if let Some(version) = self.new_version.as_ref() {
self.view.mount(
super::COMPONENT_TEXT_NEW_VERSION,
Box::new(Text::new(
PropsBuilder::default()
.with_foreground(Color::Yellow)
.with_texts(TextParts::new(None, Some(vec![format!("TermSCP {} is now available! Download it from <https://github.com/veeso/termscp/releases/latest>", version)])))
.bold()
.build()
))
);
}
}
/// ### view
///
///
/// Display view on canvas
pub fn view(&mut self) {
pub(super) fn view(&mut self) {
let mut ctx: Context = self.context.take().unwrap();
let _ = ctx.terminal.draw(|f| {
// Prepare chunks
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints(
[
Constraint::Percentage(70), // Auth Form
Constraint::Percentage(30), // Bookmarks
]
.as_ref(),
)
.split(f.size());
// Create explorer chunks
let auth_chunks = Layout::default()
.constraints(
[
Constraint::Length(5),
Constraint::Length(1), // Version
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
Constraint::Length(3),
]
.as_ref(),
)
.direction(Direction::Vertical)
.split(chunks[0]);
// Create bookmark chunks
let bookmark_chunks = Layout::default()
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
.direction(Direction::Horizontal)
.split(chunks[1]);
// Get focus holder
let focus: Option<String> = self.view.who_has_focus();
// Render
// Header
if let Some(render) = self.view.render(super::COMPONENT_TEXT_HEADER).as_ref() {
f.render_widget(render.widget, auth_chunks[0]);
}
});
}
// -- partials
/// ### view_bookmarks
///
/// Make text span from bookmarks
pub(super) fn view_bookmarks(&self) -> Vec<TextSpan> {
self.bookmarks_list
.iter()
.map(|x| TextSpan::from(x.as_str()))
.collect()
}
/// ### view_recent_connections
///
/// View recent connections
pub(super) fn view_recent_connections(&self) -> Vec<TextSpan> {
self.recents_list
.iter()
.map(|x| TextSpan::from(x.as_str()))
.collect()
}
// -- mount
/// ### mount_error
///
/// Mount error box
pub(super) fn mount_error(&mut self, text: &str) {
// Mount
self.view.mount(
super::COMPONENT_TEXT_ERROR,
Box::new(Text::new(
PropsBuilder::default()
.with_foreground(Color::Red)
.bold()
.with_texts(TextParts::new(None, Some(vec![TextSpan::from(text)])))
.build(),
)),
);
// Give focus to error
self.view.active(super::COMPONENT_TEXT_ERROR);
}
/// ### umount_error
///
/// Umount error message
pub(super) fn umount_error(&mut self) {
self.view.umount(super::COMPONENT_TEXT_ERROR);
}
/// ### mount_bookmark_del_dialog
///
/// Mount bookmark delete dialog
pub(super) fn mount_bookmark_del_dialog(&mut self) {
self.view.mount(
super::COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK,
Box::new(RadioGroup::new(
PropsBuilder::default()
.with_foreground(Color::Yellow)
.with_texts(TextParts::new(
Some(String::from("Delete bookmark?")),
Some(vec![TextSpan::from("Yes"), TextSpan::from("No")]),
))
.with_value(PropValue::Unsigned(1))
.build(),
)),
);
// Active
self.view
.active(super::COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK);
}
/// ### umount_bookmark_del_dialog
///
/// umount delete bookmark dialog
pub(super) fn umount_bookmark_del_dialog(&mut self) {
self.view
.umount(super::COMPONENT_RADIO_BOOKMARK_DEL_BOOKMARK);
}
/// ### mount_bookmark_del_dialog
///
/// Mount recent delete dialog
pub(super) fn mount_recent_del_dialog(&mut self) {
self.view.mount(
super::COMPONENT_RADIO_BOOKMARK_DEL_RECENT,
Box::new(RadioGroup::new(
PropsBuilder::default()
.with_foreground(Color::Yellow)
.with_texts(TextParts::new(
Some(String::from("Delete bookmark?")),
Some(vec![TextSpan::from("Yes"), TextSpan::from("No")]),
))
.with_value(PropValue::Unsigned(1))
.build(),
)),
);
// Active
self.view.active(super::COMPONENT_RADIO_BOOKMARK_DEL_RECENT);
}
/// ### umount_recent_del_dialog
///
/// umount delete recent dialog
pub(super) fn umount_recent_del_dialog(&mut self) {
self.view.umount(super::COMPONENT_RADIO_BOOKMARK_DEL_RECENT);
}
/// ### mount_bookmark_save_dialog
///
/// Mount bookmark save dialog
pub(super) fn mount_bookmark_save_dialog(&mut self) {
self.view.mount(
super::COMPONENT_INPUT_BOOKMARK_NAME,
Box::new(Input::new(
PropsBuilder::default()
.with_texts(TextParts::new(
Some(String::from("Save bookmark as...")),
None,
))
.build(),
)),
);
self.view.mount(
super::COMPONENT_RADIO_BOOKMARK_SAVE_PWD,
Box::new(RadioGroup::new(
PropsBuilder::default()
.with_foreground(Color::Red)
.with_texts(TextParts::new(
Some(String::from("Save password?")),
Some(vec![TextSpan::from("Yes"), TextSpan::from("No")]),
))
.with_value(PropValue::Unsigned(1))
.build(),
)),
);
// Give focus to input bookmark name
self.view.active(super::COMPONENT_INPUT_BOOKMARK_NAME);
}
/// ### umount_bookmark_save_dialog
///
/// Umount bookmark save dialog
pub(super) fn umount_bookmark_save_dialog(&mut self) {
self.view.umount(super::COMPONENT_RADIO_BOOKMARK_SAVE_PWD);
self.view.umount(super::COMPONENT_INPUT_BOOKMARK_NAME);
}
/// ### mount_help
///
/// Mount help
pub(super) fn mount_help(&mut self) {
self.view.mount(
super::COMPONENT_TEXT_HELP,
Box::new(Table::new(
PropsBuilder::default()
.with_texts(TextParts::table(
Some(String::from("Help")),
TableBuilder::default()
.add_col(
TextSpanBuilder::new("<ESC>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Quit TermSCP"))
.add_row()
.add_col(
TextSpanBuilder::new("<TAB>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Switch from form and bookmarks"))
.add_row()
.add_col(
TextSpanBuilder::new("<RIGHT/LEFT>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Switch bookmark tab"))
.add_row()
.add_col(
TextSpanBuilder::new("<UP/DOWN>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Move up/down in current tab"))
.add_row()
.add_col(
TextSpanBuilder::new("<ENTER>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Connect/Load bookmark"))
.add_row()
.add_col(
TextSpanBuilder::new("<DEL|E>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Delete selected bookmark"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+C>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Enter setup"))
.add_row()
.add_col(
TextSpanBuilder::new("<CTRL+S>")
.bold()
.with_foreground(Color::Cyan)
.build(),
)
.add_col(TextSpan::from(" Save bookmark"))
.build(),
))
.build(),
)),
);
// Active help
self.view.active(super::COMPONENT_TEXT_HELP);
}
/// ### umount_help
///
/// Umount help
pub(super) fn umount_help(&mut self) {
self.view.umount(super::COMPONENT_TEXT_HELP);
}
}