diff --git a/Cargo.lock b/Cargo.lock index c23a1ee..a6c7562 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -920,6 +920,7 @@ dependencies = [ "directories", "hyperlog-core", "ratatui", + "serde_json", "tokio", "tracing", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index df62aa1..6003edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } clap = { version = "4", features = ["derive", "env"] } dotenv = { version = "0.15" } axum = { version = "0.7" } +serde_json = "1.0.116" diff --git a/crates/hyperlog-tui/Cargo.toml b/crates/hyperlog-tui/Cargo.toml index 530c3a9..0826879 100644 --- a/crates/hyperlog-tui/Cargo.toml +++ b/crates/hyperlog-tui/Cargo.toml @@ -10,6 +10,7 @@ anyhow.workspace = true tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true +serde_json.workspace = true ratatui = "0.26.2" crossterm = { version = "0.27.0", features = ["event-stream"] } diff --git a/crates/hyperlog-tui/src/lib.rs b/crates/hyperlog-tui/src/lib.rs index 908034e..b744dfb 100644 --- a/crates/hyperlog-tui/src/lib.rs +++ b/crates/hyperlog-tui/src/lib.rs @@ -10,9 +10,11 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use hyperlog_core::state::State; +use hyperlog_core::{log::GraphItem, state::State}; use ratatui::{backend::CrosstermBackend, prelude::*, widgets::*, Frame, Terminal}; +use crate::state::SharedState; + struct TerminalInstance { terminal: Terminal>, } @@ -47,21 +49,84 @@ impl Drop for TerminalInstance { } } -pub async fn execute(state: &State) -> Result<()> { +mod state { + use std::{ops::Deref, sync::Arc}; + + use hyperlog_core::state::State; + + #[derive(Clone)] + pub struct SharedState { + state: Arc, + } + + impl Deref for SharedState { + type Target = State; + + fn deref(&self) -> &Self::Target { + &self.state + } + } + + impl From for SharedState { + fn from(value: State) -> Self { + Self { + state: Arc::new(value), + } + } + } +} + +pub async fn execute(state: State) -> Result<()> { tracing::debug!("starting hyperlog tui"); logging::initialize_panic_handler()?; logging::initialize_logging()?; + let state = SharedState::from(state); + let mut terminal = TerminalInstance::new()?; run(&mut terminal, state).context("app loop failed")?; Ok(()) } -fn run(terminal: &mut Terminal>, state: &State) -> Result<()> { +pub struct App<'a> { + state: SharedState, + + graph_explorer: GraphExplorer<'a>, +} + +impl<'a> App<'a> { + pub fn new(state: SharedState, graph_explorer: GraphExplorer<'a>) -> Self { + Self { + state, + graph_explorer, + } + } +} + +impl<'a> Widget for &mut App<'a> { + fn render(self, area: Rect, buf: &mut Buffer) + where + Self: Sized, + { + StatefulWidget::render( + GraphExplorer::new(self.state.clone()), + area, + buf, + &mut self.graph_explorer.inner, + ) + } +} + +fn run(terminal: &mut Terminal>, state: SharedState) -> Result<()> { + let mut graph_explorer = GraphExplorer::new(state.clone()); + graph_explorer.update_graph()?; + + let mut app = App::new(state.clone(), graph_explorer); + loop { - terminal.draw(|f| crate::render_app(f, &state))?; + terminal.draw(|f| crate::render_app(f, &mut app))?; if should_quit()? { break; } @@ -69,7 +134,7 @@ fn run(terminal: &mut Terminal>, state: &State) -> Resu Ok(()) } -fn render_app(frame: &mut Frame, state: &State) { +fn render_app(frame: &mut Frame, state: &mut App) { let chunks = Layout::vertical(vec![Constraint::Length(2), Constraint::Min(0)]).split(frame.size()); @@ -104,10 +169,74 @@ fn render_app(frame: &mut Frame, state: &State) { top: 2, bottom: 2, }); + //frame.render_widget(background.block(bg_block), chunks[1]); - if let Some(graph) = state.querier.get("something", Vec::::new()) {} + frame.render_widget(state, chunks[1]) +} - frame.render_widget(background.block(bg_block), chunks[1]); +struct GraphExplorer<'a> { + state: SharedState, + + inner: GraphExplorerState<'a>, +} + +struct GraphExplorerState<'a> { + current_path: Option<&'a str>, + + graph: Option, +} + +impl GraphExplorer<'_> { + pub fn new(state: SharedState) -> Self { + Self { + state, + inner: GraphExplorerState::<'_> { + current_path: None, + graph: None, + }, + } + } + + pub fn update_graph(&mut self) -> Result<&mut Self> { + let graph = self + .state + .querier + .get( + "something", + self.inner + .current_path + .map(|p| p.split('.').collect::>()) + .unwrap_or_default(), + ) + .ok_or(anyhow::anyhow!("graph should've had an item"))?; + + self.inner.graph = Some(graph); + + Ok(self) + } +} + +impl<'a> StatefulWidget for GraphExplorer<'a> { + type State = GraphExplorerState<'a>; + + fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + let Rect { height, .. } = area; + let height = height as usize; + + if let Some(graph) = &state.graph { + if let Ok(graph) = serde_json::to_string_pretty(graph) { + let lines = graph + .split('\n') + .take(height) + .map(Line::raw) + .collect::>(); + + let para = Paragraph::new(lines); + + para.render(area, buf); + } + } + } } fn should_quit() -> Result { diff --git a/crates/hyperlog/Cargo.toml b/crates/hyperlog/Cargo.toml index 4cdf0e2..a289125 100644 --- a/crates/hyperlog/Cargo.toml +++ b/crates/hyperlog/Cargo.toml @@ -14,6 +14,7 @@ tracing-subscriber.workspace = true clap.workspace = true dotenv.workspace = true axum.workspace = true +serde_json.workspace = true serde = { version = "1.0.197", features = ["derive"] } sqlx = { version = "0.7.3", features = [ @@ -25,7 +26,6 @@ sqlx = { version = "0.7.3", features = [ ] } uuid = { version = "1.7.0", features = ["v4"] } tower-http = { version = "0.5.2", features = ["cors", "trace"] } -serde_json = "1.0.116" bus = "2.4.1" dirs = "5.0.1" diff --git a/crates/hyperlog/src/cli.rs b/crates/hyperlog/src/cli.rs index ae5f005..3797a00 100644 --- a/crates/hyperlog/src/cli.rs +++ b/crates/hyperlog/src/cli.rs @@ -111,7 +111,7 @@ pub async fn execute() -> anyhow::Result<()> { println!("cleared lock file"); } None => { - hyperlog_tui::execute(&state).await?; + hyperlog_tui::execute(state).await?; } }