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;
|
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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
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::{
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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 => {
|
||||||
|
@ -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)]
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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 {}) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user