use ratatui::{
style::{Color, Modifier, Style},
text::Text,
};
use tui_tree_widget::TreeItem;
use crate::store::{ContentIdx, CourseIdx, Store, TermIdx};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NavTree {
Node {
ty: NodeTy,
children: NavTreeChildren,
},
ContentLeaf { content_idx: ContentIdx },
Loading,
Header { ty: HeaderTy },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NodeTy {
Course(CourseIdx),
Content(ContentIdx),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HeaderTy {
Welcome,
Downloads,
Term(TermIdx),
}
impl HeaderTy {
fn treeitem(&self, store: &Store) -> TreeItem<'static, TreeId> {
let title = match self {
HeaderTy::Term(idx) => store.courses_by_term().unwrap()[*idx].0.clone(),
HeaderTy::Welcome => "Welcome".to_string(),
HeaderTy::Downloads => {
let (completed, total) = store.download_queue_summary();
if total > 0 {
format!("Downloads ({} / {})", completed, total)
} else {
"Downloads".to_string()
}
}
};
TreeItem::new_leaf(
self.id(),
Text::styled(
title,
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD),
),
)
}
fn id(&self) -> TreeId {
match self {
HeaderTy::Term(i) => TreeId::TermHeader(*i),
HeaderTy::Welcome => TreeId::Welcome,
HeaderTy::Downloads => TreeId::Downloads,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NavTreeChildren {
Done(Vec<NavTree>),
Loading,
NotRequested,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TreeId {
TermHeader(TermIdx),
Course(CourseIdx),
CourseLoading(CourseIdx),
Content(CourseIdx),
ContentLoading(CourseIdx),
Loading,
Welcome,
Downloads,
}
impl NavTree {
pub fn navigate_mut<'a>(leafs: &'a mut [Self], ids: &[TreeId]) -> &'a mut NavTree {
if ids.is_empty() {
panic!("attempt to get navtree with invalid id");
}
if matches!(ids[0], TreeId::Loading) {
return &mut leafs[0];
}
let next = leafs
.iter_mut()
.find(|x| x.matches(ids[0]))
.expect("invalid id for navtree");
if ids.len() == 1 || matches!(ids[1], TreeId::CourseLoading(_) | TreeId::ContentLoading(_))
{
next
} else {
let remaining_search = &ids[1..];
match next {
NavTree::Node {
children: NavTreeChildren::Done(cs),
..
} => Self::navigate_mut(cs, remaining_search),
_ => unreachable!(),
}
}
}
fn matches(&self, id: TreeId) -> bool {
match (self, id) {
(NavTree::Node { ty, .. }, id) => ty.matches(id),
(NavTree::ContentLeaf { content_idx }, TreeId::Content(idx))
| (NavTree::ContentLeaf { content_idx }, TreeId::ContentLoading(idx)) => {
*content_idx == idx
}
(
NavTree::Header {
ty: HeaderTy::Term(term_idx),
},
TreeId::TermHeader(idx),
) => *term_idx == idx,
(
NavTree::Header {
ty: HeaderTy::Welcome,
},
TreeId::Welcome,
) => true,
(
NavTree::Header {
ty: HeaderTy::Downloads,
},
TreeId::Downloads,
) => true,
_ => false,
}
}
pub fn as_treeitem(&self, store: &Store) -> TreeItem<'static, TreeId> {
const LOADING: &str = "Loading...";
match self {
NavTree::ContentLeaf { content_idx } => TreeItem::new_leaf(
TreeId::Content(*content_idx),
store.content(*content_idx).title.to_string(),
),
NavTree::Loading => TreeItem::new_leaf(TreeId::Loading, LOADING),
NavTree::Node {
ty,
children: NavTreeChildren::NotRequested,
} => ty.treeitem_leaf(store),
NavTree::Node {
ty,
children: NavTreeChildren::Loading,
} => ty.treeitem_with(store, vec![TreeItem::new_leaf(ty.loading_id(), LOADING)]),
NavTree::Node {
ty,
children: NavTreeChildren::Done(children),
} => ty.treeitem_with(
store,
children.iter().map(|nt| nt.as_treeitem(store)).collect(),
),
NavTree::Header { ty } => ty.treeitem(store),
}
}
pub fn id(&self) -> TreeId {
match self {
NavTree::Node { ty, .. } => ty.id(),
NavTree::ContentLeaf { content_idx } => TreeId::Content(*content_idx),
NavTree::Loading => TreeId::Loading,
NavTree::Header { ty } => ty.id(),
}
}
}
impl NodeTy {
pub fn request_children(&self, store: &Store) {
match self {
NodeTy::Course(i) => store.request_course_content(*i),
NodeTy::Content(i) => store.request_content_children(*i),
}
}
pub fn new_children_loaded(&self, store: &Store) -> Option<Vec<NavTree>> {
let idxs = match self {
NodeTy::Course(i) => store.course_content(*i),
NodeTy::Content(i) => store.content_children(*i),
}?;
Some(
idxs.map(|content_idx| {
let content = store.content(content_idx);
if content.is_container() {
NavTree::Node {
ty: NodeTy::Content(content_idx),
children: NavTreeChildren::NotRequested,
}
} else {
NavTree::ContentLeaf { content_idx }
}
})
.collect(),
)
}
fn matches(&self, id: TreeId) -> bool {
match (self, id) {
(NodeTy::Course(i), TreeId::Course(j))
| (NodeTy::Course(i), TreeId::CourseLoading(j))
| (NodeTy::Content(i), TreeId::Content(j))
| (NodeTy::Content(i), TreeId::ContentLoading(j)) => *i == j,
_ => false,
}
}
fn display_name(&self, store: &Store) -> String {
match self {
NodeTy::Course(i) => store.course(*i).name.clone(),
NodeTy::Content(i) => store.content(*i).title.clone(),
}
}
fn treeitem_with(
&self,
store: &Store,
children: Vec<TreeItem<'static, TreeId>>,
) -> TreeItem<'static, TreeId> {
TreeItem::new(self.id(), self.display_name(store), children).unwrap()
}
fn treeitem_leaf(&self, store: &Store) -> TreeItem<'static, TreeId> {
TreeItem::new_leaf(self.id(), self.display_name(store))
}
fn id(&self) -> TreeId {
match self {
NodeTy::Course(i) => TreeId::Course(*i),
NodeTy::Content(i) => TreeId::Content(*i),
}
}
fn loading_id(&self) -> TreeId {
match self {
NodeTy::Course(i) => TreeId::CourseLoading(*i),
NodeTy::Content(i) => TreeId::ContentLoading(*i),
}
}
}
impl Default for TreeId {
fn default() -> Self {
Self::Loading
}
}