node_launchpad/
error.rs

1use crate::{
2    components::utils::centered_rect_fixed,
3    style::{clear_area, EUCALYPTUS, GHOST_WHITE, RED},
4    tui::Frame,
5};
6use crossterm::event::{KeyCode, KeyEvent};
7use ratatui::{
8    layout::{Alignment, Constraint, Direction, Layout, Rect},
9    style::{Style, Stylize},
10    text::{Line, Span},
11    widgets::{Block, Borders, Padding, Paragraph, Wrap},
12};
13
14/// Error popup is a popup that is used to display error messages to the user.
15///
16/// It accepts a title, a message and an error message.
17/// Handles key events to hide the popup (Enter and Esc keys).
18///
19/// How to use:
20/// 1. Create a new ErrorPopup member in your component.
21/// 2. Show the error popup by calling the `show` method.
22/// 3. Hide the error popup by calling the `hide` method.
23/// 4. Check if the error popup is visible by calling the `is_visible` method.
24/// 5. Draw the error popup by calling the `draw_error` method in your `draw` function.
25/// 6. Handle the input for the error popup by calling the `handle_input` method.
26///
27/// Example:
28/// ```ignore
29///     use crate::error::ErrorPopup;
30///
31///     pub struct MyComponent {
32///         error_popup: Option<ErrorPopup>,
33///     }
34///
35///     impl MyComponent {
36///         pub fn new() -> Self {
37///             Self {
38///                 error_popup: None,
39///             }
40///         }
41///     }
42///
43///     impl Component for MyComponent {
44///         fn handle_key_events(&mut self, key: KeyEvent) -> Result<Vec<Action>> {
45///             if let Some(error_popup) = &mut self.error_popup {
46///                 if error_popup.is_visible() {
47///                     error_popup.handle_input(key);
48///                    return Ok(vec![Action::SwitchInputMode(InputMode::Navigation)]);
49///                }
50///            }
51///            // ... Your keys being handled here ...
52///         }
53///         fn draw(&mut self, f: &mut crate::tui::Frame<'_>, area: Rect) -> Result<()> {
54///             // ... Your drawing code here ...
55///             // Be sure to include the background elements here
56///             if let Some(error_popup) = &self.error_popup {
57///                 if error_popup.is_visible() {
58///                     error_popup.draw_error(f, area);
59///                     return Ok(());
60///                 }
61///             }
62///             // Be sure to include your popups here
63///             // ... Your drawing code here ...
64///         }
65///     }
66/// ```
67///
68/// How to trigger the error
69///
70/// ```ignore
71/// self.error_popup = Some(ErrorPopup::new(
72///     "Error".to_string(),
73///     "This is a test error message".to_string(),
74///     "raw message".to_string(),
75/// ));
76/// if let Some(error_popup) = &mut self.error_popup {
77///     error_popup.show();
78/// }
79/// ```
80
81#[derive(Clone)]
82pub struct ErrorPopup {
83    visible: bool,
84    title: String,
85    message: String,
86    error_message: String,
87}
88
89impl ErrorPopup {
90    pub fn new(title: String, message: String, error_message: String) -> Self {
91        Self {
92            visible: false,
93            title,
94            message,
95            error_message,
96        }
97    }
98
99    pub fn draw_error(&self, f: &mut Frame, area: Rect) {
100        if !self.visible {
101            return;
102        }
103
104        let layer_zero = centered_rect_fixed(52, 15, area);
105
106        let layer_one = Layout::new(
107            Direction::Vertical,
108            [
109                // for the pop_up_border + padding
110                Constraint::Length(2),
111                // for the text
112                Constraint::Min(1),
113                // for the pop_up_border
114                Constraint::Length(1),
115            ],
116        )
117        .split(layer_zero);
118
119        let pop_up_border = Paragraph::new("").block(
120            Block::default()
121                .borders(Borders::ALL)
122                .title(format!(" {} ", self.title))
123                .bold()
124                .title_style(Style::new().fg(RED))
125                .padding(Padding::uniform(2))
126                .border_style(Style::new().fg(RED)),
127        );
128        clear_area(f, layer_zero);
129
130        let layer_two = Layout::new(
131            Direction::Vertical,
132            [
133                // for the message
134                Constraint::Length(4),
135                // for the error_message
136                Constraint::Length(7),
137                // gap
138                Constraint::Length(1),
139                // for the buttons
140                Constraint::Length(1),
141            ],
142        )
143        .split(layer_one[1]);
144
145        let prompt = Paragraph::new(self.message.clone())
146            .block(
147                Block::default()
148                    .padding(Padding::horizontal(2))
149                    .padding(Padding::vertical(1)),
150            )
151            .alignment(Alignment::Center)
152            .wrap(Wrap { trim: true });
153
154        f.render_widget(prompt.fg(GHOST_WHITE), layer_two[0]);
155
156        let text = Paragraph::new(self.error_message.clone())
157            .block(Block::default().padding(Padding::horizontal(2)))
158            .alignment(Alignment::Center)
159            .wrap(Wrap { trim: true });
160        f.render_widget(text.fg(GHOST_WHITE), layer_two[1]);
161
162        let dash = Block::new()
163            .borders(Borders::BOTTOM)
164            .border_style(Style::new().fg(GHOST_WHITE));
165        f.render_widget(dash, layer_two[2]);
166
167        let buttons_layer =
168            Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
169                .split(layer_two[3]);
170        let button_ok = Line::from(vec![
171            Span::styled("OK ", Style::default().fg(EUCALYPTUS)),
172            Span::styled("[Enter]   ", Style::default().fg(GHOST_WHITE)),
173        ])
174        .alignment(Alignment::Right);
175
176        f.render_widget(button_ok, buttons_layer[1]);
177
178        // We render now so the borders are on top of the other widgets
179        f.render_widget(pop_up_border, layer_zero);
180    }
181
182    pub fn handle_input(&mut self, key: KeyEvent) -> bool {
183        if self.visible && (key.code == KeyCode::Esc || key.code == KeyCode::Enter) {
184            self.hide();
185            true
186        } else {
187            false
188        }
189    }
190
191    pub fn show(&mut self) {
192        debug!("Showing error popup");
193        self.visible = true;
194    }
195
196    pub fn hide(&mut self) {
197        debug!("Hiding error popup");
198        self.visible = false;
199    }
200
201    pub fn is_visible(&self) -> bool {
202        self.visible
203    }
204}