deletion command

This commit is contained in:
Felipe 2025-06-14 18:22:12 -04:00
parent 2c7f5dc0cc
commit 308c65dc98
Signed by: pitbuster
SSH key fingerprint: SHA256:HDYu2Pm4/TmSX8GBwV49UvFWr1Ljg8XlHxKeCpjJpOk
5 changed files with 117 additions and 29 deletions

View file

@ -7,17 +7,28 @@ use super::state::{Mode, State};
/// Event handling /// Event handling
pub async fn handle_key(state: &mut State, key_event: KeyEvent) { pub async fn handle_key(state: &mut State, key_event: KeyEvent) {
match state.mode { match state.mode {
Mode::List => match key_event.code { Mode::List => {
KeyCode::Char('e') => state.mode = Mode::Edit, if state.confirmation.is_some() {
KeyCode::Home => state.selected_place.select_first(), match key_event.code {
KeyCode::End => state.selected_place.select_last(), KeyCode::Char('y') => state.proceed_confirmation().await,
KeyCode::PageUp => state.prev_page(), KeyCode::Char('n') | KeyCode::Esc => state.cancel_confirmation(),
KeyCode::PageDown => state.next_page(), _ => {}
KeyCode::Up | KeyCode::Char('k') => state.selected_place.select_previous(), }
KeyCode::Down | KeyCode::Char('j') => state.selected_place.select_next(), } else {
KeyCode::Esc | KeyCode::Char('q') => state.quit = true, match key_event.code {
_ => {} KeyCode::Char('d') => state.confirm_deletion(),
}, KeyCode::Char('e') => state.mode = Mode::Edit,
KeyCode::Home => state.selected_place.select_first(),
KeyCode::End => state.selected_place.select_last(),
KeyCode::PageUp => state.prev_page(),
KeyCode::PageDown => state.next_page(),
KeyCode::Up | KeyCode::Char('k') => state.selected_place.select_previous(),
KeyCode::Down | KeyCode::Char('j') => state.selected_place.select_next(),
KeyCode::Esc | KeyCode::Char('q') => state.quit = true,
_ => {}
}
}
}
Mode::Edit => match (key_event.modifiers, key_event.code) { Mode::Edit => match (key_event.modifiers, key_event.code) {
(KeyModifiers::NONE, KeyCode::Esc) => state.mode = Mode::List, (KeyModifiers::NONE, KeyCode::Esc) => state.mode = Mode::List,
(KeyModifiers::NONE, KeyCode::Tab) => {} (KeyModifiers::NONE, KeyCode::Tab) => {}

View file

@ -27,9 +27,6 @@ pub async fn tui(places_repository: DbPlacesRepository) -> Result<()> {
Event::Tick => { Event::Tick => {
state.fetch_places().await; state.fetch_places().await;
} }
Event::SlowTick => {
// state.cleanup_finished_tasks();
}
Event::Resize(_, h) => state.height = h, Event::Resize(_, h) => state.height = h,
Event::Quit => state.quit = true, Event::Quit => state.quit = true,
_ => {} _ => {}

View file

@ -13,6 +13,7 @@ pub struct State {
places_repository: DbPlacesRepository, places_repository: DbPlacesRepository,
pub places: Vec<Place>, pub places: Vec<Place>,
places_status: DataStatus, places_status: DataStatus,
pub confirmation: Option<ConfirmationStatus>,
pub selected_place: TableState, pub selected_place: TableState,
pub quit: bool, pub quit: bool,
} }
@ -27,6 +28,12 @@ enum DataStatus {
Old, Old,
} }
pub enum ConfirmationStatus {
Deletion(i64),
#[expect(dead_code)]
Save(i64),
}
impl State { impl State {
pub fn new(places_repository: DbPlacesRepository, height: u16) -> Self { pub fn new(places_repository: DbPlacesRepository, height: u16) -> Self {
Self { Self {
@ -37,6 +44,7 @@ impl State {
places_status: DataStatus::Old, places_status: DataStatus::Old,
places: vec![], places: vec![],
selected_place: TableState::default(), selected_place: TableState::default(),
confirmation: None,
quit: false, quit: false,
} }
} }
@ -74,4 +82,36 @@ impl State {
} }
self.places_status = DataStatus::Fresh; self.places_status = DataStatus::Fresh;
} }
pub fn confirm_deletion(&mut self) {
if let Some(Some(id)) = self
.selected_place
.selected()
.map(|index| self.places.get(index).map(|p| p.id))
{
self.confirmation = Some(ConfirmationStatus::Deletion(id))
}
}
pub fn cancel_confirmation(&mut self) {
self.confirmation = None;
}
pub async fn proceed_confirmation(&mut self) {
let Some(confirmation) = &self.confirmation else {
return;
};
match confirmation {
ConfirmationStatus::Deletion(id) => {
if let Err(err) = self.places_repository.delete_place(*id).await {
tracing::error!("{err}");
}
}
ConfirmationStatus::Save(_) => todo!(),
}
self.confirmation = None;
self.places_status = DataStatus::Old;
}
} }

View file

@ -20,8 +20,6 @@ pub enum Event {
Closed, Closed,
/// Triggers background actions /// Triggers background actions
Tick, Tick,
/// Triggers less frequent background actions
SlowTick,
/// UI Render /// UI Render
Render, Render,
FocusGained, FocusGained,
@ -49,8 +47,7 @@ pub struct Tui {
impl Tui { impl Tui {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let tick_rate = 4.0; let tick_rate = 4.0;
let slow_tick_rate = 0.25; let frame_rate = 20.0;
let frame_rate = 30.0;
let terminal = Terminal::new(CrosstermBackend::new(std::io::stderr()))?; let terminal = Terminal::new(CrosstermBackend::new(std::io::stderr()))?;
let cancellation_token = CancellationToken::new(); let cancellation_token = CancellationToken::new();
@ -60,17 +57,14 @@ impl Tui {
let cancellation_token = cancellation_token.clone(); let cancellation_token = cancellation_token.clone();
let sender = sender.clone(); let sender = sender.clone();
let tick_delay = std::time::Duration::from_secs_f64(1.0 / tick_rate); let tick_delay = std::time::Duration::from_secs_f64(1.0 / tick_rate);
let slow_tick_delay = std::time::Duration::from_secs_f64(1.0 / slow_tick_rate);
let render_delay = std::time::Duration::from_secs_f64(1.0 / frame_rate); let render_delay = std::time::Duration::from_secs_f64(1.0 / frame_rate);
tokio::spawn(async move { tokio::spawn(async move {
let mut reader = crossterm::event::EventStream::new(); let mut reader = crossterm::event::EventStream::new();
let mut tick_interval = tokio::time::interval(tick_delay); let mut tick_interval = tokio::time::interval(tick_delay);
let mut slow_tick_interval = tokio::time::interval(slow_tick_delay);
let mut render_interval = tokio::time::interval(render_delay); let mut render_interval = tokio::time::interval(render_delay);
sender.send(Event::Init)?; sender.send(Event::Init)?;
loop { loop {
let tick_delay = tick_interval.tick(); let tick_delay = tick_interval.tick();
let slow_tick_delay = slow_tick_interval.tick();
let render_delay = render_interval.tick(); let render_delay = render_interval.tick();
let crossterm_event = reader.next().fuse(); let crossterm_event = reader.next().fuse();
tokio::select! { tokio::select! {
@ -104,9 +98,6 @@ impl Tui {
_ = tick_delay => { _ = tick_delay => {
sender.send(Event::Tick)?; sender.send(Event::Tick)?;
}, },
_ = slow_tick_delay => {
sender.send(Event::SlowTick)?;
},
_ = render_delay => { _ = render_delay => {
sender.send(Event::Render)?; sender.send(Event::Render)?;
} }

View file

@ -2,12 +2,12 @@
use itertools::Itertools; use itertools::Itertools;
use ratatui::Frame; use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::layout::{Constraint, Direction, Flex, Layout, Rect};
use ratatui::style::{Color, Style, Stylize}; use ratatui::style::{Color, Style, Stylize};
use ratatui::text::{Line, Span, ToSpan}; use ratatui::text::{Line, Span, Text, ToSpan};
use ratatui::widgets::{Paragraph, Row, Table}; use ratatui::widgets::{Block, Clear, Padding, Paragraph, Row, Table};
use super::state::{Mode, State}; use super::state::{ConfirmationStatus, Mode, State};
/// UI drawing /// UI drawing
pub fn ui_draw(state: &mut State, f: &mut Frame<'_>) { pub fn ui_draw(state: &mut State, f: &mut Frame<'_>) {
@ -54,7 +54,36 @@ fn main_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) {
Mode::List => list_draw(state, f, area), Mode::List => list_draw(state, f, area),
Mode::Edit => edit_draw(state, f, area), Mode::Edit => edit_draw(state, f, area),
} }
confirmation_dialog_draw(state, f, area);
} }
fn confirmation_dialog_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) {
let Some(confirmation) = &state.confirmation else {
return;
};
let dialog_area = center(area, Constraint::Percentage(80), Constraint::Percentage(60));
let (action, id) = match confirmation {
ConfirmationStatus::Deletion(id) => ("delete", id),
ConfirmationStatus::Save(id) => ("save", id),
};
let confirmation_dialog = Paragraph::new(Text::from_iter([
Line::from_iter([
"Do you want to ".to_span(),
action.to_span(),
" place with id: ".to_span(),
id.to_span(),
]),
Line::from(""),
Line::from("Y/N".to_span().bold()),
]))
.centered()
.block(Block::bordered().padding(Padding::uniform(1)));
f.render_widget(Clear, dialog_area);
f.render_widget(confirmation_dialog, dialog_area);
}
fn list_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { fn list_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) {
let places = state.places.iter().map(|p| { let places = state.places.iter().map(|p| {
Row::new([ Row::new([
@ -99,7 +128,18 @@ fn list_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) {
f.render_stateful_widget(places_table, area, &mut state.selected_place); f.render_stateful_widget(places_table, area, &mut state.selected_place);
} }
fn edit_draw(_state: &mut State, _f: &mut Frame<'_>, _area: Rect) {} fn edit_draw(_state: &mut State, f: &mut Frame<'_>, area: Rect) {
let dialog_area = center(area, Constraint::Percentage(80), Constraint::Percentage(60));
let confirmation_dialog =
Paragraph::new(Text::from_iter([
Line::from("Not implemented yet :(").italic()
]))
.centered()
.block(Block::bordered().padding(Padding::uniform(1)));
f.render_widget(Clear, dialog_area);
f.render_widget(confirmation_dialog, dialog_area);
}
#[expect(unstable_name_collisions)] #[expect(unstable_name_collisions)]
fn footer_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) { fn footer_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) {
@ -114,6 +154,7 @@ fn footer_draw(state: &mut State, f: &mut Frame<'_>, area: Rect) {
("PgUp", "Prev Page"), ("PgUp", "Prev Page"),
("PgDown", "Next Page"), ("PgDown", "Next Page"),
("e", "Edit"), ("e", "Edit"),
("d", "Delete"),
] ]
.map(|(key, action)| keybinding(key, action).to_vec()) .map(|(key, action)| keybinding(key, action).to_vec())
.into_iter() .into_iter()
@ -145,3 +186,11 @@ fn keybinding(key: &'static str, action: &'static str) -> [Span<'static>; 5] {
Span::styled(" ", black), Span::styled(" ", black),
] ]
} }
fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect {
let [area] = Layout::horizontal([horizontal])
.flex(Flex::Center)
.areas(area);
let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area);
area
}