295 lines
9.8 KiB
Rust
295 lines
9.8 KiB
Rust
use color_eyre::Result;
|
|
use crossterm::event::KeyEvent;
|
|
use ratatui::prelude::Rect;
|
|
use serde::{Serialize, Deserialize};
|
|
use tokio::sync::mpsc::{UnboundedSender, UnboundedReceiver};
|
|
use std::sync::mpsc::Sender;
|
|
use tracing::info;
|
|
|
|
use crate::{
|
|
action::Action,
|
|
config::Config,
|
|
tui::{Event, Tui},
|
|
components::{
|
|
menu::Menu, version::Version, explorer::Explorer, wallet::Wallet,
|
|
validator::Validator, nominator::Nominator, empty::Empty,
|
|
health::Health, fps::FpsCounter,
|
|
Component,
|
|
},
|
|
};
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum Mode {
|
|
Menu,
|
|
Explorer,
|
|
Wallet,
|
|
Validator,
|
|
Nominator,
|
|
Empty,
|
|
}
|
|
|
|
impl Default for Mode {
|
|
fn default() -> Self {
|
|
Self::Explorer
|
|
}
|
|
}
|
|
|
|
pub struct App {
|
|
network_tx: Sender<Action>,
|
|
action_tx: UnboundedSender<Action>,
|
|
action_rx: UnboundedReceiver<Action>,
|
|
frame_rate: f32,
|
|
tick_rate: f32,
|
|
mouse: bool,
|
|
paste: bool,
|
|
config: Config,
|
|
components: Vec<Box<dyn Component>>,
|
|
should_quite: bool,
|
|
should_suspend: bool,
|
|
mode: Mode,
|
|
last_tick_key_events: Vec<KeyEvent>,
|
|
}
|
|
|
|
impl App {
|
|
pub fn new(
|
|
network_tx: Sender<Action>,
|
|
action_tx: UnboundedSender<Action>,
|
|
action_rx: UnboundedReceiver<Action>,
|
|
) -> Result<Self> {
|
|
Ok(Self {
|
|
network_tx,
|
|
action_tx,
|
|
action_rx,
|
|
frame_rate: 4.0,
|
|
tick_rate: 60.0,
|
|
mouse: false,
|
|
paste: false,
|
|
config: Config::new()?,
|
|
components: vec![
|
|
Box::new(Menu::default()),
|
|
Box::new(FpsCounter::default()),
|
|
Box::new(Health::default()),
|
|
Box::new(Version::default()),
|
|
Box::new(Explorer::default()),
|
|
Box::new(Wallet::default()),
|
|
Box::new(Validator::default()),
|
|
Box::new(Nominator::default()),
|
|
Box::new(Empty::default()),
|
|
],
|
|
should_quite: false,
|
|
should_suspend: false,
|
|
mode: Mode::default(),
|
|
last_tick_key_events: Vec::new(),
|
|
})
|
|
}
|
|
|
|
pub fn with_frame_rate(mut self, frame_rate: f32) -> Self {
|
|
self.frame_rate = frame_rate;
|
|
self
|
|
}
|
|
|
|
pub fn with_tick_rate(mut self, tick_rate: f32) -> Self {
|
|
self.tick_rate = tick_rate;
|
|
self
|
|
}
|
|
|
|
pub fn with_mouse(mut self, mouse: bool) -> Self {
|
|
self.mouse = mouse;
|
|
self
|
|
}
|
|
|
|
pub fn with_paste(mut self, paste: bool) -> Self {
|
|
self.paste = paste;
|
|
self
|
|
}
|
|
|
|
pub async fn run(&mut self) -> Result<()> {
|
|
let mut tui = Tui::new()?;
|
|
tui.enter()?;
|
|
|
|
for component in self.components.iter_mut() {
|
|
component.register_action_handler(self.action_tx.clone())?;
|
|
}
|
|
for component in self.components.iter_mut() {
|
|
component.register_network_handler(self.network_tx.clone())?;
|
|
}
|
|
for component in self.components.iter_mut() {
|
|
component.register_config_handler(self.config.clone())?;
|
|
}
|
|
for component in self.components.iter_mut() {
|
|
component.init(tui.size()?)?;
|
|
}
|
|
|
|
let action_tx = self.action_tx.clone();
|
|
loop {
|
|
self.handle_events(&mut tui).await?;
|
|
self.handle_actions(&mut tui)?;
|
|
if self.should_suspend {
|
|
tui.suspend()?;
|
|
action_tx.send(Action::Resume)?;
|
|
action_tx.send(Action::ClearScreen)?;
|
|
tui.enter()?;
|
|
} else if self.should_quite {
|
|
tui.stop()?;
|
|
break;
|
|
}
|
|
}
|
|
tui.exit()?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_events(&mut self, tui: &mut Tui) -> Result<()> {
|
|
let Some(event) = tui.next_event().await else {
|
|
return Ok(());
|
|
};
|
|
let action_tx = self.action_tx.clone();
|
|
match event {
|
|
Event::Quit => action_tx.send(Action::Quit)?,
|
|
Event::Tick => action_tx.send(Action::Tick)?,
|
|
Event::Render => action_tx.send(Action::Render)?,
|
|
Event::Resize(x, y) => action_tx.send(Action::Resize(x, y))?,
|
|
Event::Key(key) => self.handle_key_event(key)?,
|
|
Event::Oneshot => self.trigger_oneshot_node_events()?,
|
|
Event::Node => self.trigger_node_fast_events()?,
|
|
_ => {}
|
|
}
|
|
|
|
for component in self.components.iter_mut() {
|
|
if let Some(action) = component.handle_events(Some(event.clone()))? {
|
|
action_tx.send(action)?;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn trigger_node_fast_events(&mut self) -> Result<()> {
|
|
self.network_tx.send(Action::GetPendingExtrinsics)?;
|
|
self.network_tx.send(Action::GetConnectedPeers)?;
|
|
self.network_tx.send(Action::CheckPendingTransactions)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn trigger_oneshot_node_events(&mut self) -> Result<()> {
|
|
self.network_tx.send(Action::GetNodeName)?;
|
|
self.network_tx.send(Action::GetSystemHealth)?;
|
|
self.network_tx.send(Action::GetGenesisHash)?;
|
|
self.network_tx.send(Action::GetChainName)?;
|
|
self.network_tx.send(Action::GetChainVersion)?;
|
|
self.network_tx.send(Action::GetExistentialDeposit)?;
|
|
self.network_tx.send(Action::GetLocalIdentity)?;
|
|
self.network_tx.send(Action::GetListenAddresses)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_key_event(&mut self, key: KeyEvent) -> Result<()> {
|
|
let action_tx = self.action_tx.clone();
|
|
let Some(keymap) = self.config.keybindings.get(&self.mode) else {
|
|
return Ok(());
|
|
};
|
|
match keymap.get(&vec![key]) {
|
|
Some(action) => {
|
|
info!("got action: {action:?}");
|
|
action_tx.send(action.clone())?;
|
|
}
|
|
_ => {
|
|
self.last_tick_key_events.push(key);
|
|
if let Some(action) = keymap.get(&self.last_tick_key_events) {
|
|
info!("got action: {action:?}");
|
|
action_tx.send(action.clone())?;
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_actions(&mut self, tui: &mut Tui) -> Result<()> {
|
|
while let Ok(action) = self.action_rx.try_recv() {
|
|
match action {
|
|
Action::Tick => { self.last_tick_key_events.drain(..); },
|
|
Action::Quit => self.should_quite = true,
|
|
Action::Suspend => self.should_suspend = true,
|
|
Action::Resume => self.should_suspend = false,
|
|
Action::ClearScreen => tui.terminal.clear()?,
|
|
Action::Resize(x, y) => self.handle_resize(tui, x, y)?,
|
|
Action::Render => self.render(tui)?,
|
|
Action::SetMode(mode) => self.mode = mode,
|
|
_ => {}
|
|
}
|
|
for component in self.components.iter_mut() {
|
|
if let Some(action) = component.update(action.clone())? {
|
|
self.action_tx.send(action)?
|
|
};
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_resize(&mut self, tui: &mut Tui, w: u16, h: u16) -> Result<()> {
|
|
tui.resize(Rect::new(0, 0, w, h))?;
|
|
self.render(tui)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn render(&mut self, tui: &mut Tui) -> Result<()> {
|
|
tui.draw(|frame| {
|
|
for component in self.components.iter_mut().take(4) {
|
|
if let Err(err) = (*component).draw(frame, frame.area()) {
|
|
let _ = self
|
|
.action_tx
|
|
.send(Action::Error(format!("failed to draw: {:?}", err)));
|
|
}
|
|
}
|
|
|
|
match self.mode {
|
|
Mode::Explorer => {
|
|
if let Some(component) = self.components.get_mut(4) {
|
|
if let Err(err) = component.draw(frame, frame.area()) {
|
|
let _ = self
|
|
.action_tx
|
|
.send(Action::Error(format!("failed to draw: {:?}", err)));
|
|
}
|
|
}
|
|
},
|
|
Mode::Wallet => {
|
|
if let Some(component) = self.components.get_mut(5) {
|
|
if let Err(err) = component.draw(frame, frame.area()) {
|
|
let _ = self
|
|
.action_tx
|
|
.send(Action::Error(format!("failed to draw: {:?}", err)));
|
|
}
|
|
}
|
|
},
|
|
Mode::Validator => {
|
|
if let Some(component) = self.components.get_mut(6) {
|
|
if let Err(err) = component.draw(frame, frame.area()) {
|
|
let _ = self
|
|
.action_tx
|
|
.send(Action::Error(format!("failed to draw: {:?}", err)));
|
|
}
|
|
}
|
|
},
|
|
Mode::Nominator => {
|
|
if let Some(component) = self.components.get_mut(7) {
|
|
if let Err(err) = component.draw(frame, frame.area()) {
|
|
let _ = self
|
|
.action_tx
|
|
.send(Action::Error(format!("failed to draw: {:?}", err)));
|
|
}
|
|
}
|
|
},
|
|
_ => {
|
|
if let Some(component) = self.components.last_mut() {
|
|
if let Err(err) = component.draw(frame, frame.area()) {
|
|
let _ = self
|
|
.action_tx
|
|
.send(Action::Error(format!("failed to draw: {:?}", err)));
|
|
}
|
|
}
|
|
},
|
|
}
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|