use super::app::{App, AppMode, TabKind};
use super::app_states::ComponentFilter;
use super::state::ListNavigation;
impl App {
pub fn next_tab(&mut self) {
let has_graph_changes = self
.data
.diff_result
.as_ref()
.is_some_and(|r| !r.graph_changes.is_empty());
self.active_tab = match self.active_tab {
TabKind::Summary => TabKind::Components,
TabKind::Overview => TabKind::Tree,
TabKind::Tree => TabKind::Components,
TabKind::Components => TabKind::Dependencies,
TabKind::Dependencies => TabKind::Licenses,
TabKind::Licenses => TabKind::Vulnerabilities,
TabKind::Vulnerabilities => TabKind::Quality,
TabKind::Quality => {
if matches!(self.mode, AppMode::Diff | AppMode::View) {
TabKind::Compliance
} else {
TabKind::Summary
}
}
TabKind::Compliance => TabKind::SideBySide,
TabKind::SideBySide => {
if has_graph_changes {
TabKind::GraphChanges
} else {
TabKind::Source
}
}
TabKind::GraphChanges => TabKind::Source,
TabKind::Source => TabKind::Summary,
};
}
pub fn prev_tab(&mut self) {
let has_graph_changes = self
.data
.diff_result
.as_ref()
.is_some_and(|r| !r.graph_changes.is_empty());
self.active_tab = match self.active_tab {
TabKind::Summary => {
if matches!(self.mode, AppMode::Diff | AppMode::View) {
TabKind::Source
} else {
TabKind::Quality
}
}
TabKind::Overview => TabKind::Summary,
TabKind::Tree => TabKind::Overview,
TabKind::Components => TabKind::Summary,
TabKind::Dependencies => TabKind::Components,
TabKind::Licenses => TabKind::Dependencies,
TabKind::Vulnerabilities => TabKind::Licenses,
TabKind::Quality => TabKind::Vulnerabilities,
TabKind::Compliance => TabKind::Quality,
TabKind::SideBySide => TabKind::Compliance,
TabKind::GraphChanges => TabKind::SideBySide,
TabKind::Source => {
if has_graph_changes {
TabKind::GraphChanges
} else {
TabKind::SideBySide
}
}
};
}
pub const fn select_tab(&mut self, tab: TabKind) {
self.active_tab = tab;
}
pub fn select_up(&mut self) {
match self.active_tab {
TabKind::Components => self.components_state_mut().select_prev(),
TabKind::Vulnerabilities => self.vulnerabilities_state_mut().select_prev(),
TabKind::Licenses => self.licenses_state_mut().select_prev(),
TabKind::Source => self.source_state_mut().select_prev(),
_ => {}
}
}
pub fn select_down(&mut self) {
match self.active_tab {
TabKind::Components => self.components_state_mut().select_next(),
TabKind::Vulnerabilities => self.vulnerabilities_state_mut().select_next(),
TabKind::Licenses => self.licenses_state_mut().select_next(),
TabKind::Source => self.source_state_mut().select_next(),
_ => {}
}
}
pub fn select_first(&mut self) {
match self.active_tab {
TabKind::Components => self.components_state_mut().go_first(),
TabKind::Vulnerabilities => self.vulnerabilities_state_mut().go_first(),
TabKind::Licenses => self.licenses_state_mut().go_first(),
TabKind::Source => self.source_state_mut().select_first(),
_ => {}
}
}
pub fn select_last(&mut self) {
match self.active_tab {
TabKind::Components => self.components_state_mut().go_last(),
TabKind::Vulnerabilities => self.vulnerabilities_state_mut().go_last(),
TabKind::Source => self.source_state_mut().select_last(),
_ => {}
}
}
pub fn page_up(&mut self) {
match self.active_tab {
TabKind::Components => self.components_state_mut().page_up(),
TabKind::Vulnerabilities => self.vulnerabilities_state_mut().page_up(),
TabKind::Source => self.source_state_mut().page_up(),
_ => {}
}
}
pub fn page_down(&mut self) {
match self.active_tab {
TabKind::Components => self.components_state_mut().page_down(),
TabKind::Vulnerabilities => self.vulnerabilities_state_mut().page_down(),
TabKind::Source => self.source_state_mut().page_down(),
_ => {}
}
}
pub fn navigate_vuln_to_component(&mut self, vuln_id: &str, component_name: &str) {
let selected = self.vulnerabilities_state().selected;
self.navigation_ctx.push_breadcrumb(
TabKind::Vulnerabilities,
vuln_id.to_string(),
selected,
);
self.navigation_ctx.target_component = Some(component_name.to_string());
self.active_tab = TabKind::Components;
self.find_and_select_component(component_name);
}
pub fn navigate_dep_to_component(&mut self, dep_name: &str) {
let dep_name = dep_name
.split_once(":+:")
.map(|(_, dep)| dep)
.or_else(|| dep_name.split_once(":-:").map(|(_, dep)| dep))
.unwrap_or(dep_name);
if dep_name.starts_with("__") {
return;
}
self.navigation_ctx.push_breadcrumb(
TabKind::Dependencies,
dep_name.to_string(),
self.dependencies_state().selected,
);
self.navigation_ctx.target_component = Some(dep_name.to_string());
self.active_tab = TabKind::Components;
self.find_and_select_component(dep_name);
}
pub fn navigate_back(&mut self) -> bool {
if let Some(breadcrumb) = self.navigation_ctx.pop_breadcrumb() {
self.active_tab = breadcrumb.tab;
match breadcrumb.tab {
TabKind::Vulnerabilities => {
self.vulnerabilities_state_mut().selected = breadcrumb.selection_index;
}
TabKind::Components => {
self.components_state_mut().selected = breadcrumb.selection_index;
}
TabKind::Dependencies => {
self.dependencies_state_mut().selected = breadcrumb.selection_index;
}
TabKind::Licenses => {
self.licenses_state_mut().selected = breadcrumb.selection_index;
}
TabKind::Source => {
self.source_state_mut().active_panel_mut().selected =
breadcrumb.selection_index;
}
_ => {}
}
self.navigation_ctx.clear_targets();
true
} else {
false
}
}
pub(super) fn find_and_select_component(&mut self, name: &str) {
if self.data.diff_result.is_some() {
self.components_state_mut().filter = ComponentFilter::All;
let name_lower = name.to_lowercase();
let index = {
let items = self.diff_component_items(ComponentFilter::All);
items
.iter()
.position(|comp| comp.name.to_lowercase() == name_lower)
};
if let Some(index) = index {
self.components_state_mut().selected = index;
}
}
}
#[must_use]
pub fn has_navigation_history(&self) -> bool {
self.navigation_ctx.has_history()
}
#[must_use]
pub fn breadcrumb_trail(&self) -> String {
self.navigation_ctx.breadcrumb_trail()
}
pub(super) fn navigate_to_target(&mut self, target: super::traits::TabTarget) {
use super::traits::TabTarget;
if matches!(
target,
TabTarget::ComponentByName(_)
| TabTarget::ComponentByLicense(_)
| TabTarget::VulnerabilityById(_)
) {
let selection_index = self.current_tab_selection_index();
self.navigation_ctx
.push_breadcrumb(self.active_tab, String::new(), selection_index);
}
match target {
TabTarget::Summary => self.active_tab = TabKind::Summary,
TabTarget::Overview => self.active_tab = TabKind::Overview,
TabTarget::Tree => self.active_tab = TabKind::Tree,
TabTarget::Components => self.active_tab = TabKind::Components,
TabTarget::Dependencies => self.active_tab = TabKind::Dependencies,
TabTarget::Licenses => self.active_tab = TabKind::Licenses,
TabTarget::Vulnerabilities => self.active_tab = TabKind::Vulnerabilities,
TabTarget::Quality => self.active_tab = TabKind::Quality,
TabTarget::Compliance => self.active_tab = TabKind::Compliance,
TabTarget::SideBySide => self.active_tab = TabKind::SideBySide,
TabTarget::GraphChanges => self.active_tab = TabKind::GraphChanges,
TabTarget::Source => self.active_tab = TabKind::Source,
TabTarget::ComponentByName(name) => {
self.active_tab = TabKind::Components;
self.find_and_select_component(&name);
}
TabTarget::VulnerabilityById(id) => {
self.active_tab = TabKind::Vulnerabilities;
if let Some(idx) = self.find_vulnerability_index(&id) {
self.vulnerabilities_state_mut().selected = idx;
}
}
TabTarget::ComponentByLicense(license) => {
self.active_tab = TabKind::Components;
self.set_status_message(format!("Showing components with license: {license}"));
}
}
}
fn current_tab_selection_index(&self) -> usize {
match self.active_tab {
TabKind::Components => self.components_state().selected,
TabKind::Vulnerabilities => self.vulnerabilities_state().selected,
TabKind::Licenses => self.licenses_state().selected,
TabKind::Dependencies => self.dependencies_state().selected,
TabKind::Compliance => self.compliance_view.inner().selected_violation,
TabKind::Source => {
let state = self.source_state();
match state.active_side {
crate::tui::app_states::source::SourceSide::Old => state.old_panel.selected,
crate::tui::app_states::source::SourceSide::New => state.new_panel.selected,
}
}
_ => 0,
}
}
}