···39394040impl NodeId {
4141 // This is okay since we are practically never reaching 2^32.
4242- #[allow(clippy::cast_possible_truncation)]
4242+ #[expect(clippy::cast_possible_truncation)]
4343 pub(crate) const fn new(index: usize) -> Self {
4444 Self {
4545 index: index as u32,
···1919# Run all tests
2020test:
2121 cargo nextest r {{_cargo_flags}}
2222-2222+reset:
2323+ rm -rf ZettleKasten
2424+ rm -rf ./.data
2525+ cargo run -- init
2626+ cargo run
23272428# Only used to build / generate entities
2529dev-db := justfile_directory() + "/target/dev.db"
+6-109
src/cli/process.rs
···44 io::Write,
55};
6677-use color_eyre::eyre::{Context, Result, eyre};
88-use dto::{
99- Date, DateTime, GroupActiveModel, GroupEntity, HasOne, IntoActiveModel, TagActiveModel,
1010- TagEntity, TaskActiveModel, TaskEntity, Time, ZettelEntity,
1111-};
77+use color_eyre::eyre::{Context, Result};
128use tower_lsp::{LspService, Server};
1391410use crate::{
1511 cli::{Commands, ZettelSubcommand},
1612 config::{Config, get_config_dir},
1713 lsp::Backend,
1818- types::{Group, Kasten, Priority, Tag, Task, Zettel},
1414+ types::{Group, Kasten, Priority, Task, Zettel},
1915};
20162117impl Commands {
2222- #[expect(clippy::too_many_lines)]
2318 pub async fn process(self) -> Result<()> {
2419 match self {
2520 Self::Init { name } => {
···8277 match command {
8378 super::TodoSubcommand::Group { name, parent_id } => {
8479 // lets create a tag for this first group first
8585- let tag: Tag = TagActiveModel::builder()
8686- .set_name(name.clone())
8787- .insert(&kt.db)
8888- .await?
8989- .into();
9090-9191- let tag_id = tag.id.clone();
9292-9393- // then create the zettel for the group
9494- let zettel = Zettel::new(name.clone(), &mut kt, vec![tag]).await?;
9595-9696- // then insert that shi
9797- let inserted = GroupActiveModel::builder()
9898- .set_name(name)
9999- .set_parent_group_id(parent_id)
100100- .set_tag(
101101- TagEntity::load()
102102- .filter_by_nano_id(tag_id)
103103- .one(&kt.db)
104104- .await?
105105- .expect("Tag must exist since we just created it")
106106- .into_active_model(),
107107- )
108108- .set_zettel(
109109- ZettelEntity::load()
110110- .filter_by_nano_id(zettel.id)
111111- .one(&kt.db)
112112- .await?
113113- .expect("Zettel must exist since we just created it")
114114- .into_active_model(),
115115- )
116116- .set_priority(Priority::default())
117117- .insert(&kt.db)
118118- .await?;
119119-120120- // group should also have the accompanying tag for it.
121121- let group: Group = GroupEntity::load()
122122- .with(TagEntity)
123123- .with((ZettelEntity, TagEntity))
124124- .filter_by_nano_id(inserted.nano_id)
125125- .one(&kt.db)
126126- .await?
127127- .expect("We just inserted it")
128128- .into();
129129-8080+ let group = Group::new(name, parent_id, &mut kt).await?;
13081 println!("created group {group:#?}");
13182 }
13283 super::TodoSubcommand::Task { name, parent_id } => {
133133- // need to create the task
134134- let parent = GroupEntity::load()
135135- .with(TagEntity)
136136- .filter_by_nano_id(parent_id)
137137- .one(&kt.db)
138138- .await
139139- .with_context(|| "failed to communicate with db")?
140140- .ok_or_else(|| eyre!("could not find the group"))?;
141141-142142- let HasOne::Loaded(tag) = parent.tag else {
143143- panic!("this has to be loaded since we just loaded it right above")
144144- };
145145-146146- let zettel =
147147- Zettel::new(name.clone(), &mut kt, vec![(*tag).into()]).await?;
148148-149149- let inserted = TaskActiveModel::builder()
150150- .set_name(name)
151151- .set_group_id(parent.nano_id.clone())
152152- .set_priority(Priority::default())
153153- .set_zettel(
154154- ZettelEntity::load()
155155- .filter_by_nano_id(zettel.id)
156156- .one(&kt.db)
157157- .await?
158158- .expect("Zettel must exist since we just created it")
159159- .into_active_model(),
160160- )
161161- .set_due(Some(DateTime::new(
162162- Date::from_ymd_opt(2026, 1, 31).unwrap(),
163163- Time::from_hms_opt(10, 10, 10).unwrap(),
164164- )))
165165- .insert(&kt.db)
166166- .await?;
167167-168168- let group = GroupEntity::load()
169169- .with(TagEntity)
170170- .with((ZettelEntity, TagEntity))
171171- .filter_by_nano_id(parent.nano_id)
172172- .one(&kt.db)
173173- .await?
174174- .expect("We just inserted it");
175175-176176- let mut task = TaskEntity::load()
177177- .with((ZettelEntity, TagEntity))
178178- .filter_by_nano_id(inserted.nano_id)
179179- .one(&kt.db)
180180- .await?
181181- .expect("We just inserted it");
182182-183183- task.group = HasOne::Loaded(Box::new(group));
184184-185185- println!("task: {task:#?}");
186186-187187- let task: Task = task.into();
188188-189189- println!("created task: {task:#?}");
8484+ let task =
8585+ Task::new(name, parent_id, &mut kt, None, Priority::default()).await?;
8686+ println!("created task {task:#?}");
19087 }
19188 }
19289 }
+6-2
src/logging.rs
···11use std::{
22- fs::{File, create_dir_all},
22+ fs::{OpenOptions, create_dir_all},
33 sync::LazyLock,
44};
55···2929 create_dir_all(&directory)?;
30303131 let log_path = directory.join(LOG_FILE.clone());
3232- let log_file = File::create(log_path)?;
3232+3333+ let log_file = OpenOptions::new()
3434+ .append(true)
3535+ .create(true)
3636+ .open(log_path)?;
33373438 let env_filter = EnvFilter::builder().with_default_directive(Level::INFO.into());
3539
+1-1
src/main.rs
···6464 move || -> color_eyre::Result<()> {
6565 // block the tui on the same runtime as above
6666 tui_rt.block_on(async {
6767- let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx).await?;
6767+ let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx)?;
6868 tui.run().await?;
6969 // just close everything as soon as the tui is done running
7070 process::exit(0);
+19-9
src/tui/app.rs
···2525 components: Vec<Box<dyn Component>>,
2626 should_quit: bool,
2727 should_suspend: bool,
2828- #[allow(dead_code)]
2928 page: Page,
3029 last_tick_key_events: Vec<KeyEvent>,
3130 kh: KastenHandle,
3231 signal_tx: UnboundedSender<Signal>,
3332 signal_rx: UnboundedReceiver<Signal>,
3433 viz_signal_tx: UnboundedSender<Signal>,
3434+ raw_text: bool,
3535}
36363737/// The different regions of the application that the user can
3838/// be interacting with. Think of these kind of like the highest class of
3939/// components.
4040-#[derive(
4141- Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter, Display,
4242-)]
4040+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter, Display)]
4341pub enum Page {
4444- #[default]
4542 Zk,
4643 Todo(TodoRegion),
4744}
48454646+impl Default for Page {
4747+ fn default() -> Self {
4848+ Self::Todo(TodoRegion::Explorer)
4949+ }
5050+}
5151+4952impl App {
5053 /// Construct a new `App` instance.
5151- pub async fn new(
5454+ pub fn new(
5255 tick_rate: f64,
5356 frame_rate: f64,
5457 kh: KastenHandle,
···6063 tick_rate,
6164 frame_rate,
6265 // components: vec![Box::new(Zk::new(kh.clone()).await?)],
6363- components: vec![Box::new(Viewport::new(kh.clone()).await?)],
6666+ components: vec![Box::new(Viewport::new(kh.clone()))],
6467 should_quit: false,
6568 should_suspend: false,
6669 config: Config::parse()?,
···7073 signal_tx,
7174 signal_rx,
7275 viz_signal_tx,
7676+ raw_text: false,
7377 })
7478 }
7579···148152 fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
149153 debug!("key received: {key:#?}");
150154155155+ if self.raw_text {
156156+ debug!("Raw text enabled, refusing to interpret as Signal");
157157+ return Ok(());
158158+ }
159159+151160 let signal_tx = self.signal_tx.clone();
152161153162 let Some(page_keymap) = self.config.keymap.get(&self.page) else {
154163 return Ok(());
155164 };
156156-157157- info!("page: {:#?}, page_keymap: {page_keymap:#?}", self.page);
158165159166 if let Some(signal) = page_keymap.get(&vec![key]) {
160167 signal_tx.send(signal.clone())?;
···243250 Signal::ClearScreen => tui.terminal.clear()?,
244251 Signal::Resize(x, y) => self.handle_resize(tui, x, y)?,
245252 Signal::Render => self.render(tui)?,
253253+254254+ Signal::EnterRawText => self.raw_text = true,
255255+ Signal::ExitRawText => self.raw_text = false,
246256 _ => {}
247257 }
248258
+35-6
src/tui/components/todo/explorer.rs
···33 text::{Line, Span, Text},
44 widgets::{Block, BorderType, Borders, List, ListState},
55};
66-use tracing::info;
66+use tracing::debug;
77use tree::NodeId;
8899-use crate::types::{TodoNode, TodoNodeKind, TodoTree};
99+use crate::types::{Group, TodoNode, TodoNodeKind, TodoTree};
10101111+#[derive(Debug)]
1112pub struct Explorer<'text> {
1213 pub render_list: ratatui::widgets::List<'text>,
1313- #[allow(dead_code)]
1414 pub id_list: Vec<NodeId>,
1515 pub state: ListState,
1616- #[allow(dead_code)]
1616+ #[expect(dead_code)]
1717 pub width: u16,
1818}
1919···5858 }
59596060 pub fn set_active(&mut self) {
6161+ debug!("Explorer set active!");
6162 self.render_list = self.render_list.clone().block(
6263 Block::new()
6364 .title("[1]")
···7879 .border_type(BorderType::Rounded),
7980 );
8081 }
8282+8383+ /// Returns the parent `Group` of the current selection in the `Explorer`
8484+ pub fn group_of_current_selection<'tree>(&self, tree: &'tree TodoTree) -> Option<&'tree Group> {
8585+ let selected = self.id_list.get(self.state.selected()?)?;
8686+8787+ if let TodoNodeKind::Group(group) = &tree
8888+ .tree
8989+ .get(selected)
9090+ .expect("Invaraint Broken! This must be a valid id")
9191+ .data()
9292+ .kind
9393+ {
9494+ return Some(group);
9595+ }
9696+9797+ let mut ancestors = tree.tree.ancestors(selected).expect("Must be a valid id");
9898+9999+ ancestors
100100+ .next()
101101+ .and_then(|parent| match &parent.data().kind {
102102+ TodoNodeKind::Root => None,
103103+104104+ TodoNodeKind::Task(_) => {
105105+ panic!("Invariant broken! how is a task a parent?!")
106106+ }
107107+108108+ TodoNodeKind::Group(group) => Some(group),
109109+ })
110110+ .map(|g| &**g)
111111+ }
81112}
8211383114pub struct ExplorerListItem<'text> {
···123154 Style::default().bg(color),
124155 ));
125156 }
126126-127127- info!("{spans:#?}");
128157129158 Line::from(spans).into()
130159 }
···11use async_trait::async_trait;
22+use color_eyre::eyre::Result;
33+use crossterm::event::KeyEvent;
24use ratatui::{
35 Frame,
46 layout::{Constraint, Layout, Rect, Size},
···79use serde::{Deserialize, Serialize};
810use strum::{Display, EnumIter};
911use tokio::sync::mpsc::UnboundedSender;
1212+use tracing::debug;
10131114use crate::{
1215 tui::{Page, Signal, components::Component},
1313- types::KastenHandle,
1616+ types::{Group, KastenHandle, Priority, Task, TodoTree},
1417};
15181619mod explorer;
1720use explorer::Explorer;
1818-1921mod tasklist;
2022use tasklist::TaskList;
2123···2325use inspector::Inspector;
24262527pub struct Todo<'text> {
2626- #[expect(dead_code)]
2728 signal_tx: Option<UnboundedSender<Signal>>,
2829 kh: KastenHandle,
2930 layouts: Layouts,
3131+3032 explorer: Option<Explorer<'text>>,
3133 task_list: Option<TaskList<'text>>,
3234 inspector: Option<Inspector<'text>>,
33353636+ area: Size,
3437 active: TodoRegion,
3538}
3939+4040+pub const DEFAULT_NAME: &str = "Rename Me!";
36413742/// The different regions inside the `Todo` component
3843#[derive(
···5459 explorer: None,
5560 task_list: None,
5661 inspector: None,
6262+ area: Size::default(),
5763 active: TodoRegion::default(),
5864 }
5965 }
60666767+ pub async fn refresh(&mut self) {
6868+ let explorer = self
6969+ .explorer
7070+ .as_mut()
7171+ .expect("This should have already been init.ialized");
7272+ let task_list = self
7373+ .task_list
7474+ .as_mut()
7575+ .expect("This should have already been initialized");
7676+7777+ let explorer_selection = explorer
7878+ .state
7979+ .selected()
8080+ .and_then(|idx| explorer.id_list.get(idx));
8181+ let task_list_selection = task_list
8282+ .state
8383+ .selected()
8484+ .and_then(|idx| task_list.id_list.get(idx));
8585+8686+ let mut kt = self.kh.write().await;
8787+8888+ // fuck it we just fully rebuild the tree, how computationally expensive could it even be
8989+ kt.todo_tree = TodoTree::construct(&kt.db).await.expect("Must not error");
9090+9191+ let tree = &kt.todo_tree;
9292+9393+ debug!("tree after refresh {tree:#?}");
9494+ let splits = self
9595+ .layouts
9696+ .split(Rect::new(0, 0, self.area.width, self.area.height));
9797+9898+ let l_state = ListState::default();
9999+100100+ //TODO: instead of tree.root_id this probably should be scope.
101101+ let mut explorer = Explorer::new(tree, &tree.root_id, l_state, splits.explorer.width);
102102+ let mut task_list = TaskList::new(tree, &tree.root_id, l_state, splits.task_list.width);
103103+104104+ debug!("explorer constructed after refresh {explorer:#?}");
105105+106106+ drop(kt);
107107+108108+ let explorer_selection_idx =
109109+ explorer_selection.and_then(|id| explorer.id_list.iter().position(|e| id == e));
110110+111111+ let task_list_selection_idx =
112112+ task_list_selection.and_then(|id| task_list.id_list.iter().position(|e| id == e));
113113+114114+ explorer.state.select(explorer_selection_idx);
115115+ task_list.state.select(task_list_selection_idx);
116116+117117+ match self.active {
118118+ TodoRegion::Inspector => {
119119+ explorer.set_inactive();
120120+ task_list.set_inactive();
121121+ }
122122+ TodoRegion::TaskList => {
123123+ explorer.set_inactive();
124124+ task_list.set_active();
125125+ }
126126+ TodoRegion::Explorer => {
127127+ explorer.set_active();
128128+ task_list.set_inactive();
129129+ }
130130+ }
131131+132132+ self.explorer = Some(explorer);
133133+ self.task_list = Some(task_list);
134134+ self.update_inspector_from_selection().await;
135135+ }
136136+61137 async fn update_inspector_from_selection(&mut self) {
62138 let explorer = self
63139 .explorer
···71147 .inspector
72148 .as_mut()
73149 .expect("This should have already been initialized");
150150+74151 let selected_node_id = match self.active {
75152 TodoRegion::TaskList => {
76153 let Some(idx) = task_list.state.selected() else {
···92169 };
93170 let tree = &self.kh.read().await.todo_tree.tree;
941719595- *inspector = tree
9696- .get(selected_node_id)
9797- .expect("Nodeid must be valid")
9898- .data()
9999- .into();
172172+ inspector.inspect(
173173+ tree.get(selected_node_id)
174174+ .expect("Nodeid must be valid")
175175+ .data(),
176176+ );
100177 }
101178}
102179···112189 Constraint::Percentage(40),
113190 Constraint::Fill(100),
114191 ]),
115115- inspector_task_list: Layout::vertical(vec![
116116- Constraint::Percentage(30),
117117- Constraint::Fill(100),
118118- ]),
192192+ inspector_task_list: Layout::vertical(vec![Constraint::Min(16), Constraint::Fill(100)]),
119193 }
120194 }
121195}
···142216#[async_trait]
143217impl Component for Todo<'_> {
144218 async fn init(&mut self, area: Size) -> color_eyre::Result<()> {
219219+ self.area = area;
145220 let tree = &self.kh.read().await.todo_tree;
146221 let splits = self.layouts.split(Rect::new(0, 0, area.width, area.height));
147222···162237 )
163238 .expect("Node id must be valid");
164239165165- let mut inspector: Inspector<'_> = first.data().into();
240240+ let mut inspector: Inspector<'_> = Inspector::new(self.kh.clone(), first.data());
166241167242 explorer.set_inactive();
168243 inspector.set_inactive();
169244 task_list.set_inactive();
170245246246+ inspector.register_signal_handler(
247247+ self.signal_tx
248248+ .clone()
249249+ .expect("Must be initialized by this point"),
250250+ )?;
251251+171252 self.explorer = Some(explorer);
172253 self.task_list = Some(task_list);
173254 self.inspector = Some(inspector);
···175256 Ok(())
176257 }
177258259259+ fn register_signal_handler(&mut self, tx: UnboundedSender<Signal>) -> Result<()> {
260260+ self.signal_tx = Some(tx);
261261+ Ok(())
262262+ }
263263+264264+ #[expect(clippy::too_many_lines)]
178265 async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>> {
179266 let explorer = self
180267 .explorer
···190277 .as_mut()
191278 .expect("This should have already been initialized");
192279280280+ let signal_tx = self
281281+ .signal_tx
282282+ .as_mut()
283283+ .expect("Invariant broken, must exist");
284284+285285+ if let Ok(Some(signal)) = inspector.update(signal.clone()).await {
286286+ signal_tx.send(signal)?;
287287+ }
288288+193289 match signal {
290290+ Signal::Select { nanoid } => {
291291+ let node_id = self
292292+ .kh
293293+ .read()
294294+ .await
295295+ .todo_tree
296296+ .nanoid_to_nodeid
297297+ .get(&nanoid)
298298+ .expect("Invariant broken, why would we ever ask for this")
299299+ .clone();
300300+301301+ let Some(pos) = explorer.id_list.iter().position(|id| node_id == *id) else {
302302+ return Ok(None);
303303+ };
304304+ explorer.state.select(Some(pos));
305305+ self.update_inspector_from_selection().await;
306306+ }
307307+194308 Signal::SwitchTo {
195309 page: Page::Todo(region),
196310 } => {
···215329216330 self.update_inspector_from_selection().await;
217331 }
332332+218333 Signal::MoveDown => {
219334 match self.active {
220335 TodoRegion::TaskList => {
···244359245360 self.update_inspector_from_selection().await;
246361 }
362362+363363+ Signal::NewTask => {
364364+ if self.active != TodoRegion::Explorer {
365365+ return Ok(None);
366366+ }
367367+368368+ let mut kt = self.kh.write().await;
369369+ let Some(parent) = explorer
370370+ .group_of_current_selection(&kt.todo_tree)
371371+ // .cloned()
372372+ .map(|parent| parent.id.clone())
373373+ else {
374374+ return Ok(None);
375375+ };
376376+ let task =
377377+ Task::new(DEFAULT_NAME, parent, &mut kt, None, Priority::default()).await?;
378378+379379+ drop(kt);
380380+ debug!("created task: {task:#?}");
381381+382382+ signal_tx.send(Signal::Refresh)?;
383383+ signal_tx.send(Signal::Select { nanoid: task.id })?;
384384+ return Ok(Some(Signal::SwitchTo {
385385+ page: Page::Todo(TodoRegion::Inspector),
386386+ }));
387387+ }
388388+389389+ Signal::NewSubGroup => {
390390+ if self.active != TodoRegion::Explorer {
391391+ return Ok(None);
392392+ }
393393+ let mut kt = self.kh.write().await;
394394+ let parent = explorer
395395+ .group_of_current_selection(&kt.todo_tree)
396396+ .map(|parent| parent.id.clone());
397397+ let group = Group::new(DEFAULT_NAME, parent, &mut kt).await?;
398398+ drop(kt);
399399+ debug!("Created group: {group:#?}");
400400+401401+ signal_tx.send(Signal::Refresh)?;
402402+ signal_tx.send(Signal::Select { nanoid: group.id })?;
403403+ return Ok(Some(Signal::SwitchTo {
404404+ page: Page::Todo(TodoRegion::Inspector),
405405+ }));
406406+ }
407407+408408+ Signal::NewGroup => {
409409+ if self.active != TodoRegion::Explorer {
410410+ return Ok(None);
411411+ }
412412+ debug!("Creating Group!");
413413+ let mut kt = self.kh.write().await;
414414+ let group = Group::new(DEFAULT_NAME, None, &mut kt).await?;
415415+ drop(kt);
416416+ debug!("Created group: {group:#?}");
417417+418418+ signal_tx.send(Signal::Refresh)?;
419419+ signal_tx.send(Signal::Select { nanoid: group.id })?;
420420+ return Ok(Some(Signal::SwitchTo {
421421+ page: Page::Todo(TodoRegion::Inspector),
422422+ }));
423423+ }
424424+425425+ Signal::Refresh => {
426426+ self.refresh().await;
427427+ }
428428+247429 _ => {}
248430 }
249431 Ok(None)
432432+ }
433433+434434+ async fn handle_key_event(&mut self, key: KeyEvent) -> color_eyre::Result<Option<Signal>> {
435435+ self.inspector.as_mut().unwrap().handle_key_event(key).await
250436 }
251437252438 fn draw(&mut self, frame: &mut Frame, area: Rect) -> color_eyre::Result<()> {
253439 let explorer = self.explorer.as_mut().unwrap();
254440 let task_list = self.task_list.as_mut().unwrap();
255441442442+ let inspector = self.inspector.as_mut().unwrap();
443443+256444 let splits = self.layouts.split(area);
257445258446 frame.render_stateful_widget(&explorer.render_list, splits.explorer, &mut explorer.state);
···261449 splits.task_list,
262450 &mut task_list.state,
263451 );
264264- frame.render_widget(self.inspector.as_ref().unwrap(), splits.inspector);
452452+453453+ inspector.draw(frame, splits.inspector)?;
454454+265455 Ok(())
266456 }
267457}
+1-1
src/tui/components/todo/tasklist.rs
···2222 let render_list = List::new(
2323 tree.tree
2424 .traverse_pre_order(scope)
2525- .expect("nthis should not panic as the node id should exist inside")
2525+ .expect("This should not panic as the node id should exist inside")
2626 .zip(
2727 tree.tree
2828 .traverse_pre_order_ids(scope)
···11-use std::{path::PathBuf, str::FromStr};
11+use std::path::PathBuf;
2233-use color_eyre::eyre::eyre;
33+use dto::NanoId;
44use strum::Display;
5566use serde::{Deserialize, Serialize};
···2323 Error(String),
2424 Help,
25252626+ /// Request a refresh of the components being displayed due
2727+ /// to an update to the `Kasten`
2828+ Refresh,
2929+2630 SwitchTo {
2731 page: Page,
2832 },
···5357 zid: ZettelId,
5458 },
55596060+ /// Create a new `Group` inside the currently selected group
6161+ NewSubGroup,
6262+6363+ /// Create a new `Group` in the current scope
6464+ NewGroup,
6565+6666+ /// Create a new `Task`
6767+ NewTask,
6868+6969+ /// Edit the name of a `Task` or a `Group`.
7070+ /// Only works with the inspector
7171+ EditName,
7272+7373+ /// Edit the `Priority` of a `Task` or a `Group`.
7474+ /// Only works with the inspector
7575+ EditPriority,
7676+7777+ /// Internal Signal that tells the app to resume interpreting keys
7878+ ExitRawText,
7979+8080+ /// Internal Signal that tells the app to stop interpreting keys
8181+ /// as signals
8282+ EnterRawText,
8383+5684 /// this is fucking temporary
5785 Helix {
5886 path: PathBuf,
5987 },
6060-}
61886262-impl FromStr for Signal {
6363- type Err = color_eyre::Report;
6464-6565- fn from_str(s: &str) -> Result<Self, Self::Err> {
6666- Ok(match s.to_lowercase().as_str() {
6767- "suspend" => Self::Suspend,
6868- "resume" => Self::Resume,
6969- "quit" => Self::Quit,
7070- "movedown" => Self::MoveDown,
7171- "moveup" => Self::MoveUp,
7272- "openzettel" => Self::OpenZettel,
7373- "newzettel" => Self::NewZettel,
7474- _ => {
7575- return Err(eyre!(format!(
7676- "Attempt to construct a non-user Signal from str: {s}"
7777- )));
7878- }
7979- })
8080- }
8989+ /// Requests the `Explorer` to select the following `NanoId`.
9090+ Select {
9191+ nanoid: NanoId,
9292+ },
8193}
···11-use dto::{NanoId, TagModel, TagModelEx};
11+use color_eyre::eyre::Result;
22+use dto::{IntoActiveModel as _, NanoId, TagEntity, TagModel, TagModelEx, ZettelEntity};
2333-use crate::types::Color;
44+use crate::types::{Color, Kasten, Zettel};
4556/// Represents a `Tag` in a `ZettelKasten` note taking method.
67/// Easy way to link multiple notes under one simple word.
···1516 pub name: String,
1617 /// Color of the tag
1718 pub color: Color,
1919+}
2020+2121+impl Tag {
2222+ pub async fn alter_name(
2323+ id: NanoId,
2424+ new_name: impl Into<String>,
2525+ kt: &mut Kasten,
2626+ ) -> Result<()> {
2727+ let new_name = new_name.into();
2828+2929+ TagEntity::load()
3030+ .filter_by_nano_id(id.clone())
3131+ .one(&kt.db)
3232+ .await?
3333+ .expect("Invariant Broken: Must exist")
3434+ .into_active_model()
3535+ .set_name(new_name.as_str())
3636+ .update(&kt.db)
3737+ .await?;
3838+3939+ // fetch all the zettels for this tag
4040+ let tag = TagEntity::load()
4141+ .filter_by_nano_id(id)
4242+ .with(ZettelEntity)
4343+ .one(&kt.db)
4444+ .await?
4545+ .expect("We just saved it");
4646+4747+ assert!(
4848+ tag.zettels.is_loaded(),
4949+ "We expect the zettels to be loaded"
5050+ );
5151+5252+ for zettel in tag.zettels {
5353+ Zettel::write_tags_from_db(zettel.nano_id.into(), kt).await?;
5454+ }
5555+5656+ Ok(())
5757+ }
1858}
19592060impl From<TagModel> for Tag {
+106-9
src/types/task.rs
···11-use dto::{ DateTime, NanoId, TaskModelEx};
11+use color_eyre::eyre::{Context, Result, eyre};
22+use dto::{
33+ DateTime, GroupEntity, HasOne, IntoActiveModel as _, NanoId, TagEntity, TaskActiveModel,
44+ TaskEntity, TaskModelEx, ZettelEntity,
55+};
2633-use crate::types::{Group, Priority, Zettel, frontmatter};
77+use crate::types::{Group, Kasten, Priority, Zettel, frontmatter};
4859/// a `Task` that you have to complete!
610#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
711pub struct Task {
812 /// Should only be constructed from models.
99- _private:(),
1313+ _private: (),
10141115 pub id: NanoId,
1216 pub name: String,
···2226}
23272428impl Task {
2929+ pub async fn new(
3030+ name: impl Into<String>,
3131+ parent_id: NanoId,
3232+ kt: &mut Kasten,
3333+ due: Option<DateTime>,
3434+ priority: Priority,
3535+ ) -> Result<Self> {
3636+ let name = name.into();
3737+3838+ let parent = GroupEntity::load()
3939+ .with(TagEntity)
4040+ .filter_by_nano_id(parent_id)
4141+ .one(&kt.db)
4242+ .await
4343+ .with_context(|| "failed to communicate with db")?
4444+ .ok_or_else(|| eyre!("could not find the group"))?;
4545+4646+ let HasOne::Loaded(tag) = parent.tag else {
4747+ panic!("this has to be loaded since we just loaded it right above")
4848+ };
4949+5050+ let zettel = Zettel::new(name.clone(), kt, vec![(*tag).into()]).await?;
5151+5252+ let inserted = TaskActiveModel::builder()
5353+ .set_name(name)
5454+ .set_group_id(parent.nano_id.clone())
5555+ .set_priority(priority)
5656+ .set_zettel(
5757+ ZettelEntity::load()
5858+ .filter_by_nano_id(zettel.id)
5959+ .one(&kt.db)
6060+ .await?
6161+ .expect("Zettel must exist since we just created it")
6262+ .into_active_model(),
6363+ )
6464+ .set_due(due)
6565+ .insert(&kt.db)
6666+ .await?;
6767+6868+ let group = GroupEntity::load()
6969+ .with(TagEntity)
7070+ .with((ZettelEntity, TagEntity))
7171+ .filter_by_nano_id(parent.nano_id)
7272+ .one(&kt.db)
7373+ .await?
7474+ .expect("We just inserted it");
7575+7676+ let mut task_am = TaskEntity::load()
7777+ .with((ZettelEntity, TagEntity))
7878+ .filter_by_nano_id(inserted.nano_id)
7979+ .one(&kt.db)
8080+ .await?
8181+ .expect("We just inserted it");
8282+8383+ task_am.group = HasOne::Loaded(Box::new(group));
8484+8585+ let task: Self = task_am.into();
8686+8787+ kt.todo_tree.insert_task(&task);
8888+8989+ Ok(task)
9090+ }
9191+9292+ pub async fn alter_name(
9393+ id: NanoId,
9494+ new_name: impl Into<String>,
9595+ kt: &mut Kasten,
9696+ ) -> Result<()> {
9797+ let new_name = new_name.into();
9898+9999+ let task = TaskEntity::load()
100100+ .filter_by_nano_id(id.clone())
101101+ .one(&kt.db)
102102+ .await?
103103+ .expect("Invariant Broken: Must exist");
104104+105105+ let zettel_id = task.zettel_id.clone();
106106+107107+ let _ = task
108108+ .into_active_model()
109109+ .set_name(new_name.as_str())
110110+ .save(&kt.db)
111111+ .await?;
112112+113113+ Zettel::alter_name(zettel_id.into(), new_name, kt).await?;
114114+115115+ Ok(())
116116+ }
117117+25118 pub fn due(&self) -> Option<String> {
2626- self.due.map(|due|due.format(frontmatter::DATE_FMT_STR).to_string())
119119+ self.due
120120+ .map(|due| due.format(frontmatter::DATE_FMT_STR).to_string())
27121 }
28122 pub fn finished_at(&self) -> Option<String> {
2929- self.finished_at.
3030- map(|finished_at|finished_at.format(frontmatter::DATE_FMT_STR).to_string())
123123+ self.finished_at
124124+ .map(|finished_at| finished_at.format(frontmatter::DATE_FMT_STR).to_string())
31125 }
32126 pub fn created_at(&self) -> String {
3333- self.created_at.format(frontmatter::DATE_FMT_STR).to_string()
127127+ self.created_at
128128+ .format(frontmatter::DATE_FMT_STR)
129129+ .to_string()
34130 }
35131 pub fn modified_at(&self) -> String {
3636- self.modified_at.format(frontmatter::DATE_FMT_STR).to_string()
132132+ self.modified_at
133133+ .format(frontmatter::DATE_FMT_STR)
134134+ .to_string()
37135 }
38136}
39137···63161 "When fetching a Task from the database, we expect to always have the Group loaded!!",
64162 )
65163 .into(),
6666-67164 }
68165 }
69166}
+46-1
src/types/zettel/mod.rs
···11use std::path::Path;
2233use dto::{
44- DatabaseConnection, DateTime, TagEntity, ZettelActiveModel, ZettelEntity, ZettelModelEx,
44+ DatabaseConnection, DateTime, IntoActiveModel, TagEntity, ZettelActiveModel, ZettelEntity,
55+ ZettelModelEx,
56};
6778use color_eyre::eyre::{Context, Result};
···4041 .one(db)
4142 .await?
4243 .map(Into::into))
4444+ }
4545+4646+ pub async fn alter_name(
4747+ id: ZettelId,
4848+ new_name: impl Into<String>,
4949+ kt: &mut Kasten,
5050+ ) -> Result<()> {
5151+ let new_name = new_name.into();
5252+5353+ // ok we need to change it on the actual zettel and then change
5454+ // it in the frontmatter
5555+ let _ = ZettelEntity::load()
5656+ .filter_by_nano_id(id.clone())
5757+ .one(&kt.db)
5858+ .await?
5959+ .expect("Must exist")
6060+ .into_active_model()
6161+ .set_title(new_name)
6262+ .save(&kt.db)
6363+ .await?;
6464+6565+ let zettel = Self::fetch_from_db(&id, &kt.db)
6666+ .await?
6767+ .expect("We just saved it");
6868+6969+ let file_path = zettel.absolute_path(&kt.index);
7070+ let new_fm = FrontMatter::from(zettel);
7171+7272+ new_fm.flush_to_file(file_path)?;
7373+ kt.index.process_zid(&id)?;
7474+7575+ Ok(())
7676+ }
7777+7878+ /// Gets the tags from the database for this `Zettel` and write it to the file.
7979+ pub async fn write_tags_from_db(id: ZettelId, kt: &mut Kasten) -> Result<()> {
8080+ let zettel = Self::fetch_from_db(&id, &kt.db).await?.expect("must exist");
8181+8282+ let file_path = zettel.absolute_path(&kt.index);
8383+ let new_fm = FrontMatter::from(zettel);
8484+8585+ new_fm.flush_to_file(file_path)?;
8686+ kt.index.process_zid(&id)?;
8787+ Ok(())
4388 }
44894590 pub async fn new(title: impl Into<String>, kt: &mut Kasten, tags: Vec<Tag>) -> Result<Self> {
+7-1
src/viz/mod.rs
···9191 if let Ok(signal) = self.signal_rx.try_recv() {
9292 debug!("received signal in filaments: {signal}");
93939494- #[allow(clippy::single_match)]
9594 match signal {
9695 Signal::CreatedZettel { zid } => {
9796 block_on(async {
···104103 self.filaments.set_links_for_zid(&zid, links);
105104 }
106105106106+ // this will refresh the entire screen, esentially spawning
107107+ // in a new graph, maybe there is a better way to do this?
108108+ // might be clean with conditional name-showing...
109109+ Signal::Refresh => block_on(async {
110110+ let index = &self.kh.read().await.index;
111111+ self.filaments = Filaments::from(index);
112112+ }),
107113 _ => {}
108114 }
109115 }