My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: basic tasklist

+128 -10
+3 -3
src/tui/components/todo/explorer.rs
··· 1 1 use ratatui::{ 2 - style::{Color, Modifier}, 2 + style::{Color, Modifier, Stylize}, 3 3 text::{Line, Span, Text}, 4 4 widgets::{List, ListState}, 5 5 }; ··· 39 39 let id_list = tree 40 40 .tree 41 41 .traverse_pre_order_ids(scope) 42 - .expect("This should nto panic as the node id should exist inside") 42 + .expect("This should not panic as the node id should exist inside") 43 43 .collect::<Vec<_>>(); 44 44 45 45 Self { ··· 61 61 fn from(value: &TodoNode) -> Self { 62 62 let spacer = Span::from(" ".repeat(value.depth)); 63 63 let name = match value.kind { 64 - TodoNodeKind::Group(ref g) => Span::from(g.name.clone()), 64 + TodoNodeKind::Group(ref g) => Span::from(g.name.clone()).bg(g.tag.color), 65 65 TodoNodeKind::Task(ref t) => Span::from(t.name.clone()), 66 66 TodoNodeKind::Root => Span::from("THIS SHOULD NOT BE VISIBLE"), 67 67 };
+24 -7
src/tui/components/todo/mod.rs
··· 14 14 15 15 mod explorer; 16 16 use explorer::Explorer; 17 + mod tasklist; 18 + use tasklist::TaskList; 17 19 18 20 pub struct Todo<'text> { 19 21 #[expect(dead_code)] 20 22 signal_tx: Option<UnboundedSender<Signal>>, 21 23 kh: KastenHandle, 22 - #[expect(dead_code)] 23 24 layouts: Layouts, 24 25 explorer: Explorer<'text>, 26 + task_list: TaskList<'text>, 25 27 } 26 28 27 29 impl Todo<'_> { ··· 31 33 let mut l_state = ListState::default(); 32 34 l_state.select_first(); 33 35 let explorer = Explorer::new(&kt.todo_tree, &kt.todo_tree.root_id, l_state, 0); 36 + let task_list = TaskList::new(&kt.todo_tree, &kt.todo_tree.root_id, l_state, 0); 34 37 35 38 drop(kt); 36 39 ··· 39 42 layouts: Layouts::default(), 40 43 signal_tx: None, 41 44 explorer, 45 + task_list, 42 46 }) 43 47 } 44 48 } 45 49 46 - #[expect(dead_code)] 47 50 struct Layouts { 48 51 main: Layout, 49 52 } ··· 60 63 impl Component for Todo<'_> { 61 64 async fn init(&mut self, area: Size) -> color_eyre::Result<()> { 62 65 let total_width = area.width; 63 - 64 - let mut l_state = ListState::default(); 65 - l_state.select_first(); 66 66 let tree = &self.kh.read().await.todo_tree; 67 67 68 - let explorer = Explorer::new(tree, &tree.root_id, l_state, total_width); 68 + let explorer = Explorer::new(tree, &tree.root_id, self.explorer.state, total_width / 2); 69 + let task_list = TaskList::new(tree, &tree.root_id, self.task_list.state, total_width / 2); 69 70 self.explorer = explorer; 71 + self.task_list = task_list; 70 72 71 73 Ok(()) 72 74 } ··· 87 89 } 88 90 89 91 fn draw(&mut self, frame: &mut Frame, area: Rect) -> color_eyre::Result<()> { 90 - frame.render_stateful_widget(&self.explorer.render_list, area, &mut self.explorer.state); 92 + let (explorer_rect, task_list_rect) = { 93 + let rects = self.layouts.main.split(area); 94 + (rects[0], rects[1]) 95 + }; 96 + 97 + frame.render_stateful_widget( 98 + &self.explorer.render_list, 99 + explorer_rect, 100 + &mut self.explorer.state, 101 + ); 102 + 103 + frame.render_stateful_widget( 104 + &self.task_list.render_list, 105 + task_list_rect, 106 + &mut self.task_list.state, 107 + ); 91 108 Ok(()) 92 109 } 93 110 }
+93
src/tui/components/todo/tasklist.rs
··· 1 + #![expect(dead_code)] 2 + use ratatui::{ 3 + style::{Color, Modifier}, 4 + text::{Line, Span, Text}, 5 + widgets::{List, ListState}, 6 + }; 7 + use tree::NodeId; 8 + 9 + use crate::types::{TodoNode, TodoNodeKind, TodoTree}; 10 + 11 + pub struct TaskList<'text> { 12 + pub render_list: List<'text>, 13 + pub id_list: Vec<NodeId>, 14 + pub state: ListState, 15 + pub width: u16, 16 + } 17 + 18 + impl TaskList<'_> { 19 + pub fn new(tree: &TodoTree, scope: &NodeId, state: ListState, width: u16) -> Self { 20 + let mut id_list = vec![]; 21 + 22 + let render_list = List::new( 23 + tree.tree 24 + .traverse_pre_order(scope) 25 + .expect("nthis should not panic as the node id should exist inside") 26 + .zip( 27 + tree.tree 28 + .traverse_pre_order_ids(scope) 29 + .expect("This should not panic as the nodeid should exist inside"), 30 + ) 31 + .filter_map(|(node, id)| { 32 + let TodoNodeKind::Task(_) = node.data().kind else { 33 + return None; 34 + }; 35 + 36 + let mut tli: TaskListItem<'_> = node.data().into(); 37 + 38 + id_list.push(id); 39 + 40 + tli.width = width; 41 + Some(Text::from(tli)) 42 + }), 43 + ) 44 + .style(Color::White) 45 + .highlight_style(Modifier::REVERSED) 46 + .highlight_symbol("> "); 47 + 48 + Self { 49 + render_list, 50 + id_list, 51 + state, 52 + width, 53 + } 54 + } 55 + } 56 + 57 + pub struct TaskListItem<'text> { 58 + name: Span<'text>, 59 + group: Span<'text>, 60 + due_priority: Span<'text>, 61 + width: u16, 62 + } 63 + 64 + // its fine because if it fails, its my fault, not the users. 65 + #[expect(clippy::fallible_impl_from)] 66 + impl From<&TodoNode> for TaskListItem<'_> { 67 + fn from(value: &TodoNode) -> Self { 68 + let TodoNodeKind::Task(ref task) = value.kind else { 69 + panic!("Should not be possible"); 70 + }; 71 + 72 + let name = Span::from(task.name.clone()); 73 + let group = Span::from(task.group.name.clone()); 74 + let due_priority = task.due.map_or_else( 75 + || Span::from(task.priority.to_string()), 76 + |due_date| Span::from(due_date.to_string()), 77 + ); 78 + 79 + Self { 80 + name, 81 + group, 82 + due_priority, 83 + width: 0, 84 + } 85 + } 86 + } 87 + 88 + impl<'text> From<TaskListItem<'text>> for Text<'text> { 89 + fn from(value: TaskListItem<'text>) -> Self { 90 + let line = Line::from(vec![value.name, value.group, value.due_priority]); 91 + line.into() 92 + } 93 + }
+8
src/types/priority.rs
··· 1 + use std::fmt::Display; 2 + 1 3 use dto::PriorityDTO; 2 4 3 5 /// An Enum for the various `Priority` levels ··· 18 20 value.field1 19 21 } 20 22 } 23 + 24 + impl Display for Priority { 25 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 26 + write!(f, "{}", self.field1) 27 + } 28 + }