mirror of
https://github.com/kristoferssolo/traxor.git
synced 2025-10-21 20:10:35 +00:00
feat(move): add move functionality
This commit is contained in:
parent
095b102ecf
commit
8870478cc6
@ -13,6 +13,7 @@ delete = "d"
|
||||
delete_force = "D"
|
||||
select = " "
|
||||
toggle_help = "?"
|
||||
move = "m"
|
||||
|
||||
[colors]
|
||||
highlight_background = "magenta"
|
||||
|
||||
@ -6,7 +6,7 @@ pub enum Action {
|
||||
NextTorrent,
|
||||
PrevTorrent,
|
||||
SwitchTab(u8),
|
||||
ToggleHelp, // Add this line
|
||||
ToggleHelp,
|
||||
ToggleTorrent,
|
||||
ToggleAll,
|
||||
PauseAll,
|
||||
@ -15,4 +15,6 @@ pub enum Action {
|
||||
Delete(bool),
|
||||
Rename,
|
||||
Select,
|
||||
Submit,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
use super::{types::Selected, Torrents};
|
||||
use color_eyre::{eyre::eyre, Result};
|
||||
use std::{collections::HashSet, path::Path};
|
||||
use transmission_rpc::types::{Torrent, TorrentAction, TorrentStatus};
|
||||
|
||||
impl Torrents {
|
||||
pub async fn toggle(&mut self, ids: Selected) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn toggle(&mut self, ids: Selected) -> Result<()> {
|
||||
let ids: HashSet<_> = ids.into();
|
||||
let torrents_to_toggle: Vec<_> = self
|
||||
.torrents
|
||||
@ -20,15 +21,13 @@ impl Torrents {
|
||||
self.client
|
||||
.torrent_action(action, vec![id])
|
||||
.await
|
||||
.map_err(|e| {
|
||||
color_eyre::eyre::eyre!("Transmission RPC error: {}", e.to_string())
|
||||
})?;
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn toggle_all(&mut self) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn toggle_all(&mut self) -> Result<()> {
|
||||
let torrents_to_toggle: Vec<_> = self
|
||||
.torrents
|
||||
.iter()
|
||||
@ -49,18 +48,16 @@ impl Torrents {
|
||||
self.client
|
||||
.torrent_action(action, vec![id])
|
||||
.await
|
||||
.map_err(|e| {
|
||||
color_eyre::eyre::eyre!("Transmission RPC error: {}", e.to_string())
|
||||
})?;
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_all(&mut self) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn start_all(&mut self) -> Result<()> {
|
||||
self.action_all(TorrentAction::StartNow).await
|
||||
}
|
||||
|
||||
pub async fn stop_all(&mut self) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn stop_all(&mut self) -> Result<()> {
|
||||
self.action_all(TorrentAction::Stop).await
|
||||
}
|
||||
|
||||
@ -69,43 +66,35 @@ impl Torrents {
|
||||
torrent: &Torrent,
|
||||
location: &Path,
|
||||
move_from: Option<bool>,
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
) -> Result<()> {
|
||||
if let Some(id) = torrent.id() {
|
||||
self.client
|
||||
.torrent_set_location(vec![id], location.to_string_lossy().into(), move_from)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
color_eyre::eyre::eyre!("Transmission RPC error: {}", e.to_string())
|
||||
})?;
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
&mut self,
|
||||
ids: Selected,
|
||||
delete_local_data: bool,
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn delete(&mut self, ids: Selected, delete_local_data: bool) -> Result<()> {
|
||||
self.client
|
||||
.torrent_remove(ids.into(), delete_local_data)
|
||||
.await
|
||||
.map_err(|e| color_eyre::eyre::eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn rename(&mut self, torrent: &Torrent, name: &Path) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn rename(&mut self, torrent: &Torrent, name: &Path) -> Result<()> {
|
||||
if let (Some(id), Some(old_name)) = (torrent.id(), torrent.name.clone()) {
|
||||
self.client
|
||||
.torrent_rename_path(vec![id], old_name, name.to_string_lossy().into())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
color_eyre::eyre::eyre!("Transmission RPC error: {}", e.to_string())
|
||||
})?;
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn action_all(&mut self, action: TorrentAction) -> color_eyre::eyre::Result<()> {
|
||||
async fn action_all(&mut self, action: TorrentAction) -> Result<()> {
|
||||
let ids = self
|
||||
.torrents
|
||||
.iter()
|
||||
@ -115,7 +104,7 @@ impl Torrents {
|
||||
self.client
|
||||
.torrent_action(action, ids)
|
||||
.await
|
||||
.map_err(|e| color_eyre::eyre::eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
use crate::config::Config;
|
||||
use color_eyre::Result;
|
||||
use ratatui::widgets::TableState;
|
||||
use types::Selected;
|
||||
pub use {tab::Tab, torrent::Torrents};
|
||||
@ -20,12 +21,15 @@ pub struct App<'a> {
|
||||
pub torrents: Torrents,
|
||||
pub show_help: bool,
|
||||
pub config: Config,
|
||||
pub input: String,
|
||||
pub cursor_position: usize,
|
||||
pub input_mode: bool,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
/// Constructs a new instance of [`App`].
|
||||
/// Returns instance of `Self`.
|
||||
pub fn new(config: Config) -> color_eyre::eyre::Result<Self> {
|
||||
pub fn new(config: Config) -> Result<Self> {
|
||||
Ok(Self {
|
||||
running: true,
|
||||
tabs: &[Tab::All, Tab::Active, Tab::Downloading],
|
||||
@ -34,11 +38,14 @@ impl<'a> App<'a> {
|
||||
torrents: Torrents::new()?, // Handle the Result here
|
||||
show_help: false,
|
||||
config,
|
||||
input: String::new(),
|
||||
cursor_position: 0,
|
||||
input_mode: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Handles the tick event of the terminal.
|
||||
pub async fn tick(&mut self) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn tick(&mut self) -> Result<()> {
|
||||
self.torrents.update().await?;
|
||||
Ok(())
|
||||
}
|
||||
@ -122,20 +129,28 @@ impl<'a> App<'a> {
|
||||
self.show_help = true;
|
||||
}
|
||||
|
||||
pub async fn toggle_torrents(&mut self) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn toggle_torrents(&mut self) -> Result<()> {
|
||||
let ids = self.selected(false);
|
||||
self.torrents.toggle(ids).await?;
|
||||
self.close_help();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(&mut self, delete_local_data: bool) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn delete(&mut self, delete_local_data: bool) -> Result<()> {
|
||||
let ids = self.selected(false);
|
||||
self.torrents.delete(ids, delete_local_data).await?;
|
||||
self.close_help();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn move_torrent(&mut self) -> Result<()> {
|
||||
self.torrents.move_selection(&self.input).await?;
|
||||
self.input.clear();
|
||||
self.cursor_position = 0;
|
||||
self.input_mode = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn select(&mut self) {
|
||||
if let Selected::Current(current_id) = self.selected(true) {
|
||||
if self.torrents.selected.contains(¤t_id) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::{eyre::eyre, Result};
|
||||
use std::{collections::HashSet, fmt::Debug};
|
||||
use transmission_rpc::{
|
||||
types::{Torrent, TorrentGetField},
|
||||
types::{Id, Torrent, TorrentGetField},
|
||||
TransClient,
|
||||
};
|
||||
use url::Url;
|
||||
@ -50,11 +50,20 @@ impl Torrents {
|
||||
.client
|
||||
.torrent_get(self.fields.clone(), None)
|
||||
.await
|
||||
.map_err(|e| color_eyre::eyre::eyre!("Transmission RPC error: {}", e.to_string()))?
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?
|
||||
.arguments
|
||||
.torrents;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub async fn move_selection(&mut self, location: &str) -> Result<()> {
|
||||
let ids: Vec<Id> = self.selected.iter().map(|id| Id::Id(*id)).collect();
|
||||
self.client
|
||||
.torrent_set_location(ids, location.to_string(), Some(true))
|
||||
.await
|
||||
.map_err(|e| eyre!("Transmission RPC error: {}", e.to_string()))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Torrents {
|
||||
@ -66,7 +75,9 @@ impl Debug for Torrents {
|
||||
write!(
|
||||
f,
|
||||
"fields:
|
||||
{:?};\n\ntorrents: {:?}",
|
||||
{:?};
|
||||
|
||||
torrents: {:?}",
|
||||
fields, self.torrents
|
||||
)
|
||||
}
|
||||
|
||||
@ -11,11 +11,11 @@ use transmission_rpc::types::{
|
||||
|
||||
pub trait Wrapper {
|
||||
fn title(&self) -> String {
|
||||
"".to_string()
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn value(&self, torrent: &Torrent) -> String {
|
||||
format!("{}", torrent.name.as_ref().unwrap_or(&String::from("")))
|
||||
torrent.name.clone().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn width(&self) -> u16 {
|
||||
|
||||
@ -18,6 +18,7 @@ pub struct KeybindsConfig {
|
||||
pub delete_force: Option<String>,
|
||||
pub select: Option<String>,
|
||||
pub toggle_help: Option<String>,
|
||||
pub move_torrent: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for KeybindsConfig {
|
||||
@ -37,6 +38,7 @@ impl Default for KeybindsConfig {
|
||||
delete_force: Some("D".to_string()),
|
||||
select: Some(" ".to_string()),
|
||||
toggle_help: Some("?".to_string()),
|
||||
move_torrent: Some("m".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
|
||||
213
src/handler.rs
213
src/handler.rs
@ -1,109 +1,68 @@
|
||||
use crate::app::{action::Action, App};
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use tracing::{event, info_span, Level};
|
||||
|
||||
/// Handles the key events of [`App`].
|
||||
#[tracing::instrument]
|
||||
pub fn get_action(key_event: KeyEvent, app: &App) -> Option<Action> {
|
||||
let span = info_span!("get_action");
|
||||
let _enter = span.enter();
|
||||
event!(Level::INFO, "handling key event: {:?}", key_event);
|
||||
|
||||
let config_keybinds = &app.config.keybinds;
|
||||
|
||||
// Helper to check if a KeyEvent matches a configured keybind string
|
||||
let matches_keybind = |event: &KeyEvent, config_key: &Option<String>| {
|
||||
if let Some(key_str) = config_key {
|
||||
let parts: Vec<&str> = key_str.split('+').collect();
|
||||
let mut parsed_modifiers = KeyModifiers::NONE;
|
||||
let mut parsed_key_code = None;
|
||||
|
||||
for part in &parts {
|
||||
match part.to_lowercase().as_str() {
|
||||
"ctrl" => parsed_modifiers.insert(KeyModifiers::CONTROL),
|
||||
"alt" => parsed_modifiers.insert(KeyModifiers::ALT),
|
||||
"shift" => parsed_modifiers.insert(KeyModifiers::SHIFT),
|
||||
"esc" => parsed_key_code = Some(KeyCode::Esc),
|
||||
"enter" => parsed_key_code = Some(KeyCode::Enter),
|
||||
"left" => parsed_key_code = Some(KeyCode::Left),
|
||||
"right" => parsed_key_code = Some(KeyCode::Right),
|
||||
"up" => parsed_key_code = Some(KeyCode::Up),
|
||||
"down" => parsed_key_code = Some(KeyCode::Down),
|
||||
"tab" => parsed_key_code = Some(KeyCode::Tab),
|
||||
"backspace" => parsed_key_code = Some(KeyCode::Backspace),
|
||||
"delete" => parsed_key_code = Some(KeyCode::Delete),
|
||||
"home" => parsed_key_code = Some(KeyCode::Home),
|
||||
"end" => parsed_key_code = Some(KeyCode::End),
|
||||
"pageup" => parsed_key_code = Some(KeyCode::PageUp),
|
||||
"pagedown" => parsed_key_code = Some(KeyCode::PageDown),
|
||||
"null" => parsed_key_code = Some(KeyCode::Null),
|
||||
"insert" => parsed_key_code = Some(KeyCode::Insert),
|
||||
_ => {
|
||||
if part.len() == 1 {
|
||||
if let Some(c) = part.chars().next() {
|
||||
parsed_key_code = Some(KeyCode::Char(c));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if part.starts_with("f") && part.len() > 1 {
|
||||
if let Ok(f_num) = part[1..].parse::<u8>() {
|
||||
parsed_key_code = Some(KeyCode::F(f_num));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if parsed_key_code.is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
event.code == parsed_key_code.unwrap() && event.modifiers == parsed_modifiers
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
fn handle_input(key_event: KeyEvent, app: &mut App) -> Option<Action> {
|
||||
match key_event.code {
|
||||
_ if matches_keybind(&key_event, &config_keybinds.quit) => Some(Action::Quit),
|
||||
_ if matches_keybind(&key_event, &config_keybinds.next_tab) => Some(Action::NextTab),
|
||||
_ if matches_keybind(&key_event, &config_keybinds.prev_tab) => Some(Action::PrevTab),
|
||||
_ if matches_keybind(&key_event, &config_keybinds.next_torrent) => {
|
||||
Some(Action::NextTorrent)
|
||||
KeyCode::Enter => Some(Action::Submit),
|
||||
KeyCode::Char(c) => {
|
||||
app.input.push(c);
|
||||
app.cursor_position = app.input.len();
|
||||
None
|
||||
}
|
||||
_ if matches_keybind(&key_event, &config_keybinds.prev_torrent) => {
|
||||
Some(Action::PrevTorrent)
|
||||
KeyCode::Backspace => {
|
||||
app.input.pop();
|
||||
app.cursor_position = app.input.len();
|
||||
None
|
||||
}
|
||||
_ if matches_keybind(&key_event, &config_keybinds.switch_tab_1) => {
|
||||
Some(Action::SwitchTab(0))
|
||||
}
|
||||
_ if matches_keybind(&key_event, &config_keybinds.switch_tab_2) => {
|
||||
Some(Action::SwitchTab(1))
|
||||
}
|
||||
_ if matches_keybind(&key_event, &config_keybinds.switch_tab_3) => {
|
||||
Some(Action::SwitchTab(2))
|
||||
}
|
||||
_ if matches_keybind(&key_event, &config_keybinds.toggle_torrent) => {
|
||||
Some(Action::ToggleTorrent)
|
||||
}
|
||||
_ if matches_keybind(&key_event, &config_keybinds.toggle_all) => Some(Action::ToggleAll),
|
||||
_ if matches_keybind(&key_event, &config_keybinds.delete) => Some(Action::Delete(false)),
|
||||
_ if matches_keybind(&key_event, &config_keybinds.delete_force) => {
|
||||
Some(Action::Delete(true))
|
||||
}
|
||||
_ if matches_keybind(&key_event, &config_keybinds.select) => Some(Action::Select),
|
||||
_ if matches_keybind(&key_event, &config_keybinds.toggle_help) => Some(Action::ToggleHelp),
|
||||
KeyCode::Esc => Some(Action::Cancel),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles the key events of [`App`].
|
||||
#[tracing::instrument]
|
||||
pub fn get_action(key_event: KeyEvent, app: &mut App) -> Option<Action> {
|
||||
if app.input_mode {
|
||||
return handle_input(key_event, app);
|
||||
}
|
||||
|
||||
let span = info_span!("get_action");
|
||||
let _enter = span.enter();
|
||||
event!(Level::INFO, "handling key event: {:?}", key_event);
|
||||
|
||||
let keybinds = &app.config.keybinds;
|
||||
|
||||
let actions = [
|
||||
(Action::Quit, &keybinds.quit),
|
||||
(Action::NextTab, &keybinds.next_tab),
|
||||
(Action::PrevTab, &keybinds.prev_tab),
|
||||
(Action::NextTorrent, &keybinds.next_torrent),
|
||||
(Action::PrevTorrent, &keybinds.prev_torrent),
|
||||
(Action::SwitchTab(0), &keybinds.switch_tab_1),
|
||||
(Action::SwitchTab(1), &keybinds.switch_tab_2),
|
||||
(Action::SwitchTab(2), &keybinds.switch_tab_3),
|
||||
(Action::ToggleTorrent, &keybinds.toggle_torrent),
|
||||
(Action::ToggleAll, &keybinds.toggle_all),
|
||||
(Action::Delete(false), &keybinds.delete),
|
||||
(Action::Delete(true), &keybinds.delete_force),
|
||||
(Action::Select, &keybinds.select),
|
||||
(Action::ToggleHelp, &keybinds.toggle_help),
|
||||
(Action::Move, &keybinds.move_torrent),
|
||||
];
|
||||
|
||||
for (action, keybind) in actions {
|
||||
if matches_keybind(&key_event, keybind) {
|
||||
return Some(action);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Handles the updates of [`App`].
|
||||
#[tracing::instrument]
|
||||
pub async fn update(app: &mut App<'_>, action: Action) -> color_eyre::eyre::Result<()> {
|
||||
pub async fn update(app: &mut App<'_>, action: Action) -> Result<()> {
|
||||
let span = info_span!("update");
|
||||
let _enter = span.enter();
|
||||
event!(Level::INFO, "updating app with action: {:?}", action);
|
||||
@ -119,10 +78,76 @@ pub async fn update(app: &mut App<'_>, action: Action) -> color_eyre::eyre::Resu
|
||||
Action::ToggleAll => app.torrents.toggle_all().await?,
|
||||
Action::PauseAll => app.torrents.stop_all().await?,
|
||||
Action::StartAll => app.torrents.start_all().await?,
|
||||
Action::Move => unimplemented!(),
|
||||
Action::Move => app.input_mode = true,
|
||||
Action::Delete(x) => app.delete(x).await?,
|
||||
Action::Rename => unimplemented!(),
|
||||
Action::Select => app.select(),
|
||||
Action::Submit => app.move_torrent().await?,
|
||||
Action::Cancel => {
|
||||
app.input.clear();
|
||||
app.input_mode = false;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if a KeyEvent matches a configured keybind string
|
||||
fn matches_keybind(event: &KeyEvent, config_key: &Option<String>) -> bool {
|
||||
let Some(key_str) = config_key else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let (modifiers, key_code) = parse_keybind(key_str);
|
||||
let Some(key_code) = key_code else {
|
||||
return false;
|
||||
};
|
||||
|
||||
event.code == key_code && event.modifiers == modifiers
|
||||
}
|
||||
|
||||
fn parse_keybind(key_str: &str) -> (KeyModifiers, Option<KeyCode>) {
|
||||
let mut modifiers = KeyModifiers::NONE;
|
||||
let mut key_code = None;
|
||||
|
||||
for part in key_str.split('+') {
|
||||
match part.trim().to_lowercase().as_str() {
|
||||
"ctrl" => modifiers.insert(KeyModifiers::CONTROL),
|
||||
"alt" => modifiers.insert(KeyModifiers::ALT),
|
||||
"shift" => modifiers.insert(KeyModifiers::SHIFT),
|
||||
key @ ("esc" | "enter" | "left" | "right" | "up" | "down" | "tab" | "backspace"
|
||||
| "delete" | "home" | "end" | "pageup" | "pagedown" | "null" | "insert") => {
|
||||
key_code = Some(match key {
|
||||
"esc" => KeyCode::Esc,
|
||||
"enter" => KeyCode::Enter,
|
||||
"left" => KeyCode::Left,
|
||||
"right" => KeyCode::Right,
|
||||
"up" => KeyCode::Up,
|
||||
"down" => KeyCode::Down,
|
||||
"tab" => KeyCode::Tab,
|
||||
"backspace" => KeyCode::Backspace,
|
||||
"delete" => KeyCode::Delete,
|
||||
"home" => KeyCode::Home,
|
||||
"end" => KeyCode::End,
|
||||
"pageup" => KeyCode::PageUp,
|
||||
"pagedown" => KeyCode::PageDown,
|
||||
"null" => KeyCode::Null,
|
||||
"insert" => KeyCode::Insert,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
}
|
||||
f_key if f_key.starts_with('f') => {
|
||||
if let Ok(num) = f_key[1..].parse::<u8>() {
|
||||
key_code = Some(KeyCode::F(num));
|
||||
}
|
||||
}
|
||||
|
||||
single_char if single_char.len() == 1 => {
|
||||
if let Some(c) = single_char.chars().next() {
|
||||
key_code = Some(KeyCode::Char(c));
|
||||
}
|
||||
}
|
||||
_ => return (modifiers, None),
|
||||
}
|
||||
}
|
||||
(modifiers, key_code)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::Result;
|
||||
use tracing_appender::rolling;
|
||||
use tracing_subscriber::{self, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
mod log;
|
||||
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::Result;
|
||||
use log::setup_logger;
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
use std::io;
|
||||
@ -40,7 +40,7 @@ async fn main() -> Result<()> {
|
||||
match tui.events.next()? {
|
||||
Event::Tick => app.tick().await?,
|
||||
Event::Key(key_event) => {
|
||||
if let Some(action) = get_action(key_event, &app) {
|
||||
if let Some(action) = get_action(key_event, &mut app) {
|
||||
update(&mut app, action).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::app::App;
|
||||
use crate::event::EventHandler;
|
||||
use crate::ui;
|
||||
use color_eyre::eyre::Result;
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
|
||||
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
|
||||
use ratatui::backend::Backend;
|
||||
|
||||
25
src/ui/input.rs
Normal file
25
src/ui/input.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use crate::app::App;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
|
||||
pub fn render(f: &mut Frame, app: &mut App) {
|
||||
let size = f.area();
|
||||
let input_area = Rect::new(size.width / 4, size.height / 2 - 1, size.width / 2, 3);
|
||||
|
||||
let block = Block::default().title("Move to").borders(Borders::ALL);
|
||||
f.render_widget(Clear, input_area);
|
||||
f.render_widget(block, input_area);
|
||||
|
||||
let input = Paragraph::new(app.input.as_str()).block(Block::default());
|
||||
f.render_widget(
|
||||
input,
|
||||
input_area.inner(Margin {
|
||||
vertical: 1,
|
||||
horizontal: 1,
|
||||
}),
|
||||
);
|
||||
|
||||
f.set_cursor_position(ratatui::layout::Position::new(
|
||||
input_area.x + app.cursor_position as u16 + 1,
|
||||
input_area.y + 1,
|
||||
));
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
mod help;
|
||||
mod input;
|
||||
mod table;
|
||||
|
||||
use crate::app::{App, Tab};
|
||||
@ -63,4 +64,8 @@ pub fn render(app: &mut App, frame: &mut Frame) {
|
||||
if app.show_help {
|
||||
render_help(frame, app);
|
||||
}
|
||||
|
||||
if app.input_mode {
|
||||
input::render(frame, app);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user