From 59c41eef038ca47abb9a8860339eb8817bd13d04 Mon Sep 17 00:00:00 2001 From: suri-codes Date: Mon, 13 Apr 2026 12:27:28 -0700 Subject: [PATCH] feat: Can make groups --- .config/config.ron | 34 ++-- .config/default_config.ron | 1 + src/cli/process.rs | 52 +---- src/logging.rs | 8 +- src/main.rs | 2 +- src/tui/app.rs | 6 +- src/tui/components/todo/explorer.rs | 5 +- src/tui/components/todo/mod.rs | 94 ++++++++- src/tui/components/viewport/mod.rs | 14 +- src/tui/components/zk/mod.rs | 286 +++++++++++++++++++--------- src/tui/signal.rs | 49 +++-- src/types/group.rs | 99 +++++++++- src/types/kasten/todo_tree.rs | 2 + 13 files changed, 452 insertions(+), 200 deletions(-) diff --git a/.config/config.ron b/.config/config.ron index 8fa22d3..930cabc 100644 --- a/.config/config.ron +++ b/.config/config.ron @@ -1,18 +1,18 @@ ( directory: "/Users/suri/dev/projects/filaments/ZettelKasten", global_key_binds: { - "ctrl-z": Suspend, "down": MoveDown, "up": MoveUp, + "ctrl-z": Suspend, "ctrl-c": Quit, }, zk: ( keybinds: { + "enter": OpenZettel, + "ctrl-n": NewZettel, "tab": SwitchTo( page: Todo(Explorer), ), - "ctrl-n": NewZettel, - "enter": OpenZettel, }, ), todo: ( @@ -21,19 +21,19 @@ "2": SwitchTo( page: Todo(Inspector), ), - "k": MoveUp, + "3": SwitchTo( + page: Todo(TaskList), + ), "1": SwitchTo( page: Todo(Explorer), ), "k": MoveUp, "shift-g": NewSubGroup, + "j": MoveDown, + "k": MoveUp, "tab": SwitchTo( page: Zk, ), - "3": SwitchTo( - page: Todo(TaskList), - ), - "j": MoveDown, }, ), inspector: ( @@ -41,33 +41,33 @@ "3": SwitchTo( page: Todo(TaskList), ), - "1": SwitchTo( - page: Todo(Explorer), - ), "tab": SwitchTo( page: Zk, ), "2": SwitchTo( page: Todo(Inspector), ), + "1": SwitchTo( + page: Todo(Explorer), + ), }, ), tasklist: ( keybinds: { - "1": SwitchTo( - page: Todo(Explorer), - ), "tab": SwitchTo( page: Zk, ), - "2": SwitchTo( - page: Todo(Inspector), - ), "3": SwitchTo( page: Todo(TaskList), ), + "1": SwitchTo( + page: Todo(Explorer), + ), "j": MoveDown, "k": MoveUp, + "2": SwitchTo( + page: Todo(Inspector), + ), }, ), ), diff --git a/.config/default_config.ron b/.config/default_config.ron index 9481310..18de044 100644 --- a/.config/default_config.ron +++ b/.config/default_config.ron @@ -22,6 +22,7 @@ "3": SwitchTo(page: Todo(TaskList)), "j": MoveDown, "k": MoveUp, + "g": NewGroup }, ), inspector: ( diff --git a/src/cli/process.rs b/src/cli/process.rs index 7316723..d2fedb2 100644 --- a/src/cli/process.rs +++ b/src/cli/process.rs @@ -6,8 +6,8 @@ use std::{ use color_eyre::eyre::{Context, Result, eyre}; use dto::{ - Date, DateTime, GroupActiveModel, GroupEntity, HasOne, IntoActiveModel, TagActiveModel, - TagEntity, TaskActiveModel, TaskEntity, Time, ZettelEntity, + Date, DateTime, GroupEntity, HasOne, IntoActiveModel, TagEntity, TaskActiveModel, TaskEntity, + Time, ZettelEntity, }; use tower_lsp::{LspService, Server}; @@ -15,7 +15,7 @@ use crate::{ cli::{Commands, ZettelSubcommand}, config::{Config, get_config_dir}, lsp::Backend, - types::{Group, Kasten, Priority, Tag, Task, Zettel}, + types::{Group, Kasten, Priority, Task, Zettel}, }; impl Commands { @@ -82,51 +82,7 @@ impl Commands { match command { super::TodoSubcommand::Group { name, parent_id } => { // lets create a tag for this first group first - let tag: Tag = TagActiveModel::builder() - .set_name(name.clone()) - .insert(&kt.db) - .await? - .into(); - - let tag_id = tag.id.clone(); - - // then create the zettel for the group - let zettel = Zettel::new(name.clone(), &mut kt, vec![tag]).await?; - - // then insert that shi - let inserted = GroupActiveModel::builder() - .set_name(name) - .set_parent_group_id(parent_id) - .set_tag( - TagEntity::load() - .filter_by_nano_id(tag_id) - .one(&kt.db) - .await? - .expect("Tag must exist since we just created it") - .into_active_model(), - ) - .set_zettel( - ZettelEntity::load() - .filter_by_nano_id(zettel.id) - .one(&kt.db) - .await? - .expect("Zettel must exist since we just created it") - .into_active_model(), - ) - .set_priority(Priority::default()) - .insert(&kt.db) - .await?; - - // group should also have the accompanying tag for it. - let group: Group = GroupEntity::load() - .with(TagEntity) - .with((ZettelEntity, TagEntity)) - .filter_by_nano_id(inserted.nano_id) - .one(&kt.db) - .await? - .expect("We just inserted it") - .into(); - + let group = Group::new(name, parent_id, &mut kt).await?; println!("created group {group:#?}"); } super::TodoSubcommand::Task { name, parent_id } => { diff --git a/src/logging.rs b/src/logging.rs index a562b4b..21aa71c 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,5 +1,5 @@ use std::{ - fs::{File, create_dir_all}, + fs::{OpenOptions, create_dir_all}, sync::LazyLock, }; @@ -29,7 +29,11 @@ pub fn init() -> Result<()> { create_dir_all(&directory)?; let log_path = directory.join(LOG_FILE.clone()); - let log_file = File::create(log_path)?; + + let log_file = OpenOptions::new() + .append(true) + .create(true) + .open(log_path)?; let env_filter = EnvFilter::builder().with_default_directive(Level::INFO.into()); diff --git a/src/main.rs b/src/main.rs index 0a52dac..96a5486 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,7 +64,7 @@ fn main() -> color_eyre::Result<()> { move || -> color_eyre::Result<()> { // block the tui on the same runtime as above tui_rt.block_on(async { - let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx).await?; + let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx)?; tui.run().await?; // just close everything as soon as the tui is done running process::exit(0); diff --git a/src/tui/app.rs b/src/tui/app.rs index d2efe4a..1047c63 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -48,7 +48,7 @@ pub enum Page { impl App { /// Construct a new `App` instance. - pub async fn new( + pub fn new( tick_rate: f64, frame_rate: f64, kh: KastenHandle, @@ -60,7 +60,7 @@ impl App { tick_rate, frame_rate, // components: vec![Box::new(Zk::new(kh.clone()).await?)], - components: vec![Box::new(Viewport::new(kh.clone()).await?)], + components: vec![Box::new(Viewport::new(kh.clone()))], should_quit: false, should_suspend: false, config: Config::parse()?, @@ -154,8 +154,6 @@ impl App { return Ok(()); }; - info!("page: {:#?}, page_keymap: {page_keymap:#?}", self.page); - if let Some(signal) = page_keymap.get(&vec![key]) { signal_tx.send(signal.clone())?; } else { diff --git a/src/tui/components/todo/explorer.rs b/src/tui/components/todo/explorer.rs index 4cebeac..ff34edc 100644 --- a/src/tui/components/todo/explorer.rs +++ b/src/tui/components/todo/explorer.rs @@ -3,7 +3,7 @@ use ratatui::{ text::{Line, Span, Text}, widgets::{Block, BorderType, Borders, List, ListState}, }; -use tracing::info; +use tracing::{debug, info}; use tree::NodeId; use crate::types::{TodoNode, TodoNodeKind, TodoTree}; @@ -58,6 +58,7 @@ impl Explorer<'_> { } pub fn set_active(&mut self) { + debug!("Explorer set active!"); self.render_list = self.render_list.clone().block( Block::new() .title("[1]") @@ -124,8 +125,6 @@ impl<'text> From> for Text<'text> { )); } - info!("{spans:#?}"); - Line::from(spans).into() } } diff --git a/src/tui/components/todo/mod.rs b/src/tui/components/todo/mod.rs index c07e329..be1d82a 100644 --- a/src/tui/components/todo/mod.rs +++ b/src/tui/components/todo/mod.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use dto::NanoId; use ratatui::{ Frame, layout::{Constraint, Layout, Rect, Size}, @@ -7,15 +8,15 @@ use ratatui::{ use serde::{Deserialize, Serialize}; use strum::{Display, EnumIter}; use tokio::sync::mpsc::UnboundedSender; +use tracing::{debug, info}; use crate::{ tui::{Page, Signal, components::Component}, - types::KastenHandle, + types::{Group, KastenHandle}, }; mod explorer; use explorer::Explorer; - mod tasklist; use tasklist::TaskList; @@ -31,6 +32,7 @@ pub struct Todo<'text> { task_list: Option>, inspector: Option>, + area: Size, active: TodoRegion, } @@ -54,10 +56,73 @@ impl Todo<'_> { explorer: None, task_list: None, inspector: None, + area: Size::default(), active: TodoRegion::default(), } } + pub async fn refresh(&mut self) { + let explorer = self + .explorer + .as_mut() + .expect("This should have already been init.ialized"); + let task_list = self + .task_list + .as_mut() + .expect("This should have already been initialized"); + + let explorer_selection = explorer + .state + .selected() + .and_then(|idx| explorer.id_list.get(idx)); + let task_list_selection = task_list + .state + .selected() + .and_then(|idx| task_list.id_list.get(idx)); + let kt = self.kh.read().await; + let tree = &kt.todo_tree; + + let splits = self + .layouts + .split(Rect::new(0, 0, self.area.width, self.area.height)); + + let l_state = ListState::default(); + + //TODO: instead of tree.root_id this probably should be scope. + let mut explorer = Explorer::new(tree, &tree.root_id, l_state, splits.explorer.width); + let mut task_list = TaskList::new(tree, &tree.root_id, l_state, splits.task_list.width); + + drop(kt); + + let explorer_selection_idx = + explorer_selection.and_then(|id| explorer.id_list.iter().position(|e| id == e)); + + let task_list_selection_idx = + task_list_selection.and_then(|id| task_list.id_list.iter().position(|e| id == e)); + + explorer.state.select(explorer_selection_idx); + task_list.state.select(task_list_selection_idx); + + match self.active { + TodoRegion::Inspector => { + explorer.set_inactive(); + task_list.set_inactive(); + } + TodoRegion::TaskList => { + explorer.set_inactive(); + task_list.set_active(); + } + TodoRegion::Explorer => { + explorer.set_active(); + task_list.set_inactive(); + } + } + + self.explorer = Some(explorer); + self.task_list = Some(task_list); + self.update_inspector_from_selection().await; + } + async fn update_inspector_from_selection(&mut self) { let explorer = self .explorer @@ -71,6 +136,7 @@ impl Todo<'_> { .inspector .as_mut() .expect("This should have already been initialized"); + let selected_node_id = match self.active { TodoRegion::TaskList => { let Some(idx) = task_list.state.selected() else { @@ -142,6 +208,7 @@ impl Layouts { #[async_trait] impl Component for Todo<'_> { async fn init(&mut self, area: Size) -> color_eyre::Result<()> { + self.area = area; let tree = &self.kh.read().await.todo_tree; let splits = self.layouts.split(Rect::new(0, 0, area.width, area.height)); @@ -167,11 +234,16 @@ impl Component for Todo<'_> { explorer.set_inactive(); inspector.set_inactive(); task_list.set_inactive(); - self.explorer = Some(explorer); self.task_list = Some(task_list); self.inspector = Some(inspector); + // match self.active { + + // ins + + // } + Ok(()) } @@ -244,6 +316,22 @@ impl Component for Todo<'_> { self.update_inspector_from_selection().await; } + + Signal::NewGroup => { + if self.active != TodoRegion::Explorer { + return Ok(None); + } + debug!("Creating Group!"); + let mut kt = self.kh.write().await; + let group = Group::new(NanoId::default().to_string(), None, &mut kt).await?; + drop(kt); + debug!("Created group: {group:#?}"); + return Ok(Some(Signal::Refresh)); + } + + Signal::Refresh => { + self.refresh().await; + } _ => {} } Ok(None) diff --git a/src/tui/components/viewport/mod.rs b/src/tui/components/viewport/mod.rs index 60e5967..523644a 100644 --- a/src/tui/components/viewport/mod.rs +++ b/src/tui/components/viewport/mod.rs @@ -32,19 +32,19 @@ mod switcher; use switcher::Switcher; impl Viewport<'_> { - pub async fn new(kh: KastenHandle) -> Result { + pub fn new(kh: KastenHandle) -> Self { let mut switcher = Switcher::default(); switcher.select_region(Page::default()); - Ok(Self { + Self { signal_tx: None, _layouts: Layouts::default(), switcher, - zk: Zk::new(kh.clone()).await?, + zk: Zk::new(kh.clone()), todo: Todo::new(kh.clone()), active_page: Page::default(), kh, - }) + } } } @@ -83,10 +83,10 @@ impl Component for Viewport<'_> { debug!("active page switched to : {page}"); } - match self.active_page { - Page::Zk => self.zk.update(signal).await, - Page::Todo(_) => self.todo.update(signal).await, + if let Some(signal) = self.zk.update(signal.clone()).await? { + self.signal_tx.as_mut().unwrap().send(signal)?; } + self.todo.update(signal).await } async fn handle_key_event(&mut self, key: KeyEvent) -> color_eyre::Result> { diff --git a/src/tui/components/zk/mod.rs b/src/tui/components/zk/mod.rs index 03b3346..e80a67d 100644 --- a/src/tui/components/zk/mod.rs +++ b/src/tui/components/zk/mod.rs @@ -30,10 +30,12 @@ pub struct Zk<'text> { signal_tx: Option>, kh: KastenHandle, layouts: Layouts, - search: Search<'text>, - zettel_list: ZettelList<'text>, - zettel_view: ZettelView<'text>, - preview: Preview<'text>, + area: Size, + + search: Option>, + zettel_list: Option>, + zettel_view: Option>, + preview: Option>, } struct Layouts { @@ -42,6 +44,32 @@ struct Layouts { z_preview: Layout, } +impl Layouts { + fn split(&self, area: Rect) -> LayoutSplit { + let rects = self.left_right.split(area); + + let (left, right) = (rects[0], rects[2]); + + let l_rects = self.search_zl.split(left); + + let r_rects = self.z_preview.split(right); + + LayoutSplit { + search: l_rects[0], + zettel_list: l_rects[1], + zettel_view: r_rects[0], + preview: r_rects[1], + } + } +} + +struct LayoutSplit { + search: Rect, + zettel_list: Rect, + zettel_view: Rect, + preview: Rect, +} + impl Default for Layouts { fn default() -> Self { Self { @@ -57,74 +85,95 @@ impl Default for Layouts { } impl Zk<'_> { - pub async fn new(kh: KastenHandle) -> Result { + pub fn new(kh: KastenHandle) -> Self { + Self { + signal_tx: None, + kh, + + layouts: Layouts::default(), + area: Size::default(), + + search: None, + zettel_list: None, + zettel_view: None, + preview: None, + } + } + + async fn refresh(&mut self) -> Result<()> { + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); + + let selected = zettel_list + .state + .selected() + .and_then(|idx| zettel_list.id_list.get(idx)); + + let splits = self + .layouts + .split(Rect::new(0, 0, self.area.width, self.area.height)); + + let kt = self.kh.read().await; + let db = kt.db.clone(); + + // ideally we just keep the same selection as we had originally + let fetch_all = async || -> Result> { Ok(ZettelEntity::load() .with(TagEntity) .order_by_desc(ZettelColumns::ModifiedAt) - .all(&kh.read().await.db) + .all(&db) .await? .into_iter() .map(Into::into) .collect()) }; - let mut zettels: Vec = fetch_all().await?; + let zettels: Vec = fetch_all().await?; - if zettels.is_empty() { - let _ = Zettel::new("Welcome!", &mut *kh.write().await, vec![]).await?; - zettels = fetch_all().await?; - } + let mut zettel_list = ZettelList::new( + zettels.clone(), + ListState::default(), + splits.zettel_list.width, + ); - // in theory this is wasted compute, we should be initializing all our - // stuff inside the init function - let mut l_state = ListState::default(); - l_state.select_first(); - let zettel_list = ZettelList::new(zettels.clone(), l_state, 0); + let selected_zettel_idx = + selected.and_then(|desired| zettel_list.id_list.iter().position(|id| id == desired)); - let selected_zettel = zettel_list - .id_list - .get( - zettel_list - .state - .selected() - .expect("We explicitly select the first item"), - ) - // so technically this might not exist - .expect("There must always be one atleast one zettel"); - - let kt = kh.read().await; + zettel_list.state.select(selected_zettel_idx); let zettel = zettels .iter() - .find(|&z| &z.id == selected_zettel) + //TODO: expect probably should not look like this + .find(|&z| &z.id == selected.expect("Something should be selected")) .expect("we selected it out of the list so it must exist"); let preview = Preview::from(zettel.content(&kt.index).clone()); drop(kt); - Ok(Self { - signal_tx: None, - search: Search::new(kh.clone()), - kh, - layouts: Layouts::default(), - zettel_list, - zettel_view: zettel.into(), - preview, - }) + let search = Search::new(self.kh.clone()); + let zettel_view = zettel.into(); + + self.search = Some(search); + self.zettel_view = Some(zettel_view); + self.preview = Some(preview); + self.zettel_list = Some(zettel_list); + + Ok(()) + // what the fuck am i supposed to do here mannnn. } async fn update_views_from_zettel_list_selection(&mut self) -> Result<()> { - let selection_idx = self - .zettel_list + let zettel_list = self.zettel_list.as_mut().expect("Must be initialzied"); + + let selection_idx = zettel_list .state .selected() .expect("i have no idea what to do if this doesnt exist"); // sometimes the selection we get is over the length of the thing, so its // actually fine if this is none, just means we reached the end of the list - let Some(zid) = self.zettel_list.id_list.get(selection_idx) else { + let Some(zid) = zettel_list.id_list.get(selection_idx) else { return Ok(()); }; @@ -134,10 +183,10 @@ impl Zk<'_> { .await? .context("Unknown Behaviour, A selected zettel got deleted somehow.")?; - self.preview = zettel.content(&kh.index).clone().into(); + self.preview = Some(zettel.content(&kh.index).clone().into()); drop(kh); - self.zettel_view = zettel.into(); + self.zettel_view = Some(zettel.into()); Ok(()) } @@ -161,14 +210,25 @@ impl Zk<'_> { } pub async fn update_with_respect_to_query(&mut self) -> Result<()> { - let zettels = self + let curr_zettels = self.get_zettels_by_current_query().await?; + + let search = self .search - .rank(self.get_zettels_by_current_query().await?) - .await; + .as_mut() + .expect("Must be initalized by this point"); + + let zettel_list = self + .zettel_list + .as_mut() + .expect("Must be initialized by this point"); + + let zettels = search.rank(curr_zettels).await; + + *zettel_list = ZettelList::new(zettels, zettel_list.state, zettel_list.width); - self.zettel_list = ZettelList::new(zettels, self.zettel_list.state, self.zettel_list.width); info!("we are moving selection to first"); - self.zettel_list.state.select_first(); + + zettel_list.state.select_first(); self.update_views_from_zettel_list_selection().await?; Ok(()) @@ -179,19 +239,62 @@ impl Zk<'_> { impl Component for Zk<'_> { /// this tells us how big the space we have for this is async fn init(&mut self, area: Size) -> color_eyre::Result<()> { - let total_width = area.width; + self.area = area; + let splits = self.layouts.split(Rect::new(0, 0, area.width, area.height)); + let mut kt = self.kh.write().await; + let db = kt.db.clone(); + + let fetch_all = async || -> Result> { + Ok(ZettelEntity::load() + .with(TagEntity) + .order_by_desc(ZettelColumns::ModifiedAt) + .all(&db) + .await? + .into_iter() + .map(Into::into) + .collect()) + }; + + let mut zettels: Vec = fetch_all().await?; + + if zettels.is_empty() { + let _ = Zettel::new("Welcome!", &mut kt, vec![]).await?; + zettels = fetch_all().await?; + } // in theory this is wasted compute, we should be initializing all our + // stuff inside the init function let mut l_state = ListState::default(); l_state.select_first(); + let zettel_list = ZettelList::new(zettels.clone(), l_state, splits.zettel_list.width); - let zettel_list = ZettelList::new( - self.get_zettels_by_current_query().await?, - l_state, - total_width / 2, - ); + let selected_zettel = zettel_list + .id_list + .get( + zettel_list + .state + .selected() + .expect("We explicitly select the first item"), + ) + // so technically this might not exist + .expect("There must always be one atleast one zettel"); - self.zettel_list = zettel_list; + let zettel = zettels + .iter() + .find(|&z| &z.id == selected_zettel) + .expect("we selected it out of the list so it must exist"); + + let preview = Preview::from(zettel.content(&kt.index).clone()); + + drop(kt); + + let search = Search::new(self.kh.clone()); + let zettel_view = zettel.into(); + + self.search = Some(search); + self.zettel_view = Some(zettel_view); + self.preview = Some(preview); + self.zettel_list = Some(zettel_list); Ok(()) } @@ -202,22 +305,28 @@ impl Component for Zk<'_> { } async fn update(&mut self, signal: Signal) -> Result> { + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); + let search = self.search.as_mut().expect("Must be initialized"); match signal { + Signal::Refresh => { + self.refresh().await?; + } + Signal::MoveDown => { - self.zettel_list.state.select_next(); + zettel_list.state.select_next(); self.update_views_from_zettel_list_selection().await?; } Signal::MoveUp => { - self.zettel_list.state.select_previous(); + zettel_list.state.select_previous(); self.update_views_from_zettel_list_selection().await?; } Signal::OpenZettel => { - let Some(selcted) = self.zettel_list.state.selected() else { + let Some(selcted) = zettel_list.state.selected() else { return Ok(None); }; - let Some(zid) = self.zettel_list.id_list.get(selcted) else { + let Some(zid) = zettel_list.id_list.get(selcted) else { return Ok(None); }; @@ -236,7 +345,7 @@ impl Component for Zk<'_> { let mut kt = self.kh.write().await; // we create the zettel with the query as the - let z = Zettel::new(self.search.query(), &mut kt, vec![]) + let z = Zettel::new(search.query(), &mut kt, vec![]) .await .with_context(|| "Failed to create a new Zettel!")?; @@ -263,12 +372,14 @@ impl Component for Zk<'_> { } Signal::ClosedZettel { zid } => { - // regenerate a fresh zettel list - self.zettel_list = ZettelList::new( - self.get_zettels_by_current_query().await?, - self.zettel_list.state, - self.zettel_list.width, - ); + let curr_zettels = self.get_zettels_by_current_query().await?; // regenerate a fresh zettel list + + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); + let zettel_view = self.zettel_view.as_mut().expect("Must be initialized"); + let preview = self.preview.as_mut().expect("Must be initialized"); + let search = self.search.as_mut().expect("Must be initialized"); + + *zettel_list = ZettelList::new(curr_zettels, zettel_list.state, zettel_list.width); let kt = self.kh.read().await; @@ -276,18 +387,14 @@ impl Component for Zk<'_> { .await? .expect("invariant broken, we just closed this zettel"); - let idx = self - .zettel_list - .id_list - .iter() - .position(|id| *id == zettel.id); + let idx = zettel_list.id_list.iter().position(|id| *id == zettel.id); // reset the state of the component - self.search.clear_query(); - self.zettel_list.state.select(idx); + search.clear_query(); + zettel_list.state.select(idx); - self.zettel_view = ZettelView::from(&zettel); - self.preview = Preview::from(zettel.content(&kt.index).clone()); + *zettel_view = ZettelView::from(&zettel); + *preview = Preview::from(zettel.content(&kt.index).clone()); drop(kt); } @@ -300,7 +407,7 @@ impl Component for Zk<'_> { // NOTE: this is hardcoded for now, but I honestly think people should not // be able to change these binds, opinionated software or something... if !(key.code.is_up() || key.code.is_down() || key.code.is_enter() || key.code.is_tab()) { - self.search.query.input(key); + self.search.as_mut().unwrap().query.input(key); self.update_with_respect_to_query().await?; } @@ -312,28 +419,23 @@ impl Component for Zk<'_> { frame: &mut ratatui::Frame, area: ratatui::prelude::Rect, ) -> color_eyre::Result<()> { - let (search_layout, zettel_list_layout, zettel_layout, preview_layout) = { - let rects = self.layouts.left_right.split(area); - - let (left, right) = (rects[0], rects[2]); + let zettel_list = self.zettel_list.as_mut().expect("Must be initialized"); + let zettel_view = self.zettel_view.as_mut().expect("Must be initialized"); + let preview = self.preview.as_mut().expect("Must be initialized"); + let search = self.search.as_mut().expect("Must be initialized"); - let l_rects = self.layouts.search_zl.split(left); - - let r_rects = self.layouts.z_preview.split(right); - - (l_rects[0], l_rects[1], r_rects[0], r_rects[1]) - }; + let splits = self.layouts.split(area); - frame.render_widget(self.search.clone(), search_layout); + frame.render_widget(search.clone(), splits.search); frame.render_stateful_widget( - &self.zettel_list.render_list, - zettel_list_layout, - &mut self.zettel_list.state, + &zettel_list.render_list, + splits.zettel_list, + &mut zettel_list.state, ); - frame.render_widget(self.zettel_view.clone(), zettel_layout); - frame.render_widget(self.preview.clone(), preview_layout); + frame.render_widget(zettel_view.clone(), splits.zettel_view); + frame.render_widget(preview.clone(), splits.preview); Ok(()) } diff --git a/src/tui/signal.rs b/src/tui/signal.rs index 4b2c5b8..9b0facc 100644 --- a/src/tui/signal.rs +++ b/src/tui/signal.rs @@ -1,6 +1,5 @@ -use std::{path::PathBuf, str::FromStr}; +use std::path::PathBuf; -use color_eyre::eyre::eyre; use strum::Display; use serde::{Deserialize, Serialize}; @@ -23,6 +22,10 @@ pub enum Signal { Error(String), Help, + /// Request a refresh of the components being displayed due + /// to an update to the `Kasten` + Refresh, + SwitchTo { page: Page, }, @@ -53,29 +56,33 @@ pub enum Signal { zid: ZettelId, }, + /// Create a new `Group` + NewGroup, + /// this is fucking temporary Helix { path: PathBuf, }, } -impl FromStr for Signal { - type Err = color_eyre::Report; +// impl FromStr for Signal { +// type Err = color_eyre::Report; - fn from_str(s: &str) -> Result { - Ok(match s.to_lowercase().as_str() { - "suspend" => Self::Suspend, - "resume" => Self::Resume, - "quit" => Self::Quit, - "movedown" => Self::MoveDown, - "moveup" => Self::MoveUp, - "openzettel" => Self::OpenZettel, - "newzettel" => Self::NewZettel, - _ => { - return Err(eyre!(format!( - "Attempt to construct a non-user Signal from str: {s}" - ))); - } - }) - } -} +// fn from_str(s: &str) -> Result { +// Ok(match s.to_lowercase().as_str() { +// "suspend" => Self::Suspend, +// "resume" => Self::Resume, +// "quit" => Self::Quit, +// "movedown" => Self::MoveDown, +// "moveup" => Self::MoveUp, +// "openzettel" => Self::OpenZettel, +// "newzettel" => Self::NewZettel, +// "newgroup" +// _ => { +// return Err(eyre!(format!( +// "Attempt to construct a non-user Signal from str: {s}" +// ))); +// } +// }) +// } +// } diff --git a/src/types/group.rs b/src/types/group.rs index 1dd1dcb..e52f4ca 100644 --- a/src/types/group.rs +++ b/src/types/group.rs @@ -1,6 +1,11 @@ -use dto::{DateTime, GroupModelEx, NanoId}; +use color_eyre::eyre::Result; +use dto::{ + DateTime, GroupActiveModel, GroupEntity, GroupModelEx, IntoActiveModel as _, NanoId, + TagActiveModel, TagEntity, ZettelEntity, +}; +use tree::Node; -use crate::types::{Priority, Tag, Zettel, frontmatter}; +use crate::types::{Kasten, Priority, Tag, TodoNode, Zettel, frontmatter}; /// A `Group` which contains tasks! #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -34,6 +39,96 @@ impl Group { .format(frontmatter::DATE_FMT_STR) .to_string() } + + pub async fn new( + name: impl Into, + parent_id: Option, + kt: &mut Kasten, + ) -> Result { + let name = name.into(); + let tag: Tag = TagActiveModel::builder() + .set_name(name.clone()) + .insert(&kt.db) + .await? + .into(); + + let tag_id = tag.id.clone(); + + // then create the zettel for the group + let zettel = Zettel::new(name.clone(), kt, vec![tag]).await?; + + // then insert that shi + let inserted = GroupActiveModel::builder() + .set_name(name) + .set_parent_group_id(parent_id) + .set_tag( + TagEntity::load() + .filter_by_nano_id(tag_id) + .one(&kt.db) + .await? + .expect("Tag must exist since we just created it") + .into_active_model(), + ) + .set_zettel( + ZettelEntity::load() + .filter_by_nano_id(zettel.id) + .one(&kt.db) + .await? + .expect("Zettel must exist since we just created it") + .into_active_model(), + ) + .set_priority(Priority::default()) + .insert(&kt.db) + .await?; + + // group should also have the accompanying tag for it. + let group: Self = GroupEntity::load() + .with(TagEntity) + .with((ZettelEntity, TagEntity)) + .filter_by_nano_id(inserted.nano_id) + .one(&kt.db) + .await? + .expect("We just inserted it") + .into(); + + // we should also insert the group into the kasten + + let parent_node_id = group + .parent_id + .clone() + .and_then(|id| kt.todo_tree.nanoid_to_nodeid.get(&id)) + .unwrap_or(&kt.todo_tree.root_id); + + let my_depth = if *parent_node_id == kt.todo_tree.root_id { + 0 + } else { + kt.todo_tree + .tree + .get(parent_node_id) + .expect("Must exist inside tree") + .data() + .depth + + 1 + }; + + let inserted_node_id = kt + .todo_tree + .tree + .insert( + Node::new(TodoNode::new( + super::TodoNodeKind::Group(Box::new(group.clone())), + my_depth, + )), + tree::InsertBehavior::UnderNode(parent_node_id), + ) + .expect("Insertion of group should not error!"); + + kt.todo_tree + .nanoid_to_nodeid + .insert(group.id.clone(), inserted_node_id); + + Ok(group) + } } impl From for Group { diff --git a/src/types/kasten/todo_tree.rs b/src/types/kasten/todo_tree.rs index 858b02d..09a4652 100644 --- a/src/types/kasten/todo_tree.rs +++ b/src/types/kasten/todo_tree.rs @@ -71,6 +71,8 @@ impl TodoTree { Ok(todo_tree) } + + #[async_recursion::async_recursion] async fn add_group_to_tree( &mut self, -- 2.51.2