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 todo explorer

+140 -62
+1 -1
crates/dto/migration/src/types/color.rs
··· 6 6 /// Color type 7 7 /// 8 8 /// We store it as a u32, but its actually 00000000rrrrrrrrggggggggbbbbbbbb 9 - #[derive(Clone, Copy, PartialEq, Eq, DeriveValueType)] 9 + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Copy, DeriveValueType)] 10 10 pub struct Color(u32); 11 11 12 12 impl Color {
+1 -1
crates/dto/migration/src/types/priority.rs
··· 1 1 use sea_orm::DeriveValueType; 2 2 3 - #[derive(Clone, Debug, PartialEq, Eq, DeriveValueType, Default)] 3 + #[derive(Clone, Debug, PartialEq, Eq, DeriveValueType, Default, PartialOrd, Ord)] 4 4 #[sea_orm(value_type = "String")] 5 5 pub enum Priority { 6 6 Asap,
+68 -14
src/tui/components/todo/explorer.rs
··· 1 - #![allow(dead_code)] 1 + use ratatui::{ 2 + style::{Color, Modifier}, 3 + text::{Line, Span, Text}, 4 + widgets::{List, ListState}, 5 + }; 6 + use tree::NodeId; 2 7 3 - use dto::NanoId; 4 - use ratatui::{text::Span, widgets::ListState}; 8 + use crate::types::{TodoNode, TodoNodeKind, TodoTree}; 5 9 6 10 pub struct Explorer<'text> { 7 11 pub render_list: ratatui::widgets::List<'text>, 8 - pub id_list: Vec<NanoId>, 12 + #[allow(dead_code)] 13 + pub id_list: Vec<NodeId>, 9 14 pub state: ListState, 15 + #[allow(dead_code)] 10 16 pub width: u16, 11 17 } 12 18 19 + impl Explorer<'_> { 20 + pub fn new(tree: &TodoTree, scope: &NodeId, state: ListState, width: u16) -> Self { 21 + let render_list = List::new( 22 + tree.tree 23 + .traverse_pre_order(scope) 24 + .expect("This should not panic as the node id should exist inside") 25 + .filter_map(|node| { 26 + // we dont want to show the root 27 + if node.data().kind == TodoNodeKind::Root { 28 + return None; 29 + } 30 + let mut eli: ExplorerListItem<'_> = node.data().into(); 31 + eli.width = width; 32 + Some(Text::from(eli)) 33 + }), 34 + ) 35 + .style(Color::White) 36 + .highlight_style(Modifier::REVERSED) 37 + .highlight_symbol("> "); 38 + 39 + let id_list = tree 40 + .tree 41 + .traverse_pre_order_ids(scope) 42 + .expect("This should nto panic as the node id should exist inside") 43 + .collect::<Vec<_>>(); 44 + 45 + Self { 46 + render_list, 47 + id_list, 48 + state, 49 + width, 50 + } 51 + } 52 + } 53 + 13 54 pub struct ExplorerListItem<'text> { 55 + spacer: Span<'text>, 14 56 name: Span<'text>, 57 + width: u16, 15 58 } 16 59 17 - // impl From<&Task> for ExplorerListItem { 18 - // fn from(value: &Task) -> Self { 19 - // Self { 20 - // name: Span { style: (), content: () } 21 - // } 22 - // } 23 - // } 60 + impl From<&TodoNode> for ExplorerListItem<'_> { 61 + fn from(value: &TodoNode) -> Self { 62 + let spacer = Span::from(" ".repeat(value.depth)); 63 + let name = match value.kind { 64 + TodoNodeKind::Group(ref g) => Span::from(g.name.clone()), 65 + TodoNodeKind::Task(ref t) => Span::from(t.name.clone()), 66 + TodoNodeKind::Root => Span::from("THIS SHOULD NOT BE VISIBLE"), 67 + }; 24 68 25 - // impl Explorer { 26 - // pub async fn 69 + Self { 70 + spacer, 71 + name, 72 + width: 0, 73 + } 74 + } 75 + } 27 76 28 - // } 77 + impl<'text> From<ExplorerListItem<'text>> for Text<'text> { 78 + fn from(value: ExplorerListItem<'text>) -> Self { 79 + let line = Line::from(vec![value.spacer, value.name]); 80 + line.into() 81 + } 82 + }
+33 -21
src/tui/components/todo/mod.rs
··· 1 1 use async_trait::async_trait; 2 2 use color_eyre::eyre::Result; 3 - use dto::{ 4 - ColumnTrait as _, GroupColumns, GroupEntity, QueryFilter as _, TagEntity, TaskEntity, 5 - ZettelEntity, 6 - }; 7 3 use ratatui::{ 8 4 Frame, 9 5 layout::{Constraint, Layout, Rect, Size}, 10 - style::{Color, Stylize}, 11 - widgets::Block, 6 + widgets::ListState, 12 7 }; 13 8 use tokio::sync::mpsc::UnboundedSender; 14 9 ··· 18 13 }; 19 14 20 15 mod explorer; 16 + use explorer::Explorer; 21 17 22 - #[expect(dead_code)] 23 - pub struct Todo { 18 + pub struct Todo<'text> { 19 + #[expect(dead_code)] 24 20 signal_tx: Option<UnboundedSender<Signal>>, 25 21 kh: KastenHandle, 22 + #[expect(dead_code)] 26 23 layouts: Layouts, 24 + explorer: Explorer<'text>, 27 25 } 28 26 29 - impl Todo { 27 + impl Todo<'_> { 30 28 pub async fn new(kh: KastenHandle) -> Result<Self> { 31 29 let kt = kh.read().await; 32 30 33 - let _roots = GroupEntity::load() 34 - .with(TagEntity) 35 - .with(TaskEntity) 36 - .with((ZettelEntity, TagEntity)) 37 - .filter(GroupColumns::ParentGroupId.is_null()) 38 - .all(&kt.db) 39 - .await?; 31 + let mut l_state = ListState::default(); 32 + l_state.select_first(); 33 + let explorer = Explorer::new(&kt.todo_tree, &kt.todo_tree.root_id, l_state, 0); 40 34 41 35 drop(kt); 42 36 ··· 44 38 kh, 45 39 layouts: Layouts::default(), 46 40 signal_tx: None, 41 + explorer, 47 42 }) 48 43 } 49 44 } ··· 62 57 } 63 58 64 59 #[async_trait] 65 - impl Component for Todo { 60 + impl Component for Todo<'_> { 66 61 async fn init(&mut self, area: Size) -> color_eyre::Result<()> { 67 - let _ = area; // to appease clippy 62 + let total_width = area.width; 63 + 64 + let mut l_state = ListState::default(); 65 + l_state.select_first(); 66 + let tree = &self.kh.read().await.todo_tree; 67 + 68 + let explorer = Explorer::new(tree, &tree.root_id, l_state, total_width); 69 + self.explorer = explorer; 68 70 69 71 Ok(()) 70 72 } 71 73 72 - async fn update(&mut self, _signal: Signal) -> color_eyre::Result<Option<Signal>> { 74 + async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>> { 75 + match signal { 76 + Signal::MoveDown => { 77 + self.explorer.state.select_next(); 78 + // self.update_views_from_zettel_list_selection().await?; 79 + } 80 + 81 + Signal::MoveUp => { 82 + self.explorer.state.select_previous(); 83 + } 84 + _ => {} 85 + } 73 86 Ok(None) 74 87 } 75 88 76 89 fn draw(&mut self, frame: &mut Frame, area: Rect) -> color_eyre::Result<()> { 77 - frame.render_widget(Block::new().bg(Color::Red), area); 78 - 90 + frame.render_stateful_widget(&self.explorer.render_list, area, &mut self.explorer.state); 79 91 Ok(()) 80 92 } 81 93 }
+1 -1
src/tui/components/viewport/mod.rs
··· 25 25 switcher: Switcher<'text>, 26 26 active_region: Region, 27 27 zk: Zk<'text>, 28 - todo: Todo, 28 + todo: Todo<'text>, 29 29 } 30 30 31 31 mod switcher;
+1 -1
src/types/color.rs
··· 5 5 6 6 /// Agnostic Color type, 7 7 /// internally represented as rgb 8 - #[derive(Debug, Copy, Clone, Default)] 8 + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Copy, Default)] 9 9 pub struct Color(ColorDTO); 10 10 11 11 impl Display for Color {
+1 -2
src/types/group.rs
··· 3 3 use crate::types::{Priority, Tag, Zettel}; 4 4 5 5 /// A `Group` which contains tasks! 6 - #[expect(dead_code)] 7 - #[derive(Debug, Clone)] 6 + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 8 7 pub struct Group { 9 8 /// Should only be constructed from models. 10 9 _private: (),
+2 -1
src/types/kasten/mod.rs
··· 17 17 pub use index::Index; 18 18 pub use index::ZettelOnDisk; 19 19 mod todo_tree; 20 - pub use todo_tree::{TodoNode, TodoTree}; 20 + 21 + pub use todo_tree::{TodoNode, TodoNodeKind, TodoTree}; 21 22 22 23 #[derive(Debug)] 23 24 pub struct Kasten {
+26 -12
src/types/kasten/todo_tree.rs
··· 9 9 10 10 use crate::types::{Group, Task}; 11 11 12 - #[expect(dead_code)] 13 - #[derive(Debug, Clone)] 14 - pub enum TodoNode { 12 + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 13 + pub enum TodoNodeKind { 15 14 Root, 16 15 Group(Box<Group>), 17 16 Task(Box<Task>), 18 17 } 19 18 19 + #[derive(Debug, Clone)] 20 + pub struct TodoNode { 21 + pub depth: usize, 22 + pub kind: TodoNodeKind, 23 + } 24 + 25 + impl TodoNode { 26 + pub const fn new(kind: TodoNodeKind, depth: usize) -> Self { 27 + Self { depth, kind } 28 + } 29 + } 30 + 20 31 #[derive(Debug)] 21 32 pub struct TodoTree { 22 - tree: Tree<TodoNode>, 23 - nanoid_to_nodeid: HashMap<NanoId, NodeId>, 24 - #[expect(dead_code)] 25 - root_id: NodeId, 33 + pub tree: Tree<TodoNode>, 34 + pub nanoid_to_nodeid: HashMap<NanoId, NodeId>, 35 + pub root_id: NodeId, 26 36 } 27 37 28 38 impl TodoTree { 29 39 pub async fn construct(db: &DatabaseConnection) -> Result<Self> { 30 40 let mut tree = Tree::<TodoNode>::new(); 31 41 let root_id = tree 32 - .insert(Node::new(TodoNode::Root), InsertBehavior::AsRoot) 42 + .insert( 43 + Node::new(TodoNode::new(TodoNodeKind::Root, 0)), 44 + InsertBehavior::AsRoot, 45 + ) 33 46 .with_context(|| "Could not create root node.")?; 34 47 35 48 let root_groups: Vec<Group> = GroupEntity::load() ··· 51 64 52 65 for group in root_groups { 53 66 todo_tree 54 - .add_group_to_tree(db, &root_id, Box::new(group)) 67 + .add_group_to_tree(db, &root_id, Box::new(group), 0) 55 68 .await?; 56 69 } 57 70 ··· 64 77 db: &DatabaseConnection, 65 78 parent_node_id: &NodeId, 66 79 group: Box<Group>, 80 + depth: usize, 67 81 ) -> Result<()> { 68 82 let group_id = group.id.clone(); 69 83 70 84 let group_node_id = self.tree.insert( 71 - Node::new(TodoNode::Group(group)), 85 + Node::new(TodoNode::new(TodoNodeKind::Group(group), depth)), 72 86 InsertBehavior::UnderNode(parent_node_id), 73 87 )?; 74 88 ··· 98 112 for task in tasks { 99 113 let task_id = task.id.clone(); 100 114 let task_node_id = self.tree.insert( 101 - Node::new(TodoNode::Task(Box::new(task))), 115 + Node::new(TodoNode::new(TodoNodeKind::Task(Box::new(task)), depth + 1)), 102 116 InsertBehavior::UnderNode(&group_node_id), 103 117 )?; 104 118 ··· 117 131 .collect(); 118 132 119 133 for group in children_groups { 120 - self.add_group_to_tree(db, &group_node_id, Box::new(group)) 134 + self.add_group_to_tree(db, &group_node_id, Box::new(group), depth + 1) 121 135 .await?; 122 136 } 123 137
+1 -3
src/types/mod.rs
··· 24 24 pub use filaments::Filaments; 25 25 26 26 mod kasten; 27 - 28 27 pub use kasten::Index; 29 28 pub use kasten::Kasten; 30 29 pub use kasten::KastenHandle; 31 - #[expect(unused_imports)] 32 30 pub use kasten::TodoNode; 33 - #[expect(unused_imports)] 31 + pub use kasten::TodoNodeKind; 34 32 pub use kasten::TodoTree; 35 33 36 34 mod frontmatter;
+1 -1
src/types/priority.rs
··· 2 2 3 3 /// An Enum for the various `Priority` levels 4 4 /// for `Task`s and `Group`s 5 - #[derive(Debug, Clone, Default)] 5 + #[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] 6 6 pub struct Priority { 7 7 field1: PriorityDTO, 8 8 }
+1 -1
src/types/tag.rs
··· 4 4 5 5 /// Represents a `Tag` in a `ZettelKasten` note taking method. 6 6 /// Easy way to link multiple notes under one simple word. 7 - #[derive(Debug, Clone)] 7 + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 8 8 pub struct Tag { 9 9 /// Should only be constructed from models. 10 10 _private: (),
+1 -2
src/types/task.rs
··· 3 3 use crate::types::{Group, Priority, Zettel}; 4 4 5 5 /// a `Task` that you have to complete! 6 - #[expect(dead_code)] 7 - #[derive(Debug, Clone)] 6 + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 8 7 pub struct Task { 9 8 /// Should only be constructed from models. 10 9 _private:(),
+2 -1
src/types/zettel/mod.rs
··· 19 19 /// A `Zettel` is a note about a single idea. 20 20 /// It can have many `Tag`s, just meaning it can fall under many 21 21 /// categories. 22 - #[derive(Debug, Clone)] 22 + 23 + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 23 24 pub struct Zettel { 24 25 /// Should only be constructed from models. 25 26 _private: (),