feat: with async commands instead of inline mutations phew.
All checks were successful
continuous-integration/drone/push Build is passing

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2024-05-12 21:07:21 +02:00
parent 2d63d3ad4c
commit 9bb5bc9e87
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
19 changed files with 589 additions and 141 deletions

View File

@ -3,9 +3,14 @@ syntax = "proto3";
package hyperlog; package hyperlog;
service Graph { service Graph {
rpc GetAvailableRoots(GetAvailableRootsRequest) returns (GetAvailableRootsResponse);
rpc Get(GetRequest) returns (GetReply); rpc Get(GetRequest) returns (GetReply);
} }
message GetAvailableRootsRequest {}
message GetAvailableRootsResponse {
repeated string roots = 1;
}
message UserGraphItem { message UserGraphItem {
map<string, GraphItem> items = 1; map<string, GraphItem> items = 1;

View File

@ -2,7 +2,7 @@ use hyperlog_protos::hyperlog::{
graph_server::{Graph, GraphServer}, graph_server::{Graph, GraphServer},
*, *,
}; };
use std::net::SocketAddr; use std::{collections::HashMap, net::SocketAddr};
use tonic::{transport, Response}; use tonic::{transport, Response};
use crate::{ use crate::{
@ -33,15 +33,37 @@ impl Graph for Server {
Ok(Response::new(GetReply { Ok(Response::new(GetReply {
item: Some(GraphItem { item: Some(GraphItem {
path: "some.path".into(), path: "kjuulh".into(),
contents: Some(graph_item::Contents::Item(ItemGraphItem { contents: Some(graph_item::Contents::User(UserGraphItem {
title: "some-title".into(), items: HashMap::from([(
description: "some-description".into(), "some".to_string(),
item_state: Some(item_graph_item::ItemState::Done(ItemStateDone {})), 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<GetAvailableRootsRequest>,
) -> std::result::Result<tonic::Response<GetAvailableRootsResponse>, 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 { pub trait ServerExt {

View File

@ -6,9 +6,9 @@ use ratatui::{
use crate::{ use crate::{
command_parser::CommandParser, command_parser::CommandParser,
commander,
commands::{batch::BatchCommand, IntoCommand}, commands::{batch::BatchCommand, IntoCommand},
components::graph_explorer::GraphExplorer, components::graph_explorer::GraphExplorer,
models::IOEvent,
state::SharedState, state::SharedState,
Msg, Msg,
}; };
@ -30,10 +30,10 @@ pub enum Dialog {
} }
impl Dialog { impl Dialog {
pub fn get_command(&self) -> Option<commander::Command> { pub fn get_command(&self) -> Option<impl IntoCommand> {
match self { match self {
Dialog::CreateItem { state } => state.get_command(), Dialog::CreateItem { state } => state.get_command().map(|c| c.into_command()),
Dialog::EditItem { state } => state.get_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(); let mut batch = BatchCommand::default();
match &msg { 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::MoveRight => self.graph_explorer.move_right()?,
Msg::MoveLeft => self.graph_explorer.move_left()?, Msg::MoveLeft => self.graph_explorer.move_left()?,
Msg::MoveDown => self.graph_explorer.move_down()?, Msg::MoveDown => self.graph_explorer.move_down()?,
@ -117,11 +123,9 @@ impl<'a> App<'a> {
if command.is_write() { if command.is_write() {
if let Some(dialog) = &self.dialog { if let Some(dialog) = &self.dialog {
if let Some(output) = dialog.get_command() { 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() { if command.is_quit() {
@ -172,7 +176,7 @@ impl<'a> App<'a> {
self.focus = AppFocus::Dialog; self.focus = AppFocus::Dialog;
self.dialog = Some(Dialog::CreateItem { 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(); let path = self.graph_explorer.get_current_path();
self.dialog = Some(Dialog::EditItem { self.dialog = Some(Dialog::EditItem {
state: EditItemState::new(root, path, item), state: EditItemState::new(&self.state, root, path, item),
}); });
self.command = None; self.command = None;
self.focus = AppFocus::Dialog; self.focus = AppFocus::Dialog;

View File

@ -1,7 +1,12 @@
use hyperlog_core::log::ItemState;
use itertools::Itertools; use itertools::Itertools;
use ratatui::{prelude::*, widgets::*}; use ratatui::{prelude::*, widgets::*};
use crate::{commander, models::Msg}; use crate::{
commands::{create_item::CreateItemCommandExt, IntoCommand},
models::Msg,
state::SharedState,
};
use super::{InputBuffer, InputField}; use super::{InputBuffer, InputField};
@ -23,10 +28,16 @@ pub struct CreateItemState {
description: InputBuffer, description: InputBuffer,
focused: CreateItemFocused, focused: CreateItemFocused,
state: SharedState,
} }
impl CreateItemState { impl CreateItemState {
pub fn new(root: impl Into<String>, path: impl IntoIterator<Item = impl Into<String>>) -> Self { pub fn new(
state: &SharedState,
root: impl Into<String>,
path: impl IntoIterator<Item = impl Into<String>>,
) -> Self {
let root = root.into(); let root = root.into();
let path = path.into_iter().map(|p| p.into()).collect_vec(); let path = path.into_iter().map(|p| p.into()).collect_vec();
@ -37,6 +48,8 @@ impl CreateItemState {
title: Default::default(), title: Default::default(),
description: Default::default(), description: Default::default(),
focused: Default::default(), focused: Default::default(),
state: state.clone(),
} }
} }
@ -61,7 +74,7 @@ impl CreateItemState {
Ok(()) Ok(())
} }
pub fn get_command(&self) -> Option<commander::Command> { pub fn get_command(&self) -> Option<impl IntoCommand> {
let title = self.title.string(); let title = self.title.string();
let description = self.description.string(); let description = self.description.string();
@ -69,13 +82,21 @@ impl CreateItemState {
let mut path = self.path.clone(); let mut path = self.path.clone();
path.push(title.replace([' ', '.'], "-")); path.push(title.replace([' ', '.'], "-"));
Some(commander::Command::CreateItem { Some(self.state.create_item_command().command(
root: self.root.clone(), &self.root,
path, &path.iter().map(|i| i.as_str()).collect_vec(),
title: title.trim().into(), title.trim(),
description: description.trim().into(), description.trim(),
state: hyperlog_core::log::ItemState::NotDone, &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 { } else {
None None
} }

View File

@ -2,7 +2,11 @@ use hyperlog_core::log::GraphItem;
use itertools::Itertools; use itertools::Itertools;
use ratatui::{prelude::*, widgets::*}; use ratatui::{prelude::*, widgets::*};
use crate::{commander, models::Msg}; use crate::{
commands::{update_item::UpdateItemCommandExt, IntoCommand},
models::Msg,
state::SharedState,
};
use super::{InputBuffer, InputField}; use super::{InputBuffer, InputField};
@ -26,10 +30,13 @@ pub struct EditItemState {
item: GraphItem, item: GraphItem,
focused: EditItemFocused, focused: EditItemFocused,
state: SharedState,
} }
impl EditItemState { impl EditItemState {
pub fn new( pub fn new(
state: &SharedState,
root: impl Into<String>, root: impl Into<String>,
path: impl IntoIterator<Item = impl Into<String>>, path: impl IntoIterator<Item = impl Into<String>>,
item: &GraphItem, item: &GraphItem,
@ -47,6 +54,8 @@ impl EditItemState {
title.set_position(title_len); title.set_position(title_len);
Self { Self {
state: state.clone(),
root, root,
path, path,
@ -82,24 +91,36 @@ impl EditItemState {
Ok(()) Ok(())
} }
pub fn get_command(&self) -> Option<commander::Command> { pub fn get_command(&self) -> Option<impl IntoCommand> {
let title = self.title.string(); let title = self.title.string();
let description = self.description.string(); let description = self.description.string();
if !title.is_empty() { if !title.is_empty() {
let path = self.path.clone(); let path = self.path.clone();
Some(commander::Command::UpdateItem { Some(self.state.update_item_command().command(
root: self.root.clone(), &self.root,
path, &path.iter().map(|s| s.as_str()).collect_vec(),
title: title.trim().into(), title.trim(),
description: description.trim().into(), description.trim(),
state: match &self.item { match &self.item {
GraphItem::User(_) => Default::default(), GraphItem::User(_) => Default::default(),
GraphItem::Section(_) => Default::default(), GraphItem::Section(_) => Default::default(),
GraphItem::Item { state, .. } => state.clone(), 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 { } else {
None None
} }

View File

@ -1,6 +1,4 @@
use std::collections::BTreeMap; use hyperlog_core::log::ItemState;
use hyperlog_core::log::{GraphItem, ItemState};
use serde::Serialize; use serde::Serialize;
use crate::{events::Events, shared_engine::SharedEngine, storage::Storage}; 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 { pub struct Commander {
engine: SharedEngine, variant: CommanderVariant,
storage: Storage,
events: Events,
} }
impl Commander { impl Commander {
pub fn new(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result<Self> { pub fn local(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result<Self> {
Ok(Self { Ok(Self {
engine, variant: CommanderVariant::Local(local::Commander::new(engine, storage, events)?),
storage,
events,
}) })
} }
pub fn execute(&self, cmd: Command) -> anyhow::Result<()> { pub async fn execute(&self, cmd: Command) -> anyhow::Result<()> {
tracing::debug!("executing event: {}", serde_json::to_string(&cmd)?); match &self.variant {
CommanderVariant::Local(commander) => commander.execute(cmd),
match cmd.clone() { }
Command::CreateRoot { root } => { }
self.engine.create_root(&root)?; }
}
Command::CreateSection { root, path } => { mod local {
self.engine.create( use std::collections::BTreeMap;
&root,
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(), use hyperlog_core::log::GraphItem;
GraphItem::Section(BTreeMap::default()),
)?; use crate::{events::Events, shared_engine::SharedEngine, storage::Storage};
}
Command::CreateItem { use super::Command;
root,
path, #[derive(Clone)]
title, pub struct Commander {
description, engine: SharedEngine,
state, storage: Storage,
} => self.engine.create( events: Events,
&root, }
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
GraphItem::Item { impl Commander {
title, pub fn new(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result<Self> {
description, Ok(Self {
state, engine,
}, storage,
)?, events,
Command::Move { root, src, dest } => self.engine.section_move( })
&root, }
&src.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
&dest.iter().map(|p| p.as_str()).collect::<Vec<_>>(), pub fn execute(&self, cmd: Command) -> anyhow::Result<()> {
)?, tracing::debug!("executing event: {}", serde_json::to_string(&cmd)?);
Command::ToggleItem { root, path } => self
.engine match cmd.clone() {
.toggle_item(&root, &path.iter().map(|p| p.as_str()).collect::<Vec<_>>())?, Command::CreateRoot { root } => {
Command::UpdateItem { self.engine.create_root(&root)?;
root, }
path, Command::CreateSection { root, path } => {
title, self.engine.create(
description, &root,
state, &path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
} => self.engine.update_item( GraphItem::Section(BTreeMap::default()),
&root, )?;
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(), }
GraphItem::Item { Command::CreateItem {
title, root,
description, path,
state, title,
}, description,
)?, state,
} => self.engine.create(
&root,
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
GraphItem::Item {
title,
description,
state,
},
)?,
Command::Move { root, src, dest } => self.engine.section_move(
&root,
&src.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
&dest.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
)?,
Command::ToggleItem { root, path } => self
.engine
.toggle_item(&root, &path.iter().map(|p| p.as_str()).collect::<Vec<_>>())?,
Command::UpdateItem {
root,
path,
title,
description,
state,
} => self.engine.update_item(
&root,
&path.iter().map(|p| p.as_str()).collect::<Vec<_>>(),
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(())
} }
} }

View File

@ -2,7 +2,11 @@ use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
pub mod batch; pub mod batch;
pub mod create_item;
pub mod create_section;
pub mod toggle_item;
pub mod update_graph; pub mod update_graph;
pub mod update_item;
use crate::models::Msg; use crate::models::Msg;

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -5,8 +5,11 @@ use ratatui::{prelude::*, widgets::*};
use crate::{ use crate::{
command_parser::Commands, command_parser::Commands,
commander, commands::{
commands::{update_graph::UpdateGraphCommandExt, Command, IntoCommand}, batch::BatchCommand, create_section::CreateSectionCommandExt,
toggle_item::ToggleItemCommandExt, update_graph::UpdateGraphCommandExt, Command,
IntoCommand,
},
components::movement_graph::GraphItemType, components::movement_graph::GraphItemType,
models::{GraphUpdatedEvent, Msg}, models::{GraphUpdatedEvent, Msg},
state::SharedState, state::SharedState,
@ -227,6 +230,8 @@ impl<'a> GraphExplorer<'a> {
} }
pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Command>> { pub fn execute_command(&mut self, command: &Commands) -> anyhow::Result<Option<Command>> {
let mut batch = BatchCommand::default();
match command { match command {
Commands::Archive => { Commands::Archive => {
if !self.get_current_path().is_empty() { if !self.get_current_path().is_empty() {
@ -238,12 +243,19 @@ impl<'a> GraphExplorer<'a> {
let mut path = self.get_current_path(); let mut path = self.get_current_path();
path.push(name.replace(" ", "-").replace(".", "-")); path.push(name.replace(" ", "-").replace(".", "-"));
self.state // self.state
.commander // .commander
.execute(commander::Command::CreateSection { // .execute(commander::Command::CreateSection {
root: self.inner.root.clone(), // root: self.inner.root.clone(),
path, // 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 => { Commands::Edit => {
@ -294,24 +306,37 @@ impl<'a> GraphExplorer<'a> {
//self.update_graph()?; //self.update_graph()?;
Ok(Some(self.new_update_graph())) Ok(Some(batch.into_command()))
} }
pub(crate) fn interact(&mut self) -> anyhow::Result<Command> { pub(crate) fn interact(&mut self) -> anyhow::Result<Command> {
let mut batch = BatchCommand::default();
if !self.get_current_path().is_empty() { if !self.get_current_path().is_empty() {
tracing::info!("toggling state of items"); tracing::info!("toggling state of items");
self.state // self.state
.commander // .commander
.execute(commander::Command::ToggleItem { // .execute(commander::Command::ToggleItem {
root: self.inner.root.to_string(), // root: self.inner.root.to_string(),
path: self.get_current_path(), // 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()?; //self.update_graph()?;
Ok(self.new_update_graph()) Ok(batch.into_command())
} }
} }

View File

@ -24,17 +24,23 @@ impl State {
let engine = storage.load()?; let engine = storage.load()?;
let events = Events::default(); let events = Events::default();
let engine = SharedEngine::from(engine); let engine = SharedEngine::from(engine);
let querier = match backend { let querier = match backend {
Backend::Local => Querier::local(&engine), Backend::Local => Querier::local(&engine),
Backend::Remote => Querier::remote().await?, Backend::Remote => Querier::remote().await?,
}; };
let commander = match backend {
Backend::Local => Commander::local(engine.clone(), storage.clone(), events.clone())?,
Backend::Remote => todo!(),
};
Ok(Self { Ok(Self {
engine: engine.clone(), engine: engine.clone(),
storage: storage.clone(), storage: storage.clone(),
events: events.clone(), events: events.clone(),
commander: Commander::new(engine.clone(), storage, events)?, commander,
querier, querier,
}) })
} }

View File

@ -51,7 +51,7 @@ pub async fn execute(state: State) -> Result<()> {
} }
async fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedState) -> Result<()> { async fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, 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 // TODO: maybe present choose root screen
Some(roots) => roots.first().cloned().unwrap(), Some(roots) => roots.first().cloned().unwrap(),
None => { None => {

View File

@ -22,6 +22,19 @@ pub enum Msg {
Edit(EditMsg), Edit(EditMsg),
GraphUpdated(GraphUpdatedEvent), GraphUpdated(GraphUpdatedEvent),
ItemCreated(IOEvent<()>),
ItemUpdated(IOEvent<()>),
SectionCreated(IOEvent<()>),
ItemToggled(IOEvent<()>),
}
#[derive(Debug)]
pub enum IOEvent<T> {
Initialized,
Optimistic(T),
Success(T),
Failure(String),
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -61,7 +61,7 @@ impl Querier {
pub async fn get_available_roots_async(&self) -> anyhow::Result<Option<Vec<String>>> { pub async fn get_available_roots_async(&self) -> anyhow::Result<Option<Vec<String>>> {
match &self.variant { match &self.variant {
QuerierVariant::Local(querier) => Ok(querier.get_available_roots()), 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,
} }
} }
} }

View File

@ -36,7 +36,10 @@ impl Querier {
path.len() path.len()
); );
self.engine let item = self
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>()) .engine
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>());
item
} }
} }

View File

@ -1,7 +1,9 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use hyperlog_core::log::GraphItem; 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 itertools::Itertools;
use tonic::transport::Channel; use tonic::transport::Channel;
@ -21,9 +23,21 @@ impl Querier {
Ok(Self { channel }) Ok(Self { channel })
} }
pub async fn get_available_roots(&self) -> Option<Vec<String>> { pub async fn get_available_roots(&self) -> anyhow::Result<Option<Vec<String>>> {
//self.engine.get_roots() let channel = self.channel.clone();
todo!()
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( pub async fn get(
@ -54,7 +68,8 @@ impl Querier {
let graph_item = response.into_inner(); let graph_item = response.into_inner();
if let Some(item) = graph_item.item { 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 { } else {
Ok(None) Ok(None)
} }

View File

@ -115,19 +115,25 @@ pub async fn execute() -> anyhow::Result<()> {
Some(Commands::Exec { commands }) => { Some(Commands::Exec { commands }) => {
let state = State::new(backend.into()).await?; let state = State::new(backend.into()).await?;
match commands { match commands {
ExecCommands::CreateRoot { root } => state ExecCommands::CreateRoot { root } => {
.commander state
.execute(commander::Command::CreateRoot { root })?, .commander
.execute(commander::Command::CreateRoot { root })
.await?
}
ExecCommands::CreateSection { root, path } => { ExecCommands::CreateSection { root, path } => {
state.commander.execute(commander::Command::CreateSection { state
root, .commander
path: path .execute(commander::Command::CreateSection {
.unwrap_or_default() root,
.split('.') path: path
.map(|s| s.to_string()) .unwrap_or_default()
.filter(|s| !s.is_empty()) .split('.')
.collect::<Vec<String>>(), .map(|s| s.to_string())
})? .filter(|s| !s.is_empty())
.collect::<Vec<String>>(),
})
.await?
} }
} }
} }
@ -152,7 +158,8 @@ pub async fn execute() -> anyhow::Result<()> {
let state = State::new(backend.into()).await?; let state = State::new(backend.into()).await?;
state state
.commander .commander
.execute(commander::Command::CreateRoot { root: name })?; .execute(commander::Command::CreateRoot { root: name })
.await?;
println!("Root was successfully created, now run:\n\n$ hyperlog"); println!("Root was successfully created, now run:\n\n$ hyperlog");
} }
Some(Commands::Info {}) => { Some(Commands::Info {}) => {