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: Can make groups

+452 -200
+17 -17
.config/config.ron
··· 1 1 ( 2 2 directory: "/Users/suri/dev/projects/filaments/ZettelKasten", 3 3 global_key_binds: { 4 - "ctrl-z": Suspend, 5 4 "down": MoveDown, 6 5 "up": MoveUp, 6 + "ctrl-z": Suspend, 7 7 "ctrl-c": Quit, 8 8 }, 9 9 zk: ( 10 10 keybinds: { 11 + "enter": OpenZettel, 12 + "ctrl-n": NewZettel, 11 13 "tab": SwitchTo( 12 14 page: Todo(Explorer), 13 15 ), 14 - "ctrl-n": NewZettel, 15 - "enter": OpenZettel, 16 16 }, 17 17 ), 18 18 todo: ( ··· 21 21 "2": SwitchTo( 22 22 page: Todo(Inspector), 23 23 ), 24 - "k": MoveUp, 24 + "3": SwitchTo( 25 + page: Todo(TaskList), 26 + ), 25 27 "1": SwitchTo( 26 28 page: Todo(Explorer), 27 29 ), 28 30 "k": MoveUp, 29 31 "shift-g": NewSubGroup, 32 + "j": MoveDown, 33 + "k": MoveUp, 30 34 "tab": SwitchTo( 31 35 page: Zk, 32 36 ), 33 - "3": SwitchTo( 34 - page: Todo(TaskList), 35 - ), 36 - "j": MoveDown, 37 37 }, 38 38 ), 39 39 inspector: ( ··· 41 41 "3": SwitchTo( 42 42 page: Todo(TaskList), 43 43 ), 44 - "1": SwitchTo( 45 - page: Todo(Explorer), 46 - ), 47 44 "tab": SwitchTo( 48 45 page: Zk, 49 46 ), 50 47 "2": SwitchTo( 51 48 page: Todo(Inspector), 49 + ), 50 + "1": SwitchTo( 51 + page: Todo(Explorer), 52 52 ), 53 53 }, 54 54 ), 55 55 tasklist: ( 56 56 keybinds: { 57 - "1": SwitchTo( 58 - page: Todo(Explorer), 59 - ), 60 57 "tab": SwitchTo( 61 58 page: Zk, 62 59 ), 63 - "2": SwitchTo( 64 - page: Todo(Inspector), 65 - ), 66 60 "3": SwitchTo( 67 61 page: Todo(TaskList), 68 62 ), 63 + "1": SwitchTo( 64 + page: Todo(Explorer), 65 + ), 69 66 "j": MoveDown, 70 67 "k": MoveUp, 68 + "2": SwitchTo( 69 + page: Todo(Inspector), 70 + ), 71 71 }, 72 72 ), 73 73 ),
+1
.config/default_config.ron
··· 22 22 "3": SwitchTo(page: Todo(TaskList)), 23 23 "j": MoveDown, 24 24 "k": MoveUp, 25 + "g": NewGroup 25 26 }, 26 27 ), 27 28 inspector: (
+4 -48
src/cli/process.rs
··· 6 6 7 7 use color_eyre::eyre::{Context, Result, eyre}; 8 8 use dto::{ 9 - Date, DateTime, GroupActiveModel, GroupEntity, HasOne, IntoActiveModel, TagActiveModel, 10 - TagEntity, TaskActiveModel, TaskEntity, Time, ZettelEntity, 9 + Date, DateTime, GroupEntity, HasOne, IntoActiveModel, TagEntity, TaskActiveModel, TaskEntity, 10 + Time, ZettelEntity, 11 11 }; 12 12 use tower_lsp::{LspService, Server}; 13 13 ··· 15 15 cli::{Commands, ZettelSubcommand}, 16 16 config::{Config, get_config_dir}, 17 17 lsp::Backend, 18 - types::{Group, Kasten, Priority, Tag, Task, Zettel}, 18 + types::{Group, Kasten, Priority, Task, Zettel}, 19 19 }; 20 20 21 21 impl Commands { ··· 82 82 match command { 83 83 super::TodoSubcommand::Group { name, parent_id } => { 84 84 // lets create a tag for this first group first 85 - let tag: Tag = TagActiveModel::builder() 86 - .set_name(name.clone()) 87 - .insert(&kt.db) 88 - .await? 89 - .into(); 90 - 91 - let tag_id = tag.id.clone(); 92 - 93 - // then create the zettel for the group 94 - let zettel = Zettel::new(name.clone(), &mut kt, vec![tag]).await?; 95 - 96 - // then insert that shi 97 - let inserted = GroupActiveModel::builder() 98 - .set_name(name) 99 - .set_parent_group_id(parent_id) 100 - .set_tag( 101 - TagEntity::load() 102 - .filter_by_nano_id(tag_id) 103 - .one(&kt.db) 104 - .await? 105 - .expect("Tag must exist since we just created it") 106 - .into_active_model(), 107 - ) 108 - .set_zettel( 109 - ZettelEntity::load() 110 - .filter_by_nano_id(zettel.id) 111 - .one(&kt.db) 112 - .await? 113 - .expect("Zettel must exist since we just created it") 114 - .into_active_model(), 115 - ) 116 - .set_priority(Priority::default()) 117 - .insert(&kt.db) 118 - .await?; 119 - 120 - // group should also have the accompanying tag for it. 121 - let group: Group = GroupEntity::load() 122 - .with(TagEntity) 123 - .with((ZettelEntity, TagEntity)) 124 - .filter_by_nano_id(inserted.nano_id) 125 - .one(&kt.db) 126 - .await? 127 - .expect("We just inserted it") 128 - .into(); 129 - 85 + let group = Group::new(name, parent_id, &mut kt).await?; 130 86 println!("created group {group:#?}"); 131 87 } 132 88 super::TodoSubcommand::Task { name, parent_id } => {
+6 -2
src/logging.rs
··· 1 1 use std::{ 2 - fs::{File, create_dir_all}, 2 + fs::{OpenOptions, create_dir_all}, 3 3 sync::LazyLock, 4 4 }; 5 5 ··· 29 29 create_dir_all(&directory)?; 30 30 31 31 let log_path = directory.join(LOG_FILE.clone()); 32 - let log_file = File::create(log_path)?; 32 + 33 + let log_file = OpenOptions::new() 34 + .append(true) 35 + .create(true) 36 + .open(log_path)?; 33 37 34 38 let env_filter = EnvFilter::builder().with_default_directive(Level::INFO.into()); 35 39
+1 -1
src/main.rs
··· 64 64 move || -> color_eyre::Result<()> { 65 65 // block the tui on the same runtime as above 66 66 tui_rt.block_on(async { 67 - let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx).await?; 67 + let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx)?; 68 68 tui.run().await?; 69 69 // just close everything as soon as the tui is done running 70 70 process::exit(0);
+2 -4
src/tui/app.rs
··· 48 48 49 49 impl App { 50 50 /// Construct a new `App` instance. 51 - pub async fn new( 51 + pub fn new( 52 52 tick_rate: f64, 53 53 frame_rate: f64, 54 54 kh: KastenHandle, ··· 60 60 tick_rate, 61 61 frame_rate, 62 62 // components: vec![Box::new(Zk::new(kh.clone()).await?)], 63 - components: vec![Box::new(Viewport::new(kh.clone()).await?)], 63 + components: vec![Box::new(Viewport::new(kh.clone()))], 64 64 should_quit: false, 65 65 should_suspend: false, 66 66 config: Config::parse()?, ··· 153 153 let Some(page_keymap) = self.config.keymap.get(&self.page) else { 154 154 return Ok(()); 155 155 }; 156 - 157 - info!("page: {:#?}, page_keymap: {page_keymap:#?}", self.page); 158 156 159 157 if let Some(signal) = page_keymap.get(&vec![key]) { 160 158 signal_tx.send(signal.clone())?;
+2 -3
src/tui/components/todo/explorer.rs
··· 3 3 text::{Line, Span, Text}, 4 4 widgets::{Block, BorderType, Borders, List, ListState}, 5 5 }; 6 - use tracing::info; 6 + use tracing::{debug, info}; 7 7 use tree::NodeId; 8 8 9 9 use crate::types::{TodoNode, TodoNodeKind, TodoTree}; ··· 58 58 } 59 59 60 60 pub fn set_active(&mut self) { 61 + debug!("Explorer set active!"); 61 62 self.render_list = self.render_list.clone().block( 62 63 Block::new() 63 64 .title("[1]") ··· 123 124 Style::default().bg(color), 124 125 )); 125 126 } 126 - 127 - info!("{spans:#?}"); 128 127 129 128 Line::from(spans).into() 130 129 }
+91 -3
src/tui/components/todo/mod.rs
··· 1 1 use async_trait::async_trait; 2 + use dto::NanoId; 2 3 use ratatui::{ 3 4 Frame, 4 5 layout::{Constraint, Layout, Rect, Size}, ··· 7 8 use serde::{Deserialize, Serialize}; 8 9 use strum::{Display, EnumIter}; 9 10 use tokio::sync::mpsc::UnboundedSender; 11 + use tracing::{debug, info}; 10 12 11 13 use crate::{ 12 14 tui::{Page, Signal, components::Component}, 13 - types::KastenHandle, 15 + types::{Group, KastenHandle}, 14 16 }; 15 17 16 18 mod explorer; 17 19 use explorer::Explorer; 18 - 19 20 mod tasklist; 20 21 use tasklist::TaskList; 21 22 ··· 31 32 task_list: Option<TaskList<'text>>, 32 33 inspector: Option<Inspector<'text>>, 33 34 35 + area: Size, 34 36 active: TodoRegion, 35 37 } 36 38 ··· 54 56 explorer: None, 55 57 task_list: None, 56 58 inspector: None, 59 + area: Size::default(), 57 60 active: TodoRegion::default(), 58 61 } 59 62 } 60 63 64 + pub async fn refresh(&mut self) { 65 + let explorer = self 66 + .explorer 67 + .as_mut() 68 + .expect("This should have already been init.ialized"); 69 + let task_list = self 70 + .task_list 71 + .as_mut() 72 + .expect("This should have already been initialized"); 73 + 74 + let explorer_selection = explorer 75 + .state 76 + .selected() 77 + .and_then(|idx| explorer.id_list.get(idx)); 78 + let task_list_selection = task_list 79 + .state 80 + .selected() 81 + .and_then(|idx| task_list.id_list.get(idx)); 82 + let kt = self.kh.read().await; 83 + let tree = &kt.todo_tree; 84 + 85 + let splits = self 86 + .layouts 87 + .split(Rect::new(0, 0, self.area.width, self.area.height)); 88 + 89 + let l_state = ListState::default(); 90 + 91 + //TODO: instead of tree.root_id this probably should be scope. 92 + let mut explorer = Explorer::new(tree, &tree.root_id, l_state, splits.explorer.width); 93 + let mut task_list = TaskList::new(tree, &tree.root_id, l_state, splits.task_list.width); 94 + 95 + drop(kt); 96 + 97 + let explorer_selection_idx = 98 + explorer_selection.and_then(|id| explorer.id_list.iter().position(|e| id == e)); 99 + 100 + let task_list_selection_idx = 101 + task_list_selection.and_then(|id| task_list.id_list.iter().position(|e| id == e)); 102 + 103 + explorer.state.select(explorer_selection_idx); 104 + task_list.state.select(task_list_selection_idx); 105 + 106 + match self.active { 107 + TodoRegion::Inspector => { 108 + explorer.set_inactive(); 109 + task_list.set_inactive(); 110 + } 111 + TodoRegion::TaskList => { 112 + explorer.set_inactive(); 113 + task_list.set_active(); 114 + } 115 + TodoRegion::Explorer => { 116 + explorer.set_active(); 117 + task_list.set_inactive(); 118 + } 119 + } 120 + 121 + self.explorer = Some(explorer); 122 + self.task_list = Some(task_list); 123 + self.update_inspector_from_selection().await; 124 + } 125 + 61 126 async fn update_inspector_from_selection(&mut self) { 62 127 let explorer = self 63 128 .explorer ··· 71 136 .inspector 72 137 .as_mut() 73 138 .expect("This should have already been initialized"); 139 + 74 140 let selected_node_id = match self.active { 75 141 TodoRegion::TaskList => { 76 142 let Some(idx) = task_list.state.selected() else { ··· 142 208 #[async_trait] 143 209 impl Component for Todo<'_> { 144 210 async fn init(&mut self, area: Size) -> color_eyre::Result<()> { 211 + self.area = area; 145 212 let tree = &self.kh.read().await.todo_tree; 146 213 let splits = self.layouts.split(Rect::new(0, 0, area.width, area.height)); 147 214 ··· 167 234 explorer.set_inactive(); 168 235 inspector.set_inactive(); 169 236 task_list.set_inactive(); 170 - 171 237 self.explorer = Some(explorer); 172 238 self.task_list = Some(task_list); 173 239 self.inspector = Some(inspector); 240 + 241 + // match self.active { 242 + 243 + // ins 244 + 245 + // } 174 246 175 247 Ok(()) 176 248 } ··· 243 315 } 244 316 245 317 self.update_inspector_from_selection().await; 318 + } 319 + 320 + Signal::NewGroup => { 321 + if self.active != TodoRegion::Explorer { 322 + return Ok(None); 323 + } 324 + debug!("Creating Group!"); 325 + let mut kt = self.kh.write().await; 326 + let group = Group::new(NanoId::default().to_string(), None, &mut kt).await?; 327 + drop(kt); 328 + debug!("Created group: {group:#?}"); 329 + return Ok(Some(Signal::Refresh)); 330 + } 331 + 332 + Signal::Refresh => { 333 + self.refresh().await; 246 334 } 247 335 _ => {} 248 336 }
+7 -7
src/tui/components/viewport/mod.rs
··· 32 32 use switcher::Switcher; 33 33 34 34 impl Viewport<'_> { 35 - pub async fn new(kh: KastenHandle) -> Result<Self> { 35 + pub fn new(kh: KastenHandle) -> Self { 36 36 let mut switcher = Switcher::default(); 37 37 switcher.select_region(Page::default()); 38 38 39 - Ok(Self { 39 + Self { 40 40 signal_tx: None, 41 41 _layouts: Layouts::default(), 42 42 switcher, 43 - zk: Zk::new(kh.clone()).await?, 43 + zk: Zk::new(kh.clone()), 44 44 todo: Todo::new(kh.clone()), 45 45 active_page: Page::default(), 46 46 kh, 47 - }) 47 + } 48 48 } 49 49 } 50 50 ··· 83 83 debug!("active page switched to : {page}"); 84 84 } 85 85 86 - match self.active_page { 87 - Page::Zk => self.zk.update(signal).await, 88 - Page::Todo(_) => self.todo.update(signal).await, 86 + if let Some(signal) = self.zk.update(signal.clone()).await? { 87 + self.signal_tx.as_mut().unwrap().send(signal)?; 89 88 } 89 + self.todo.update(signal).await 90 90 } 91 91 92 92 async fn handle_key_event(&mut self, key: KeyEvent) -> color_eyre::Result<Option<Signal>> {
+194 -92
src/tui/components/zk/mod.rs
··· 30 30 signal_tx: Option<UnboundedSender<Signal>>, 31 31 kh: KastenHandle, 32 32 layouts: Layouts, 33 - search: Search<'text>, 34 - zettel_list: ZettelList<'text>, 35 - zettel_view: ZettelView<'text>, 36 - preview: Preview<'text>, 33 + area: Size, 34 + 35 + search: Option<Search<'text>>, 36 + zettel_list: Option<ZettelList<'text>>, 37 + zettel_view: Option<ZettelView<'text>>, 38 + preview: Option<Preview<'text>>, 37 39 } 38 40 39 41 struct Layouts { 40 42 left_right: Layout, 41 43 search_zl: Layout, 42 44 z_preview: Layout, 45 + } 46 + 47 + impl Layouts { 48 + fn split(&self, area: Rect) -> LayoutSplit { 49 + let rects = self.left_right.split(area); 50 + 51 + let (left, right) = (rects[0], rects[2]); 52 + 53 + let l_rects = self.search_zl.split(left); 54 + 55 + let r_rects = self.z_preview.split(right); 56 + 57 + LayoutSplit { 58 + search: l_rects[0], 59 + zettel_list: l_rects[1], 60 + zettel_view: r_rects[0], 61 + preview: r_rects[1], 62 + } 63 + } 64 + } 65 + 66 + struct LayoutSplit { 67 + search: Rect, 68 + zettel_list: Rect, 69 + zettel_view: Rect, 70 + preview: Rect, 43 71 } 44 72 45 73 impl Default for Layouts { ··· 57 85 } 58 86 59 87 impl Zk<'_> { 60 - pub async fn new(kh: KastenHandle) -> Result<Self> { 88 + pub fn new(kh: KastenHandle) -> Self { 89 + Self { 90 + signal_tx: None, 91 + kh, 92 + 93 + layouts: Layouts::default(), 94 + area: Size::default(), 95 + 96 + search: None, 97 + zettel_list: None, 98 + zettel_view: None, 99 + preview: None, 100 + } 101 + } 102 + 103 + async fn refresh(&mut self) -> Result<()> { 104 + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); 105 + 106 + let selected = zettel_list 107 + .state 108 + .selected() 109 + .and_then(|idx| zettel_list.id_list.get(idx)); 110 + 111 + let splits = self 112 + .layouts 113 + .split(Rect::new(0, 0, self.area.width, self.area.height)); 114 + 115 + let kt = self.kh.read().await; 116 + let db = kt.db.clone(); 117 + 118 + // ideally we just keep the same selection as we had originally 119 + 61 120 let fetch_all = async || -> Result<Vec<Zettel>> { 62 121 Ok(ZettelEntity::load() 63 122 .with(TagEntity) 64 123 .order_by_desc(ZettelColumns::ModifiedAt) 65 - .all(&kh.read().await.db) 124 + .all(&db) 66 125 .await? 67 126 .into_iter() 68 127 .map(Into::into) 69 128 .collect()) 70 129 }; 71 130 72 - let mut zettels: Vec<Zettel> = fetch_all().await?; 131 + let zettels: Vec<Zettel> = fetch_all().await?; 73 132 74 - if zettels.is_empty() { 75 - let _ = Zettel::new("Welcome!", &mut *kh.write().await, vec![]).await?; 76 - zettels = fetch_all().await?; 77 - } 78 - 79 - // in theory this is wasted compute, we should be initializing all our 80 - // stuff inside the init function 81 - let mut l_state = ListState::default(); 82 - l_state.select_first(); 83 - let zettel_list = ZettelList::new(zettels.clone(), l_state, 0); 133 + let mut zettel_list = ZettelList::new( 134 + zettels.clone(), 135 + ListState::default(), 136 + splits.zettel_list.width, 137 + ); 84 138 85 - let selected_zettel = zettel_list 86 - .id_list 87 - .get( 88 - zettel_list 89 - .state 90 - .selected() 91 - .expect("We explicitly select the first item"), 92 - ) 93 - // so technically this might not exist 94 - .expect("There must always be one atleast one zettel"); 139 + let selected_zettel_idx = 140 + selected.and_then(|desired| zettel_list.id_list.iter().position(|id| id == desired)); 95 141 96 - let kt = kh.read().await; 142 + zettel_list.state.select(selected_zettel_idx); 97 143 98 144 let zettel = zettels 99 145 .iter() 100 - .find(|&z| &z.id == selected_zettel) 146 + //TODO: expect probably should not look like this 147 + .find(|&z| &z.id == selected.expect("Something should be selected")) 101 148 .expect("we selected it out of the list so it must exist"); 102 149 103 150 let preview = Preview::from(zettel.content(&kt.index).clone()); 104 151 105 152 drop(kt); 106 153 107 - Ok(Self { 108 - signal_tx: None, 109 - search: Search::new(kh.clone()), 110 - kh, 111 - layouts: Layouts::default(), 112 - zettel_list, 113 - zettel_view: zettel.into(), 114 - preview, 115 - }) 154 + let search = Search::new(self.kh.clone()); 155 + let zettel_view = zettel.into(); 156 + 157 + self.search = Some(search); 158 + self.zettel_view = Some(zettel_view); 159 + self.preview = Some(preview); 160 + self.zettel_list = Some(zettel_list); 161 + 162 + Ok(()) 163 + // what the fuck am i supposed to do here mannnn. 116 164 } 117 165 118 166 async fn update_views_from_zettel_list_selection(&mut self) -> Result<()> { 119 - let selection_idx = self 120 - .zettel_list 167 + let zettel_list = self.zettel_list.as_mut().expect("Must be initialzied"); 168 + 169 + let selection_idx = zettel_list 121 170 .state 122 171 .selected() 123 172 .expect("i have no idea what to do if this doesnt exist"); 124 173 125 174 // sometimes the selection we get is over the length of the thing, so its 126 175 // actually fine if this is none, just means we reached the end of the list 127 - let Some(zid) = self.zettel_list.id_list.get(selection_idx) else { 176 + let Some(zid) = zettel_list.id_list.get(selection_idx) else { 128 177 return Ok(()); 129 178 }; 130 179 ··· 134 183 .await? 135 184 .context("Unknown Behaviour, A selected zettel got deleted somehow.")?; 136 185 137 - self.preview = zettel.content(&kh.index).clone().into(); 186 + self.preview = Some(zettel.content(&kh.index).clone().into()); 138 187 drop(kh); 139 188 140 - self.zettel_view = zettel.into(); 189 + self.zettel_view = Some(zettel.into()); 141 190 142 191 Ok(()) 143 192 } ··· 161 210 } 162 211 163 212 pub async fn update_with_respect_to_query(&mut self) -> Result<()> { 164 - let zettels = self 213 + let curr_zettels = self.get_zettels_by_current_query().await?; 214 + 215 + let search = self 165 216 .search 166 - .rank(self.get_zettels_by_current_query().await?) 167 - .await; 217 + .as_mut() 218 + .expect("Must be initalized by this point"); 168 219 169 - self.zettel_list = ZettelList::new(zettels, self.zettel_list.state, self.zettel_list.width); 220 + let zettel_list = self 221 + .zettel_list 222 + .as_mut() 223 + .expect("Must be initialized by this point"); 224 + 225 + let zettels = search.rank(curr_zettels).await; 226 + 227 + *zettel_list = ZettelList::new(zettels, zettel_list.state, zettel_list.width); 228 + 170 229 info!("we are moving selection to first"); 171 - self.zettel_list.state.select_first(); 230 + 231 + zettel_list.state.select_first(); 172 232 self.update_views_from_zettel_list_selection().await?; 173 233 174 234 Ok(()) ··· 179 239 impl Component for Zk<'_> { 180 240 /// this tells us how big the space we have for this is 181 241 async fn init(&mut self, area: Size) -> color_eyre::Result<()> { 182 - let total_width = area.width; 242 + self.area = area; 243 + let splits = self.layouts.split(Rect::new(0, 0, area.width, area.height)); 244 + let mut kt = self.kh.write().await; 245 + let db = kt.db.clone(); 246 + 247 + let fetch_all = async || -> Result<Vec<Zettel>> { 248 + Ok(ZettelEntity::load() 249 + .with(TagEntity) 250 + .order_by_desc(ZettelColumns::ModifiedAt) 251 + .all(&db) 252 + .await? 253 + .into_iter() 254 + .map(Into::into) 255 + .collect()) 256 + }; 257 + 258 + let mut zettels: Vec<Zettel> = fetch_all().await?; 259 + 260 + if zettels.is_empty() { 261 + let _ = Zettel::new("Welcome!", &mut kt, vec![]).await?; 262 + zettels = fetch_all().await?; 263 + } 183 264 184 265 // in theory this is wasted compute, we should be initializing all our 266 + // stuff inside the init function 185 267 let mut l_state = ListState::default(); 186 268 l_state.select_first(); 269 + let zettel_list = ZettelList::new(zettels.clone(), l_state, splits.zettel_list.width); 187 270 188 - let zettel_list = ZettelList::new( 189 - self.get_zettels_by_current_query().await?, 190 - l_state, 191 - total_width / 2, 192 - ); 271 + let selected_zettel = zettel_list 272 + .id_list 273 + .get( 274 + zettel_list 275 + .state 276 + .selected() 277 + .expect("We explicitly select the first item"), 278 + ) 279 + // so technically this might not exist 280 + .expect("There must always be one atleast one zettel"); 193 281 194 - self.zettel_list = zettel_list; 282 + let zettel = zettels 283 + .iter() 284 + .find(|&z| &z.id == selected_zettel) 285 + .expect("we selected it out of the list so it must exist"); 286 + 287 + let preview = Preview::from(zettel.content(&kt.index).clone()); 288 + 289 + drop(kt); 290 + 291 + let search = Search::new(self.kh.clone()); 292 + let zettel_view = zettel.into(); 293 + 294 + self.search = Some(search); 295 + self.zettel_view = Some(zettel_view); 296 + self.preview = Some(preview); 297 + self.zettel_list = Some(zettel_list); 195 298 196 299 Ok(()) 197 300 } ··· 202 305 } 203 306 204 307 async fn update(&mut self, signal: Signal) -> Result<Option<crate::tui::Signal>> { 308 + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); 309 + let search = self.search.as_mut().expect("Must be initialized"); 205 310 match signal { 311 + Signal::Refresh => { 312 + self.refresh().await?; 313 + } 314 + 206 315 Signal::MoveDown => { 207 - self.zettel_list.state.select_next(); 316 + zettel_list.state.select_next(); 208 317 self.update_views_from_zettel_list_selection().await?; 209 318 } 210 319 Signal::MoveUp => { 211 - self.zettel_list.state.select_previous(); 320 + zettel_list.state.select_previous(); 212 321 self.update_views_from_zettel_list_selection().await?; 213 322 } 214 323 215 324 Signal::OpenZettel => { 216 - let Some(selcted) = self.zettel_list.state.selected() else { 325 + let Some(selcted) = zettel_list.state.selected() else { 217 326 return Ok(None); 218 327 }; 219 328 220 - let Some(zid) = self.zettel_list.id_list.get(selcted) else { 329 + let Some(zid) = zettel_list.id_list.get(selcted) else { 221 330 return Ok(None); 222 331 }; 223 332 ··· 236 345 let mut kt = self.kh.write().await; 237 346 238 347 // we create the zettel with the query as the 239 - let z = Zettel::new(self.search.query(), &mut kt, vec![]) 348 + let z = Zettel::new(search.query(), &mut kt, vec![]) 240 349 .await 241 350 .with_context(|| "Failed to create a new Zettel!")?; 242 351 ··· 263 372 } 264 373 265 374 Signal::ClosedZettel { zid } => { 266 - // regenerate a fresh zettel list 267 - self.zettel_list = ZettelList::new( 268 - self.get_zettels_by_current_query().await?, 269 - self.zettel_list.state, 270 - self.zettel_list.width, 271 - ); 375 + let curr_zettels = self.get_zettels_by_current_query().await?; // regenerate a fresh zettel list 376 + 377 + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); 378 + let zettel_view = self.zettel_view.as_mut().expect("Must be initialized"); 379 + let preview = self.preview.as_mut().expect("Must be initialized"); 380 + let search = self.search.as_mut().expect("Must be initialized"); 381 + 382 + *zettel_list = ZettelList::new(curr_zettels, zettel_list.state, zettel_list.width); 272 383 273 384 let kt = self.kh.read().await; 274 385 ··· 276 387 .await? 277 388 .expect("invariant broken, we just closed this zettel"); 278 389 279 - let idx = self 280 - .zettel_list 281 - .id_list 282 - .iter() 283 - .position(|id| *id == zettel.id); 390 + let idx = zettel_list.id_list.iter().position(|id| *id == zettel.id); 284 391 285 392 // reset the state of the component 286 - self.search.clear_query(); 287 - self.zettel_list.state.select(idx); 393 + search.clear_query(); 394 + zettel_list.state.select(idx); 288 395 289 - self.zettel_view = ZettelView::from(&zettel); 290 - self.preview = Preview::from(zettel.content(&kt.index).clone()); 396 + *zettel_view = ZettelView::from(&zettel); 397 + *preview = Preview::from(zettel.content(&kt.index).clone()); 291 398 drop(kt); 292 399 } 293 400 ··· 300 407 // NOTE: this is hardcoded for now, but I honestly think people should not 301 408 // be able to change these binds, opinionated software or something... 302 409 if !(key.code.is_up() || key.code.is_down() || key.code.is_enter() || key.code.is_tab()) { 303 - self.search.query.input(key); 410 + self.search.as_mut().unwrap().query.input(key); 304 411 self.update_with_respect_to_query().await?; 305 412 } 306 413 ··· 312 419 frame: &mut ratatui::Frame, 313 420 area: ratatui::prelude::Rect, 314 421 ) -> color_eyre::Result<()> { 315 - let (search_layout, zettel_list_layout, zettel_layout, preview_layout) = { 316 - let rects = self.layouts.left_right.split(area); 422 + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); 423 + let zettel_view = self.zettel_view.as_mut().expect("Must be initialized"); 424 + let preview = self.preview.as_mut().expect("Must be initialized"); 425 + let search = self.search.as_mut().expect("Must be initialized"); 317 426 318 - let (left, right) = (rects[0], rects[2]); 427 + let splits = self.layouts.split(area); 319 428 320 - let l_rects = self.layouts.search_zl.split(left); 321 - 322 - let r_rects = self.layouts.z_preview.split(right); 323 - 324 - (l_rects[0], l_rects[1], r_rects[0], r_rects[1]) 325 - }; 326 - 327 - frame.render_widget(self.search.clone(), search_layout); 429 + frame.render_widget(search.clone(), splits.search); 328 430 329 431 frame.render_stateful_widget( 330 - &self.zettel_list.render_list, 331 - zettel_list_layout, 332 - &mut self.zettel_list.state, 432 + &zettel_list.render_list, 433 + splits.zettel_list, 434 + &mut zettel_list.state, 333 435 ); 334 436 335 - frame.render_widget(self.zettel_view.clone(), zettel_layout); 336 - frame.render_widget(self.preview.clone(), preview_layout); 437 + frame.render_widget(zettel_view.clone(), splits.zettel_view); 438 + frame.render_widget(preview.clone(), splits.preview); 337 439 338 440 Ok(()) 339 441 }
+28 -21
src/tui/signal.rs
··· 1 - use std::{path::PathBuf, str::FromStr}; 1 + use std::path::PathBuf; 2 2 3 - use color_eyre::eyre::eyre; 4 3 use strum::Display; 5 4 6 5 use serde::{Deserialize, Serialize}; ··· 22 21 ClearScreen, 23 22 Error(String), 24 23 Help, 24 + 25 + /// Request a refresh of the components being displayed due 26 + /// to an update to the `Kasten` 27 + Refresh, 25 28 26 29 SwitchTo { 27 30 page: Page, ··· 53 56 zid: ZettelId, 54 57 }, 55 58 59 + /// Create a new `Group` 60 + NewGroup, 61 + 56 62 /// this is fucking temporary 57 63 Helix { 58 64 path: PathBuf, 59 65 }, 60 66 } 61 67 62 - impl FromStr for Signal { 63 - type Err = color_eyre::Report; 68 + // impl FromStr for Signal { 69 + // type Err = color_eyre::Report; 64 70 65 - fn from_str(s: &str) -> Result<Self, Self::Err> { 66 - Ok(match s.to_lowercase().as_str() { 67 - "suspend" => Self::Suspend, 68 - "resume" => Self::Resume, 69 - "quit" => Self::Quit, 70 - "movedown" => Self::MoveDown, 71 - "moveup" => Self::MoveUp, 72 - "openzettel" => Self::OpenZettel, 73 - "newzettel" => Self::NewZettel, 74 - _ => { 75 - return Err(eyre!(format!( 76 - "Attempt to construct a non-user Signal from str: {s}" 77 - ))); 78 - } 79 - }) 80 - } 81 - } 71 + // fn from_str(s: &str) -> Result<Self, Self::Err> { 72 + // Ok(match s.to_lowercase().as_str() { 73 + // "suspend" => Self::Suspend, 74 + // "resume" => Self::Resume, 75 + // "quit" => Self::Quit, 76 + // "movedown" => Self::MoveDown, 77 + // "moveup" => Self::MoveUp, 78 + // "openzettel" => Self::OpenZettel, 79 + // "newzettel" => Self::NewZettel, 80 + // "newgroup" 81 + // _ => { 82 + // return Err(eyre!(format!( 83 + // "Attempt to construct a non-user Signal from str: {s}" 84 + // ))); 85 + // } 86 + // }) 87 + // } 88 + // }
+97 -2
src/types/group.rs
··· 1 - use dto::{DateTime, GroupModelEx, NanoId}; 1 + use color_eyre::eyre::Result; 2 + use dto::{ 3 + DateTime, GroupActiveModel, GroupEntity, GroupModelEx, IntoActiveModel as _, NanoId, 4 + TagActiveModel, TagEntity, ZettelEntity, 5 + }; 6 + use tree::Node; 2 7 3 - use crate::types::{Priority, Tag, Zettel, frontmatter}; 8 + use crate::types::{Kasten, Priority, Tag, TodoNode, Zettel, frontmatter}; 4 9 5 10 /// A `Group` which contains tasks! 6 11 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] ··· 33 38 self.modified_at 34 39 .format(frontmatter::DATE_FMT_STR) 35 40 .to_string() 41 + } 42 + 43 + pub async fn new( 44 + name: impl Into<String>, 45 + parent_id: Option<NanoId>, 46 + kt: &mut Kasten, 47 + ) -> Result<Self> { 48 + let name = name.into(); 49 + let tag: Tag = TagActiveModel::builder() 50 + .set_name(name.clone()) 51 + .insert(&kt.db) 52 + .await? 53 + .into(); 54 + 55 + let tag_id = tag.id.clone(); 56 + 57 + // then create the zettel for the group 58 + let zettel = Zettel::new(name.clone(), kt, vec![tag]).await?; 59 + 60 + // then insert that shi 61 + let inserted = GroupActiveModel::builder() 62 + .set_name(name) 63 + .set_parent_group_id(parent_id) 64 + .set_tag( 65 + TagEntity::load() 66 + .filter_by_nano_id(tag_id) 67 + .one(&kt.db) 68 + .await? 69 + .expect("Tag must exist since we just created it") 70 + .into_active_model(), 71 + ) 72 + .set_zettel( 73 + ZettelEntity::load() 74 + .filter_by_nano_id(zettel.id) 75 + .one(&kt.db) 76 + .await? 77 + .expect("Zettel must exist since we just created it") 78 + .into_active_model(), 79 + ) 80 + .set_priority(Priority::default()) 81 + .insert(&kt.db) 82 + .await?; 83 + 84 + // group should also have the accompanying tag for it. 85 + let group: Self = GroupEntity::load() 86 + .with(TagEntity) 87 + .with((ZettelEntity, TagEntity)) 88 + .filter_by_nano_id(inserted.nano_id) 89 + .one(&kt.db) 90 + .await? 91 + .expect("We just inserted it") 92 + .into(); 93 + 94 + // we should also insert the group into the kasten 95 + 96 + let parent_node_id = group 97 + .parent_id 98 + .clone() 99 + .and_then(|id| kt.todo_tree.nanoid_to_nodeid.get(&id)) 100 + .unwrap_or(&kt.todo_tree.root_id); 101 + 102 + let my_depth = if *parent_node_id == kt.todo_tree.root_id { 103 + 0 104 + } else { 105 + kt.todo_tree 106 + .tree 107 + .get(parent_node_id) 108 + .expect("Must exist inside tree") 109 + .data() 110 + .depth 111 + + 1 112 + }; 113 + 114 + let inserted_node_id = kt 115 + .todo_tree 116 + .tree 117 + .insert( 118 + Node::new(TodoNode::new( 119 + super::TodoNodeKind::Group(Box::new(group.clone())), 120 + my_depth, 121 + )), 122 + tree::InsertBehavior::UnderNode(parent_node_id), 123 + ) 124 + .expect("Insertion of group should not error!"); 125 + 126 + kt.todo_tree 127 + .nanoid_to_nodeid 128 + .insert(group.id.clone(), inserted_node_id); 129 + 130 + Ok(group) 36 131 } 37 132 } 38 133
+2
src/types/kasten/todo_tree.rs
··· 71 71 Ok(todo_tree) 72 72 } 73 73 74 + 75 + 74 76 #[async_recursion::async_recursion] 75 77 async fn add_group_to_tree( 76 78 &mut self,