From 9bb5bc9e87ca17c471749a25dc77f8595f097d0c Mon Sep 17 00:00:00 2001 From: kjuulh Date: Sun, 12 May 2024 21:07:21 +0200 Subject: [PATCH] feat: with async commands instead of inline mutations phew. Signed-off-by: kjuulh --- crates/hyperlog-protos/proto/hyperlog.proto | 5 + crates/hyperlog-server/src/external_grpc.rs | 34 +++- crates/hyperlog-tui/src/app.rs | 22 ++- .../src/app/dialog/create_item.rs | 41 +++-- .../hyperlog-tui/src/app/dialog/edit_item.rs | 39 +++- crates/hyperlog-tui/src/commander.rs | 169 +++++++++++------- crates/hyperlog-tui/src/commands.rs | 4 + .../hyperlog-tui/src/commands/create_item.rs | 75 ++++++++ .../src/commands/create_section.rs | 59 ++++++ .../hyperlog-tui/src/commands/toggle_item.rs | 59 ++++++ .../hyperlog-tui/src/commands/update_item.rs | 76 ++++++++ .../src/components/graph_explorer.rs | 57 ++++-- crates/hyperlog-tui/src/core_state.rs | 8 +- crates/hyperlog-tui/src/lib.rs | 2 +- crates/hyperlog-tui/src/models.rs | 13 ++ crates/hyperlog-tui/src/querier.rs | 2 +- crates/hyperlog-tui/src/querier/local.rs | 7 +- crates/hyperlog-tui/src/querier/remote.rs | 25 ++- crates/hyperlog/src/cli.rs | 33 ++-- 19 files changed, 589 insertions(+), 141 deletions(-) create mode 100644 crates/hyperlog-tui/src/commands/create_item.rs create mode 100644 crates/hyperlog-tui/src/commands/create_section.rs create mode 100644 crates/hyperlog-tui/src/commands/toggle_item.rs create mode 100644 crates/hyperlog-tui/src/commands/update_item.rs diff --git a/crates/hyperlog-protos/proto/hyperlog.proto b/crates/hyperlog-protos/proto/hyperlog.proto index 9a41397..592d4fb 100644 --- a/crates/hyperlog-protos/proto/hyperlog.proto +++ b/crates/hyperlog-protos/proto/hyperlog.proto @@ -3,9 +3,14 @@ syntax = "proto3"; package hyperlog; service Graph { + rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse); rpc Get(GetRequest) returns (GetReply); } +message GetAvailableRootsRequest {} +message GetAvailableRootsResponse { + repeated string roots = 1; +} message UserGraphItem { map items = 1; diff --git a/crates/hyperlog-server/src/external_grpc.rs b/crates/hyperlog-server/src/external_grpc.rs index e637f48..eadf9d7 100644 --- a/crates/hyperlog-server/src/external_grpc.rs +++ b/crates/hyperlog-server/src/external_grpc.rs @@ -2,7 +2,7 @@ use hyperlog_protos::hyperlog::{ graph_server::{Graph, GraphServer}, *, }; -use std::net::SocketAddr; +use std::{collections::HashMap, net::SocketAddr}; use tonic::{transport, Response}; use crate::{ @@ -33,15 +33,37 @@ impl Graph for Server { Ok(Response::new(GetReply { item: Some(GraphItem { - path: "some.path".into(), - contents: Some(graph_item::Contents::Item(ItemGraphItem { - title: "some-title".into(), - description: "some-description".into(), - item_state: Some(item_graph_item::ItemState::Done(ItemStateDone {})), + path: "kjuulh".into(), + contents: Some(graph_item::Contents::User(UserGraphItem { + items: HashMap::from([( + "some".to_string(), + GraphItem { + path: "some".into(), + contents: Some(graph_item::Contents::Item(ItemGraphItem { + title: "some-title".into(), + description: "some-description".into(), + item_state: Some(item_graph_item::ItemState::NotDone( + ItemStateNotDone {}, + )), + })), + }, + )]), })), }), })) } + + async fn get_available_roots( + &self, + request: tonic::Request, + ) -> std::result::Result, tonic::Status> { + let req = request.into_inner(); + tracing::trace!("get available roots: req({:?})", req); + + Ok(Response::new(GetAvailableRootsResponse { + roots: vec!["kjuulh".into()], + })) + } } pub trait ServerExt { diff --git a/crates/hyperlog-tui/src/app.rs b/crates/hyperlog-tui/src/app.rs index 5c3f105..6dde906 100644 --- a/crates/hyperlog-tui/src/app.rs +++ b/crates/hyperlog-tui/src/app.rs @@ -6,9 +6,9 @@ use ratatui::{ use crate::{ command_parser::CommandParser, - commander, commands::{batch::BatchCommand, IntoCommand}, components::graph_explorer::GraphExplorer, + models::IOEvent, state::SharedState, Msg, }; @@ -30,10 +30,10 @@ pub enum Dialog { } impl Dialog { - pub fn get_command(&self) -> Option { + pub fn get_command(&self) -> Option { match self { - Dialog::CreateItem { state } => state.get_command(), - Dialog::EditItem { state } => state.get_command(), + Dialog::CreateItem { state } => state.get_command().map(|c| c.into_command()), + Dialog::EditItem { state } => state.get_command().map(|c| c.into_command()), } } } @@ -89,6 +89,12 @@ impl<'a> App<'a> { let mut batch = BatchCommand::default(); match &msg { + Msg::ItemCreated(IOEvent::Success(())) + | Msg::ItemUpdated(IOEvent::Success(())) + | Msg::SectionCreated(IOEvent::Success(())) + | Msg::ItemToggled(IOEvent::Success(())) => { + batch.with(self.graph_explorer.new_update_graph()); + } Msg::MoveRight => self.graph_explorer.move_right()?, Msg::MoveLeft => self.graph_explorer.move_left()?, Msg::MoveDown => self.graph_explorer.move_down()?, @@ -117,11 +123,9 @@ impl<'a> App<'a> { if command.is_write() { if let Some(dialog) = &self.dialog { if let Some(output) = dialog.get_command() { - self.state.commander.execute(output)?; + batch.with(output.into_command()); } } - - batch.with(self.graph_explorer.new_update_graph()); } if command.is_quit() { @@ -172,7 +176,7 @@ impl<'a> App<'a> { self.focus = AppFocus::Dialog; self.dialog = Some(Dialog::CreateItem { - state: CreateItemState::new(root, path), + state: CreateItemState::new(&self.state, root, path), }); } } @@ -183,7 +187,7 @@ impl<'a> App<'a> { let path = self.graph_explorer.get_current_path(); self.dialog = Some(Dialog::EditItem { - state: EditItemState::new(root, path, item), + state: EditItemState::new(&self.state, root, path, item), }); self.command = None; self.focus = AppFocus::Dialog; diff --git a/crates/hyperlog-tui/src/app/dialog/create_item.rs b/crates/hyperlog-tui/src/app/dialog/create_item.rs index 37b20ac..c04e032 100644 --- a/crates/hyperlog-tui/src/app/dialog/create_item.rs +++ b/crates/hyperlog-tui/src/app/dialog/create_item.rs @@ -1,7 +1,12 @@ +use hyperlog_core::log::ItemState; use itertools::Itertools; use ratatui::{prelude::*, widgets::*}; -use crate::{commander, models::Msg}; +use crate::{ + commands::{create_item::CreateItemCommandExt, IntoCommand}, + models::Msg, + state::SharedState, +}; use super::{InputBuffer, InputField}; @@ -23,10 +28,16 @@ pub struct CreateItemState { description: InputBuffer, focused: CreateItemFocused, + + state: SharedState, } impl CreateItemState { - pub fn new(root: impl Into, path: impl IntoIterator>) -> Self { + pub fn new( + state: &SharedState, + root: impl Into, + path: impl IntoIterator>, + ) -> Self { let root = root.into(); let path = path.into_iter().map(|p| p.into()).collect_vec(); @@ -37,6 +48,8 @@ impl CreateItemState { title: Default::default(), description: Default::default(), focused: Default::default(), + + state: state.clone(), } } @@ -61,7 +74,7 @@ impl CreateItemState { Ok(()) } - pub fn get_command(&self) -> Option { + pub fn get_command(&self) -> Option { let title = self.title.string(); let description = self.description.string(); @@ -69,13 +82,21 @@ impl CreateItemState { let mut path = self.path.clone(); path.push(title.replace([' ', '.'], "-")); - Some(commander::Command::CreateItem { - root: self.root.clone(), - path, - title: title.trim().into(), - description: description.trim().into(), - state: hyperlog_core::log::ItemState::NotDone, - }) + Some(self.state.create_item_command().command( + &self.root, + &path.iter().map(|i| i.as_str()).collect_vec(), + title.trim(), + description.trim(), + &ItemState::NotDone, + )) + + // Some(commander::Command::CreateItem { + // root: self.root.clone(), + // path, + // title: title.trim().into(), + // description: description.trim().into(), + // state: hyperlog_core::log::ItemState::NotDone, + // }) } else { None } diff --git a/crates/hyperlog-tui/src/app/dialog/edit_item.rs b/crates/hyperlog-tui/src/app/dialog/edit_item.rs index 62598bc..85f10a4 100644 --- a/crates/hyperlog-tui/src/app/dialog/edit_item.rs +++ b/crates/hyperlog-tui/src/app/dialog/edit_item.rs @@ -2,7 +2,11 @@ use hyperlog_core::log::GraphItem; use itertools::Itertools; use ratatui::{prelude::*, widgets::*}; -use crate::{commander, models::Msg}; +use crate::{ + commands::{update_item::UpdateItemCommandExt, IntoCommand}, + models::Msg, + state::SharedState, +}; use super::{InputBuffer, InputField}; @@ -26,10 +30,13 @@ pub struct EditItemState { item: GraphItem, focused: EditItemFocused, + + state: SharedState, } impl EditItemState { pub fn new( + state: &SharedState, root: impl Into, path: impl IntoIterator>, item: &GraphItem, @@ -47,6 +54,8 @@ impl EditItemState { title.set_position(title_len); Self { + state: state.clone(), + root, path, @@ -82,24 +91,36 @@ impl EditItemState { Ok(()) } - pub fn get_command(&self) -> Option { + pub fn get_command(&self) -> Option { let title = self.title.string(); let description = self.description.string(); if !title.is_empty() { let path = self.path.clone(); - Some(commander::Command::UpdateItem { - root: self.root.clone(), - path, - title: title.trim().into(), - description: description.trim().into(), - state: match &self.item { + Some(self.state.update_item_command().command( + &self.root, + &path.iter().map(|s| s.as_str()).collect_vec(), + title.trim(), + description.trim(), + match &self.item { GraphItem::User(_) => Default::default(), GraphItem::Section(_) => Default::default(), GraphItem::Item { state, .. } => state.clone(), }, - }) + )) + + // Some(commander::Command::UpdateItem { + // root: self.root.clone(), + // path, + // title: title.trim().into(), + // description: description.trim().into(), + // state: match &self.item { + // GraphItem::User(_) => Default::default(), + // GraphItem::Section(_) => Default::default(), + // GraphItem::Item { state, .. } => state.clone(), + // }, + // }) } else { None } diff --git a/crates/hyperlog-tui/src/commander.rs b/crates/hyperlog-tui/src/commander.rs index f15558f..7658320 100644 --- a/crates/hyperlog-tui/src/commander.rs +++ b/crates/hyperlog-tui/src/commander.rs @@ -1,6 +1,4 @@ -use std::collections::BTreeMap; - -use hyperlog_core::log::{GraphItem, ItemState}; +use hyperlog_core::log::ItemState; use serde::Serialize; use crate::{events::Events, shared_engine::SharedEngine, storage::Storage}; @@ -39,79 +37,114 @@ pub enum Command { }, } +#[derive(Clone)] +enum CommanderVariant { + Local(local::Commander), +} + +#[derive(Clone)] pub struct Commander { - engine: SharedEngine, - storage: Storage, - events: Events, + variant: CommanderVariant, } impl Commander { - pub fn new(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result { + pub fn local(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result { Ok(Self { - engine, - storage, - events, + variant: CommanderVariant::Local(local::Commander::new(engine, storage, events)?), }) } - pub fn execute(&self, cmd: Command) -> anyhow::Result<()> { - tracing::debug!("executing event: {}", serde_json::to_string(&cmd)?); - - match cmd.clone() { - Command::CreateRoot { root } => { - self.engine.create_root(&root)?; - } - Command::CreateSection { root, path } => { - self.engine.create( - &root, - &path.iter().map(|p| p.as_str()).collect::>(), - GraphItem::Section(BTreeMap::default()), - )?; - } - Command::CreateItem { - root, - path, - title, - description, - state, - } => self.engine.create( - &root, - &path.iter().map(|p| p.as_str()).collect::>(), - GraphItem::Item { - title, - description, - state, - }, - )?, - Command::Move { root, src, dest } => self.engine.section_move( - &root, - &src.iter().map(|p| p.as_str()).collect::>(), - &dest.iter().map(|p| p.as_str()).collect::>(), - )?, - Command::ToggleItem { root, path } => self - .engine - .toggle_item(&root, &path.iter().map(|p| p.as_str()).collect::>())?, - Command::UpdateItem { - root, - path, - title, - description, - state, - } => self.engine.update_item( - &root, - &path.iter().map(|p| p.as_str()).collect::>(), - GraphItem::Item { - title, - description, - state, - }, - )?, + pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> { + match &self.variant { + CommanderVariant::Local(commander) => commander.execute(cmd), + } + } +} + +mod local { + use std::collections::BTreeMap; + + use hyperlog_core::log::GraphItem; + + use crate::{events::Events, shared_engine::SharedEngine, storage::Storage}; + + use super::Command; + + #[derive(Clone)] + pub struct Commander { + engine: SharedEngine, + storage: Storage, + events: Events, + } + + impl Commander { + pub fn new(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result { + Ok(Self { + engine, + storage, + events, + }) + } + + pub fn execute(&self, cmd: Command) -> anyhow::Result<()> { + tracing::debug!("executing event: {}", serde_json::to_string(&cmd)?); + + match cmd.clone() { + Command::CreateRoot { root } => { + self.engine.create_root(&root)?; + } + Command::CreateSection { root, path } => { + self.engine.create( + &root, + &path.iter().map(|p| p.as_str()).collect::>(), + GraphItem::Section(BTreeMap::default()), + )?; + } + Command::CreateItem { + root, + path, + title, + description, + state, + } => self.engine.create( + &root, + &path.iter().map(|p| p.as_str()).collect::>(), + GraphItem::Item { + title, + description, + state, + }, + )?, + Command::Move { root, src, dest } => self.engine.section_move( + &root, + &src.iter().map(|p| p.as_str()).collect::>(), + &dest.iter().map(|p| p.as_str()).collect::>(), + )?, + Command::ToggleItem { root, path } => self + .engine + .toggle_item(&root, &path.iter().map(|p| p.as_str()).collect::>())?, + Command::UpdateItem { + root, + path, + title, + description, + state, + } => self.engine.update_item( + &root, + &path.iter().map(|p| p.as_str()).collect::>(), + GraphItem::Item { + title, + description, + state, + }, + )?, + } + + self.storage.store(&self.engine)?; + + self.events.enque_command(cmd)?; + + Ok(()) } - - self.storage.store(&self.engine)?; - - self.events.enque_command(cmd)?; - - Ok(()) } } diff --git a/crates/hyperlog-tui/src/commands.rs b/crates/hyperlog-tui/src/commands.rs index 1f69951..630cf6d 100644 --- a/crates/hyperlog-tui/src/commands.rs +++ b/crates/hyperlog-tui/src/commands.rs @@ -2,7 +2,11 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; pub mod batch; +pub mod create_item; +pub mod create_section; +pub mod toggle_item; pub mod update_graph; +pub mod update_item; use crate::models::Msg; diff --git a/crates/hyperlog-tui/src/commands/create_item.rs b/crates/hyperlog-tui/src/commands/create_item.rs new file mode 100644 index 0000000..5028b15 --- /dev/null +++ b/crates/hyperlog-tui/src/commands/create_item.rs @@ -0,0 +1,75 @@ +use hyperlog_core::log::ItemState; +use itertools::Itertools; + +use crate::{ + commander::{self, Commander}, + models::IOEvent, + state::SharedState, +}; + +pub struct CreateItemCommand { + commander: Commander, +} + +impl CreateItemCommand { + pub fn new(commander: Commander) -> Self { + Self { commander } + } + + pub fn command( + self, + root: &str, + path: &[&str], + title: &str, + description: &str, + state: &ItemState, + ) -> super::Command { + let root = root.to_owned(); + let path = path.iter().map(|s| s.to_string()).collect_vec(); + let title = title.to_string(); + let description = description.to_string(); + let state = state.clone(); + + super::Command::new(|dispatch| { + tokio::spawn(async move { + dispatch.send(crate::models::Msg::ItemCreated(IOEvent::Initialized)); + + match self + .commander + .execute(commander::Command::CreateItem { + root, + path, + title, + description, + state, + }) + .await + { + Ok(()) => { + #[cfg(debug_assertions)] + { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + dispatch.send(crate::models::Msg::ItemCreated(IOEvent::Success(()))); + } + Err(e) => { + dispatch.send(crate::models::Msg::ItemCreated(IOEvent::Failure( + e.to_string(), + ))); + } + } + }); + None + }) + } +} + +pub trait CreateItemCommandExt { + fn create_item_command(&self) -> CreateItemCommand; +} + +impl CreateItemCommandExt for SharedState { + fn create_item_command(&self) -> CreateItemCommand { + CreateItemCommand::new(self.commander.clone()) + } +} diff --git a/crates/hyperlog-tui/src/commands/create_section.rs b/crates/hyperlog-tui/src/commands/create_section.rs new file mode 100644 index 0000000..107f005 --- /dev/null +++ b/crates/hyperlog-tui/src/commands/create_section.rs @@ -0,0 +1,59 @@ +use itertools::Itertools; + +use crate::{ + commander::{self, Commander}, + models::IOEvent, + state::SharedState, +}; + +pub struct CreateSectionCommand { + commander: Commander, +} + +impl CreateSectionCommand { + pub fn new(commander: Commander) -> Self { + Self { commander } + } + + pub fn command(self, root: &str, path: &[&str]) -> super::Command { + let root = root.to_owned(); + let path = path.iter().map(|s| s.to_string()).collect_vec(); + + super::Command::new(|dispatch| { + tokio::spawn(async move { + dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Initialized)); + + match self + .commander + .execute(commander::Command::CreateSection { root, path }) + .await + { + Ok(()) => { + #[cfg(debug_assertions)] + { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Success(()))); + } + Err(e) => { + dispatch.send(crate::models::Msg::SectionCreated(IOEvent::Failure( + e.to_string(), + ))); + } + } + }); + None + }) + } +} + +pub trait CreateSectionCommandExt { + fn create_section_command(&self) -> CreateSectionCommand; +} + +impl CreateSectionCommandExt for SharedState { + fn create_section_command(&self) -> CreateSectionCommand { + CreateSectionCommand::new(self.commander.clone()) + } +} diff --git a/crates/hyperlog-tui/src/commands/toggle_item.rs b/crates/hyperlog-tui/src/commands/toggle_item.rs new file mode 100644 index 0000000..9940773 --- /dev/null +++ b/crates/hyperlog-tui/src/commands/toggle_item.rs @@ -0,0 +1,59 @@ +use itertools::Itertools; + +use crate::{ + commander::{self, Commander}, + models::IOEvent, + state::SharedState, +}; + +pub struct ToggleItemCommand { + commander: Commander, +} + +impl ToggleItemCommand { + pub fn new(commander: Commander) -> Self { + Self { commander } + } + + pub fn command(self, root: &str, path: &[&str]) -> super::Command { + let root = root.to_owned(); + let path = path.iter().map(|s| s.to_string()).collect_vec(); + + super::Command::new(|dispatch| { + tokio::spawn(async move { + dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Initialized)); + + match self + .commander + .execute(commander::Command::ToggleItem { root, path }) + .await + { + Ok(()) => { + #[cfg(debug_assertions)] + { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Success(()))); + } + Err(e) => { + dispatch.send(crate::models::Msg::ItemToggled(IOEvent::Failure( + e.to_string(), + ))); + } + } + }); + None + }) + } +} + +pub trait ToggleItemCommandExt { + fn toggle_item_command(&self) -> ToggleItemCommand; +} + +impl ToggleItemCommandExt for SharedState { + fn toggle_item_command(&self) -> ToggleItemCommand { + ToggleItemCommand::new(self.commander.clone()) + } +} diff --git a/crates/hyperlog-tui/src/commands/update_item.rs b/crates/hyperlog-tui/src/commands/update_item.rs new file mode 100644 index 0000000..123d062 --- /dev/null +++ b/crates/hyperlog-tui/src/commands/update_item.rs @@ -0,0 +1,76 @@ +use hyperlog_core::log::ItemState; +use itertools::Itertools; + +use crate::{ + commander::{self, Commander}, + models::IOEvent, + state::SharedState, +}; + +pub struct UpdateItemCommand { + commander: Commander, +} + +impl UpdateItemCommand { + pub fn new(commander: Commander) -> Self { + Self { commander } + } + + pub fn command( + self, + root: &str, + path: &[&str], + title: &str, + description: &str, + state: ItemState, + ) -> super::Command { + let root = root.to_owned(); + let path = path.iter().map(|s| s.to_string()).collect_vec(); + let title = title.to_string(); + let description = description.to_string(); + let state = state.clone(); + + super::Command::new(|dispatch| { + tokio::spawn(async move { + dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Initialized)); + + match self + .commander + .execute(commander::Command::UpdateItem { + root, + path, + title, + description, + state, + }) + .await + { + Ok(()) => { + #[cfg(debug_assertions)] + { + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + + dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Success(()))); + } + Err(e) => { + dispatch.send(crate::models::Msg::ItemUpdated(IOEvent::Failure( + e.to_string(), + ))); + } + } + }); + None + }) + } +} + +pub trait UpdateItemCommandExt { + fn update_item_command(&self) -> UpdateItemCommand; +} + +impl UpdateItemCommandExt for SharedState { + fn update_item_command(&self) -> UpdateItemCommand { + UpdateItemCommand::new(self.commander.clone()) + } +} diff --git a/crates/hyperlog-tui/src/components/graph_explorer.rs b/crates/hyperlog-tui/src/components/graph_explorer.rs index 81f18d1..a3d2087 100644 --- a/crates/hyperlog-tui/src/components/graph_explorer.rs +++ b/crates/hyperlog-tui/src/components/graph_explorer.rs @@ -5,8 +5,11 @@ use ratatui::{prelude::*, widgets::*}; use crate::{ command_parser::Commands, - commander, - commands::{update_graph::UpdateGraphCommandExt, Command, IntoCommand}, + commands::{ + batch::BatchCommand, create_section::CreateSectionCommandExt, + toggle_item::ToggleItemCommandExt, update_graph::UpdateGraphCommandExt, Command, + IntoCommand, + }, components::movement_graph::GraphItemType, models::{GraphUpdatedEvent, Msg}, state::SharedState, @@ -227,6 +230,8 @@ impl<'a> GraphExplorer<'a> { } pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result> { + let mut batch = BatchCommand::default(); + match command { Commands::Archive => { if !self.get_current_path().is_empty() { @@ -238,12 +243,19 @@ impl<'a> GraphExplorer<'a> { let mut path = self.get_current_path(); path.push(name.replace(" ", "-").replace(".", "-")); - self.state - .commander - .execute(commander::Command::CreateSection { - root: self.inner.root.clone(), - path, - })?; + // self.state + // .commander + // .execute(commander::Command::CreateSection { + // root: self.inner.root.clone(), + // path, + // })?; + + let cmd = self.state.create_section_command().command( + &self.inner.root, + &path.iter().map(|i| i.as_str()).collect_vec(), + ); + + batch.with(cmd.into_command()); } } Commands::Edit => { @@ -294,24 +306,37 @@ impl<'a> GraphExplorer<'a> { //self.update_graph()?; - Ok(Some(self.new_update_graph())) + Ok(Some(batch.into_command())) } pub(crate) fn interact(&mut self) -> anyhow::Result { + let mut batch = BatchCommand::default(); + if !self.get_current_path().is_empty() { tracing::info!("toggling state of items"); - self.state - .commander - .execute(commander::Command::ToggleItem { - root: self.inner.root.to_string(), - path: self.get_current_path(), - })?; + // self.state + // .commander + // .execute(commander::Command::ToggleItem { + // root: self.inner.root.to_string(), + // path: self.get_current_path(), + // })?; + + let cmd = self.state.toggle_item_command().command( + &self.inner.root, + &self + .get_current_path() + .iter() + .map(|i| i.as_str()) + .collect_vec(), + ); + + batch.with(cmd.into_command()); } //self.update_graph()?; - Ok(self.new_update_graph()) + Ok(batch.into_command()) } } diff --git a/crates/hyperlog-tui/src/core_state.rs b/crates/hyperlog-tui/src/core_state.rs index a879326..2f7d275 100644 --- a/crates/hyperlog-tui/src/core_state.rs +++ b/crates/hyperlog-tui/src/core_state.rs @@ -24,17 +24,23 @@ impl State { let engine = storage.load()?; let events = Events::default(); let engine = SharedEngine::from(engine); + let querier = match backend { Backend::Local => Querier::local(&engine), Backend::Remote => Querier::remote().await?, }; + let commander = match backend { + Backend::Local => Commander::local(engine.clone(), storage.clone(), events.clone())?, + Backend::Remote => todo!(), + }; + Ok(Self { engine: engine.clone(), storage: storage.clone(), events: events.clone(), - commander: Commander::new(engine.clone(), storage, events)?, + commander, querier, }) } diff --git a/crates/hyperlog-tui/src/lib.rs b/crates/hyperlog-tui/src/lib.rs index 728ffc8..c0bb1c9 100644 --- a/crates/hyperlog-tui/src/lib.rs +++ b/crates/hyperlog-tui/src/lib.rs @@ -51,7 +51,7 @@ pub async fn execute(state: State) -> Result<()> { } async fn run(terminal: &mut Terminal>, state: SharedState) -> Result<()> { - let root = match state.querier.get_available_roots() { + let root = match state.querier.get_available_roots_async().await? { // TODO: maybe present choose root screen Some(roots) => roots.first().cloned().unwrap(), None => { diff --git a/crates/hyperlog-tui/src/models.rs b/crates/hyperlog-tui/src/models.rs index 04ef7d1..45abcda 100644 --- a/crates/hyperlog-tui/src/models.rs +++ b/crates/hyperlog-tui/src/models.rs @@ -22,6 +22,19 @@ pub enum Msg { Edit(EditMsg), GraphUpdated(GraphUpdatedEvent), + + ItemCreated(IOEvent<()>), + ItemUpdated(IOEvent<()>), + SectionCreated(IOEvent<()>), + ItemToggled(IOEvent<()>), +} + +#[derive(Debug)] +pub enum IOEvent { + Initialized, + Optimistic(T), + Success(T), + Failure(String), } #[derive(Debug)] diff --git a/crates/hyperlog-tui/src/querier.rs b/crates/hyperlog-tui/src/querier.rs index 32cdbab..c3ee698 100644 --- a/crates/hyperlog-tui/src/querier.rs +++ b/crates/hyperlog-tui/src/querier.rs @@ -61,7 +61,7 @@ impl Querier { pub async fn get_available_roots_async(&self) -> anyhow::Result>> { match &self.variant { QuerierVariant::Local(querier) => Ok(querier.get_available_roots()), - QuerierVariant::Remote(querier) => Ok(querier.get_available_roots().await), + QuerierVariant::Remote(querier) => querier.get_available_roots().await, } } } diff --git a/crates/hyperlog-tui/src/querier/local.rs b/crates/hyperlog-tui/src/querier/local.rs index e7e2552..df1ca76 100644 --- a/crates/hyperlog-tui/src/querier/local.rs +++ b/crates/hyperlog-tui/src/querier/local.rs @@ -36,7 +36,10 @@ impl Querier { path.len() ); - self.engine - .get(root, &path.iter().map(|i| i.as_str()).collect::>()) + let item = self + .engine + .get(root, &path.iter().map(|i| i.as_str()).collect::>()); + + item } } diff --git a/crates/hyperlog-tui/src/querier/remote.rs b/crates/hyperlog-tui/src/querier/remote.rs index a951a38..d6e6368 100644 --- a/crates/hyperlog-tui/src/querier/remote.rs +++ b/crates/hyperlog-tui/src/querier/remote.rs @@ -1,7 +1,9 @@ use std::collections::BTreeMap; use hyperlog_core::log::GraphItem; -use hyperlog_protos::hyperlog::{graph_client::GraphClient, graph_item::Contents, GetRequest}; +use hyperlog_protos::hyperlog::{ + graph_client::GraphClient, graph_item::Contents, GetAvailableRootsRequest, GetRequest, +}; use itertools::Itertools; use tonic::transport::Channel; @@ -21,9 +23,21 @@ impl Querier { Ok(Self { channel }) } - pub async fn get_available_roots(&self) -> Option> { - //self.engine.get_roots() - todo!() + pub async fn get_available_roots(&self) -> anyhow::Result>> { + let channel = self.channel.clone(); + + let mut client = GraphClient::new(channel); + + let request = tonic::Request::new(GetAvailableRootsRequest {}); + let response = client.get_available_roots(request).await?; + + let roots = response.into_inner(); + + if roots.roots.is_empty() { + Ok(None) + } else { + Ok(Some(roots.roots)) + } } pub async fn get( @@ -54,7 +68,8 @@ impl Querier { let graph_item = response.into_inner(); if let Some(item) = graph_item.item { - Ok(transform_proto_to_local(&item)) + let local_graph = transform_proto_to_local(&item); + Ok(local_graph) } else { Ok(None) } diff --git a/crates/hyperlog/src/cli.rs b/crates/hyperlog/src/cli.rs index f668d8d..ed4352d 100644 --- a/crates/hyperlog/src/cli.rs +++ b/crates/hyperlog/src/cli.rs @@ -115,19 +115,25 @@ pub async fn execute() -> anyhow::Result<()> { Some(Commands::Exec { commands }) => { let state = State::new(backend.into()).await?; match commands { - ExecCommands::CreateRoot { root } => state - .commander - .execute(commander::Command::CreateRoot { root })?, + ExecCommands::CreateRoot { root } => { + state + .commander + .execute(commander::Command::CreateRoot { root }) + .await? + } ExecCommands::CreateSection { root, path } => { - state.commander.execute(commander::Command::CreateSection { - root, - path: path - .unwrap_or_default() - .split('.') - .map(|s| s.to_string()) - .filter(|s| !s.is_empty()) - .collect::>(), - })? + state + .commander + .execute(commander::Command::CreateSection { + root, + path: path + .unwrap_or_default() + .split('.') + .map(|s| s.to_string()) + .filter(|s| !s.is_empty()) + .collect::>(), + }) + .await? } } } @@ -152,7 +158,8 @@ pub async fn execute() -> anyhow::Result<()> { let state = State::new(backend.into()).await?; state .commander - .execute(commander::Command::CreateRoot { root: name })?; + .execute(commander::Command::CreateRoot { root: name }) + .await?; println!("Root was successfully created, now run:\n\n$ hyperlog"); } Some(Commands::Info {}) => {