use std::time::Instant;
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
layout::Rect,
widgets::{Block, Borders, Paragraph},
Frame,
};
use tokio::sync::mpsc;
use crate::api::client::MockForgeClient;
use crate::event::Event;
use crate::screens::Screen;
use crate::theme::Theme;
use crate::widgets::json_viewer;
const FETCH_INTERVAL: u64 = 10;
pub struct WorldStateScreen {
data: Option<serde_json::Value>,
error: Option<String>,
last_fetch: Option<Instant>,
scroll_offset: u16,
}
impl WorldStateScreen {
pub fn new() -> Self {
Self {
data: None,
error: None,
last_fetch: None,
scroll_offset: 0,
}
}
}
impl Screen for WorldStateScreen {
fn title(&self) -> &str {
"World State"
}
fn handle_key(&mut self, key: KeyEvent) -> bool {
match key.code {
KeyCode::Char('j') | KeyCode::Down => {
self.scroll_offset = self.scroll_offset.saturating_add(1);
true
}
KeyCode::Char('k') | KeyCode::Up => {
self.scroll_offset = self.scroll_offset.saturating_sub(1);
true
}
KeyCode::Char('g') => {
self.scroll_offset = 0;
true
}
_ => false,
}
}
fn render(&self, frame: &mut Frame, area: Rect) {
let Some(ref data) = self.data else {
let loading = Paragraph::new("Loading world state...").style(Theme::dim()).block(
Block::default()
.title(" World State ")
.borders(Borders::ALL)
.border_style(Theme::dim()),
);
frame.render_widget(loading, area);
return;
};
json_viewer::render_scrollable(frame, area, "World State", data, self.scroll_offset);
}
fn tick(&mut self, client: &MockForgeClient, tx: &mpsc::UnboundedSender<Event>) {
let should_fetch =
self.last_fetch.map_or(true, |t| t.elapsed().as_secs() >= FETCH_INTERVAL);
if !should_fetch {
return;
}
self.last_fetch = Some(Instant::now());
let client = client.clone();
let tx = tx.clone();
tokio::spawn(async move {
match client.get_world_state().await {
Ok(data) => {
let json = serde_json::to_string(&data).unwrap_or_default();
let _ = tx.send(Event::Data {
screen: "world_state",
payload: json,
});
}
Err(e) => {
let _ = tx.send(Event::ApiError {
screen: "world_state",
message: e.to_string(),
});
}
}
});
}
fn on_data(&mut self, payload: &str) {
match serde_json::from_str::<serde_json::Value>(payload) {
Ok(data) => {
self.data = Some(data);
self.error = None;
}
Err(e) => {
self.error = Some(format!("Parse error: {e}"));
}
}
}
fn on_error(&mut self, message: &str) {
self.error = Some(message.to_string());
}
fn error(&self) -> Option<&str> {
self.error.as_deref()
}
fn force_refresh(&mut self) {
self.last_fetch = None;
}
fn status_hint(&self) -> &str {
"j/k:scroll g:top r:refresh"
}
}