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: Renaming of Tasks and Groups in the todo interface

+446 -95
+26 -18
.config/config.ron
··· 1 1 ( 2 2 directory: "/Users/suri/dev/projects/filaments/ZettelKasten", 3 3 global_key_binds: { 4 + "down": MoveDown, 4 5 "up": MoveUp, 5 6 "ctrl-c": Quit, 6 7 "ctrl-z": Suspend, 7 - "down": MoveDown, 8 8 }, 9 9 zk: ( 10 10 keybinds: { ··· 18 18 todo: ( 19 19 explorer: ( 20 20 keybinds: { 21 + "2": SwitchTo( 22 + page: Todo(Inspector), 23 + ), 21 24 "g": NewSubGroup, 22 25 "tab": SwitchTo( 23 26 page: Zk, 24 27 ), 25 - "2": SwitchTo( 26 - page: Todo(Inspector), 28 + "shift-g": NewGroup, 29 + "j": MoveDown, 30 + "k": MoveUp, 31 + "1": SwitchTo( 32 + page: Todo(Explorer), 27 33 ), 28 34 "3": SwitchTo( 29 35 page: Todo(TaskList), 30 36 ), 31 - "1": SwitchTo( 32 - page: Todo(Explorer), 37 + "t": NewTask, 38 + "enter": SwitchTo( 39 + page: Todo(Inspector), 33 40 ), 34 - "k": MoveUp, 35 - "t": NewTask, 36 - "shift-g": NewGroup, 37 - "j": MoveDown, 38 41 }, 39 42 ), 40 43 inspector: ( 41 44 keybinds: { 42 - "tab": SwitchTo( 43 - page: Zk, 44 - ), 45 45 "1": SwitchTo( 46 46 page: Todo(Explorer), 47 47 ), 48 - "3": SwitchTo( 49 - page: Todo(TaskList), 48 + "tab": SwitchTo( 49 + page: Zk, 50 50 ), 51 51 "2": SwitchTo( 52 52 page: Todo(Inspector), 53 53 ), 54 + "3": SwitchTo( 55 + page: Todo(TaskList), 56 + ), 57 + "n": EditName, 58 + "p": EditPriority, 54 59 }, 55 60 ), 56 61 tasklist: ( 57 62 keybinds: { 58 - "1": SwitchTo( 59 - page: Todo(Explorer), 63 + "k": MoveUp, 64 + "enter": SwitchTo( 65 + page: Todo(Inspector), 60 66 ), 61 67 "tab": SwitchTo( 62 68 page: Zk, 63 69 ), 64 - "j": MoveDown, 65 - "k": MoveUp, 70 + "1": SwitchTo( 71 + page: Todo(Explorer), 72 + ), 66 73 "2": SwitchTo( 67 74 page: Todo(Inspector), 68 75 ), 69 76 "3": SwitchTo( 70 77 page: Todo(TaskList), 71 78 ), 79 + "j": MoveDown, 72 80 }, 73 81 ), 74 82 ),
+4
.config/default_config.ron
··· 25 25 "shift-g": NewGroup, 26 26 "g": NewSubGroup, 27 27 "t": NewTask, 28 + "enter": SwitchTo(page: Todo(Inspector)) 28 29 }, 29 30 ), 30 31 inspector: ( ··· 33 34 "1": SwitchTo(page: Todo(Explorer)), 34 35 "2": SwitchTo(page: Todo(Inspector)), 35 36 "3": SwitchTo(page: Todo(TaskList)), 37 + "n": EditName, 38 + "p": EditPriority 36 39 }, 37 40 ), 38 41 tasklist: ( ··· 43 46 "3": SwitchTo(page: Todo(TaskList)), 44 47 "j": MoveDown, 45 48 "k": MoveUp, 49 + "enter": SwitchTo(page: Todo(Inspector)) 46 50 }, 47 51 ), 48 52 ),
+1
justfile
··· 22 22 reset: 23 23 rm -rf ZettleKasten 24 24 cargo run -- init 25 + cargo run 25 26 26 27 # Only used to build / generate entities 27 28 dev-db := justfile_directory() + "/target/dev.db"
+10
src/tui/app.rs
··· 32 32 signal_tx: UnboundedSender<Signal>, 33 33 signal_rx: UnboundedReceiver<Signal>, 34 34 viz_signal_tx: UnboundedSender<Signal>, 35 + raw_text: bool, 35 36 } 36 37 37 38 /// The different regions of the application that the user can ··· 73 74 signal_tx, 74 75 signal_rx, 75 76 viz_signal_tx, 77 + raw_text: false, 76 78 }) 77 79 } 78 80 ··· 151 153 fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> { 152 154 debug!("key received: {key:#?}"); 153 155 156 + if self.raw_text { 157 + debug!("Raw text enabled, refusing to interpret as Signal"); 158 + return Ok(()); 159 + } 160 + 154 161 let signal_tx = self.signal_tx.clone(); 155 162 156 163 let Some(page_keymap) = self.config.keymap.get(&self.page) else { ··· 244 251 Signal::ClearScreen => tui.terminal.clear()?, 245 252 Signal::Resize(x, y) => self.handle_resize(tui, x, y)?, 246 253 Signal::Render => self.render(tui)?, 254 + 255 + Signal::EnterRawText => self.raw_text = true, 256 + Signal::ExitRawText => self.raw_text = false, 247 257 _ => {} 248 258 } 249 259
+2 -2
src/tui/components/todo/inspector/groupview.rs
··· 9 9 10 10 #[derive(Debug, Clone)] 11 11 pub struct GroupView<'text> { 12 - name: TextArea<'text>, 13 - priority: TextArea<'text>, 12 + pub name: TextArea<'text>, 13 + pub priority: TextArea<'text>, 14 14 created_at: Paragraph<'text>, 15 15 layouts: Layouts, 16 16 }
+279 -48
src/tui/components/todo/inspector/mod.rs
··· 1 + use async_trait::async_trait; 2 + use crossterm::event::{KeyCode, KeyEvent}; 3 + use dto::{GroupEntity, IntoActiveModel, NanoId, TagEntity, TaskEntity}; 1 4 use ratatui::{ 2 - layout::{Constraint, Direction, Layout}, 5 + Frame, 6 + layout::{Constraint, Direction, Layout, Rect}, 3 7 style::{Color, Style}, 4 - widgets::{Block, BorderType, Borders, Widget}, 8 + widgets::{Block, BorderType, Borders}, 5 9 }; 10 + use ratatui_textarea::CursorMove; 11 + use tokio::sync::mpsc::UnboundedSender; 6 12 7 - use crate::types::{TodoNode, TodoNodeKind}; 13 + use crate::{ 14 + tui::{ 15 + Signal, 16 + components::{Component, DEFAULT_NAME}, 17 + }, 18 + types::{KastenHandle, TodoNode, TodoNodeKind}, 19 + }; 8 20 9 21 mod rootview; 10 22 use rootview::RootView; ··· 16 28 use groupview::GroupView; 17 29 18 30 pub struct Inspector<'text> { 19 - render_data: RenderData<'text>, 31 + pub render_data: RenderData<'text>, 20 32 margins: Layout, 21 33 block: Block<'text>, 34 + kh: KastenHandle, 35 + signal_tx: Option<UnboundedSender<Signal>>, 36 + is_active: bool, 37 + editing: Option<Edit>, 38 + // this is the `NanoId` of the thing we are actually inspecting 39 + inspecting: Option<NanoId>, 40 + } 41 + 42 + enum Edit { 43 + Name, 44 + Priority, 22 45 } 23 46 24 47 impl Inspector<'_> { 48 + pub fn new(kh: KastenHandle, node: &TodoNode) -> Self { 49 + let margins = Layout::new(Direction::Horizontal, [Constraint::Percentage(100)]) 50 + .horizontal_margin(3) 51 + .vertical_margin(2); 52 + 53 + let block = Block::new() 54 + .title("[2]") 55 + .title("Inspector") 56 + .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM) 57 + .border_style(Style::new().fg(Color::Gray)) 58 + .border_type(BorderType::Rounded); 59 + 60 + let mut nanoid = None; 61 + 62 + let render_data = match node.kind { 63 + TodoNodeKind::Root => RenderData::Root { 64 + widget: Box::new(RootView::default()), 65 + }, 66 + TodoNodeKind::Group(ref group) => { 67 + nanoid = Some(group.id.clone()); 68 + 69 + RenderData::Group { 70 + widget: Box::new(GroupView::from(&**group)), 71 + } 72 + } 73 + TodoNodeKind::Task(ref task) => { 74 + nanoid = Some(task.id.clone()); 75 + 76 + RenderData::Task { 77 + widget: Box::new(TaskView::from(&**task)), 78 + } 79 + } 80 + }; 81 + 82 + Self { 83 + render_data, 84 + margins, 85 + block, 86 + kh, 87 + is_active: false, 88 + editing: None, 89 + inspecting: nanoid, 90 + signal_tx: None, 91 + } 92 + } 93 + 25 94 pub fn set_active(&mut self) { 95 + self.is_active = true; 96 + 26 97 self.block = Block::new() 27 98 .title("[2]") 28 99 .title("Inspector") ··· 32 103 } 33 104 34 105 pub fn set_inactive(&mut self) { 106 + self.is_active = false; 35 107 self.block = Block::new() 36 108 .title("[2]") 37 109 .title("Inspector") ··· 39 111 .border_style(Style::new().fg(Color::Gray)) 40 112 .border_type(BorderType::Rounded); 41 113 } 114 + 115 + pub fn inspect(&mut self, node: &TodoNode) { 116 + self.render_data = match node.kind { 117 + TodoNodeKind::Root => { 118 + self.inspecting = None; 119 + RenderData::Root { 120 + widget: Box::new(RootView::default()), 121 + } 122 + } 123 + TodoNodeKind::Group(ref group) => { 124 + self.inspecting = Some(group.id.clone()); 125 + RenderData::Group { 126 + widget: Box::new(GroupView::from(&**group)), 127 + } 128 + } 129 + TodoNodeKind::Task(ref task) => { 130 + self.inspecting = Some(task.id.clone()); 131 + RenderData::Task { 132 + widget: Box::new(TaskView::from(&**task)), 133 + } 134 + } 135 + } 136 + } 42 137 } 43 138 44 - enum RenderData<'text> { 139 + pub enum RenderData<'text> { 45 140 Root { widget: Box<RootView<'text>> }, 46 141 Task { widget: Box<TaskView<'text>> }, 47 142 Group { widget: Box<GroupView<'text>> }, 48 143 } 49 144 50 - impl From<&TodoNode> for Inspector<'_> { 51 - fn from(value: &TodoNode) -> Self { 52 - let margins = Layout::new(Direction::Horizontal, [Constraint::Percentage(100)]) 53 - .horizontal_margin(3) 54 - .vertical_margin(2); 145 + #[async_trait] 146 + impl Component for Inspector<'_> { 147 + fn register_signal_handler(&mut self, tx: UnboundedSender<Signal>) -> color_eyre::Result<()> { 148 + self.signal_tx = Some(tx); 149 + Ok(()) 150 + } 151 + 152 + #[allow(clippy::too_many_lines)] 153 + async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>> { 154 + match signal { 155 + Signal::EditName => { 156 + let name = match &mut self.render_data { 157 + RenderData::Root { widget: _ } => return Ok(None), 158 + RenderData::Task { widget } => &mut widget.name, 159 + RenderData::Group { widget } => &mut widget.name, 160 + }; 161 + 162 + name.set_block( 163 + name.block() 164 + .cloned() 165 + .expect("All of them should have blocks") 166 + .border_style(Style::default().fg(Color::Green)), 167 + ); 168 + 169 + if name.lines()[0].as_str().contains(DEFAULT_NAME) { 170 + name.delete_line_by_end(); 171 + } else { 172 + name.move_cursor(CursorMove::End); 173 + } 174 + 175 + name.set_cursor_style(Style::default().reversed()); 176 + name.set_cursor_line_style(Style::default().underlined()); 177 + 178 + self.editing = Some(Edit::Name); 179 + return Ok(Some(Signal::EnterRawText)); 180 + } 181 + Signal::EditPriority => { 182 + let priority = match &mut self.render_data { 183 + RenderData::Root { widget: _ } => return Ok(None), 184 + RenderData::Task { widget } => &mut widget.priority, 185 + RenderData::Group { widget } => &mut widget.priority, 186 + }; 187 + 188 + priority.set_block( 189 + priority 190 + .block() 191 + .cloned() 192 + .expect("All of them should have blocks") 193 + .border_style(Style::default().fg(Color::Green)), 194 + ); 195 + 196 + priority.set_cursor_style(Style::default().reversed()); 197 + priority.set_cursor_line_style(Style::default().underlined()); 198 + 199 + self.editing = Some(Edit::Priority); 200 + return Ok(Some(Signal::EnterRawText)); 201 + } 202 + 203 + _ => {} 204 + } 205 + Ok(None) 206 + } 207 + 208 + #[allow(clippy::too_many_lines)] 209 + async fn handle_key_event(&mut self, key: KeyEvent) -> color_eyre::Result<Option<Signal>> { 210 + let signal_tx = self 211 + .signal_tx 212 + .as_mut() 213 + .expect("Invariant Broken, signal_tx must be initialized"); 214 + 215 + match self.editing { 216 + Some(Edit::Name) => { 217 + let name = match &mut self.render_data { 218 + RenderData::Root { widget: _ } => return Ok(None), 219 + RenderData::Task { widget } => &mut widget.name, 220 + RenderData::Group { widget } => &mut widget.name, 221 + }; 222 + 223 + if key.code == KeyCode::Enter { 224 + name.set_cursor_style(Style::reset()); 225 + name.set_cursor_line_style(Style::reset()); 226 + name.set_block( 227 + name.block() 228 + .cloned() 229 + .expect("All of them should have blocks") 230 + .border_style(Style::default().fg(Color::Reset)), 231 + ); 232 + self.editing = None; 233 + signal_tx.send(Signal::ExitRawText)?; 234 + 235 + let new_name = name.lines()[0].clone(); 236 + let id = self 237 + .inspecting 238 + .clone() 239 + .expect("Invariant Broken, this must be some id"); 240 + 241 + let kt = self.kh.read().await; 242 + match &self.render_data { 243 + RenderData::Task { .. } => { 244 + let _ = TaskEntity::load() 245 + .filter_by_nano_id(id.clone()) 246 + .one(&kt.db) 247 + .await? 248 + .expect("Invariant Broken: Must exist") 249 + .into_active_model() 250 + .set_name(new_name.as_str()) 251 + .save(&kt.db) 252 + .await?; 253 + } 254 + RenderData::Group { .. } => { 255 + let g = GroupEntity::load() 256 + .filter_by_nano_id(id.clone()) 257 + .with(TagEntity) 258 + .one(&kt.db) 259 + .await? 260 + .expect("Invariant Broken: Must exist"); 261 + let tag_id = g.tag.as_ref().expect("Must be loaded").nano_id.clone(); 262 + 263 + let _ = g 264 + .into_active_model() 265 + .set_name(new_name.as_str()) 266 + .save(&kt.db) 267 + .await?; 268 + 269 + TagEntity::load() 270 + .filter_by_nano_id(tag_id) 271 + .one(&kt.db) 272 + .await? 273 + .expect("Invariant Broken: Must exist") 274 + .into_active_model() 275 + .set_name(new_name.as_str()) 276 + .save(&kt.db) 277 + .await?; 278 + } 279 + RenderData::Root { .. } => unreachable!("Already returned above"), 280 + } 281 + 282 + drop(kt); 283 + 284 + Ok(Some(Signal::Refresh)) 285 + } else { 286 + name.input_without_shortcuts(key); 287 + Ok(None) 288 + } 289 + } 290 + Some(Edit::Priority) => { 291 + let priority = match &mut self.render_data { 292 + RenderData::Root { widget: _ } => return Ok(None), 293 + RenderData::Task { widget } => &mut widget.priority, 294 + RenderData::Group { widget } => &mut widget.priority, 295 + }; 296 + 297 + if key.code == KeyCode::Enter { 298 + priority.set_cursor_style(Style::reset()); 299 + priority.set_cursor_line_style(Style::reset()); 300 + 301 + priority.set_block( 302 + priority 303 + .block() 304 + .cloned() 305 + .expect("All of them should have blocks") 306 + .border_style(Style::default().fg(Color::Reset)), 307 + ); 55 308 56 - let block = Block::new() 57 - .title("[2]") 58 - .title("Inspector") 59 - .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM) 60 - .border_style(Style::new().fg(Color::Gray)) 61 - .border_type(BorderType::Rounded); 62 - match value.kind { 63 - TodoNodeKind::Root => Self { 64 - render_data: RenderData::Root { 65 - widget: Box::new(RootView::default()), 66 - }, 67 - margins, 68 - block, 69 - }, 70 - TodoNodeKind::Group(ref group) => Self { 71 - render_data: RenderData::Group { 72 - widget: Box::new(GroupView::from(&**group)), 73 - }, 74 - margins, 75 - block, 76 - }, 77 - TodoNodeKind::Task(ref task) => Self { 78 - render_data: RenderData::Task { 79 - widget: Box::new(TaskView::from(&**task)), 80 - }, 81 - margins, 82 - block, 83 - }, 309 + self.editing = None; 310 + Ok(Some(Signal::ExitRawText)) 311 + } else { 312 + priority.input_without_shortcuts(key); 313 + Ok(None) 314 + } 315 + } 316 + 317 + None => return Ok(None), 84 318 } 85 319 } 86 - } 87 320 88 - impl Widget for &Inspector<'_> { 89 - fn render(self, area: ratatui::prelude::Rect, buf: &mut ratatui::prelude::Buffer) 90 - where 91 - Self: Sized, 92 - { 93 - self.block.clone().render(area, buf); 321 + fn draw(&mut self, frame: &mut Frame, area: Rect) -> color_eyre::Result<()> { 322 + frame.render_widget(self.block.clone(), area); 94 323 95 324 let area = self.margins.split(area)[0]; 96 325 97 326 match &self.render_data { 98 - RenderData::Root { widget } => widget.clone().render(area, buf), 99 - RenderData::Task { widget } => widget.clone().render(area, buf), 100 - RenderData::Group { widget } => widget.clone().render(area, buf), 327 + RenderData::Root { widget } => frame.render_widget(*widget.clone(), area), 328 + RenderData::Task { widget } => frame.render_widget(*widget.clone(), area), 329 + RenderData::Group { widget } => frame.render_widget(*widget.clone(), area), 101 330 } 331 + 332 + Ok(()) 102 333 } 103 334 }
+2 -2
src/tui/components/todo/inspector/taskview.rs
··· 9 9 10 10 #[derive(Debug, Clone)] 11 11 pub struct TaskView<'text> { 12 - name: TextArea<'text>, 13 - priority: TextArea<'text>, 12 + pub name: TextArea<'text>, 13 + pub priority: TextArea<'text>, 14 14 parent_group: Paragraph<'text>, 15 15 due_finished_at: Paragraph<'text>, 16 16 layouts: Layouts,
+86 -24
src/tui/components/todo/mod.rs
··· 1 1 use async_trait::async_trait; 2 - use dto::NanoId; 2 + use color_eyre::eyre::Result; 3 + use crossterm::event::KeyEvent; 3 4 use ratatui::{ 4 5 Frame, 5 6 layout::{Constraint, Layout, Rect, Size}, ··· 12 13 13 14 use crate::{ 14 15 tui::{Page, Signal, components::Component}, 15 - types::{Group, KastenHandle, Priority, Task}, 16 + types::{Group, KastenHandle, Priority, Task, TodoTree}, 16 17 }; 17 18 18 19 mod explorer; ··· 24 25 use inspector::Inspector; 25 26 26 27 pub struct Todo<'text> { 27 - #[expect(dead_code)] 28 28 signal_tx: Option<UnboundedSender<Signal>>, 29 29 kh: KastenHandle, 30 30 layouts: Layouts, ··· 36 36 area: Size, 37 37 active: TodoRegion, 38 38 } 39 + 40 + pub const DEFAULT_NAME: &str = "Rename Me!"; 39 41 40 42 /// The different regions inside the `Todo` component 41 43 #[derive( ··· 80 82 .state 81 83 .selected() 82 84 .and_then(|idx| task_list.id_list.get(idx)); 83 - let kt = self.kh.read().await; 85 + 86 + let mut kt = self.kh.write().await; 87 + 88 + // fuck it we just fully rebuild the tree, how computationally expensive could it even be 89 + kt.todo_tree = TodoTree::construct(&kt.db).await.expect("Must not error"); 90 + 84 91 let tree = &kt.todo_tree; 85 92 86 93 debug!("tree after refresh {tree:#?}"); ··· 162 169 }; 163 170 let tree = &self.kh.read().await.todo_tree.tree; 164 171 165 - *inspector = tree 166 - .get(selected_node_id) 167 - .expect("Nodeid must be valid") 168 - .data() 169 - .into(); 172 + inspector.inspect( 173 + tree.get(selected_node_id) 174 + .expect("Nodeid must be valid") 175 + .data(), 176 + ); 170 177 } 171 178 } 172 179 ··· 230 237 ) 231 238 .expect("Node id must be valid"); 232 239 233 - let mut inspector: Inspector<'_> = first.data().into(); 240 + let mut inspector: Inspector<'_> = Inspector::new(self.kh.clone(), first.data()); 234 241 235 242 explorer.set_inactive(); 236 243 inspector.set_inactive(); ··· 239 246 self.task_list = Some(task_list); 240 247 self.inspector = Some(inspector); 241 248 249 + self.inspector 250 + .as_mut() 251 + .unwrap() 252 + .register_signal_handler(self.signal_tx.clone().unwrap())?; 253 + Ok(()) 254 + } 255 + 256 + fn register_signal_handler(&mut self, tx: UnboundedSender<Signal>) -> Result<()> { 257 + self.signal_tx = Some(tx); 242 258 Ok(()) 243 259 } 244 260 ··· 258 274 .as_mut() 259 275 .expect("This should have already been initialized"); 260 276 277 + let signal_tx = self 278 + .signal_tx 279 + .as_mut() 280 + .expect("Invariant broken, must exist"); 281 + 282 + if let Ok(Some(signal)) = inspector.update(signal.clone()).await { 283 + signal_tx.send(signal)?; 284 + } 285 + 261 286 match signal { 287 + Signal::Select { nanoid } => { 288 + let node_id = self 289 + .kh 290 + .read() 291 + .await 292 + .todo_tree 293 + .nanoid_to_nodeid 294 + .get(&nanoid) 295 + .expect("Invariant broken, why would we ever ask for this") 296 + .clone(); 297 + 298 + let Some(pos) = explorer.id_list.iter().position(|id| node_id == *id) else { 299 + return Ok(None); 300 + }; 301 + explorer.state.select(Some(pos)); 302 + self.update_inspector_from_selection().await; 303 + } 304 + 262 305 Signal::SwitchTo { 263 306 page: Page::Todo(region), 264 307 } => { ··· 283 326 284 327 self.update_inspector_from_selection().await; 285 328 } 329 + 286 330 Signal::MoveDown => { 287 331 match self.active { 288 332 TodoRegion::TaskList => { ··· 326 370 else { 327 371 return Ok(None); 328 372 }; 329 - let task = Task::new( 330 - NanoId::default().to_string(), 331 - parent, 332 - &mut kt, 333 - None, 334 - Priority::default(), 335 - ) 336 - .await?; 373 + let task = 374 + Task::new(DEFAULT_NAME, parent, &mut kt, None, Priority::default()).await?; 337 375 338 376 drop(kt); 339 377 debug!("created task: {task:#?}"); 340 - return Ok(Some(Signal::Refresh)); 378 + 379 + signal_tx.send(Signal::Refresh)?; 380 + signal_tx.send(Signal::Select { nanoid: task.id })?; 381 + return Ok(Some(Signal::SwitchTo { 382 + page: Page::Todo(TodoRegion::Inspector), 383 + })); 341 384 } 342 385 343 386 Signal::NewSubGroup => { ··· 348 391 let parent = explorer 349 392 .group_of_current_selection(&kt.todo_tree) 350 393 .map(|parent| parent.id.clone()); 351 - let group = Group::new(NanoId::default().to_string(), parent, &mut kt).await?; 394 + let group = Group::new(DEFAULT_NAME, parent, &mut kt).await?; 352 395 drop(kt); 353 396 debug!("Created group: {group:#?}"); 354 - return Ok(Some(Signal::Refresh)); 397 + 398 + signal_tx.send(Signal::Refresh)?; 399 + signal_tx.send(Signal::Select { nanoid: group.id })?; 400 + return Ok(Some(Signal::SwitchTo { 401 + page: Page::Todo(TodoRegion::Inspector), 402 + })); 355 403 } 356 404 357 405 Signal::NewGroup => { ··· 360 408 } 361 409 debug!("Creating Group!"); 362 410 let mut kt = self.kh.write().await; 363 - let group = Group::new(NanoId::default().to_string(), None, &mut kt).await?; 411 + let group = Group::new(DEFAULT_NAME, None, &mut kt).await?; 364 412 drop(kt); 365 413 debug!("Created group: {group:#?}"); 366 - return Ok(Some(Signal::Refresh)); 414 + 415 + signal_tx.send(Signal::Refresh)?; 416 + signal_tx.send(Signal::Select { nanoid: group.id })?; 417 + return Ok(Some(Signal::SwitchTo { 418 + page: Page::Todo(TodoRegion::Inspector), 419 + })); 367 420 } 368 421 369 422 Signal::Refresh => { 370 423 self.refresh().await; 371 424 } 425 + 372 426 _ => {} 373 427 } 374 428 Ok(None) 429 + } 430 + 431 + async fn handle_key_event(&mut self, key: KeyEvent) -> color_eyre::Result<Option<Signal>> { 432 + self.inspector.as_mut().unwrap().handle_key_event(key).await 375 433 } 376 434 377 435 fn draw(&mut self, frame: &mut Frame, area: Rect) -> color_eyre::Result<()> { 378 436 let explorer = self.explorer.as_mut().unwrap(); 379 437 let task_list = self.task_list.as_mut().unwrap(); 380 438 439 + let inspector = self.inspector.as_mut().unwrap(); 440 + 381 441 let splits = self.layouts.split(area); 382 442 383 443 frame.render_stateful_widget(&explorer.render_list, splits.explorer, &mut explorer.state); ··· 386 446 splits.task_list, 387 447 &mut task_list.state, 388 448 ); 389 - frame.render_widget(self.inspector.as_ref().unwrap(), splits.inspector); 449 + 450 + inspector.draw(frame, splits.inspector)?; 451 + 390 452 Ok(()) 391 453 } 392 454 }
+1 -1
src/tui/components/todo/tasklist.rs
··· 22 22 let render_list = List::new( 23 23 tree.tree 24 24 .traverse_pre_order(scope) 25 - .expect("nthis should not panic as the node id should exist inside") 25 + .expect("This should not panic as the node id should exist inside") 26 26 .zip( 27 27 tree.tree 28 28 .traverse_pre_order_ids(scope)
+17
src/tui/signal.rs
··· 1 1 use std::path::PathBuf; 2 2 3 + use dto::NanoId; 3 4 use strum::Display; 4 5 5 6 use serde::{Deserialize, Serialize}; ··· 69 70 /// Only works with the inspector 70 71 EditName, 71 72 73 + /// Edit the `Priority` of a `Task` or a `Group`. 74 + /// Only works with the inspector 75 + EditPriority, 76 + 77 + /// Internal Signal that tells the app to resume interpreting keys 78 + ExitRawText, 79 + 80 + /// Internal Signal that tells the app to stop interpreting keys 81 + /// as signals 82 + EnterRawText, 83 + 72 84 /// this is fucking temporary 73 85 Helix { 74 86 path: PathBuf, 87 + }, 88 + 89 + /// Requests the `Explorer` to select the following `NanoId`. 90 + Select { 91 + nanoid: NanoId, 75 92 }, 76 93 } 77 94
+18
src/types/kasten/todo_tree.rs
··· 234 234 Ordering::Equal 235 235 }); 236 236 } 237 + 238 + pub fn get_node_by_nano_id(&self, nano_id: &NanoId) -> &Node<TodoNode> { 239 + let node_id = self 240 + .nanoid_to_nodeid 241 + .get(nano_id) 242 + .expect("invariant broken!"); 243 + 244 + self.tree.get(node_id).expect("Invariant Broken!") 245 + } 246 + 247 + pub fn get_node_mut_by_nano_id(&mut self, nano_id: &NanoId) -> &mut Node<TodoNode> { 248 + let node_id = self 249 + .nanoid_to_nodeid 250 + .get(nano_id) 251 + .expect("invariant broken!"); 252 + 253 + self.tree.get_mut(node_id).expect("Invariant Broken!") 254 + } 237 255 }