node_launchpad/components/popup/
reset_nodes.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use super::super::{utils::centered_rect_fixed, Component};
10use crate::{
11    action::{Action, OptionsActions},
12    mode::{InputMode, Scene},
13    style::{clear_area, EUCALYPTUS, GHOST_WHITE, INDIGO, LIGHT_PERIWINKLE, VIVID_SKY_BLUE},
14};
15use color_eyre::Result;
16use crossterm::event::{Event, KeyCode, KeyEvent};
17use ratatui::{prelude::*, widgets::*};
18use tui_input::{backend::crossterm::EventHandler, Input};
19
20const INPUT_SIZE: u16 = 5;
21const INPUT_AREA: u16 = INPUT_SIZE + 2; // +2 for the left and right padding
22
23#[derive(Default)]
24pub struct ResetNodesPopup {
25    /// Whether the component is active right now, capturing keystrokes + draw things.
26    active: bool,
27    confirmation_input_field: Input,
28    can_reset: bool,
29}
30
31impl Component for ResetNodesPopup {
32    fn handle_key_events(&mut self, key: KeyEvent) -> Result<Vec<Action>> {
33        if !self.active {
34            return Ok(vec![]);
35        }
36        let send_back = match key.code {
37            KeyCode::Enter => {
38                if self.can_reset {
39                    debug!("Got reset, sending Reset action and switching to Options");
40                    vec![
41                        Action::OptionsActions(OptionsActions::ResetNodes),
42                        Action::SwitchScene(Scene::Options),
43                    ]
44                } else {
45                    vec![]
46                }
47            }
48            KeyCode::Esc => {
49                debug!("Got Esc, switching to Options");
50                vec![Action::SwitchScene(Scene::Options)]
51            }
52            KeyCode::Char(' ') => vec![],
53            KeyCode::Backspace => {
54                // if max limit reached, we should allow Backspace to work.
55                self.confirmation_input_field.handle_event(&Event::Key(key));
56                let input = self.confirmation_input_field.value().to_string();
57                self.can_reset = input.to_lowercase() == "reset";
58                vec![]
59            }
60            _ => {
61                // max char limit
62                if self.confirmation_input_field.value().chars().count() < INPUT_SIZE as usize {
63                    self.confirmation_input_field.handle_event(&Event::Key(key));
64                }
65                let input = self.confirmation_input_field.value().to_string();
66                self.can_reset = input.to_lowercase() == "reset";
67                vec![]
68            }
69        };
70        Ok(send_back)
71    }
72
73    fn update(&mut self, action: Action) -> Result<Option<Action>> {
74        let send_back = match action {
75            Action::SwitchScene(scene) => match scene {
76                Scene::ResetNodesPopUp => {
77                    self.active = true;
78                    self.confirmation_input_field = self
79                        .confirmation_input_field
80                        .clone()
81                        .with_value(String::new());
82                    // set to entry input mode as we want to handle everything within our handle_key_events
83                    // so by default if this scene is active, we capture inputs.
84                    Some(Action::SwitchInputMode(InputMode::Entry))
85                }
86                _ => {
87                    self.active = false;
88                    None
89                }
90            },
91            _ => None,
92        };
93        Ok(send_back)
94    }
95
96    fn draw(&mut self, f: &mut crate::tui::Frame<'_>, area: Rect) -> Result<()> {
97        if !self.active {
98            return Ok(());
99        }
100
101        let layer_zero = centered_rect_fixed(52, 15, area);
102
103        let layer_one = Layout::new(
104            Direction::Vertical,
105            [
106                // for the pop_up_border
107                Constraint::Length(2),
108                // for the input field
109                Constraint::Min(1),
110                // for the pop_up_border
111                Constraint::Length(1),
112            ],
113        )
114        .split(layer_zero);
115
116        // layer zero
117        let pop_up_border = Paragraph::new("").block(
118            Block::default()
119                .borders(Borders::ALL)
120                .title(" Reset Nodes ")
121                .bold()
122                .title_style(Style::new().fg(VIVID_SKY_BLUE))
123                .padding(Padding::uniform(2))
124                .border_style(Style::new().fg(VIVID_SKY_BLUE)),
125        );
126        clear_area(f, layer_zero);
127
128        // split into 4 parts, for the prompt, input, text, dash , and buttons
129        let layer_two = Layout::new(
130            Direction::Vertical,
131            [
132                // for the prompt text
133                Constraint::Length(4),
134                // for the input
135                Constraint::Length(2),
136                // for the text
137                Constraint::Length(3),
138                // gap
139                Constraint::Length(3),
140                // for the buttons
141                Constraint::Length(1),
142            ],
143        )
144        .split(layer_one[1]);
145
146        let prompt = Paragraph::new("Type in 'reset' and press Enter to Reset all your nodes")
147            .wrap(Wrap { trim: false })
148            .block(Block::new().padding(Padding::horizontal(2)))
149            .alignment(Alignment::Center)
150            .fg(GHOST_WHITE);
151
152        f.render_widget(prompt, layer_two[0]);
153
154        let spaces =
155            " ".repeat((INPUT_AREA - 1) as usize - self.confirmation_input_field.value().len());
156
157        let input = Paragraph::new(Span::styled(
158            format!("{}{} ", spaces, self.confirmation_input_field.value()),
159            Style::default().fg(VIVID_SKY_BLUE).bg(INDIGO).underlined(),
160        ))
161        .alignment(Alignment::Center);
162
163        f.render_widget(input, layer_two[1]);
164
165        let text = Paragraph::new("This will clear out all the nodes and all the stored data. You should still keep all your earned rewards.")
166            .wrap(Wrap { trim: false })
167            .block(Block::new().padding(Padding::horizontal(2)))
168            .alignment(Alignment::Center)
169            .fg(GHOST_WHITE);
170        f.render_widget(text, layer_two[2]);
171
172        let dash = Block::new()
173            .borders(Borders::BOTTOM)
174            .border_style(Style::new().fg(GHOST_WHITE));
175        f.render_widget(dash, layer_two[3]);
176
177        let buttons_layer =
178            Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)])
179                .split(layer_two[4]);
180
181        let button_no = Line::from(vec![Span::styled(
182            "No, Cancel [Esc]",
183            Style::default().fg(LIGHT_PERIWINKLE),
184        )]);
185
186        f.render_widget(
187            Paragraph::new(button_no)
188                .block(Block::default().padding(Padding::horizontal(2)))
189                .alignment(Alignment::Left),
190            buttons_layer[0],
191        );
192
193        let button_yes = Line::from(vec![Span::styled(
194            "Reset Nodes [Enter]",
195            if self.can_reset {
196                Style::default().fg(EUCALYPTUS)
197            } else {
198                Style::default().fg(LIGHT_PERIWINKLE)
199            },
200        )])
201        .alignment(Alignment::Right);
202
203        f.render_widget(
204            Paragraph::new(button_yes)
205                .block(Block::default().padding(Padding::horizontal(2)))
206                .alignment(Alignment::Right),
207            buttons_layer[1],
208        );
209
210        f.render_widget(pop_up_border, layer_zero);
211
212        Ok(())
213    }
214}