use color_eyre::Result; use crossterm::event::{KeyCode, KeyEvent}; use ratatui::{ layout::{Constraint, Layout, Rect}, Frame, }; use std::sync::mpsc::Sender; use tokio::sync::mpsc::UnboundedSender; use super::Component; use crate::{action::Action, app::Mode, config::Config}; mod event_log; mod peers; mod stash_info; mod nominators; mod listen_addresses; mod history; mod withdrawals; mod stash_details; mod staking_details; mod reward_details; mod bond_popup; mod payout_popup; mod rotate_popup; use stash_details::StashDetails; use staking_details::StakingDetails; use reward_details::RewardDetails; use event_log::EventLogs; use peers::Peers; use stash_info::StashInfo; use listen_addresses::ListenAddresses; use nominators::NominatorsByValidator; use history::History; use withdrawals::Withdrawals; use bond_popup::BondPopup; use payout_popup::PayoutPopup; use rotate_popup::RotatePopup; #[derive(Debug, Copy, Clone, PartialEq)] pub enum CurrentTab { Nothing, StashInfo, ListenAddresses, NominatorsByValidator, History, Withdrawals, Peers, EventLogs, BondPopup, PayoutPopup, RotatePopup, } pub trait PartialComponent: Component { fn set_active(&mut self, current_tab: CurrentTab); } pub struct Validator { is_active: bool, current_tab: CurrentTab, previous_tab: CurrentTab, components: Vec>, } impl Default for Validator { fn default() -> Self { Self { is_active: false, current_tab: CurrentTab::Nothing, previous_tab: CurrentTab::Nothing, components: vec![ Box::new(StashInfo::default()), Box::new(NominatorsByValidator::default()), Box::new(StashDetails::default()), Box::new(StakingDetails::default()), Box::new(RewardDetails::default()), Box::new(History::default()), Box::new(Withdrawals::default()), Box::new(Peers::default()), Box::new(ListenAddresses::default()), Box::new(EventLogs::default()), Box::new(BondPopup::default()), Box::new(PayoutPopup::default()), Box::new(RotatePopup::default()), ], } } } impl Validator { fn move_left(&mut self) { match self.current_tab { CurrentTab::EventLogs => self.current_tab = CurrentTab::Peers, CurrentTab::Peers => self.current_tab = CurrentTab::Withdrawals, CurrentTab::Withdrawals => self.current_tab = CurrentTab::History, CurrentTab::History => self.current_tab = CurrentTab::NominatorsByValidator, CurrentTab::NominatorsByValidator => self.current_tab = CurrentTab::ListenAddresses, CurrentTab::ListenAddresses => self.current_tab = CurrentTab::StashInfo, _ => {} } } fn move_right(&mut self) { match self.current_tab { CurrentTab::Nothing => self.current_tab = CurrentTab::StashInfo, CurrentTab::StashInfo => self.current_tab = CurrentTab::ListenAddresses, CurrentTab::ListenAddresses => self.current_tab = CurrentTab::NominatorsByValidator, CurrentTab::NominatorsByValidator => self.current_tab = CurrentTab::History, CurrentTab::History => self.current_tab = CurrentTab::Withdrawals, CurrentTab::Withdrawals => self.current_tab = CurrentTab::Peers, CurrentTab::Peers => self.current_tab = CurrentTab::EventLogs, _ => {} } } } impl Component for Validator { fn register_network_handler(&mut self, tx: Sender) -> Result<()> { for component in self.components.iter_mut() { component.register_network_handler(tx.clone())?; } Ok(()) } fn register_action_handler(&mut self, tx: UnboundedSender) -> Result<()> { for component in self.components.iter_mut() { component.register_action_handler(tx.clone())?; } Ok(()) } fn register_config_handler(&mut self, config: Config) -> Result<()> { for component in self.components.iter_mut() { component.register_config_handler(config.clone())?; } Ok(()) } fn handle_key_event(&mut self, key: KeyEvent) -> Result> { if !self.is_active { return Ok(None) } match self.current_tab { CurrentTab::BondPopup | CurrentTab::RotatePopup | CurrentTab::PayoutPopup => match key.code { KeyCode::Esc => { self.current_tab = self.previous_tab; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, _ => { for component in self.components.iter_mut() { component.handle_key_event(key)?; } } }, _ => match key.code { KeyCode::Esc => { self.is_active = false; self.current_tab = CurrentTab::Nothing; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } return Ok(Some(Action::SetActiveScreen(Mode::Menu))); }, KeyCode::Char('l') | KeyCode::Right => { self.move_right(); for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, KeyCode::Char('h') | KeyCode::Left => { self.move_left(); for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, KeyCode::Char('R') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::RotatePopup; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, KeyCode::Char('B') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::BondPopup; for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); } }, _ => { for component in self.components.iter_mut() { component.handle_key_event(key)?; } } } } Ok(None) } fn update(&mut self, action: Action) -> Result> { match action { Action::SetActiveScreen(Mode::Validator) => { self.is_active = true; self.current_tab = CurrentTab::StashInfo; } Action::PayoutValidatorPopup(_, _) => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::PayoutPopup; } Action::ClosePopup => { self.current_tab = self.previous_tab; }, _ => {}, } for component in self.components.iter_mut() { component.set_active(self.current_tab.clone()); component.update(action.clone())?; } Ok(None) } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { let screen = super::screen_layout(area); for component in self.components.iter_mut() { component.draw(frame, screen)?; } Ok(()) } } pub fn validator_layout(area: Rect) -> [Rect; 4] { Layout::vertical([ Constraint::Length(16), Constraint::Fill(1), Constraint::Fill(1), Constraint::Percentage(25), ]).areas(area) } pub fn validator_details_layout(area: Rect) -> [Rect; 2] { let [place, _, _, _] = validator_layout(area); Layout::horizontal([ Constraint::Length(31), Constraint::Fill(1), ]).areas(place) } pub fn validator_session_and_listen_layout(area: Rect) -> [Rect; 2] { let [_, place] = validator_details_layout(area); Layout::vertical([ Constraint::Length(6), Constraint::Fill(1), ]).areas(place) } pub fn validator_statistics_layout(area: Rect) -> [Rect; 3] { let [_, place, _, _] = validator_layout(area); Layout::horizontal([ Constraint::Percentage(30), Constraint::Percentage(40), Constraint::Percentage(30), ]).areas(place) } pub fn validator_balance_layout(area: Rect) -> [Rect; 3] { let [place, _] = validator_details_layout(area); Layout::vertical([ Constraint::Length(6), Constraint::Length(5), Constraint::Length(5), ]).areas(place) }