feat(ux): implement a warning modal on deleting a file/folder while s… (#60)
* feat(app) implement a warning modal on deleting a file/folder while scanning add keypress handler for warning_modal update apps ui state of warning modal implement autoclose functionality for warningmodal * remove auto close functionality from warning modal * fix(controls): use delete key when loading Co-authored-by: Aram Drevekenin <aram@poor.dev>
This commit is contained in:
@@ -19,6 +19,7 @@ pub enum UiMode {
|
||||
DeleteFile(FileToDelete),
|
||||
ErrorMessage(String),
|
||||
Exiting { app_loaded: bool },
|
||||
WarningMessage(FileToDelete),
|
||||
}
|
||||
|
||||
pub struct App<B>
|
||||
@@ -120,6 +121,12 @@ where
|
||||
}
|
||||
};
|
||||
}
|
||||
pub fn show_warning_modal(&mut self) {
|
||||
if let Some(file_to_delete) = self.get_file_to_delete() {
|
||||
self.ui_mode = UiMode::WarningMessage(file_to_delete);
|
||||
self.render();
|
||||
}
|
||||
}
|
||||
pub fn prompt_exit(&mut self) {
|
||||
self.ui_mode = UiMode::Exiting {
|
||||
app_loaded: self.loaded,
|
||||
|
||||
@@ -52,6 +52,9 @@ pub fn handle_keypress_loading_mode<B: Backend>(evt: Event, app: &mut App<B>) {
|
||||
key!(char '\n') => {
|
||||
app.handle_enter();
|
||||
}
|
||||
key!(Delete) => {
|
||||
app.show_warning_modal();
|
||||
}
|
||||
key!(Esc) | key!(Backspace) => {
|
||||
app.go_up();
|
||||
}
|
||||
@@ -137,3 +140,11 @@ pub fn handle_keypress_exiting_mode<B: Backend>(evt: Event, app: &mut App<B>) {
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn handle_keypress_warning_message<B: Backend>(evt: Event, app: &mut App<B>) {
|
||||
match evt {
|
||||
_ => {
|
||||
app.reset_ui_mode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use ::tui::backend::Backend;
|
||||
use crate::input::{
|
||||
handle_keypress_delete_file_mode, handle_keypress_error_message, handle_keypress_exiting_mode,
|
||||
handle_keypress_loading_mode, handle_keypress_normal_mode, handle_keypress_screen_too_small,
|
||||
handle_keypress_warning_message,
|
||||
};
|
||||
use crate::{App, UiMode};
|
||||
|
||||
@@ -85,6 +86,9 @@ where
|
||||
UiMode::Exiting { app_loaded: _ } => {
|
||||
handle_keypress_exiting_mode(evt, app);
|
||||
}
|
||||
UiMode::WarningMessage(_) => {
|
||||
handle_keypress_warning_message(evt, app);
|
||||
}
|
||||
}
|
||||
if !app.is_running {
|
||||
break;
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::state::files::FileTree;
|
||||
use crate::state::tiles::Board;
|
||||
use crate::state::UiEffects;
|
||||
use crate::ui::grid::RectangleGrid;
|
||||
use crate::ui::modals::{ConfirmBox, ErrorBox, MessageBox};
|
||||
use crate::ui::modals::{ConfirmBox, ErrorBox, MessageBox, WarningBox};
|
||||
use crate::ui::title::TitleLine;
|
||||
use crate::ui::{BottomLine, TermTooSmall};
|
||||
use crate::UiMode;
|
||||
@@ -246,6 +246,36 @@ where
|
||||
);
|
||||
f.render_widget(ConfirmBox::new(), full_screen);
|
||||
}
|
||||
UiMode::WarningMessage(_) => {
|
||||
f.render_widget(
|
||||
TitleLine::new(
|
||||
base_path_info,
|
||||
current_path_info,
|
||||
file_tree.space_freed,
|
||||
)
|
||||
.progress_indicator(ui_effects.loading_progress_indicator)
|
||||
.path_error(ui_effects.current_path_is_red)
|
||||
.read_errors(file_tree.failed_to_read)
|
||||
.show_loading(),
|
||||
chunks[0],
|
||||
);
|
||||
f.render_widget(
|
||||
RectangleGrid::new(
|
||||
&board.tiles,
|
||||
board.unrenderable_tile_coordinates,
|
||||
board.selected_index,
|
||||
),
|
||||
chunks[1],
|
||||
);
|
||||
f.render_widget(
|
||||
BottomLine::new()
|
||||
.currently_selected(board.currently_selected())
|
||||
.last_read_path(ui_effects.last_read_path.as_ref())
|
||||
.hide_delete(),
|
||||
chunks[2],
|
||||
);
|
||||
f.render_widget(WarningBox::new(), full_screen);
|
||||
}
|
||||
};
|
||||
})
|
||||
.expect("failed to draw");
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
mod confirm_box;
|
||||
mod error_box;
|
||||
mod message_box;
|
||||
mod warning_box;
|
||||
|
||||
pub use confirm_box::*;
|
||||
pub use error_box::*;
|
||||
pub use message_box::*;
|
||||
pub use warning_box::*;
|
||||
|
||||
92
src/ui/modals/warning_box.rs
Normal file
92
src/ui/modals/warning_box.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use tui::buffer::Buffer;
|
||||
use tui::layout::Rect;
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
use tui::widgets::Widget;
|
||||
|
||||
use crate::ui::format::truncate_end;
|
||||
use crate::ui::grid::draw_filled_rect;
|
||||
|
||||
pub struct WarningBox {}
|
||||
|
||||
impl<'a> WarningBox {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Widget for WarningBox {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let (width, height) = if area.width > 150 {
|
||||
(150, 10)
|
||||
} else if area.width >= 50 {
|
||||
(area.width / 2, 10)
|
||||
} else {
|
||||
unreachable!("app should not be rendered if window is so small")
|
||||
};
|
||||
|
||||
// position self in the middle of the rect
|
||||
let x = ((area.x + area.width) / 2) - width / 2;
|
||||
let y = ((area.y + area.height) / 2) - height / 2;
|
||||
|
||||
let warning_rect = Rect {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
let fill_style = Style::default()
|
||||
.bg(Color::Black)
|
||||
.fg(Color::Yellow)
|
||||
.modifier(Modifier::BOLD);
|
||||
let text_max_length = warning_rect.width - 4;
|
||||
let mut warning_text_start_position: u16 = 0;
|
||||
|
||||
let possible_warning_texts = [
|
||||
"Sorry, deletion is only allowed once the scanning has completed",
|
||||
"Deletion is not allowed while scanning",
|
||||
"Can't delete while scanning",
|
||||
"Can't delete now",
|
||||
];
|
||||
// set default value of the warning_text
|
||||
// to the longest one from possible_warning_texts array
|
||||
let mut warning_text = String::from(possible_warning_texts[0]);
|
||||
for line in possible_warning_texts.iter() {
|
||||
// "+5" here is to make sure confirm message has always some padding
|
||||
if warning_rect.width >= (line.chars().count() as u16) + 5 {
|
||||
// here we truncate the end and not the middle because
|
||||
// when dealing with warning messages, the beginning tends
|
||||
// to be the important part
|
||||
warning_text = truncate_end(line, text_max_length);
|
||||
warning_text_start_position =
|
||||
((warning_rect.width - warning_text.len() as u16) as f64 / 2.0).ceil() as u16
|
||||
+ warning_rect.x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let controls_text = ["(Press any key to dismiss)", "(any key to dismiss)"];
|
||||
|
||||
draw_filled_rect(buf, fill_style, &warning_rect);
|
||||
buf.set_string(
|
||||
warning_text_start_position,
|
||||
warning_rect.y + warning_rect.height / 2 - 2,
|
||||
warning_text,
|
||||
fill_style,
|
||||
);
|
||||
|
||||
for line in controls_text.iter() {
|
||||
if text_max_length >= line.chars().count() as u16 {
|
||||
let start_position =
|
||||
((warning_rect.width - line.chars().count() as u16) as f64 / 2.0).ceil() as u16
|
||||
+ warning_rect.x;
|
||||
buf.set_string(
|
||||
start_position,
|
||||
warning_rect.y + warning_rect.height / 2 + 2,
|
||||
line,
|
||||
fill_style,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user