feat: add command pattern
All checks were successful
continuous-integration/drone/push Build is passing

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2024-05-12 14:29:14 +02:00
parent cf26422673
commit 5548d8e36e
10 changed files with 318 additions and 207 deletions

View File

@@ -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) {

View File

@@ -1,5 +1,9 @@
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
pub mod batch;
pub mod update_graph;
use crate::models::Msg;
pub trait IntoCommand {

View 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
})
}
}

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

View File

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

View File

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

View File

@@ -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 {

View File

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

View File

@@ -2,6 +2,7 @@ use hyperlog_core::log::GraphItem;
use crate::shared_engine::SharedEngine;
#[derive(Clone)]
pub struct Querier {
engine: SharedEngine,
}