sbom_tools/tui/
app_impl_nav.rs1use super::app::{App, AppMode, TabKind};
4use super::app_states::ComponentFilter;
5
6impl App {
7 pub fn next_tab(&mut self) {
9 let has_graph_changes = self
10 .data.diff_result
11 .as_ref()
12 .map(|r| !r.graph_changes.is_empty())
13 .unwrap_or(false);
14
15 self.active_tab = match self.active_tab {
16 TabKind::Summary => TabKind::Components,
17 TabKind::Components => TabKind::Dependencies,
18 TabKind::Dependencies => TabKind::Licenses,
19 TabKind::Licenses => TabKind::Vulnerabilities,
20 TabKind::Vulnerabilities => TabKind::Quality,
21 TabKind::Quality => {
22 if self.mode == AppMode::Diff {
23 TabKind::Compliance
24 } else {
25 TabKind::Summary
26 }
27 }
28 TabKind::Compliance => TabKind::SideBySide,
29 TabKind::SideBySide => {
30 if has_graph_changes {
31 TabKind::GraphChanges
32 } else {
33 TabKind::Source
34 }
35 }
36 TabKind::GraphChanges => TabKind::Source,
37 TabKind::Source => TabKind::Summary,
38 };
39 }
40
41 pub fn prev_tab(&mut self) {
43 let has_graph_changes = self
44 .data.diff_result
45 .as_ref()
46 .map(|r| !r.graph_changes.is_empty())
47 .unwrap_or(false);
48
49 self.active_tab = match self.active_tab {
50 TabKind::Summary => {
51 if self.mode == AppMode::Diff {
52 TabKind::Source
53 } else {
54 TabKind::Quality
55 }
56 }
57 TabKind::Components => TabKind::Summary,
58 TabKind::Dependencies => TabKind::Components,
59 TabKind::Licenses => TabKind::Dependencies,
60 TabKind::Vulnerabilities => TabKind::Licenses,
61 TabKind::Quality => TabKind::Vulnerabilities,
62 TabKind::Compliance => TabKind::Quality,
63 TabKind::SideBySide => TabKind::Compliance,
64 TabKind::GraphChanges => TabKind::SideBySide,
65 TabKind::Source => {
66 if has_graph_changes {
67 TabKind::GraphChanges
68 } else {
69 TabKind::SideBySide
70 }
71 }
72 };
73 }
74
75 pub fn select_tab(&mut self, tab: TabKind) {
77 self.active_tab = tab;
78 }
79
80 pub fn select_up(&mut self) {
82 match self.active_tab {
83 TabKind::Components => self.tabs.components.select_prev(),
84 TabKind::Vulnerabilities => self.tabs.vulnerabilities.select_prev(),
85 TabKind::Licenses => self.tabs.licenses.select_prev(),
86 TabKind::Source => self.tabs.source.active_panel_mut().select_prev(),
87 _ => {}
88 }
89 }
90
91 pub fn select_down(&mut self) {
93 match self.active_tab {
94 TabKind::Components => self.tabs.components.select_next(),
95 TabKind::Vulnerabilities => self.tabs.vulnerabilities.select_next(),
96 TabKind::Licenses => self.tabs.licenses.select_next(),
97 TabKind::Source => self.tabs.source.active_panel_mut().select_next(),
98 _ => {}
99 }
100 }
101
102 pub fn select_first(&mut self) {
104 match self.active_tab {
105 TabKind::Components => self.tabs.components.selected = 0,
106 TabKind::Vulnerabilities => self.tabs.vulnerabilities.selected = 0,
107 TabKind::Licenses => self.tabs.licenses.selected = 0,
108 TabKind::Source => self.tabs.source.active_panel_mut().select_first(),
109 _ => {}
110 }
111 }
112
113 pub fn select_last(&mut self) {
115 match self.active_tab {
116 TabKind::Components => {
117 if self.tabs.components.total > 0 {
118 self.tabs.components.selected = self.tabs.components.total - 1;
119 }
120 }
121 TabKind::Vulnerabilities => {
122 if self.tabs.vulnerabilities.total > 0 {
123 self.tabs.vulnerabilities.selected = self.tabs.vulnerabilities.total - 1;
124 }
125 }
126 TabKind::Source => self.tabs.source.active_panel_mut().select_last(),
127 _ => {}
128 }
129 }
130
131 pub fn page_up(&mut self) {
133 let page_size = 10;
134 match self.active_tab {
135 TabKind::Components => {
136 self.tabs.components.selected =
137 self.tabs.components.selected.saturating_sub(page_size);
138 }
139 TabKind::Vulnerabilities => {
140 self.tabs.vulnerabilities.selected = self
141 .tabs.vulnerabilities
142 .selected
143 .saturating_sub(page_size);
144 }
145 TabKind::Source => self.tabs.source.active_panel_mut().page_up(),
146 _ => {}
147 }
148 }
149
150 pub fn page_down(&mut self) {
152 let page_size = 10;
153 match self.active_tab {
154 TabKind::Components => {
155 self.tabs.components.selected = (self.tabs.components.selected + page_size)
156 .min(self.tabs.components.total.saturating_sub(1));
157 }
158 TabKind::Vulnerabilities => {
159 self.tabs.vulnerabilities.selected = (self.tabs.vulnerabilities.selected
160 + page_size)
161 .min(self.tabs.vulnerabilities.total.saturating_sub(1));
162 }
163 TabKind::Source => self.tabs.source.active_panel_mut().page_down(),
164 _ => {}
165 }
166 }
167
168 pub fn navigate_vuln_to_component(&mut self, vuln_id: &str, component_name: &str) {
174 self.navigation_ctx.push_breadcrumb(
176 TabKind::Vulnerabilities,
177 vuln_id.to_string(),
178 self.tabs.vulnerabilities.selected,
179 );
180
181 self.navigation_ctx.target_component = Some(component_name.to_string());
183 self.active_tab = TabKind::Components;
184
185 self.find_and_select_component(component_name);
187 }
188
189 pub fn navigate_dep_to_component(&mut self, dep_name: &str) {
191 let dep_name = dep_name
192 .split_once(":+:")
193 .map(|(_, dep)| dep)
194 .or_else(|| dep_name.split_once(":-:").map(|(_, dep)| dep))
195 .unwrap_or(dep_name);
196
197 if dep_name.starts_with("__") {
198 return;
199 }
200
201 self.navigation_ctx.push_breadcrumb(
203 TabKind::Dependencies,
204 dep_name.to_string(),
205 self.tabs.dependencies.selected,
206 );
207
208 self.navigation_ctx.target_component = Some(dep_name.to_string());
210 self.active_tab = TabKind::Components;
211
212 self.find_and_select_component(dep_name);
214 }
215
216 pub fn navigate_back(&mut self) -> bool {
218 if let Some(breadcrumb) = self.navigation_ctx.pop_breadcrumb() {
219 self.active_tab = breadcrumb.tab;
220
221 match breadcrumb.tab {
223 TabKind::Vulnerabilities => {
224 self.tabs.vulnerabilities.selected = breadcrumb.selection_index;
225 }
226 TabKind::Components => {
227 self.tabs.components.selected = breadcrumb.selection_index;
228 }
229 TabKind::Dependencies => {
230 self.tabs.dependencies.selected = breadcrumb.selection_index;
231 }
232 TabKind::Licenses => {
233 self.tabs.licenses.selected = breadcrumb.selection_index;
234 }
235 TabKind::Source => {
236 self.tabs.source.active_panel_mut().selected = breadcrumb.selection_index;
237 }
238 _ => {}
239 }
240
241 self.navigation_ctx.clear_targets();
242 true
243 } else {
244 false
245 }
246 }
247
248 pub(super) fn find_and_select_component(&mut self, name: &str) {
250 if self.data.diff_result.is_some() {
251 self.tabs.components.filter = ComponentFilter::All;
253
254 let name_lower = name.to_lowercase();
255 let index = {
256 let items = self.diff_component_items(ComponentFilter::All);
257 items
258 .iter()
259 .position(|comp| comp.name.to_lowercase() == name_lower)
260 };
261
262 if let Some(index) = index {
263 self.tabs.components.selected = index;
264 }
265 }
266 }
267
268 pub fn has_navigation_history(&self) -> bool {
270 self.navigation_ctx.has_history()
271 }
272
273 pub fn breadcrumb_trail(&self) -> String {
275 self.navigation_ctx.breadcrumb_trail()
276 }
277
278 pub(super) fn navigate_to_target(&mut self, target: super::traits::TabTarget) {
280 use super::traits::TabTarget;
281
282 match target {
283 TabTarget::Summary => self.active_tab = TabKind::Summary,
284 TabTarget::Components => self.active_tab = TabKind::Components,
285 TabTarget::Dependencies => self.active_tab = TabKind::Dependencies,
286 TabTarget::Licenses => self.active_tab = TabKind::Licenses,
287 TabTarget::Vulnerabilities => self.active_tab = TabKind::Vulnerabilities,
288 TabTarget::Quality => self.active_tab = TabKind::Quality,
289 TabTarget::Compliance => self.active_tab = TabKind::Compliance,
290 TabTarget::SideBySide => self.active_tab = TabKind::SideBySide,
291 TabTarget::GraphChanges => self.active_tab = TabKind::GraphChanges,
292 TabTarget::Source => self.active_tab = TabKind::Source,
293 TabTarget::ComponentByName(name) => {
294 self.active_tab = TabKind::Components;
295 self.find_and_select_component(&name);
296 }
297 TabTarget::VulnerabilityById(id) => {
298 self.active_tab = TabKind::Vulnerabilities;
299 if let Some(idx) = self.find_vulnerability_index(&id) {
300 self.tabs.vulnerabilities.selected = idx;
301 }
302 }
303 }
304 }
305}