feat: add command pattern
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:
@@ -5,8 +5,12 @@ use ratatui::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
command_parser::CommandParser, commander, commands::IntoCommand,
|
||||
components::graph_explorer::GraphExplorer, state::SharedState, Msg,
|
||||
command_parser::CommandParser,
|
||||
commander,
|
||||
commands::{batch::BatchCommand, IntoCommand},
|
||||
components::graph_explorer::GraphExplorer,
|
||||
state::SharedState,
|
||||
Msg,
|
||||
};
|
||||
|
||||
use self::{
|
||||
@@ -82,6 +86,8 @@ impl<'a> App<'a> {
|
||||
pub fn update(&mut self, msg: Msg) -> anyhow::Result<impl IntoCommand> {
|
||||
tracing::trace!("handling msg: {:?}", msg);
|
||||
|
||||
let mut batch = BatchCommand::default();
|
||||
|
||||
match &msg {
|
||||
Msg::MoveRight => self.graph_explorer.move_right()?,
|
||||
Msg::MoveLeft => self.graph_explorer.move_left()?,
|
||||
@@ -97,7 +103,10 @@ impl<'a> App<'a> {
|
||||
}
|
||||
Msg::Interact => match self.focus {
|
||||
AppFocus::Dialog => {}
|
||||
AppFocus::Graph => self.graph_explorer.interact()?,
|
||||
AppFocus::Graph => {
|
||||
let cmd = self.graph_explorer.interact()?;
|
||||
batch.with(cmd);
|
||||
}
|
||||
},
|
||||
Msg::SubmitCommand { command } => {
|
||||
tracing::info!("submitting command");
|
||||
@@ -112,7 +121,7 @@ impl<'a> App<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
self.graph_explorer.update_graph()?;
|
||||
batch.with(self.graph_explorer.new_update_graph());
|
||||
}
|
||||
|
||||
if command.is_quit() {
|
||||
@@ -121,26 +130,31 @@ impl<'a> App<'a> {
|
||||
}
|
||||
}
|
||||
AppFocus::Graph => {
|
||||
if let Some(msg) = self.graph_explorer.execute_command(&command)? {
|
||||
if let Some(cmd) = self.graph_explorer.execute_command(&command)? {
|
||||
self.command = None;
|
||||
return Ok(msg.into_command());
|
||||
batch.with(cmd);
|
||||
}
|
||||
|
||||
if command.is_quit() {
|
||||
return Ok(Msg::QuitApp.into_command());
|
||||
batch.with(Msg::QuitApp.into_command());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.command = None;
|
||||
return Ok(Msg::EnterViewMode.into_command());
|
||||
batch.with(Msg::EnterViewMode.into_command());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let cmd = self.graph_explorer.inner.update(&msg);
|
||||
if let Some(cmd) = cmd {
|
||||
batch.with(cmd);
|
||||
}
|
||||
|
||||
if let Some(command) = &mut self.command {
|
||||
let cmd = command.update(&msg)?;
|
||||
return Ok(cmd.into_command());
|
||||
batch.with(cmd);
|
||||
} else if let Some(dialog) = &mut self.dialog {
|
||||
match dialog {
|
||||
Dialog::CreateItem { state } => state.update(&msg)?,
|
||||
@@ -148,7 +162,7 @@ impl<'a> App<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(().into_command())
|
||||
Ok(batch.into_command())
|
||||
}
|
||||
|
||||
fn open_dialog(&mut self) {
|
||||
|
@@ -1,5 +1,9 @@
|
||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
|
||||
pub mod batch;
|
||||
|
||||
pub mod update_graph;
|
||||
|
||||
use crate::models::Msg;
|
||||
|
||||
pub trait IntoCommand {
|
||||
|
42
crates/hyperlog-tui/src/commands/batch.rs
Normal file
42
crates/hyperlog-tui/src/commands/batch.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use super::IntoCommand;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BatchCommand {
|
||||
commands: Vec<super::Command>,
|
||||
}
|
||||
|
||||
impl BatchCommand {
|
||||
pub fn with(&mut self, cmd: impl IntoCommand) -> &mut Self {
|
||||
self.commands.push(cmd.into_command());
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoCommand for Vec<super::Command> {
|
||||
fn into_command(self) -> super::Command {
|
||||
BatchCommand::from(self).into_command()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<super::Command>> for BatchCommand {
|
||||
fn from(value: Vec<super::Command>) -> Self {
|
||||
BatchCommand { commands: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoCommand for BatchCommand {
|
||||
fn into_command(self) -> super::Command {
|
||||
super::Command::new(|dispatch| {
|
||||
for command in self.commands {
|
||||
let msg = command.execute(dispatch.clone());
|
||||
if let Some(msg) = msg {
|
||||
tracing::info!("executing batch command for msg: {:?}", msg);
|
||||
dispatch.send(msg);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
65
crates/hyperlog-tui/src/commands/update_graph.rs
Normal file
65
crates/hyperlog-tui/src/commands/update_graph.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
models::{GraphUpdatedEvent, Msg},
|
||||
querier::Querier,
|
||||
state::SharedState,
|
||||
};
|
||||
|
||||
use super::IntoCommand;
|
||||
|
||||
pub struct UpdateGraphCommand {
|
||||
querier: Querier,
|
||||
}
|
||||
|
||||
impl UpdateGraphCommand {
|
||||
pub fn new(querier: Querier) -> Self {
|
||||
Self { querier }
|
||||
}
|
||||
|
||||
pub fn command(self, root: &str, path: &[&str]) -> super::Command {
|
||||
let root = root.to_owned();
|
||||
let path = path.iter().map(|i| i.to_string()).collect_vec();
|
||||
|
||||
super::Command::new(|dispatch| {
|
||||
tokio::spawn(async move {
|
||||
let now = std::time::SystemTime::now();
|
||||
dispatch.send(Msg::GraphUpdated(GraphUpdatedEvent::Initiated));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
match self
|
||||
.querier
|
||||
.get_async(&root, path)
|
||||
.await
|
||||
.ok_or(anyhow::anyhow!("failed to find path"))
|
||||
{
|
||||
Ok(graph) => {
|
||||
dispatch.send(Msg::GraphUpdated(GraphUpdatedEvent::Success(graph)))
|
||||
}
|
||||
Err(e) => dispatch.send(Msg::GraphUpdated(GraphUpdatedEvent::Failure(
|
||||
format!("{e}"),
|
||||
))),
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().expect("to be able to get time");
|
||||
tracing::trace!("UpdateGraphCommand took: {}nanos", elapsed.as_nanos());
|
||||
});
|
||||
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait UpdateGraphCommandExt {
|
||||
fn update_graph_command(&self) -> UpdateGraphCommand;
|
||||
}
|
||||
|
||||
impl UpdateGraphCommandExt for SharedState {
|
||||
fn update_graph_command(&self) -> UpdateGraphCommand {
|
||||
UpdateGraphCommand::new(self.querier.clone())
|
||||
}
|
||||
}
|
@@ -1,13 +1,14 @@
|
||||
use anyhow::Result;
|
||||
use hyperlog_core::log::GraphItem;
|
||||
use itertools::Itertools;
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
|
||||
use crate::{
|
||||
command_parser::Commands,
|
||||
commander,
|
||||
commands::{Command, IntoCommand},
|
||||
commands::{update_graph::UpdateGraphCommandExt, Command, IntoCommand},
|
||||
components::movement_graph::GraphItemType,
|
||||
models::Msg,
|
||||
models::{GraphUpdatedEvent, Msg},
|
||||
state::SharedState,
|
||||
};
|
||||
|
||||
@@ -50,6 +51,27 @@ pub struct GraphExplorerState<'a> {
|
||||
graph: Option<GraphItem>,
|
||||
}
|
||||
|
||||
impl<'a> GraphExplorerState<'a> {
|
||||
pub fn update(&mut self, msg: &Msg) -> Option<Command> {
|
||||
if let Msg::GraphUpdated(graph_update) = msg {
|
||||
match graph_update {
|
||||
GraphUpdatedEvent::Initiated => {
|
||||
tracing::trace!("initialized graph");
|
||||
}
|
||||
GraphUpdatedEvent::Success(graph) => {
|
||||
tracing::trace!("graph updated successfully");
|
||||
self.graph = Some(graph.clone());
|
||||
}
|
||||
GraphUpdatedEvent::Failure(e) => {
|
||||
tracing::error!("graph update failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> GraphExplorer<'a> {
|
||||
pub fn new(root: String, state: SharedState) -> Self {
|
||||
Self {
|
||||
@@ -64,19 +86,31 @@ impl<'a> GraphExplorer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_graph(&mut self) -> Result<&mut Self> {
|
||||
pub fn new_update_graph(&self) -> Command {
|
||||
self.state.update_graph_command().command(
|
||||
&self.inner.root,
|
||||
&self
|
||||
.inner
|
||||
.current_path
|
||||
.map(|p| p.split(".").collect_vec())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn update_graph(&mut self) -> Result<&mut Self> {
|
||||
let now = std::time::SystemTime::now();
|
||||
|
||||
let graph = self
|
||||
.state
|
||||
.querier
|
||||
.get(
|
||||
.get_async(
|
||||
&self.inner.root,
|
||||
self.inner
|
||||
.current_path
|
||||
.map(|p| p.split('.').collect::<Vec<_>>())
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
.await
|
||||
.ok_or(anyhow::anyhow!("graph should've had an item"))?;
|
||||
|
||||
self.inner.graph = Some(graph);
|
||||
@@ -254,12 +288,12 @@ impl<'a> GraphExplorer<'a> {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.update_graph()?;
|
||||
//self.update_graph()?;
|
||||
|
||||
Ok(None)
|
||||
Ok(Some(self.new_update_graph()))
|
||||
}
|
||||
|
||||
pub(crate) fn interact(&mut self) -> anyhow::Result<()> {
|
||||
pub(crate) fn interact(&mut self) -> anyhow::Result<Command> {
|
||||
if !self.get_current_path().is_empty() {
|
||||
tracing::info!("toggling state of items");
|
||||
|
||||
@@ -271,9 +305,9 @@ impl<'a> GraphExplorer<'a> {
|
||||
})?;
|
||||
}
|
||||
|
||||
self.update_graph()?;
|
||||
//self.update_graph()?;
|
||||
|
||||
Ok(())
|
||||
Ok(self.new_update_graph())
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -60,7 +60,7 @@ async fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>, state: SharedSta
|
||||
};
|
||||
|
||||
let mut graph_explorer = GraphExplorer::new(root.clone(), state.clone());
|
||||
graph_explorer.update_graph()?;
|
||||
graph_explorer.update_graph().await?;
|
||||
|
||||
let mut app = App::new(&root, state.clone(), graph_explorer);
|
||||
let (dispatch, mut receiver) = commands::create_dispatch();
|
||||
|
@@ -20,6 +20,15 @@ pub enum Msg {
|
||||
SubmitCommand { command: String },
|
||||
|
||||
Edit(EditMsg),
|
||||
|
||||
GraphUpdated(GraphUpdatedEvent),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GraphUpdatedEvent {
|
||||
Initiated,
|
||||
Success(GraphItem),
|
||||
Failure(String),
|
||||
}
|
||||
|
||||
impl IntoCommand for Msg {
|
||||
|
@@ -5,10 +5,12 @@ use crate::shared_engine::SharedEngine;
|
||||
mod local;
|
||||
mod remote;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum QuerierVariant {
|
||||
Local(local::Querier),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Querier {
|
||||
variant: QuerierVariant,
|
||||
}
|
||||
@@ -30,6 +32,16 @@ impl Querier {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_async(
|
||||
&self,
|
||||
root: &str,
|
||||
path: impl IntoIterator<Item = impl Into<String>>,
|
||||
) -> Option<GraphItem> {
|
||||
match &self.variant {
|
||||
QuerierVariant::Local(querier) => querier.get(root, path),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_available_roots(&self) -> Option<Vec<String>> {
|
||||
match &self.variant {
|
||||
QuerierVariant::Local(querier) => querier.get_available_roots(),
|
||||
|
@@ -2,6 +2,7 @@ use hyperlog_core::log::GraphItem;
|
||||
|
||||
use crate::shared_engine::SharedEngine;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Querier {
|
||||
engine: SharedEngine,
|
||||
}
|
||||
|
Reference in New Issue
Block a user