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}