mirror of
https://github.com/kristoferssolo/traxor.git
synced 2026-01-14 20:46:14 +00:00
feat: add confirmation dialog before deleting torrents
This commit is contained in:
parent
33b446d440
commit
8ed46b1285
@ -34,6 +34,8 @@ pub enum Action {
|
|||||||
Select,
|
Select,
|
||||||
#[display("Submit")]
|
#[display("Submit")]
|
||||||
Submit,
|
Submit,
|
||||||
|
#[display("Confirm Yes")]
|
||||||
|
ConfirmYes,
|
||||||
#[display("Cancel")]
|
#[display("Cancel")]
|
||||||
Cancel,
|
Cancel,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,8 @@ pub enum InputMode {
|
|||||||
None,
|
None,
|
||||||
Move,
|
Move,
|
||||||
Rename,
|
Rename,
|
||||||
|
/// Confirm delete dialog. Bool indicates whether to delete local data.
|
||||||
|
ConfirmDelete(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main Application.
|
/// Main Application.
|
||||||
@ -167,16 +169,6 @@ impl App {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Errors
|
|
||||||
///
|
|
||||||
/// TODO: add error types
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move selected or highlighted torrent(s) to a new location.
|
/// Move selected or highlighted torrent(s) to a new location.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
@ -232,6 +224,26 @@ impl App {
|
|||||||
self.close_help();
|
self.close_help();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prepare delete confirmation dialog.
|
||||||
|
pub const fn prepare_delete(&mut self, delete_local_data: bool) {
|
||||||
|
self.input_mode = InputMode::ConfirmDelete(delete_local_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the confirmed delete action.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns an error if the RPC call fails.
|
||||||
|
pub async fn confirm_delete(&mut self) -> Result<()> {
|
||||||
|
let InputMode::ConfirmDelete(delete_local_data) = self.input_mode else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let ids = self.selected(false);
|
||||||
|
self.torrents.delete(ids, delete_local_data).await?;
|
||||||
|
self.clear_input();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn select(&mut self) {
|
pub fn select(&mut self) {
|
||||||
if let Selected::Current(current_id) = self.selected(true) {
|
if let Selected::Current(current_id) = self.selected(true) {
|
||||||
if self.torrents.selected.contains(¤t_id) {
|
if self.torrents.selected.contains(¤t_id) {
|
||||||
|
|||||||
@ -6,6 +6,15 @@ use tracing::{debug, info};
|
|||||||
|
|
||||||
#[tracing::instrument(name = "Handling input", skip(app))]
|
#[tracing::instrument(name = "Handling input", skip(app))]
|
||||||
async fn handle_input(key_event: KeyEvent, app: &mut App) -> Result<Option<Action>> {
|
async fn handle_input(key_event: KeyEvent, app: &mut App) -> Result<Option<Action>> {
|
||||||
|
// Handle confirmation dialogs separately
|
||||||
|
if matches!(app.input_mode, InputMode::ConfirmDelete(_)) {
|
||||||
|
return match key_event.code {
|
||||||
|
KeyCode::Char('y' | 'Y') => Ok(Some(Action::ConfirmYes)),
|
||||||
|
KeyCode::Char('n' | 'N') | KeyCode::Esc => Ok(Some(Action::Cancel)),
|
||||||
|
_ => Ok(None),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
KeyCode::Enter => Ok(Some(Action::Submit)),
|
KeyCode::Enter => Ok(Some(Action::Submit)),
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
@ -84,13 +93,14 @@ pub async fn update(app: &mut App, action: Action) -> Result<()> {
|
|||||||
Action::StartAll => app.torrents.start_all().await?,
|
Action::StartAll => app.torrents.start_all().await?,
|
||||||
Action::Move => app.prepare_move_action(),
|
Action::Move => app.prepare_move_action(),
|
||||||
Action::Rename => app.prepare_rename_action(),
|
Action::Rename => app.prepare_rename_action(),
|
||||||
Action::Delete(x) => app.delete(x).await?,
|
Action::Delete(delete_local_data) => app.prepare_delete(delete_local_data),
|
||||||
Action::Select => app.select(),
|
Action::Select => app.select(),
|
||||||
Action::Submit => match app.input_mode {
|
Action::Submit => match app.input_mode {
|
||||||
InputMode::Move => app.move_torrent().await?,
|
InputMode::Move => app.move_torrent().await?,
|
||||||
InputMode::Rename => app.rename_torrent().await?,
|
InputMode::Rename => app.rename_torrent().await?,
|
||||||
InputMode::None => {}
|
InputMode::None | InputMode::ConfirmDelete(_) => {}
|
||||||
},
|
},
|
||||||
|
Action::ConfirmYes => app.confirm_delete().await?,
|
||||||
Action::Cancel => {
|
Action::Cancel => {
|
||||||
app.input_handler.clear();
|
app.input_handler.clear();
|
||||||
app.input_mode = InputMode::None;
|
app.input_mode = InputMode::None;
|
||||||
|
|||||||
@ -1,18 +1,27 @@
|
|||||||
use crate::app::{App, InputMode};
|
use crate::app::{App, InputMode};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
text::Line,
|
||||||
widgets::{Block, Borders, Clear, Paragraph},
|
widgets::{Block, Borders, Clear, Paragraph},
|
||||||
};
|
};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
pub fn render(f: &mut Frame, app: &App) {
|
pub fn render(f: &mut Frame, app: &App) {
|
||||||
|
match app.input_mode {
|
||||||
|
InputMode::Move | InputMode::Rename => render_text_input(f, app),
|
||||||
|
InputMode::ConfirmDelete(delete_local_data) => render_confirm_delete(f, delete_local_data),
|
||||||
|
InputMode::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_text_input(f: &mut Frame, app: &App) {
|
||||||
let size = f.area();
|
let size = f.area();
|
||||||
let input_area = Rect::new(size.width / 4, size.height / 2 - 1, size.width / 2, 3);
|
let input_area = Rect::new(size.width / 4, size.height / 2 - 1, size.width / 2, 3);
|
||||||
|
|
||||||
let title = match app.input_mode {
|
let title = match app.input_mode {
|
||||||
InputMode::Move => "Move to",
|
InputMode::Move => "Move to",
|
||||||
InputMode::Rename => "Rename",
|
InputMode::Rename => "Rename",
|
||||||
InputMode::None => return,
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let block = Block::default().title(title).borders(Borders::ALL);
|
let block = Block::default().title(title).borders(Borders::ALL);
|
||||||
@ -38,3 +47,47 @@ pub fn render(f: &mut Frame, app: &App) {
|
|||||||
input_area.y + 1,
|
input_area.y + 1,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_confirm_delete(f: &mut Frame, delete_local_data: bool) {
|
||||||
|
let size = f.area();
|
||||||
|
let dialog_width = 40;
|
||||||
|
let dialog_height = 5;
|
||||||
|
let dialog_area = Rect::new(
|
||||||
|
(size.width.saturating_sub(dialog_width)) / 2,
|
||||||
|
(size.height.saturating_sub(dialog_height)) / 2,
|
||||||
|
dialog_width.min(size.width),
|
||||||
|
dialog_height.min(size.height),
|
||||||
|
);
|
||||||
|
|
||||||
|
let title = if delete_local_data {
|
||||||
|
"Delete with data?"
|
||||||
|
} else {
|
||||||
|
"Delete torrent?"
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = Block::default()
|
||||||
|
.title(title)
|
||||||
|
.title_style(Style::default().fg(Color::Red).bold())
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(Style::default().fg(Color::Red));
|
||||||
|
|
||||||
|
f.render_widget(Clear, dialog_area);
|
||||||
|
f.render_widget(block, dialog_area);
|
||||||
|
|
||||||
|
let first_line = if delete_local_data {
|
||||||
|
"This will delete local files!"
|
||||||
|
} else {
|
||||||
|
"Remove from list?"
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = Paragraph::new(vec![Line::from(first_line), Line::from("(y)es / (n)o")])
|
||||||
|
.alignment(Alignment::Center);
|
||||||
|
|
||||||
|
f.render_widget(
|
||||||
|
text,
|
||||||
|
dialog_area.inner(Margin {
|
||||||
|
vertical: 1,
|
||||||
|
horizontal: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user