Working on input events for filetransfer activity

This commit is contained in:
ChristianVisintin
2020-11-25 11:22:26 +01:00
parent 4431c70a65
commit 2d90eb15af

View File

@@ -40,7 +40,9 @@ use crate::filetransfer::FileTransfer;
use crossterm::event::Event as InputEvent; use crossterm::event::Event as InputEvent;
use crossterm::event::KeyCode; use crossterm::event::KeyCode;
use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::collections::VecDeque;
use std::io::Stdout; use std::io::Stdout;
use std::time::Instant;
use tui::{ use tui::{
backend::CrosstermBackend, backend::CrosstermBackend,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@@ -53,6 +55,7 @@ use unicode_width::UnicodeWidthStr;
// Types // Types
type DialogCallback = fn(); type DialogCallback = fn();
type OnInputSubmitCallback = fn(String);
/// ### FileTransferParams /// ### FileTransferParams
/// ///
@@ -74,23 +77,33 @@ enum InputField {
Logs, Logs,
} }
/// ### DialogYesNoOption
///
/// Current yes/no dialog option
#[derive(std::cmp::PartialEq, Clone)]
enum DialogYesNoOption {
Yes,
No,
}
/// ## PopupType /// ## PopupType
/// ///
/// PopupType describes the type of popup /// PopupType describes the type of popup
#[derive(std::cmp::PartialEq)] #[derive(std::cmp::PartialEq, Clone)]
enum PopupType { enum PopupType {
Alert(Color, String), Alert(Color, String), // Block color; Block text
Wait(String),
Fatal(String), // Must quit after being hidden Fatal(String), // Must quit after being hidden
Progress(String), Input(String, OnInputSubmitCallback), // Input description; Callback for submit
YesNo(String, DialogCallback, DialogCallback), // Yes, no callback Progress(String), // Progress block text
Wait(String), // Wait block text
YesNo(String, DialogYesNoOption, DialogCallback, DialogCallback), // Yes, no callback
} }
/// ## InputMode /// ## InputMode
/// ///
/// InputMode describes the current input mode /// InputMode describes the current input mode
/// Each input mode handle the input events in a different way /// Each input mode handle the input events in a different way
#[derive(std::cmp::PartialEq)] #[derive(std::cmp::PartialEq, Clone)]
enum InputMode { enum InputMode {
Explorer, Explorer,
Popup(PopupType), Popup(PopupType),
@@ -133,6 +146,7 @@ enum LogLevel {
/// ///
/// Log record entry /// Log record entry
struct LogRecord { struct LogRecord {
pub time: Instant,
pub level: LogLevel, pub level: LogLevel,
pub msg: String, pub msg: String,
} }
@@ -143,6 +157,7 @@ impl LogRecord {
/// Instantiates a new LogRecord /// Instantiates a new LogRecord
pub fn new(level: LogLevel, msg: &str) -> LogRecord { pub fn new(level: LogLevel, msg: &str) -> LogRecord {
LogRecord { LogRecord {
time: Instant::now(),
level: level, level: level,
msg: String::from(msg), msg: String::from(msg),
} }
@@ -153,18 +168,20 @@ impl LogRecord {
/// ///
/// FileTransferActivity is the data holder for the file transfer activity /// FileTransferActivity is the data holder for the file transfer activity
pub struct FileTransferActivity { pub struct FileTransferActivity {
pub disconnected: bool, pub disconnected: bool, // Has disconnected from remote?
pub quit: bool, pub quit: bool, // Has quit term scp?
params: FileTransferParams, params: FileTransferParams, // FT connection params
local: FileExplorer, client: Box<dyn FileTransfer>, // File transfer client
remote: FileExplorer, local: FileExplorer, // Local File explorer state
tab: FileExplorerTab, remote: FileExplorer, // Remote File explorer state
log_index: usize, tab: FileExplorerTab, // Current selected tab
log_records: Vec<LogRecord>, log_index: usize, // Current log index entry selected
progress: usize, log_records: VecDeque<LogRecord>, // Log records
input_mode: InputMode, log_size: usize, // Log records size (max)
input_field: InputField, progress: usize, // Current progress percentage
client: Box<dyn FileTransfer>, input_mode: InputMode, // Current input mode
input_field: InputField, // Current selected input mode
input_txt: String, // Input text
} }
impl FileTransferActivity { impl FileTransferActivity {
@@ -181,10 +198,12 @@ impl FileTransferActivity {
remote: FileExplorer::new(), remote: FileExplorer::new(),
tab: FileExplorerTab::Local, tab: FileExplorerTab::Local,
log_index: 0, log_index: 0,
log_records: Vec::new(), log_records: VecDeque::with_capacity(256), // 256 events is enough I guess
log_size: 256, // Must match with capacity
progress: 0, progress: 0,
input_mode: InputMode::Explorer, input_mode: InputMode::Explorer,
input_field: InputField::Explorer, input_field: InputField::Explorer,
input_txt: String::new(),
client: match protocol { client: match protocol {
FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new()), FileTransferProtocol::Sftp => Box::new(SftpFileTransfer::new()),
FileTransferProtocol::Ftp => Box::new(SftpFileTransfer::new()), // FIXME: FTP FileTransferProtocol::Ftp => Box::new(SftpFileTransfer::new()), // FIXME: FTP
@@ -209,6 +228,237 @@ impl FileTransferActivity {
} }
} }
/// ### log
///
/// Add message to log events
fn log(&mut self, level: LogLevel, msg: &str) {
// Create log record
let record: LogRecord = LogRecord::new(level, msg);
//Check if history overflows the size
if self.log_records.len() + 1 > self.log_size {
self.log_records.pop_back(); // Start cleaning events from back
}
// Eventually push front the new record
self.log_records.push_front(record);
// Set log index
self.log_index = self.log_records.len();
}
/// ### switch_input_field
///
/// Switch input field based on current input field
fn switch_input_field(&mut self) {
self.input_field = match self.input_field {
InputField::Explorer => InputField::Logs,
InputField::Logs => InputField::Explorer,
}
}
/// ### handle_input_event
///
/// Handle input event based on current input mode
fn handle_input_event(&mut self, context: &mut Context, ev: &InputEvent) {
match &self.input_mode {
InputMode::Explorer => self.handle_input_event_mode_explorer(context, ev),
InputMode::Popup(ptype) => self.handle_input_event_mode_popup(ev, ptype.clone()),
}
}
/// ### handle_input_event_mode_explorer
///
/// Input event handler for explorer mode
fn handle_input_event_mode_explorer(&mut self, context: &mut Context, ev: &InputEvent) {
// Match input field
match self.input_field {
InputField::Explorer => match self.tab { // Match current selected tab
FileExplorerTab::Local => self.handle_input_event_mode_explorer_tab_local(context, ev),
FileExplorerTab::Remote => self.handle_input_event_mode_explorer_tab_remote(context, ev)
},
InputField::Logs => self.handle_input_event_mode_explorer_log(ev)
}
}
/// ### handle_input_event_mode_explorer_tab_local
///
/// Input event handler for explorer mode when localhost tab is selected
fn handle_input_event_mode_explorer_tab_local(&mut self, context: &mut Context, ev: &InputEvent) {
// TODO: implement
}
/// ### handle_input_event_mode_explorer_tab_local
///
/// Input event handler for explorer mode when remote tab is selected
fn handle_input_event_mode_explorer_tab_remote(&mut self, context: &mut Context, ev: &InputEvent) {
// TODO: implement
}
/// ### handle_input_event_mode_explorer_log
///
/// Input even handler for explorer mode when log tab is selected
fn handle_input_event_mode_explorer_log(&mut self, ev: &InputEvent) {
// Match event
let records_block: usize = 16;
match ev {
InputEvent::Key(key) => {
match key.code {
KeyCode::Tab => self.switch_input_field(), // <TAB> switch tab
KeyCode::Up => {
// Decrease log index
if self.log_index > 0 {
self.log_index = self.log_index - 1;
}
},
KeyCode::Down => {
// Increase log index
if self.log_index + 1 >= self.log_size {
self.log_index = self.log_index + 1;
}
},
KeyCode::PageUp => {
// Fast decreasing of log index
if self.log_index >= records_block {
self.log_index = self.log_index - records_block; // Decrease by `records_block` if possible
} else {
self.log_index = 0; // Set to 0 otherwise
}
},
KeyCode::PageDown => {
// Fast increasing of log index
if self.log_index + records_block >= self.log_size { // If overflows, set to size
self.log_index = self.log_size - 1;
} else {
self.log_index = self.log_index + records_block; // Increase by `records_block`
}
}
_ => { /* Nothing to do */ }
}
}
_ => { /* Nothing to do */ }
}
}
/// ### handle_input_event_mode_explorer
///
/// Input event handler for popup mode. Handler is then based on Popup type
fn handle_input_event_mode_popup(&mut self, ev: &InputEvent, popup: PopupType) {
match popup {
PopupType::Alert(_, _) => self.handle_input_event_mode_popup_alert(ev),
PopupType::Fatal(_) => self.handle_input_event_mode_popup_fatal(ev),
PopupType::Input(_, cb) => self.handle_input_event_mode_popup_input(ev, cb),
PopupType::Progress(_) => self.handle_input_event_mode_popup_progress(ev),
PopupType::Wait(_) => self.handle_input_event_mode_popup_wait(ev),
PopupType::YesNo(_, opt, yes_cb, no_cb) => self.handle_input_event_mode_popup_yesno(ev, opt, yes_cb, no_cb),
}
}
/// ### handle_input_event_mode_explorer_alert
///
/// Input event handler for popup alert
fn handle_input_event_mode_popup_alert(&mut self, ev: &InputEvent) {
// If enter, close popup
match ev {
InputEvent::Key(key) => {
match key.code {
KeyCode::Enter => {
// Set input mode back to explorer
self.input_mode = InputMode::Explorer;
}
_ => { /* Nothing to do */ }
}
}
_ => { /* Nothing to do */ }
}
}
/// ### handle_input_event_mode_explorer_alert
///
/// Input event handler for popup alert
fn handle_input_event_mode_popup_fatal(&mut self, ev: &InputEvent) {
// If enter, close popup
match ev {
InputEvent::Key(key) => {
match key.code {
KeyCode::Enter => {
// Set quit to true; since a fatal error happened
self.quit = true;
}
_ => { /* Nothing to do */ }
}
}
_ => { /* Nothing to do */ }
}
}
/// ### handle_input_event_mode_popup_input
///
/// Input event handler for input popup
fn handle_input_event_mode_popup_input(&mut self, ev: &InputEvent, cb: OnInputSubmitCallback) {
// If enter, close popup, otherwise push chars to input
match ev {
InputEvent::Key(key) => {
match key.code {
KeyCode::Enter => {
// Submit
let input_text: String = self.input_txt.clone();
// Clear current input text
self.input_txt.clear();
// Set mode back to explorer BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh?
self.input_mode = InputMode::Explorer;
// Call cb
cb(input_text);
}
KeyCode::Char(ch) => self.input_txt.push(ch),
_ => { /* Nothing to do */ }
}
}
_ => { /* Nothing to do */ }
}
}
/// ### handle_input_event_mode_explorer_alert
///
/// Input event handler for popup alert
fn handle_input_event_mode_popup_progress(&mut self, ev: &InputEvent) {
// There's nothing you can do here I guess... maybe ctrl+c in the future idk
match ev {
_ => { /* Nothing to do */ }
}
}
/// ### handle_input_event_mode_explorer_alert
///
/// Input event handler for popup alert
fn handle_input_event_mode_popup_wait(&mut self, ev: &InputEvent) {
// There's nothing you can do here I guess... maybe ctrl+c in the future idk
match ev {
_ => { /* Nothing to do */ }
}
}
/// ### handle_input_event_mode_explorer_alert
///
/// Input event handler for popup alert
fn handle_input_event_mode_popup_yesno(&mut self, ev: &InputEvent, opt: DialogYesNoOption, yes_cb: DialogCallback, no_cb: DialogCallback) {
// If enter, close popup
match ev {
InputEvent::Key(key) => {
match key.code {
KeyCode::Enter => {
// @! Set input mode to Explorer BEFORE CALLBACKS!!! Callback can then overwrite this, clever uh?
self.input_mode = InputMode::Explorer;
// Check if user selected yes or not
match opt {
DialogYesNoOption::No => no_cb(),
DialogYesNoOption::Yes => yes_cb()
}
}
_ => { /* Nothing to do */ }
}
}
_ => { /* Nothing to do */ }
}
}
/// ### draw /// ### draw
/// ///
/// Draw UI /// Draw UI
@@ -239,7 +489,7 @@ impl FileTransferActivity {
), ),
Span::raw("quit\t"), Span::raw("quit\t"),
Span::styled( Span::styled(
"<UP,DOWN>", "<TAB>",
Style::default() Style::default()
.bg(Color::Cyan) .bg(Color::Cyan)
.fg(Color::White) .fg(Color::White)
@@ -283,6 +533,12 @@ impl FileTransferActivity {
} }
} }
/**
* Activity Trait
* Keep it clean :)
* Use methods instead!
*/
impl Activity for FileTransferActivity { impl Activity for FileTransferActivity {
/// ### on_create /// ### on_create
/// ///
@@ -293,8 +549,6 @@ impl Activity for FileTransferActivity {
let _ = enable_raw_mode(); let _ = enable_raw_mode();
// Clear terminal // Clear terminal
let _ = context.terminal.clear(); let _ = context.terminal.clear();
// Set init state to connecting popup
self.input_mode = InputMode::Popup(PopupType::Wait(format!("Connecting to {}:{}...", self.params.address, self.params.port)));
} }
/// ### on_draw /// ### on_draw
@@ -302,17 +556,28 @@ impl Activity for FileTransferActivity {
/// `on_draw` is the function which draws the graphical interface. /// `on_draw` is the function which draws the graphical interface.
/// This function must be called at each tick to refresh the interface /// This function must be called at each tick to refresh the interface
fn on_draw(&mut self, context: &mut Context) { fn on_draw(&mut self, context: &mut Context) {
// draw interface // Check if connected
if ! self.client.is_connected() {
// Set init state to connecting popup
self.input_mode = InputMode::Popup(PopupType::Wait(format!("Connecting to {}:{}...", self.params.address, self.params.port)));
// Force ui draw
let _ = context.terminal.draw(|f| { let _ = context.terminal.draw(|f| {
self.draw(f); self.draw(f);
}); });
// Check if connected
if ! self.client.is_connected() {
// Connect to remote // Connect to remote
self.connect(); self.connect();
} }
// TODO: logic // Handle input events FIXME: read one or multiple?
// TODO: handle input events if let Ok(event) = context.input_hnd.read_event() {
// Iterate over input events
if let Some(event) = event {
self.handle_input_event(context, &event);
}
}
// @! draw interface
let _ = context.terminal.draw(|f| {
self.draw(f);
});
} }
/// ### on_destroy /// ### on_destroy