reducer.rs
1 use super::{App, AppAction, AppEffect, COMMAND_PALETTE_ITEMS, FocusArea}; 2 3 impl App { 4 pub fn close_command_palette(&mut self) { 5 self.command_palette_open = false; 6 self.command_palette_query.clear(); 7 self.command_palette_selected_index = 0; 8 } 9 10 pub fn open_command_palette(&mut self) { 11 self.command_palette_open = true; 12 self.command_palette_query.clear(); 13 self.command_palette_selected_index = 0; 14 } 15 16 pub fn open_api_base_url_editor(&mut self) { 17 self.api_base_url_editor_open = true; 18 self.api_base_url_editor_buffer = self.api_base_url.clone(); 19 } 20 21 pub fn close_api_base_url_editor(&mut self) { 22 self.api_base_url_editor_open = false; 23 self.api_base_url_editor_buffer.clear(); 24 } 25 26 fn apply_api_base_url_editor(&mut self) { 27 let trimmed_value = self.api_base_url_editor_buffer.trim(); 28 if trimmed_value.is_empty() { 29 self.close_api_base_url_editor(); 30 return; 31 } 32 33 self.api_base_url = 34 if trimmed_value.starts_with("http://") || trimmed_value.starts_with("https://") { 35 trimmed_value.trim_end_matches('/').to_owned() 36 } else { 37 format!("http://{}", trimmed_value.trim_end_matches('/')) 38 }; 39 40 self.close_api_base_url_editor(); 41 } 42 43 pub fn filtered_command_item_indices(&self) -> Vec<usize> { 44 let query = self.command_palette_query.trim().to_lowercase(); 45 COMMAND_PALETTE_ITEMS 46 .iter() 47 .enumerate() 48 .filter_map(|(index, command_palette_item)| { 49 if query.is_empty() { 50 return Some(index); 51 } 52 53 let haystack = format!( 54 "{} {} {}", 55 command_palette_item.label, 56 command_palette_item.keywords, 57 command_palette_item.shortcut 58 ) 59 .to_lowercase(); 60 61 if haystack.contains(&query) { 62 Some(index) 63 } else { 64 None 65 } 66 }) 67 .collect() 68 } 69 70 pub fn command_palette_selected_index(&self) -> usize { 71 self.command_palette_selected_index 72 } 73 74 fn normalize_command_palette_selection(&mut self) { 75 let filtered_length = self.filtered_command_item_indices().len(); 76 if filtered_length == 0 { 77 self.command_palette_selected_index = 0; 78 return; 79 } 80 81 if self.command_palette_selected_index >= filtered_length { 82 self.command_palette_selected_index = filtered_length - 1; 83 } 84 } 85 86 fn move_command_palette_selection_previous(&mut self) { 87 let filtered_length = self.filtered_command_item_indices().len(); 88 if filtered_length == 0 { 89 self.command_palette_selected_index = 0; 90 return; 91 } 92 93 self.command_palette_selected_index = 94 Self::wrapped_previous_index(self.command_palette_selected_index, filtered_length); 95 } 96 97 fn move_command_palette_selection_next(&mut self) { 98 let filtered_length = self.filtered_command_item_indices().len(); 99 if filtered_length == 0 { 100 self.command_palette_selected_index = 0; 101 return; 102 } 103 104 self.command_palette_selected_index = 105 Self::wrapped_next_index(self.command_palette_selected_index, filtered_length); 106 } 107 108 fn append_command_palette_query_char(&mut self, character: char) { 109 self.command_palette_query.push(character); 110 self.normalize_command_palette_selection(); 111 } 112 113 fn backspace_command_palette_query(&mut self) { 114 self.command_palette_query.pop(); 115 self.normalize_command_palette_selection(); 116 } 117 118 fn execute_command_palette_selection(&mut self) -> Option<AppAction> { 119 let filtered_indices = self.filtered_command_item_indices(); 120 let Some(command_palette_item_index) = filtered_indices 121 .get(self.command_palette_selected_index) 122 .copied() 123 else { 124 return None; 125 }; 126 127 let selected_action = COMMAND_PALETTE_ITEMS[command_palette_item_index].action; 128 self.close_command_palette(); 129 Some(selected_action) 130 } 131 132 pub fn reduce_action(&mut self, action: AppAction) -> (Option<AppAction>, Option<AppEffect>) { 133 match action { 134 AppAction::Quit => self.exit = true, 135 AppAction::NextPane => self.focus_area = self.focus_area.next(), 136 AppAction::PreviousPane => self.focus_area = self.focus_area.previous(), 137 AppAction::FocusMeasurementsPane => self.focus_area = FocusArea::Measurements, 138 AppAction::FocusNetworkPane => self.focus_area = FocusArea::Network, 139 AppAction::FocusFileSystemPane => self.focus_area = FocusArea::FileSystem, 140 AppAction::MoveSelectionUp => self.move_selection_previous(), 141 AppAction::MoveSelectionDown => self.move_selection_next(), 142 AppAction::SelectPreviousMeasurementTab => { 143 self.measurement_tab = self.measurement_tab.previous(); 144 } 145 AppAction::SelectNextMeasurementTab => { 146 self.measurement_tab = self.measurement_tab.next(); 147 } 148 AppAction::RefreshFileSystem => return (None, Some(AppEffect::RefreshFileSystem)), 149 AppAction::ScanNetwork => return (None, Some(AppEffect::ScanNetwork)), 150 AppAction::OpenCommandPalette => self.open_command_palette(), 151 AppAction::CloseCommandPalette => self.close_command_palette(), 152 AppAction::CommandPaletteInputChar(character) => { 153 self.append_command_palette_query_char(character) 154 } 155 AppAction::CommandPaletteBackspace => self.backspace_command_palette_query(), 156 AppAction::CommandPaletteSelectUp => self.move_command_palette_selection_previous(), 157 AppAction::CommandPaletteSelectDown => self.move_command_palette_selection_next(), 158 AppAction::CommandPaletteExecute => { 159 return (self.execute_command_palette_selection(), None); 160 } 161 AppAction::OpenApiBaseUrlEditor => self.open_api_base_url_editor(), 162 AppAction::CloseApiBaseUrlEditor => self.close_api_base_url_editor(), 163 AppAction::ApiBaseUrlEditorInputChar(character) => { 164 self.api_base_url_editor_buffer.push(character) 165 } 166 AppAction::ApiBaseUrlEditorBackspace => { 167 self.api_base_url_editor_buffer.pop(); 168 } 169 AppAction::ApplyApiBaseUrlEditor => self.apply_api_base_url_editor(), 170 AppAction::MouseHover { column, row } => { 171 if !self.command_palette_open 172 && !self.api_base_url_editor_open 173 && let Some(focus_area) = self.dashboard_areas.focus_area_at(column, row) 174 { 175 self.focus_area = focus_area; 176 } 177 } 178 AppAction::Noop => {} 179 } 180 181 (None, None) 182 } 183 184 fn wrapped_previous_index(current_index: usize, length: usize) -> usize { 185 if current_index == 0 { 186 length - 1 187 } else { 188 current_index - 1 189 } 190 } 191 192 fn wrapped_next_index(current_index: usize, length: usize) -> usize { 193 if current_index + 1 >= length { 194 0 195 } else { 196 current_index + 1 197 } 198 } 199 200 fn move_selection_previous(&mut self) { 201 match self.focus_area { 202 FocusArea::Measurements => {} 203 FocusArea::Network => { 204 if self.wireless_networks.is_empty() { 205 self.network_table_state.select(None); 206 return; 207 } 208 let current_index = self.network_table_state.selected().unwrap_or(0); 209 let previous_index = 210 Self::wrapped_previous_index(current_index, self.wireless_networks.len()); 211 self.network_table_state.select(Some(previous_index)); 212 } 213 FocusArea::FileSystem => { 214 #[cfg(not(target_arch = "wasm32"))] 215 { 216 self.file_system_tree_state.key_up(); 217 } 218 219 #[cfg(target_arch = "wasm32")] 220 { 221 let total_rows = self.file_system_render_row_count(); 222 if total_rows == 0 { 223 self.file_system_list_state.select(None); 224 return; 225 } 226 let current_index = self.file_system_list_state.selected().unwrap_or(0); 227 let previous_index = Self::wrapped_previous_index(current_index, total_rows); 228 self.file_system_list_state.select(Some(previous_index)); 229 } 230 } 231 } 232 } 233 234 fn move_selection_next(&mut self) { 235 match self.focus_area { 236 FocusArea::Measurements => {} 237 FocusArea::Network => { 238 if self.wireless_networks.is_empty() { 239 self.network_table_state.select(None); 240 return; 241 } 242 let current_index = self.network_table_state.selected().unwrap_or(0); 243 let next_index = 244 Self::wrapped_next_index(current_index, self.wireless_networks.len()); 245 self.network_table_state.select(Some(next_index)); 246 } 247 FocusArea::FileSystem => { 248 #[cfg(not(target_arch = "wasm32"))] 249 { 250 self.file_system_tree_state.key_down(); 251 } 252 253 #[cfg(target_arch = "wasm32")] 254 { 255 let total_rows = self.file_system_render_row_count(); 256 if total_rows == 0 { 257 self.file_system_list_state.select(None); 258 return; 259 } 260 let current_index = self.file_system_list_state.selected().unwrap_or(0); 261 let next_index = Self::wrapped_next_index(current_index, total_rows); 262 self.file_system_list_state.select(Some(next_index)); 263 } 264 } 265 } 266 } 267 }