use crossterm::event::KeyCode;
use ratatui::{prelude::Rect, Frame};
use tui_tree_widget::{Tree, TreeItem, TreeState};
use super::{Action, Document, Pane};
use crate::{event::Event, store::Store, styles::error_text};
mod tree;
use tree::*;
#[derive(Debug, Default)]
pub struct Navigation {
tree_state: TreeState<TreeId>,
nav_tree: Vec<NavTree>,
cached_view_tree: Option<Vec<TreeItem<'static, TreeId>>>,
last_download_summary: (usize, usize),
}
impl Pane for Navigation {
fn draw(&mut self, store: &Store, frame: &mut Frame, area: Rect) {
if self.refresh_tree(store) || self.cached_view_tree.is_none() {
self.cached_view_tree = Some(
self.nav_tree
.iter()
.map(|i| i.as_treeitem(store))
.collect(),
);
}
frame.render_stateful_widget(
Tree::new(self.cached_view_tree.clone().unwrap())
.unwrap()
.highlight_symbol(">>"),
area,
&mut self.tree_state,
);
}
fn handle_event(&mut self, store: &mut Store, event: Event) -> Action {
let Event::Key(key) = event else {
return Action::None;
};
match key.code {
KeyCode::Esc | KeyCode::Char('q') => {
return Action::Exit;
}
KeyCode::Down | KeyCode::Char('j') => {
self.tree_state
.key_down(self.cached_view_tree.as_ref().unwrap());
}
KeyCode::Up | KeyCode::Char('k') => {
self.tree_state
.key_up(self.cached_view_tree.as_ref().unwrap());
}
KeyCode::Enter | KeyCode::Tab => {
let sel = self.tree_state.selected();
let sel_node = NavTree::navigate_mut(&mut self.nav_tree, &sel);
match sel_node {
NavTree::Node {
children: NavTreeChildren::Done(_),
..
} => self.tree_state.toggle(sel),
NavTree::Node {
ty,
children: children @ NavTreeChildren::NotRequested,
} => {
ty.request_children(store);
*children = NavTreeChildren::Loading;
self.tree_state.open(sel);
self.cached_view_tree = None;
}
NavTree::ContentLeaf { content_idx } => {
return Action::Show(Document::Content(*content_idx));
}
NavTree::Header {
ty: HeaderTy::Welcome,
} => {
return Action::Show(Document::Welcome);
}
NavTree::Header {
ty: HeaderTy::Downloads,
} => {
return Action::Show(Document::Downloads);
}
NavTree::Node {
children: NavTreeChildren::Loading,
..
} => (),
NavTree::Loading => (),
NavTree::Header { .. } => (),
}
}
KeyCode::Char('b') => {
let sel = self.tree_state.selected();
let sel_node = NavTree::navigate_mut(&mut self.nav_tree, &sel);
if let NavTree::ContentLeaf { content_idx }
| NavTree::Node {
ty: NodeTy::Content(content_idx),
..
} = sel_node
{
let content = store.content(*content_idx);
if let Err(e) = open::that(content.browser_link()) {
return Action::Flash(error_text(format!("Error opening in browser: {e}")));
}
}
}
_ => (),
};
Action::None
}
}
impl Navigation {
fn refresh_tree(&mut self, store: &Store) -> bool {
if self.nav_tree.is_empty() {
self.nav_tree = vec![NavTree::Loading];
self.tree_state.select(vec![TreeId::Loading]);
store.request_my_courses();
return true;
}
let mut changed = false;
let loading = self.nav_tree.len() == 1 && self.nav_tree[0] == NavTree::Loading;
if loading {
if let Some(all_courses) = store.courses_by_term() {
self.nav_tree.clear();
self.nav_tree.push(NavTree::Header {
ty: HeaderTy::Welcome,
});
self.nav_tree.push(NavTree::Header {
ty: HeaderTy::Downloads,
});
for (term_idx, (_, courses)) in all_courses.iter().enumerate() {
self.nav_tree.push(NavTree::Header {
ty: HeaderTy::Term(term_idx),
});
for course_idx in courses {
self.nav_tree.push(NavTree::Node {
ty: NodeTy::Course(*course_idx),
children: NavTreeChildren::NotRequested,
});
}
}
self.tree_state.select(vec![TreeId::Welcome]);
changed = true;
} else {
return false;
}
}
for item in self.nav_tree.iter_mut() {
match &item {
NavTree::Node {
ty: NodeTy::Course(course_idx),
..
} => {
changed |= Self::refresh_subtree(
&mut self.tree_state,
store,
&mut vec![TreeId::Course(*course_idx)],
item,
);
}
NavTree::Header {
ty: HeaderTy::Downloads,
} => {
let summary = store.download_queue_summary();
changed |= summary != self.last_download_summary;
self.last_download_summary = summary;
}
_ => (),
};
}
changed
}
fn refresh_subtree(
tree_state: &mut TreeState<TreeId>,
store: &Store,
id: &mut Vec<TreeId>,
item: &mut NavTree,
) -> bool {
match item {
NavTree::Node {
children: NavTreeChildren::NotRequested,
..
} => false,
NavTree::ContentLeaf { .. } => false,
NavTree::Loading => false,
NavTree::Header { .. } => false,
NavTree::Node {
children: NavTreeChildren::Done(cs),
..
} => cs
.iter_mut()
.map(|c| {
id.push(c.id());
let res = Self::refresh_subtree(tree_state, store, id, c);
id.pop();
res
})
.fold(false, |acc, changed| acc | changed),
NavTree::Node {
ty,
children: children @ NavTreeChildren::Loading,
} => {
if let Some(new_children) = ty.new_children_loaded(store) {
*children = NavTreeChildren::Done(new_children);
tree_state.open(id.clone());
true
} else {
false
}
}
}
}
}