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: stylistic improvements to task_list

+129 -35
+1
crates/dto/src/entity/group.rs
··· 2 2 3 3 use migration::prelude::Local; 4 4 use migration::types::*; 5 + 5 6 use sea_orm::ActiveValue::Set; 6 7 use sea_orm::entity::prelude::*; 7 8 use std::future::ready;
+2
crates/dto/src/lib.rs
··· 22 22 /// and add additional functionality to it. 23 23 pub use migration::types::Priority as PriorityDTO; 24 24 25 + pub use sea_orm::entity::prelude::Date; 25 26 pub use sea_orm::entity::prelude::DateTime; 27 + pub use sea_orm::entity::prelude::Time; 26 28 27 29 /// Color type, exporting as DTO because I might 28 30 /// want to newtype wrap this, might not have to, depending
+6 -2
src/cli/process.rs
··· 6 6 7 7 use color_eyre::eyre::{Context, Result, eyre}; 8 8 use dto::{ 9 - GroupActiveModel, GroupEntity, HasOne, IntoActiveModel, TagActiveModel, TagEntity, 10 - TaskActiveModel, TaskEntity, ZettelEntity, 9 + Date, DateTime, GroupActiveModel, GroupEntity, HasOne, IntoActiveModel, TagActiveModel, 10 + TagEntity, TaskActiveModel, TaskEntity, Time, ZettelEntity, 11 11 }; 12 12 use tower_lsp::{LspService, Server}; 13 13 ··· 158 158 .expect("Zettel must exist since we just created it") 159 159 .into_active_model(), 160 160 ) 161 + .set_due(Some(DateTime::new( 162 + Date::from_ymd_opt(2026, 1, 31).unwrap(), 163 + Time::from_hms_opt(10, 10, 10).unwrap(), 164 + ))) 161 165 .insert(&kt.db) 162 166 .await?; 163 167
+1 -1
src/tui/components/todo/explorer.rs
··· 51 51 } 52 52 } 53 53 54 + #[expect(dead_code)] 54 55 pub fn set_active(&mut self) { 55 56 self.render_list = self.render_list.clone().block( 56 57 Block::new() ··· 62 63 ); 63 64 } 64 65 65 - #[expect(dead_code)] 66 66 pub fn set_inactive(&mut self) { 67 67 self.render_list = self.render_list.clone().block( 68 68 Block::new()
+53 -18
src/tui/components/todo/mod.rs
··· 2 2 use ratatui::{ 3 3 Frame, 4 4 layout::{Constraint, Layout, Rect, Size}, 5 - widgets::ListState, 5 + style::{Color, Stylize}, 6 + widgets::{Block, ListState}, 6 7 }; 7 8 use tokio::sync::mpsc::UnboundedSender; 8 9 ··· 38 39 } 39 40 40 41 struct Layouts { 41 - main: Layout, 42 + explorer_right: Layout, 43 + inspector_task_list: Layout, 42 44 } 43 45 44 46 impl Default for Layouts { 45 47 fn default() -> Self { 46 48 Self { 47 - main: Layout::horizontal(vec![Constraint::Percentage(50), Constraint::Percentage(50)]), 49 + explorer_right: Layout::horizontal(vec![ 50 + Constraint::Percentage(40), 51 + Constraint::Fill(100), 52 + ]), 53 + inspector_task_list: Layout::vertical(vec![ 54 + Constraint::Percentage(30), 55 + Constraint::Fill(100), 56 + ]), 57 + } 58 + } 59 + } 60 + 61 + struct LayoutSplit { 62 + explorer: Rect, 63 + inspector: Rect, 64 + task_list: Rect, 65 + } 66 + 67 + impl Layouts { 68 + fn split(&self, area: Rect) -> LayoutSplit { 69 + let rects = self.explorer_right.split(area); 70 + let r_rects = self.inspector_task_list.split(rects[1]); 71 + 72 + LayoutSplit { 73 + explorer: rects[0], 74 + inspector: r_rects[0], 75 + task_list: r_rects[1], 48 76 } 49 77 } 50 78 } ··· 52 80 #[async_trait] 53 81 impl Component for Todo<'_> { 54 82 async fn init(&mut self, area: Size) -> color_eyre::Result<()> { 55 - let total_width = area.width; 56 83 let tree = &self.kh.read().await.todo_tree; 84 + let splits = self.layouts.split(Rect::new(0, 0, area.width, area.height)); 57 85 58 86 let mut l_state = ListState::default(); 59 87 60 88 l_state.select_first(); 61 89 62 - let mut explorer = Explorer::new(tree, &tree.root_id, l_state, total_width / 2); 63 - let task_list = TaskList::new(tree, &tree.root_id, l_state, total_width / 2); 90 + let mut explorer = Explorer::new(tree, &tree.root_id, l_state, splits.explorer.width); 91 + let mut task_list = TaskList::new(tree, &tree.root_id, l_state, splits.task_list.width); 64 92 65 - explorer.set_active(); 93 + // explorer.set_active(); 94 + explorer.set_inactive(); 95 + // task_list.set_inactive(); 96 + task_list.set_active(); 66 97 67 98 self.explorer = Some(explorer); 68 99 self.task_list = Some(task_list); ··· 71 102 } 72 103 73 104 async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>> { 74 - let explorer = self 105 + let _explorer = self 75 106 .explorer 76 107 .as_mut() 77 108 .expect("This should have already been initialized"); 78 109 79 - let _task_list = self 110 + let task_list = self 80 111 .task_list 81 112 .as_mut() 82 113 .expect("This should have already been initialized"); 83 114 84 115 match signal { 85 116 Signal::MoveDown => { 86 - explorer.state.select_next(); 117 + // explorer.state.select_next(); 118 + task_list.state.select_next(); 87 119 // self.update_views_from_zettel_list_selection().await?; 88 120 } 89 121 90 122 Signal::MoveUp => { 91 - explorer.state.select_previous(); 123 + // explorer.state.select_previous(); 124 + task_list.state.select_previous(); 92 125 } 93 126 _ => {} 94 127 } ··· 96 129 } 97 130 98 131 fn draw(&mut self, frame: &mut Frame, area: Rect) -> color_eyre::Result<()> { 99 - let (explorer_rect, task_list_rect) = { 100 - let rects = self.layouts.main.split(area); 101 - (rects[0], rects[1]) 102 - }; 103 - 104 132 let explorer = self.explorer.as_mut().unwrap(); 105 133 let task_list = self.task_list.as_mut().unwrap(); 106 134 107 - frame.render_stateful_widget(&explorer.render_list, explorer_rect, &mut explorer.state); 108 - frame.render_stateful_widget(&task_list.render_list, task_list_rect, &mut task_list.state); 135 + let splits = self.layouts.split(area); 136 + 137 + frame.render_stateful_widget(&explorer.render_list, splits.explorer, &mut explorer.state); 138 + frame.render_stateful_widget( 139 + &task_list.render_list, 140 + splits.task_list, 141 + &mut task_list.state, 142 + ); 143 + frame.render_widget(Block::new().bg(Color::Green), splits.inspector); 109 144 Ok(()) 110 145 } 111 146 }
+49 -12
src/tui/components/todo/tasklist.rs
··· 1 1 #![expect(dead_code)] 2 2 use ratatui::{ 3 - style::{Color, Modifier}, 3 + style::{Color, Style}, 4 4 text::{Line, Span, Text}, 5 - widgets::{List, ListState}, 5 + widgets::{Block, BorderType, Borders, List, ListState}, 6 6 }; 7 7 use tree::NodeId; 8 8 ··· 42 42 }), 43 43 ) 44 44 .style(Color::White) 45 - .highlight_style(Modifier::REVERSED) 46 - .highlight_symbol("> "); 45 + .highlight_style(Style::new().on_dark_gray()); 46 + // .highlight_symbol("> "); 47 47 48 48 Self { 49 49 render_list, ··· 52 52 width, 53 53 } 54 54 } 55 + 56 + pub fn set_active(&mut self) { 57 + self.render_list = self.render_list.clone().block( 58 + Block::new() 59 + .title("[2]") 60 + .title("TodoList") 61 + .borders(Borders::TOP | Borders::LEFT) 62 + .border_style(Style::new().fg(Color::Green)) 63 + .border_type(BorderType::Rounded), 64 + ); 65 + } 66 + 67 + pub fn set_inactive(&mut self) { 68 + self.render_list = self.render_list.clone().block( 69 + Block::new() 70 + .title("[2]") 71 + .title("TodoList") 72 + .borders(Borders::TOP | Borders::LEFT) 73 + .border_style(Style::new().fg(Color::Gray)) 74 + .border_type(BorderType::Rounded), 75 + ); 76 + } 55 77 } 56 78 57 79 pub struct TaskListItem<'text> { ··· 69 91 panic!("Should not be possible"); 70 92 }; 71 93 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 - ); 94 + let color = task.group.tag.color; 95 + 96 + let name = Span::from(task.name.clone()).style(Style::new().fg(color.into())); 97 + let group = Span::from(task.group.name.clone()).style(Style::new().fg(color.into())); 98 + let due_priority = task 99 + .due() 100 + .map_or_else(|| Span::from(task.priority.to_string()), Span::from) 101 + .style(Style::new().fg(color.into())); 78 102 79 103 Self { 80 104 name, ··· 87 111 88 112 impl<'text> From<TaskListItem<'text>> for Text<'text> { 89 113 fn from(value: TaskListItem<'text>) -> Self { 90 - let line = Line::from(vec![value.name, value.group, value.due_priority]); 91 - line.into() 114 + let total_width = value.width.saturating_sub(2) as usize; 115 + let name_col = total_width / 2; 116 + let due_content = value.due_priority.content.as_ref(); 117 + let due_col = due_content.len(); 118 + let group_col = total_width.saturating_sub(name_col + due_col); 119 + 120 + let name_str = format!("{:<width$}", value.name.content, width = name_col); 121 + let group_str = format!("{:<width$}", value.group.content, width = group_col); 122 + let due_str = format!("{due_content:>due_col$}"); 123 + 124 + let name = Span::styled(name_str, value.name.style); 125 + let group = Span::styled(group_str, value.group.style); 126 + let due = Span::styled(due_str, value.due_priority.style); 127 + 128 + Line::from(vec![name, group, due]).into() 92 129 } 93 130 }
-1
src/tui/components/viewport/mod.rs
··· 109 109 Region::Todo => self.todo.draw(frame, area), 110 110 }?; 111 111 112 - // frame.render_widget(self.switcher.clone(), area); 113 112 Ok(()) 114 113 } 115 114 }
+17 -1
src/types/task.rs
··· 1 1 use dto::{ DateTime, NanoId, TaskModelEx}; 2 2 3 - use crate::types::{Group, Priority, Zettel}; 3 + use crate::types::{Group, Priority, Zettel, frontmatter}; 4 4 5 5 /// a `Task` that you have to complete! 6 6 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] ··· 19 19 /// Each task has its own related `Zettel`. 20 20 pub zettel: Zettel, 21 21 pub group: Group, 22 + } 23 + 24 + impl Task { 25 + pub fn due(&self) -> Option<String> { 26 + self.due.map(|due|due.format(frontmatter::DATE_FMT_STR).to_string()) 27 + } 28 + pub fn finished_at(&self) -> Option<String> { 29 + self.finished_at. 30 + map(|finished_at|finished_at.format(frontmatter::DATE_FMT_STR).to_string()) 31 + } 32 + pub fn created_at(&self) -> String { 33 + self.created_at.format(frontmatter::DATE_FMT_STR).to_string() 34 + } 35 + pub fn modified_at(&self) -> String { 36 + self.modified_at.format(frontmatter::DATE_FMT_STR).to_string() 37 + } 22 38 } 23 39 24 40 impl From<TaskModelEx> for Task {