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 bond_popup; mod change_blocks_popup; mod chill_popup; mod event_log; mod gatekeeper_details; mod gatekeeper_endpoints_popup; mod gatekeepers; mod history; mod listen_addresses; mod nominators; mod payee_popup; mod payout_all_popup; mod payout_popup; mod peers; mod rebond_popup; mod reward_details; mod rotate_popup; mod staking_details; mod stash_details; mod stash_info; mod unbond_popup; mod validate_popup; mod withdraw_popup; mod withdrawals; use bond_popup::BondPopup; use change_blocks_popup::ChangeBlocksPopup; use chill_popup::ChillPopup; use event_log::EventLogs; use gatekeeper_details::GatekeeperDetails; use gatekeeper_endpoints_popup::GatekeeperEndpoints; use gatekeepers::Gatekeepers; use history::History; use listen_addresses::ListenAddresses; use nominators::NominatorsByValidator; use payee_popup::PayeePopup; use payout_all_popup::PayoutAllPopup; use payout_popup::PayoutPopup; use peers::Peers; use rebond_popup::RebondPopup; use reward_details::RewardDetails; use rotate_popup::RotatePopup; use staking_details::StakingDetails; use stash_details::StashDetails; use stash_info::StashInfo; use unbond_popup::UnbondPopup; use validate_popup::ValidatePopup; use withdraw_popup::WithdrawPopup; use withdrawals::Withdrawals; #[derive(Debug, Copy, Clone, PartialEq)] pub enum CurrentTab { Nothing, ListenAddresses, Gatekeepers, NominatorsByValidator, History, Withdrawals, Peers, EventLogs, BondPopup, PayoutPopup, PayoutAllPopup, RotatePopup, ValidatePopup, ChillPopup, UnbondPopup, RebondPopup, WithdrawPopup, PayeePopup, ChangeBlocksPopup, GatekeeperEndpoints, } 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(GatekeeperDetails::default()), Box::new(History::default()), Box::new(Withdrawals::default()), Box::new(Peers::default()), Box::new(ListenAddresses::default()), Box::new(Gatekeepers::default()), Box::new(EventLogs::default()), Box::new(BondPopup::default()), Box::new(PayoutPopup::default()), Box::new(PayoutAllPopup::default()), Box::new(RotatePopup::default()), Box::new(ValidatePopup::default()), Box::new(ChillPopup::default()), Box::new(UnbondPopup::default()), Box::new(RebondPopup::default()), Box::new(WithdrawPopup::default()), Box::new(PayeePopup::default()), Box::new(ChangeBlocksPopup::default()), Box::new(GatekeeperEndpoints::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::Gatekeepers, CurrentTab::ListenAddresses => self.current_tab = CurrentTab::Gatekeepers, _ => {} } } fn move_right(&mut self) { match self.current_tab { CurrentTab::ListenAddresses => self.current_tab = CurrentTab::Gatekeepers, CurrentTab::Gatekeepers => self.current_tab = CurrentTab::NominatorsByValidator, CurrentTab::Nothing => 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::ValidatePopup | CurrentTab::ChillPopup | CurrentTab::UnbondPopup | CurrentTab::RebondPopup | CurrentTab::WithdrawPopup | CurrentTab::PayoutPopup | CurrentTab::PayoutAllPopup | CurrentTab::ChangeBlocksPopup | CurrentTab::GatekeeperEndpoints => { 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('R') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::RotatePopup; } KeyCode::Char('V') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::ValidatePopup; } KeyCode::Char('U') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::UnbondPopup; } KeyCode::Char('C') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::ChillPopup; } KeyCode::Char('B') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::BondPopup; } KeyCode::Char('E') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::RebondPopup; } KeyCode::Char('W') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::WithdrawPopup; } KeyCode::Char('L') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::ListenAddresses; } KeyCode::Char('I') => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::PayeePopup; } KeyCode::Char('l') | KeyCode::Right => self.move_right(), KeyCode::Char('h') | KeyCode::Left => self.move_left(), _ => { 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.previous_tab = CurrentTab::NominatorsByValidator; self.current_tab = CurrentTab::NominatorsByValidator; } Action::PayoutValidatorPopup(_) => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::PayoutPopup; } Action::PayoutAllValidatorPopup(_) => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::PayoutAllPopup; } Action::WithdrawValidatorPopup => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::WithdrawPopup; } Action::ChangeBlocksPopup => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::ChangeBlocksPopup; } Action::GatekeeperEndpoints => { self.previous_tab = self.current_tab; self.current_tab = CurrentTab::GatekeeperEndpoints; } 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::layouts::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(18), 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; 3] { let [_, place] = validator_details_layout(area); Layout::vertical([ Constraint::Length(6), Constraint::Length(6), Constraint::Fill(1), ]) .areas(place) } pub fn validator_gatekeeped_networks_layout(area: Rect) -> [Rect; 2] { let [_, _, place] = validator_session_and_listen_layout(area); Layout::horizontal([Constraint::Fill(1), Constraint::Length(30)]).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(6), Constraint::Fill(1), ]) .areas(place) }