use crossterm::event::{KeyCode, KeyEvent}; use jocalsend::ReceiveDialog; use log::{debug, error, warn}; use tui_input::backend::crossterm::EventHandler; use crate::app::{App, CurrentScreen, FileMode, SendingScreen}; impl App { pub(super) async fn handle_key_event( &mut self, key_event: KeyEvent, event: crossterm::event::Event, ) { let code = key_event.code; let mode = self.screen.last_mut().unwrap(); match mode { CurrentScreen::Main | CurrentScreen::Help | CurrentScreen::Logging | CurrentScreen::Receiving | CurrentScreen::Sending(SendingScreen::Files(FileMode::Picking)) | CurrentScreen::Sending(SendingScreen::Peers) => match code { KeyCode::Char('q') => self.exit().await, KeyCode::Char('s') => self.send(), KeyCode::Char('r') => self.recv(), KeyCode::Char('l') => self.logs(), KeyCode::Char('m') => self.main(), KeyCode::Char('h') | KeyCode::Char('?') => self.help(), KeyCode::Char('c') => self.service.refresh_peers().await, KeyCode::Esc => self.pop(), _ => match mode { CurrentScreen::Main | CurrentScreen::Help => {} CurrentScreen::Logging => match code { KeyCode::Left => super::change_log_level(-1), KeyCode::Right => super::change_log_level(1), _ => {} }, CurrentScreen::Receiving => match code { KeyCode::Up => self.receiving_state.select_previous(), KeyCode::Down => self.receiving_state.select_next(), KeyCode::Char('a') => self.accept(), KeyCode::Char('d') => self.deny(), _ => {} }, CurrentScreen::Sending(sending_screen) => match sending_screen { // we can only be in picking mode SendingScreen::Files(fmode) => match code { KeyCode::Char('t') => *sending_screen = SendingScreen::Text, KeyCode::Tab => *sending_screen = SendingScreen::Peers, KeyCode::Enter => self.chdir_or_send_file().await, KeyCode::Char('/') => { *fmode = FileMode::Fuzzy; } _ => self.file_finder.handle(&event).unwrap_or_default(), }, SendingScreen::Peers => match code { KeyCode::Tab => { *sending_screen = SendingScreen::Files(FileMode::Picking) } KeyCode::Char('t') => *sending_screen = SendingScreen::Text, KeyCode::Enter => self.send_content().await, KeyCode::Up => self.peer_state.select_previous(), KeyCode::Down => self.peer_state.select_next(), _ => {} }, SendingScreen::Text => unreachable!(), }, CurrentScreen::Stopping => unreachable!(), }, }, // we only need to deal with sending text now or doing fuzzy matching CurrentScreen::Sending(sending_screen) => match sending_screen { SendingScreen::Text => match code { KeyCode::Tab => *sending_screen = SendingScreen::Peers, KeyCode::Enter => self.send_text().await, KeyCode::Esc => { self.text = None; self.input.reset(); *sending_screen = SendingScreen::Files(FileMode::Picking); } _ => { if let Some(changed) = self.input.handle_event(&event) && changed.value { if self.input.value().is_empty() { self.text = None; } else { self.text = Some(self.input.to_string()); } } } }, SendingScreen::Files(fmode) => { if *fmode == FileMode::Fuzzy { match code { KeyCode::Tab => *sending_screen = SendingScreen::Peers, KeyCode::Enter => self.chdir_or_send_file().await, KeyCode::Esc => { self.file_finder.reset_fuzzy(); *fmode = FileMode::Picking; } KeyCode::Up | KeyCode::Down => { if let Err(e) = self.file_finder.handle(&event) { log::error!("error selecting file: {e:?}"); } } _ => { self.file_finder.index(); if let Some(changed) = self.file_finder.input.handle_event(&event) && changed.value { let id = self .file_finder .fuzzy .search(self.file_finder.input.value()) .first() .copied() .unwrap_or(0); self.file_finder.explorer.set_selected_idx(id); } } } } } SendingScreen::Peers => unreachable!(), }, CurrentScreen::Stopping => {} } } pub async fn exit(&mut self) { self.screen.push(CurrentScreen::Stopping); self.service.stop().await; } pub fn send(&mut self) { let last = self.screen.last(); match last { Some(CurrentScreen::Sending(_)) => {} _ => self .screen .push(CurrentScreen::Sending(SendingScreen::Files( FileMode::Picking, ))), } } pub fn recv(&mut self) { let last = self.screen.last(); match last { Some(CurrentScreen::Receiving) => {} _ => self.screen.push(CurrentScreen::Receiving), } } pub fn logs(&mut self) { let last = self.screen.last(); match last { Some(CurrentScreen::Logging) => {} _ => self.screen.push(CurrentScreen::Logging), } } pub fn pop(&mut self) { self.screen.pop(); if self.screen.last().is_none() { self.screen.push(CurrentScreen::Main); } } pub fn main(&mut self) { let last = self.screen.last(); match last { Some(CurrentScreen::Main) => {} _ => self.screen.push(CurrentScreen::Main), } } pub fn help(&mut self) { let last = self.screen.last(); match last { Some(CurrentScreen::Help) => {} _ => self.screen.push(CurrentScreen::Help), } } // accept a content receive request fn accept(&mut self) { let Some(idx) = self.receiving_state.selected() else { return; }; // keys are sorted, so we can use the table selection index let keys: Vec<_> = self.receive_requests.keys().collect(); let Some(key) = keys.get(idx) else { warn!("could not get id from selection index {idx}"); return; }; let Some(req) = self.receive_requests.get(key) else { return; }; if let Err(e) = req.tx.send(ReceiveDialog::Approve) { error!("got error sending upload confirmation: {e:?}"); }; } // reject an content receive request fn deny(&mut self) { let Some(idx) = self.receiving_state.selected() else { return; }; // keys are sorted, so we can use the table selection index let keys: Vec<_> = self.receive_requests.keys().cloned().collect(); let Some(key) = keys.get(idx) else { warn!("could not get id from selection index {idx}"); return; }; let Some(req) = self.receive_requests.get(key).cloned() else { return; }; if let Err(e) = req.tx.send(ReceiveDialog::Deny) { error!("got error sending upload confirmation: {e:?}"); }; self.receive_requests.remove(key); } async fn chdir_or_send_file(&mut self) { let file = self.file_finder.explorer.current().path().clone(); if file.is_dir() && let Err(e) = self.file_finder.set_cwd(&file) { error!("could not list directory {file:?}: {e}"); return; } else if file.is_dir() { return; } let Some(peer_idx) = self.peer_state.selected() else { warn!("no peer selected to send to"); return; }; let Some(peer) = self.peers.get(peer_idx) else { warn!("invalid peer index {peer_idx}"); return; }; if file.is_file() { debug!("sending {file:?}"); if let Err(e) = self.service.send_file(&peer.fingerprint, file).await { error!("got error sending content: {e:?}"); } } } // send content to selected peer, or change directories in the file explorer async fn send_text(&mut self) { debug!("sending text"); let Some(peer_idx) = self.peer_state.selected() else { debug!("no peer selected to send to"); return; }; let Some(peer) = self.peers.get(peer_idx) else { warn!("invalid peer index {peer_idx}"); return; }; let Some(text) = &self.text else { debug!("no text to send"); return; }; if let Err(e) = self.service.send_text(&peer.fingerprint, text).await { error!("got error sending \"{text}\" to {}: {e:?}", peer.alias); } } async fn send_content(&mut self) { if self.text.is_some() { self.send_text().await; } else { self.chdir_or_send_file().await; } } }