feat: with async commands instead of inline mutations phew.
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
2d63d3ad4c
commit
9bb5bc9e87
@ -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<string, GraphItem> items = 1;
|
||||
|
@ -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<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 {
|
||||
|
@ -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<commander::Command> {
|
||||
pub fn get_command(&self) -> Option<impl IntoCommand> {
|
||||
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;
|
||||
|
@ -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<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 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<commander::Command> {
|
||||
pub fn get_command(&self) -> Option<impl IntoCommand> {
|
||||
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
|
||||
}
|
||||
|
@ -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<String>,
|
||||
path: impl IntoIterator<Item = impl Into<String>>,
|
||||
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<commander::Command> {
|
||||
pub fn get_command(&self) -> Option<impl IntoCommand> {
|
||||
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
|
||||
}
|
||||
|
@ -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<Self> {
|
||||
pub fn local(engine: SharedEngine, storage: Storage, events: Events) -> anyhow::Result<Self> {
|
||||
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::<Vec<_>>(),
|
||||
GraphItem::Section(BTreeMap::default()),
|
||||
)?;
|
||||
}
|
||||
Command::CreateItem {
|
||||
root,
|
||||
path,
|
||||
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,
|
||||
},
|
||||
)?,
|
||||
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<Self> {
|
||||
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::<Vec<_>>(),
|
||||
GraphItem::Section(BTreeMap::default()),
|
||||
)?;
|
||||
}
|
||||
Command::CreateItem {
|
||||
root,
|
||||
path,
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
75
crates/hyperlog-tui/src/commands/create_item.rs
Normal file
75
crates/hyperlog-tui/src/commands/create_item.rs
Normal 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())
|
||||
}
|
||||
}
|
59
crates/hyperlog-tui/src/commands/create_section.rs
Normal file
59
crates/hyperlog-tui/src/commands/create_section.rs
Normal 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())
|
||||
}
|
||||
}
|
59
crates/hyperlog-tui/src/commands/toggle_item.rs
Normal file
59
crates/hyperlog-tui/src/commands/toggle_item.rs
Normal 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())
|
||||
}
|
||||
}
|
76
crates/hyperlog-tui/src/commands/update_item.rs
Normal file
76
crates/hyperlog-tui/src/commands/update_item.rs
Normal 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())
|
||||
}
|
||||
}
|
@ -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<Option<Command>> {
|
||||
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<Command> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ pub async fn execute(state: State) -> 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
|
||||
Some(roots) => roots.first().cloned().unwrap(),
|
||||
None => {
|
||||
|
@ -22,6 +22,19 @@ pub enum Msg {
|
||||
Edit(EditMsg),
|
||||
|
||||
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)]
|
||||
|
@ -61,7 +61,7 @@ impl Querier {
|
||||
pub async fn get_available_roots_async(&self) -> anyhow::Result<Option<Vec<String>>> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,10 @@ impl Querier {
|
||||
path.len()
|
||||
);
|
||||
|
||||
self.engine
|
||||
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>())
|
||||
let item = self
|
||||
.engine
|
||||
.get(root, &path.iter().map(|i| i.as_str()).collect::<Vec<_>>());
|
||||
|
||||
item
|
||||
}
|
||||
}
|
||||
|
@ -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<Vec<String>> {
|
||||
//self.engine.get_roots()
|
||||
todo!()
|
||||
pub async fn get_available_roots(&self) -> anyhow::Result<Option<Vec<String>>> {
|
||||
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)
|
||||
}
|
||||
|
@ -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::<Vec<String>>(),
|
||||
})?
|
||||
state
|
||||
.commander
|
||||
.execute(commander::Command::CreateSection {
|
||||
root,
|
||||
path: path
|
||||
.unwrap_or_default()
|
||||
.split('.')
|
||||
.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?;
|
||||
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 {}) => {
|
||||
|
Loading…
Reference in New Issue
Block a user