Skip to main content

romm_cli/tui/screens/setup_wizard/
input.rs

1//! Setup wizard keyboard and paste input.
2
3use anyhow::Result;
4use crossterm::event::{KeyCode, KeyEvent, KeyEventKind};
5
6use crate::config::normalize_romm_origin;
7use crate::core::download::validate_configured_download_directory;
8use crate::tui::path_picker::PathPickerEvent;
9
10use super::types::{AuthKind, SetupWizard, Step};
11
12impl SetupWizard {
13    fn add_char_url(&mut self, c: char) {
14        let pos = self.url_cursor.min(self.url.len());
15        self.url.insert(pos, c);
16        self.url_cursor = pos + 1;
17    }
18
19    fn del_char_url(&mut self) {
20        if self.url_cursor > 0 && self.url_cursor <= self.url.len() {
21            self.url.remove(self.url_cursor - 1);
22            self.url_cursor -= 1;
23        }
24    }
25
26    fn advance_from_auth_menu(&mut self) {
27        self.auth_kind = Self::auth_kind_from_index(self.auth_menu_selected);
28        self.step = match self.auth_kind {
29            AuthKind::Basic => Step::BasicUser,
30            AuthKind::Bearer => Step::Bearer,
31            AuthKind::ApiKey => Step::ApiHeader,
32            AuthKind::Pairing => {
33                self.pairing_cursor = self.pairing_code.len();
34                Step::PairingCode
35            }
36        };
37    }
38
39    fn advance_after_auth_credentials(&mut self) {
40        self.step = if self.skip_custom_console_paths {
41            Step::Summary
42        } else {
43            Step::CustomConsolePaths
44        };
45    }
46
47    fn advance_step(&mut self) -> Result<()> {
48        self.error = None;
49        match self.step {
50            Step::Url => {
51                if normalize_romm_origin(self.url.trim()).is_empty() {
52                    self.error = Some("Enter a valid server URL".to_string());
53                    return Ok(());
54                }
55                self.step = Step::Https;
56            }
57            Step::Https => {
58                self.step = Step::Download;
59            }
60            Step::Download => {}
61            Step::CustomConsolePaths => {
62                self.step = Step::Summary;
63            }
64            Step::AuthMenu => self.advance_from_auth_menu(),
65            Step::BasicUser => self.step = Step::BasicPass,
66            Step::BasicPass => self.advance_after_auth_credentials(),
67            Step::Bearer => self.advance_after_auth_credentials(),
68            Step::ApiHeader => self.step = Step::ApiKey,
69            Step::ApiKey => self.advance_after_auth_credentials(),
70            Step::PairingCode => self.advance_after_auth_credentials(),
71            Step::Summary => {}
72        }
73        Ok(())
74    }
75
76    pub fn handle_key(&mut self, key: &KeyEvent) -> Result<bool> {
77        if key.kind != KeyEventKind::Press {
78            return Ok(false);
79        }
80        if key.code == KeyCode::Esc {
81            return Ok(true); // Signal to caller that we should exit/cancel
82        }
83
84        if self.testing {
85            return Ok(false);
86        }
87
88        match self.step {
89            Step::Url => match key.code {
90                KeyCode::Enter => {
91                    let _ = self.advance_step();
92                }
93                KeyCode::Char(c) => self.add_char_url(c),
94                KeyCode::Backspace => self.del_char_url(),
95                KeyCode::Left if self.url_cursor > 0 => {
96                    self.url_cursor -= 1;
97                }
98                KeyCode::Right if self.url_cursor < self.url.len() => {
99                    self.url_cursor += 1;
100                }
101                _ => {}
102            },
103            Step::Https => match key.code {
104                KeyCode::Enter => {
105                    let _ = self.advance_step();
106                }
107                KeyCode::Char(' ') => self.use_https = !self.use_https,
108                _ => {}
109            },
110            Step::Download => match self.download_picker.handle_key(key) {
111                PathPickerEvent::Confirmed(p) => {
112                    self.error = None;
113                    match validate_configured_download_directory(p.to_string_lossy().as_ref()) {
114                        Ok(canonical) => {
115                            self.download_picker
116                                .set_path_text(canonical.display().to_string());
117                            self.step = Step::AuthMenu;
118                        }
119                        Err(e) => {
120                            self.error = Some(format!("{e:#}"));
121                        }
122                    }
123                }
124                PathPickerEvent::None => {}
125            },
126            Step::CustomConsolePaths => {
127                if key.code == KeyCode::Enter {
128                    let _ = self.advance_step();
129                }
130            }
131            Step::AuthMenu => match key.code {
132                KeyCode::Up | KeyCode::Char('k') if self.auth_menu_selected > 0 => {
133                    self.auth_menu_selected -= 1;
134                }
135                KeyCode::Down | KeyCode::Char('j') if self.auth_menu_selected < 3 => {
136                    self.auth_menu_selected += 1;
137                }
138                KeyCode::Enter => {
139                    let _ = self.advance_step();
140                }
141                _ => {}
142            },
143            Step::BasicUser => match key.code {
144                KeyCode::Tab => self.step = Step::BasicPass,
145                KeyCode::Enter => {
146                    let _ = self.advance_step();
147                }
148                KeyCode::Char(c) => {
149                    let pos = self.user_cursor.min(self.username.len());
150                    self.username.insert(pos, c);
151                    self.user_cursor = pos + 1;
152                }
153                KeyCode::Backspace
154                    if self.user_cursor > 0 && self.user_cursor <= self.username.len() =>
155                {
156                    self.username.remove(self.user_cursor - 1);
157                    self.user_cursor -= 1;
158                }
159                KeyCode::Left if self.user_cursor > 0 => {
160                    self.user_cursor -= 1;
161                }
162                KeyCode::Right if self.user_cursor < self.username.len() => {
163                    self.user_cursor += 1;
164                }
165                _ => {}
166            },
167            Step::BasicPass => match key.code {
168                KeyCode::Tab => self.step = Step::BasicUser,
169                KeyCode::Enter => {
170                    let _ = self.advance_step();
171                }
172                KeyCode::Char(c) => {
173                    self.reuse_keyring_password = false;
174                    self.password.push(c);
175                }
176                KeyCode::Backspace => {
177                    self.password.pop();
178                }
179                _ => {}
180            },
181            Step::Bearer => match key.code {
182                KeyCode::Enter => {
183                    let _ = self.advance_step();
184                }
185                KeyCode::Char(c) => {
186                    self.reuse_keyring_bearer = false;
187                    let pos = self.bearer_cursor.min(self.bearer_token.len());
188                    self.bearer_token.insert(pos, c);
189                    self.bearer_cursor = pos + 1;
190                }
191                KeyCode::Backspace
192                    if self.bearer_cursor > 0 && self.bearer_cursor <= self.bearer_token.len() =>
193                {
194                    self.bearer_token.remove(self.bearer_cursor - 1);
195                    self.bearer_cursor -= 1;
196                }
197                KeyCode::Left if self.bearer_cursor > 0 => {
198                    self.bearer_cursor -= 1;
199                }
200                KeyCode::Right if self.bearer_cursor < self.bearer_token.len() => {
201                    self.bearer_cursor += 1;
202                }
203                _ => {}
204            },
205            Step::PairingCode => match key.code {
206                KeyCode::Enter => {
207                    let _ = self.advance_step();
208                }
209                KeyCode::Char(c) => {
210                    let pos = self.pairing_cursor.min(self.pairing_code.len());
211                    self.pairing_code.insert(pos, c);
212                    self.pairing_cursor = pos + 1;
213                }
214                KeyCode::Backspace
215                    if self.pairing_cursor > 0
216                        && self.pairing_cursor <= self.pairing_code.len() =>
217                {
218                    self.pairing_code.remove(self.pairing_cursor - 1);
219                    self.pairing_cursor -= 1;
220                }
221                KeyCode::Left if self.pairing_cursor > 0 => {
222                    self.pairing_cursor -= 1;
223                }
224                KeyCode::Right if self.pairing_cursor < self.pairing_code.len() => {
225                    self.pairing_cursor += 1;
226                }
227                _ => {}
228            },
229            Step::ApiHeader => match key.code {
230                KeyCode::Tab => self.step = Step::ApiKey,
231                KeyCode::Enter => {
232                    let _ = self.advance_step();
233                }
234                KeyCode::Char(c) => {
235                    let pos = self.header_cursor.min(self.api_header.len());
236                    self.api_header.insert(pos, c);
237                    self.header_cursor = pos + 1;
238                }
239                KeyCode::Backspace
240                    if self.header_cursor > 0 && self.header_cursor <= self.api_header.len() =>
241                {
242                    self.api_header.remove(self.header_cursor - 1);
243                    self.header_cursor -= 1;
244                }
245                KeyCode::Left if self.header_cursor > 0 => {
246                    self.header_cursor -= 1;
247                }
248                KeyCode::Right if self.header_cursor < self.api_header.len() => {
249                    self.header_cursor += 1;
250                }
251                _ => {}
252            },
253            Step::ApiKey => match key.code {
254                KeyCode::Tab => self.step = Step::ApiHeader,
255                KeyCode::Enter => {
256                    let _ = self.advance_step();
257                }
258                KeyCode::Char(c) => {
259                    self.reuse_keyring_api_key = false;
260                    let pos = self.api_key_cursor.min(self.api_key.len());
261                    self.api_key.insert(pos, c);
262                    self.api_key_cursor = pos + 1;
263                }
264                KeyCode::Backspace
265                    if self.api_key_cursor > 0 && self.api_key_cursor <= self.api_key.len() =>
266                {
267                    self.api_key.remove(self.api_key_cursor - 1);
268                    self.api_key_cursor -= 1;
269                }
270                KeyCode::Left if self.api_key_cursor > 0 => {
271                    self.api_key_cursor -= 1;
272                }
273                KeyCode::Right if self.api_key_cursor < self.api_key.len() => {
274                    self.api_key_cursor += 1;
275                }
276                _ => {}
277            },
278            Step::Summary => {
279                if key.code == KeyCode::Enter {
280                    self.testing = true;
281                    self.error = None;
282                    // The caller handles the actual async try_connect_and_persist call
283                    // when they see testing = true.
284                }
285            }
286        }
287        Ok(false)
288    }
289
290    pub fn handle_paste(&mut self, text: &str) {
291        // Remove any newlines or carriage returns that might break single-line fields
292        let clean_text = text.replace(['\n', '\r'], "");
293        if clean_text.is_empty() {
294            return;
295        }
296
297        match self.step {
298            Step::Url => {
299                let pos = self.url_cursor.min(self.url.len());
300                self.url.insert_str(pos, &clean_text);
301                self.url_cursor += clean_text.len();
302            }
303            Step::BasicUser => {
304                let pos = self.user_cursor.min(self.username.len());
305                self.username.insert_str(pos, &clean_text);
306                self.user_cursor += clean_text.len();
307            }
308            Step::BasicPass => {
309                self.reuse_keyring_password = false;
310                self.password.push_str(&clean_text);
311            }
312            Step::Bearer => {
313                self.reuse_keyring_bearer = false;
314                let pos = self.bearer_cursor.min(self.bearer_token.len());
315                self.bearer_token.insert_str(pos, &clean_text);
316                self.bearer_cursor += clean_text.len();
317            }
318            Step::PairingCode => {
319                let pos = self.pairing_cursor.min(self.pairing_code.len());
320                self.pairing_code.insert_str(pos, &clean_text);
321                self.pairing_cursor += clean_text.len();
322            }
323            Step::ApiHeader => {
324                let pos = self.header_cursor.min(self.api_header.len());
325                self.api_header.insert_str(pos, &clean_text);
326                self.header_cursor += clean_text.len();
327            }
328            Step::ApiKey => {
329                self.reuse_keyring_api_key = false;
330                let pos = self.api_key_cursor.min(self.api_key.len());
331                self.api_key.insert_str(pos, &clean_text);
332                self.api_key_cursor += clean_text.len();
333            }
334            _ => {}
335        }
336    }
337}