more use of generic components

Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
Uncle Stretch 2025-08-26 20:58:30 +03:00
parent 682deadb48
commit 0f9c0aa1f6
Signed by: str3tch
GPG Key ID: 84F3190747EE79AA
24 changed files with 389 additions and 438 deletions

View File

@ -2,7 +2,7 @@
name = "ghost-eye" name = "ghost-eye"
authors = ["str3tch <stretch@ghostchain.io>"] authors = ["str3tch <stretch@ghostchain.io>"]
description = "Application for interacting with Casper/Ghost nodes that are exposing RPC only to the localhost" description = "Application for interacting with Casper/Ghost nodes that are exposing RPC only to the localhost"
version = "0.3.67" version = "0.3.68"
edition = "2021" edition = "2021"
homepage = "https://git.ghostchain.io/ghostchain" homepage = "https://git.ghostchain.io/ghostchain"
repository = "https://git.ghostchain.io/ghostchain/ghost-eye" repository = "https://git.ghostchain.io/ghostchain/ghost-eye"

View File

@ -1,5 +1,4 @@
use color_eyre::Result; use color_eyre::Result;
use tokio::sync::mpsc::UnboundedSender;
use crossterm::event::{KeyEvent, KeyCode}; use crossterm::event::{KeyEvent, KeyCode};
use ratatui::{ use ratatui::{
layout::{Alignment, Rect}, layout::{Alignment, Rect},
@ -10,13 +9,20 @@ use ratatui::{
use super::Component; use super::Component;
use crate::{ use crate::{
components::generic::Activatable,
action::Action, app::Mode action::Action, app::Mode
}; };
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Empty { pub struct Empty {
is_active: bool, is_active: bool,
action_tx: Option<UnboundedSender<Action>>, }
impl Activatable for Empty {
fn is_active(&self) -> bool { self.is_active }
fn is_inactive(&self) -> bool { !self.is_active }
fn set_inactive(&mut self) { self.is_active = false; }
fn set_active(&mut self) { self.is_active = true; }
} }
impl Empty { impl Empty {
@ -88,20 +94,15 @@ impl Empty {
} }
fn move_out(&mut self) -> Result<Option<Action>> { fn move_out(&mut self) -> Result<Option<Action>> {
self.is_active = false; self.set_inactive();
Ok(Some(Action::SetActiveScreen(Mode::Menu))) Ok(Some(Action::SetActiveScreen(Mode::Menu)))
} }
} }
impl Component for Empty { impl Component for Empty {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.action_tx = Some(tx);
Ok(())
}
fn update(&mut self, action: Action) -> Result<Option<Action>> { fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action { match action {
Action::SetActiveScreen(Mode::Empty) => self.is_active = true, Action::SetActiveScreen(Mode::Empty) => self.set_active(),
_ => {} _ => {}
}; };
Ok(None) Ok(None)
@ -109,15 +110,20 @@ impl Component for Empty {
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> { fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
match key.code { match key.code {
KeyCode::Esc if self.is_active => self.move_out(), KeyCode::Esc if self.is_active() => self.move_out(),
_ => Ok(None), _ => Ok(None),
} }
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let screen = super::screen_layout(area); let screen = super::layouts::screen_layout(area);
let lines = if self.is_active() {
self.prepare_active_text()
} else {
self.prepare_inactive_text()
};
let lines = if self.is_active { self.prepare_active_text() } else { self.prepare_inactive_text() };
let lines_len = lines.len() as u16; let lines_len = lines.len() as u16;
let padding_top = screen let padding_top = screen
.as_size() .as_size()

View File

@ -1,5 +1,5 @@
use color_eyre::Result; use color_eyre::Result;
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::KeyEvent;
use ratatui::layout::{Constraint, Margin}; use ratatui::layout::{Constraint, Margin};
use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation}; use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation};
use ratatui::{ use ratatui::{
@ -9,13 +9,11 @@ use ratatui::{
Frame Frame
}; };
use subxt::utils::H256; use subxt::utils::H256;
use tokio::sync::mpsc::UnboundedSender;
use super::{Component, PartialComponent, CurrentTab}; use super::{Component, CurrentTab};
use crate::{ use crate::{
action::Action, components::generic::{Activatable, Scrollable, PartialComponent},
config::Config, action::Action, config::Config, palette::StylePalette,
palette::StylePalette,
}; };
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -26,7 +24,6 @@ struct BlockInfo {
pub struct BlockExplorer { pub struct BlockExplorer {
is_active: bool, is_active: bool,
action_tx: Option<UnboundedSender<Action>>,
blocks: std::collections::VecDeque<BlockInfo>, blocks: std::collections::VecDeque<BlockInfo>,
block_headers: std::collections::HashMap<u32, H256>, block_headers: std::collections::HashMap<u32, H256>,
block_authors: std::collections::HashMap<H256, String>, block_authors: std::collections::HashMap<H256, String>,
@ -41,6 +38,49 @@ impl Default for BlockExplorer {
} }
} }
impl Activatable for BlockExplorer {
fn is_active(&self) -> bool { self.is_active }
fn is_inactive(&self) -> bool { !self.is_active }
fn set_inactive(&mut self) { self.is_active = false; }
fn set_active(&mut self) { self.is_active = true; }
}
impl Scrollable for BlockExplorer {
type IndexType = usize;
fn selected_index(&self) -> Option<Self::IndexType> {
self.table_state.selected()
}
fn items_length(&self) -> Self::IndexType {
self.blocks.len()
}
fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>> {
self.table_state.select(Some(new_index));
self.scroll_state = self.scroll_state.position(new_index);
self.send_used_explorer_block(new_index)
}
fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>> {
self.apply_next_row(new_index)
}
fn apply_first_row(&mut self) -> Result<Option<Action>> {
match self.items_length() > 0 {
true => self.apply_next_row(0),
false => Ok(None),
}
}
fn apply_last_row(&mut self) -> Result<Option<Action>> {
match self.items_length().checked_sub(1) {
Some(last_idx) => self.apply_next_row(last_idx),
None => Ok(None),
}
}
}
impl BlockExplorer { impl BlockExplorer {
const MAX_BLOCKS: usize = 50; const MAX_BLOCKS: usize = 50;
@ -48,7 +88,6 @@ impl BlockExplorer {
Self { Self {
is_active: false, is_active: false,
blocks: Default::default(), blocks: Default::default(),
action_tx: None,
block_authors: Default::default(), block_authors: Default::default(),
block_headers: Default::default(), block_headers: Default::default(),
scroll_state: ScrollbarState::new(0), scroll_state: ScrollbarState::new(0),
@ -61,7 +100,7 @@ impl BlockExplorer {
&mut self, &mut self,
hash: H256, hash: H256,
block_number: u32, block_number: u32,
) { ) -> Result<Option<Action>> {
let front_block_number = self.blocks let front_block_number = self.blocks
.front() .front()
.map(|block| block.block_number) .map(|block| block.block_number)
@ -74,7 +113,7 @@ impl BlockExplorer {
}); });
self.block_headers.insert(block_number, hash); self.block_headers.insert(block_number, hash);
if self.blocks.len() > Self::MAX_BLOCKS { if self.items_length() > Self::MAX_BLOCKS {
if let Some(block) = self.blocks.pop_back() { if let Some(block) = self.blocks.pop_back() {
if let Some(hash) = self.block_headers.remove(&block.block_number) { if let Some(hash) = self.block_headers.remove(&block.block_number) {
self.block_authors.remove(&hash); self.block_authors.remove(&hash);
@ -82,19 +121,21 @@ impl BlockExplorer {
} }
} }
self.scroll_state = self.scroll_state.content_length(self.blocks.len()); self.scroll_state = self.scroll_state.content_length(self.items_length());
if self.table_state.selected().is_some() { return match self.table_state.selected() {
self.next_row(); Some(_) => self.next_row(),
None => Ok(None),
} }
} }
Ok(None)
} }
fn update_finalized_block_info( fn update_finalized_block_info(
&mut self, &mut self,
header: H256, header: H256,
block_number: u32, block_number: u32,
) { ) -> Result<Option<Action>> {
for idx in 0..self.blocks.len() { for idx in 0..self.items_length() {
if self.blocks[idx].finalized { break; } if self.blocks[idx].finalized { break; }
else if self.blocks[idx].block_number > block_number { continue; } else if self.blocks[idx].block_number > block_number { continue; }
else { else {
@ -102,86 +143,30 @@ impl BlockExplorer {
self.blocks[idx].finalized = true; self.blocks[idx].finalized = true;
} }
} }
Ok(None)
} }
fn send_used_explorer_block(&mut self, index: usize) { fn send_used_explorer_block(&mut self, index: usize) -> Result<Option<Action>> {
if let Some(action_tx) = &self.action_tx {
let maybe_block_number = self.blocks.get(index).map(|info| info.block_number); let maybe_block_number = self.blocks.get(index).map(|info| info.block_number);
let _ = action_tx.send(Action::UsedExplorerBlock(maybe_block_number)); Ok(Some(Action::UsedExplorerBlock(maybe_block_number)))
} }
} }
fn first_row(&mut self) { impl PartialComponent<CurrentTab> for BlockExplorer {
if self.blocks.len() > 0 { fn set_active_tab(&mut self, current_tab: CurrentTab) {
self.table_state.select(Some(0));
self.scroll_state = self.scroll_state.position(0);
self.send_used_explorer_block(0);
}
}
fn next_row(&mut self) {
let i = match self.table_state.selected() {
Some(i) => {
if i >= self.blocks.len() - 1 {
i
} else {
i + 1
}
},
None => 0,
};
self.table_state.select(Some(i));
self.scroll_state = self.scroll_state.position(i);
self.send_used_explorer_block(i);
}
fn last_row(&mut self) {
if self.blocks.len() > 0 {
let last = self.blocks.len() - 1;
self.table_state.select(Some(last));
self.scroll_state = self.scroll_state.position(last);
self.send_used_explorer_block(last);
}
}
fn previous_row(&mut self) {
let i = match self.table_state.selected() {
Some(i) => {
if i == 0 {
0
} else {
i - 1
}
},
None => 0
};
self.table_state.select(Some(i));
self.scroll_state = self.scroll_state.position(i);
self.send_used_explorer_block(i);
}
}
impl PartialComponent for BlockExplorer {
fn set_active(&mut self, current_tab: CurrentTab) {
match current_tab { match current_tab {
CurrentTab::Blocks => self.is_active = true, CurrentTab::Blocks => self.set_active(),
CurrentTab::Extrinsics => self.is_active = false, CurrentTab::Extrinsics => self.set_inactive(),
CurrentTab::Nothing => { CurrentTab::Nothing => {
self.is_active = false; self.set_inactive();
self.table_state.select(None); self.table_state.select(None);
self.scroll_state = self.scroll_state.position(0); self.scroll_state = self.scroll_state.position(0);
self.send_used_explorer_block(usize::MAX);
}, },
} }
} }
} }
impl Component for BlockExplorer { impl Component for BlockExplorer {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.action_tx = Some(tx);
Ok(())
}
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) { if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
self.palette.with_normal_style(style.get("normal_style").copied()); self.palette.with_normal_style(style.get("normal_style").copied());
@ -202,28 +187,23 @@ impl Component for BlockExplorer {
Action::FinalizedBlockInformation(header, block_number) => self.update_finalized_block_info(header, block_number), Action::FinalizedBlockInformation(header, block_number) => self.update_finalized_block_info(header, block_number),
Action::SetBlockAuthor(header, author) => { Action::SetBlockAuthor(header, author) => {
let _ = self.block_authors.insert(header, author); let _ = self.block_authors.insert(header, author);
},
_ => {},
};
Ok(None) Ok(None)
},
_ => Ok(None),
}
} }
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> { fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
match key.code { match self.is_active() {
KeyCode::Char('k') | KeyCode::Up if self.is_active => self.previous_row(), true => self.handle_scrollable_key_codes(key.code),
KeyCode::Char('j') | KeyCode::Down if self.is_active => self.next_row(), false => Ok(None),
KeyCode::Char('g') if self.is_active => self.first_row(), }
KeyCode::Char('G') if self.is_active => self.last_row(),
_ => {},
};
Ok(None)
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [place, _] = super::explorer_scrollbars_layout(area); let [place, _] = super::layouts::explorer_scrollbars_layout(area);
let (border_style, border_type) = self.palette.create_border_style(self.is_active); let (border_style, border_type) = self.palette.create_border_style(self.is_active());
let default_hash = H256::repeat_byte(69u8); let default_hash = H256::repeat_byte(69u8);
let default_author = "...".to_string(); let default_author = "...".to_string();
let rows = self.blocks let rows = self.blocks

View File

@ -7,12 +7,11 @@ use ratatui::{
Frame Frame
}; };
use super::{Component, PartialComponent, CurrentTab}; use super::{Component, CurrentTab};
use crate::{ use crate::{
config::Config, components::generic::PartialComponent,
config::Config, action::Action, palette::StylePalette,
widgets::{BigText, PixelSize}, widgets::{BigText, PixelSize},
action::Action,
palette::StylePalette,
}; };
#[derive(Debug)] #[derive(Debug)]
@ -37,18 +36,15 @@ impl BlockTicker {
} }
} }
fn block_found(&mut self, block: u32) -> Result<()> { fn block_found(&mut self, block: u32) {
if self.last_block < block { if self.last_block < block {
self.last_block_time = Instant::now(); self.last_block_time = Instant::now();
self.last_block = block; self.last_block = block;
} }
Ok(())
} }
} }
impl PartialComponent for BlockTicker { impl PartialComponent<CurrentTab> for BlockTicker {}
fn set_active(&mut self, _current_tab: CurrentTab) {}
}
impl Component for BlockTicker { impl Component for BlockTicker {
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
@ -65,14 +61,14 @@ impl Component for BlockTicker {
fn update(&mut self, action: Action) -> Result<Option<Action>> { fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action { match action {
Action::BestBlockUpdated(block) => self.block_found(block)?, Action::BestBlockUpdated(block) => self.block_found(block),
_ => {} _ => {}
}; };
Ok(None) Ok(None)
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [place, _, _] = super::explorer_block_info_layout(area); let [place, _, _] = super::layouts::explorer_block_info_layout(area);
let passed = (Instant::now() - self.last_block_time).as_secs_f64(); let passed = (Instant::now() - self.last_block_time).as_secs_f64();
let text = if passed < 60.0 { let text = if passed < 60.0 {

View File

@ -6,11 +6,10 @@ use ratatui::{
Frame, Frame,
}; };
use super::{Component, PartialComponent, CurrentTab}; use super::{Component, CurrentTab};
use crate::{ use crate::{
config::Config, components::generic::PartialComponent,
action::Action, config::Config, action::Action, palette::StylePalette,
palette::StylePalette,
widgets::{PixelSize, BigText}, widgets::{PixelSize, BigText},
}; };
@ -26,17 +25,9 @@ impl CurrentEpoch {
const SESSION_LENGTH: u64 = 2_400; const SESSION_LENGTH: u64 = 2_400;
const SECONDS_IN_DAY: u64 = 86_400; const SECONDS_IN_DAY: u64 = 86_400;
const SECONDS_IN_HOUR: u64 = 3_600; const SECONDS_IN_HOUR: u64 = 3_600;
fn update_epoch_progress(&mut self, number: u64, progress: u64) -> Result<()> {
self.number = number;
self.progress = progress;
Ok(())
}
} }
impl PartialComponent for CurrentEpoch { impl PartialComponent<CurrentTab> for CurrentEpoch {}
fn set_active(&mut self, _current_tab: CurrentTab) {}
}
impl Component for CurrentEpoch { impl Component for CurrentEpoch {
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
@ -53,15 +44,17 @@ impl Component for CurrentEpoch {
fn update(&mut self, action: Action) -> Result<Option<Action>> { fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action { match action {
Action::SetEpochProgress(number, progress) => Action::SetEpochProgress(number, progress) => {
self.update_epoch_progress(number, progress)?, self.number = number;
self.progress = progress;
},
_ => {} _ => {}
}; };
Ok(None) Ok(None)
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [place, _] = super::explorer_era_info_layout(area); let [place, _] = super::layouts::explorer_era_info_layout(area);
let seconds_to_next = Self::SESSION_LENGTH.saturating_sub(self.progress) * Self::SECONDS_IN_BLOCK; let seconds_to_next = Self::SESSION_LENGTH.saturating_sub(self.progress) * Self::SECONDS_IN_BLOCK;
let hours = (seconds_to_next % Self::SECONDS_IN_DAY) / Self::SECONDS_IN_HOUR; let hours = (seconds_to_next % Self::SECONDS_IN_DAY) / Self::SECONDS_IN_HOUR;

View File

@ -6,13 +6,10 @@ use ratatui::{
Frame, Frame,
}; };
use super::{Component, PartialComponent, CurrentTab}; use super::{Component, CurrentTab};
use crate::{ use crate::{
config::Config, config::Config, action::Action, palette::StylePalette, types::EraInfo,
action::Action, components::generic::PartialComponent, widgets::{PixelSize, BigText},
palette::StylePalette,
types::EraInfo,
widgets::{PixelSize, BigText},
}; };
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -28,16 +25,9 @@ impl CurrentEra {
const MILLIS_IN_DAY: u64 = 86_400_000; const MILLIS_IN_DAY: u64 = 86_400_000;
const MILLIS_IN_HOUR: u64 = 3_600_000; const MILLIS_IN_HOUR: u64 = 3_600_000;
const MILLIS_IN_MINUTE: u64 = 60_000; const MILLIS_IN_MINUTE: u64 = 60_000;
fn update_era(&mut self, era_info: EraInfo) -> Result<()> {
self.era = era_info;
Ok(())
}
} }
impl PartialComponent for CurrentEra { impl PartialComponent<CurrentTab> for CurrentEra {}
fn set_active(&mut self, _current_tab: CurrentTab) {}
}
impl Component for CurrentEra { impl Component for CurrentEra {
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
@ -54,14 +44,14 @@ impl Component for CurrentEra {
fn update(&mut self, action: Action) -> Result<Option<Action>> { fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action { match action {
Action::SetActiveEra(era_info) => self.update_era(era_info)?, Action::SetActiveEra(era_info) => self.era = era_info,
_ => {} _ => {},
}; };
Ok(None) Ok(None)
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, place] = super::explorer_era_info_layout(area); let [_, place] = super::layouts::explorer_era_info_layout(area);
let current_time = std::time::SystemTime::now() let current_time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH) .duration_since(std::time::UNIX_EPOCH)

View File

@ -1,7 +1,7 @@
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
use std::usize; use std::usize;
use color_eyre::Result; use color_eyre::Result;
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::KeyEvent;
use ratatui::layout::{Constraint, Margin}; use ratatui::layout::{Constraint, Margin};
use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation}; use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation};
use ratatui::{ use ratatui::{
@ -10,19 +10,16 @@ use ratatui::{
widgets::{Block, ScrollbarState, Cell, Row, Table, TableState}, widgets::{Block, ScrollbarState, Cell, Row, Table, TableState},
Frame Frame
}; };
use tokio::sync::mpsc::UnboundedSender;
use super::{Component, CurrentTab, PartialComponent}; use super::{Component, CurrentTab};
use crate::{ use crate::{
components::generic::{Activatable, Scrollable, PartialComponent},
types::CasperExtrinsicDetails, types::CasperExtrinsicDetails,
action::Action, action::Action, config::Config, palette::StylePalette,
config::Config,
palette::StylePalette,
}; };
pub struct ExtrinsicExplorer { pub struct ExtrinsicExplorer {
is_active: bool, is_active: bool,
action_tx: Option<UnboundedSender<Action>>,
extrinsics: HashMap<u32, Vec<CasperExtrinsicDetails>>, extrinsics: HashMap<u32, Vec<CasperExtrinsicDetails>>,
current_extrinsics: Option<Vec<CasperExtrinsicDetails>>, current_extrinsics: Option<Vec<CasperExtrinsicDetails>>,
block_numbers: VecDeque<u32>, block_numbers: VecDeque<u32>,
@ -37,13 +34,58 @@ impl Default for ExtrinsicExplorer {
} }
} }
impl Activatable for ExtrinsicExplorer {
fn is_active(&self) -> bool { self.is_active }
fn is_inactive(&self) -> bool { !self.is_active }
fn set_inactive(&mut self) { self.is_active = false; }
fn set_active(&mut self) { self.is_active = true; }
}
impl Scrollable for ExtrinsicExplorer {
type IndexType = usize;
fn selected_index(&self) -> Option<Self::IndexType> {
self.table_state.selected()
}
fn items_length(&self) -> Self::IndexType {
self.current_extrinsics
.as_ref()
.map(|exts| exts.len())
.unwrap_or_default()
}
fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>> {
self.table_state.select(Some(new_index));
self.scroll_state = self.scroll_state.position(new_index);
self.send_used_explorer_log(new_index)
}
fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>> {
self.apply_next_row(new_index)
}
fn apply_first_row(&mut self) -> Result<Option<Action>> {
match self.items_length() > 0 {
true => self.apply_next_row(0),
false => Ok(None),
}
}
fn apply_last_row(&mut self) -> Result<Option<Action>> {
match self.items_length().checked_sub(1) {
Some(last_idx) => self.apply_next_row(last_idx),
None => Ok(None),
}
}
}
impl ExtrinsicExplorer { impl ExtrinsicExplorer {
const MAX_BLOCKS: usize = 50; const MAX_BLOCKS: usize = 50;
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
is_active: false, is_active: false,
action_tx: None,
current_extrinsics: None, current_extrinsics: None,
extrinsics: Default::default(), extrinsics: Default::default(),
block_numbers: Default::default(), block_numbers: Default::default(),
@ -53,6 +95,20 @@ impl ExtrinsicExplorer {
} }
} }
fn update_used_explorer_block(&mut self, block_number: u32) {
let maybe_exts = self.extrinsics
.get(&block_number)
.map(|exts| exts.to_vec());
let exts_length = self.extrinsics
.get(&block_number)
.map(|exts| exts.len())
.unwrap_or_default();
self.current_extrinsics = maybe_exts;
self.scroll_state = self.scroll_state.content_length(exts_length);
}
fn update_extrinsics_for_header( fn update_extrinsics_for_header(
&mut self, &mut self,
block_number: u32, block_number: u32,
@ -68,109 +124,30 @@ impl ExtrinsicExplorer {
} }
} }
fn send_used_explorer_log(&mut self, index: usize) { fn send_used_explorer_log(&mut self, index: usize) -> Result<Option<Action>> {
if let Some(action_tx) = &self.action_tx {
let maybe_log = self.current_extrinsics let maybe_log = self.current_extrinsics
.as_ref() .as_ref()
.map(|ext| { .map(|ext| ext.get(index).map(|ext| {
ext.get(index).map(|ext| {
hex::encode(&ext.field_bytes.clone()) hex::encode(&ext.field_bytes.clone())
}) }))
})
.flatten(); .flatten();
let _ = action_tx.send(Action::UsedExplorerLog(maybe_log.clone())); Ok(Some(Action::UsedExplorerLog(maybe_log.clone())))
} }
} }
fn first_row(&mut self) { impl PartialComponent<CurrentTab> for ExtrinsicExplorer {
match &self.current_extrinsics { fn set_active_tab(&mut self, current_tab: CurrentTab) {
Some(exts) if exts.len() > 0 => self.table_state.select(Some(0)),
_ => self.table_state.select(None),
}
self.scroll_state = self.scroll_state.position(0);
self.send_used_explorer_log(0);
}
fn next_row(&mut self) {
match &self.current_extrinsics {
Some(exts) => {
let i = match self.table_state.selected() {
Some(i) => {
if i >= exts.len() - 1 {
i
} else {
i + 1
}
},
None => 0,
};
self.table_state.select(Some(i));
self.scroll_state = self.scroll_state.position(i);
self.send_used_explorer_log(i);
},
None => {
self.table_state.select(None);
self.scroll_state = self.scroll_state.position(0);
self.send_used_explorer_log(0);
}
}
}
fn last_row(&mut self) {
match &self.current_extrinsics {
Some(exts) => {
let last = exts.len().saturating_sub(1);
self.table_state.select(Some(last));
self.scroll_state = self.scroll_state.position(last);
self.send_used_explorer_log(last);
},
None => {
self.table_state.select(None);
self.scroll_state = self.scroll_state.position(0);
self.send_used_explorer_log(0);
}
}
}
fn previous_row(&mut self) {
match &self.current_extrinsics {
Some(_) => {
let i = self.table_state
.selected()
.map(|i| i.saturating_sub(1))
.unwrap_or_default();
self.table_state.select(Some(i));
self.scroll_state = self.scroll_state.position(i);
self.send_used_explorer_log(i);
},
None => {
self.table_state.select(None);
self.scroll_state = self.scroll_state.position(0);
self.send_used_explorer_log(0);
}
}
}
}
impl PartialComponent for ExtrinsicExplorer {
fn set_active(&mut self, current_tab: CurrentTab) {
if current_tab == CurrentTab::Extrinsics { if current_tab == CurrentTab::Extrinsics {
self.is_active = true; self.set_active();
} else { } else {
self.is_active = false; self.set_inactive();
self.table_state.select(None); self.table_state.select(None);
self.scroll_state = self.scroll_state.position(0); self.scroll_state = self.scroll_state.position(0);
self.send_used_explorer_log(usize::MAX);
} }
} }
} }
impl Component for ExtrinsicExplorer { impl Component for ExtrinsicExplorer {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.action_tx = Some(tx);
Ok(())
}
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) { if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
self.palette.with_normal_style(style.get("normal_style").copied()); self.palette.with_normal_style(style.get("normal_style").copied());
@ -187,16 +164,8 @@ impl Component for ExtrinsicExplorer {
fn update(&mut self, action: Action) -> Result<Option<Action>> { fn update(&mut self, action: Action) -> Result<Option<Action>> {
match action { match action {
Action::UsedExplorerBlock(maybe_block_number) => { Action::UsedExplorerBlock(maybe_block_number) =>
let block_number = maybe_block_number.unwrap_or_default(); self.update_used_explorer_block(maybe_block_number.unwrap_or_default()),
if let Some(exts) = self.extrinsics.get(&block_number) {
self.current_extrinsics = Some(exts.to_vec());
self.scroll_state = self.scroll_state.content_length(exts.len());
} else {
self.current_extrinsics = None;
self.scroll_state = self.scroll_state.content_length(0);
}
},
Action::ExtrinsicsForBlock(block_number, extrinsics) => Action::ExtrinsicsForBlock(block_number, extrinsics) =>
self.update_extrinsics_for_header(block_number, extrinsics), self.update_extrinsics_for_header(block_number, extrinsics),
_ => {}, _ => {},
@ -205,21 +174,15 @@ impl Component for ExtrinsicExplorer {
} }
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> { fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
match key.code { match self.is_active() {
KeyCode::Char('k') | KeyCode::Up if self.is_active => self.previous_row(), true => self.handle_scrollable_key_codes(key.code),
KeyCode::Char('j') | KeyCode::Down if self.is_active => self.next_row(), false => Ok(None),
KeyCode::Char('g') if self.is_active => self.first_row(), }
KeyCode::Char('G') if self.is_active => self.last_row(),
_ => {},
};
Ok(None)
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, place] = super::explorer_scrollbars_layout(area); let [_, place] = super::layouts::explorer_scrollbars_layout(area);
let (border_style, border_type) = self.palette.create_border_style(self.is_active());
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
let mut longest_pallet_name_length = 0; let mut longest_pallet_name_length = 0;
let mut longest_variant_name_length = 0; let mut longest_variant_name_length = 0;

View File

@ -7,8 +7,11 @@ use ratatui::{
Frame, Frame,
}; };
use super::{Component, PartialComponent, CurrentTab}; use super::{Component, CurrentTab};
use crate::{palette::StylePalette, config::Config, action::Action}; use crate::{
components::generic::PartialComponent,
palette::StylePalette, config::Config, action::Action,
};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct ExtrinsicsChart { pub struct ExtrinsicsChart {
@ -70,9 +73,7 @@ impl ExtrinsicsChart {
} }
} }
impl PartialComponent for ExtrinsicsChart { impl PartialComponent<CurrentTab> for ExtrinsicsChart {}
fn set_active(&mut self, _current_tab: CurrentTab) {}
}
impl Component for ExtrinsicsChart { impl Component for ExtrinsicsChart {
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
@ -96,7 +97,7 @@ impl Component for ExtrinsicsChart {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, place] = super::explorer_header_layout(area); let [_, place] = super::layouts::explorer_header_layout(area);
frame.render_widget(self.extrinsics_bar_chart(place.as_size().width), place); frame.render_widget(self.extrinsics_bar_chart(place.as_size().width), place);
Ok(()) Ok(())
} }

View File

@ -5,11 +5,10 @@ use ratatui::{
Frame, Frame,
}; };
use super::{Component, PartialComponent, CurrentTab}; use super::{Component, CurrentTab};
use crate::{ use crate::{
config::Config, components::generic::PartialComponent,
action::Action, config::Config, action::Action, palette::StylePalette,
palette::StylePalette,
widgets::{PixelSize, BigText}, widgets::{PixelSize, BigText},
}; };
@ -26,9 +25,7 @@ impl FinalizedBlock {
} }
} }
impl PartialComponent for FinalizedBlock { impl PartialComponent<CurrentTab> for FinalizedBlock {}
fn set_active(&mut self, _current_tab: CurrentTab) {}
}
impl Component for FinalizedBlock { impl Component for FinalizedBlock {
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
@ -52,7 +49,7 @@ impl Component for FinalizedBlock {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, _, place] = super::explorer_block_info_layout(area); let [_, _, place] = super::layouts::explorer_block_info_layout(area);
let text = self.number.to_string(); let text = self.number.to_string();
let (border_style, border_type) = self.palette.create_border_style(false); let (border_style, border_type) = self.palette.create_border_style(false);

View File

@ -5,11 +5,10 @@ use ratatui::{
Frame, Frame,
}; };
use super::{Component, PartialComponent, CurrentTab}; use super::{Component, CurrentTab};
use crate::{ use crate::{
config::Config, components::generic::PartialComponent,
action::Action, config::Config, action::Action, palette::StylePalette,
palette::StylePalette,
widgets::{PixelSize, BigText}, widgets::{PixelSize, BigText},
}; };
@ -26,9 +25,7 @@ impl LatestBlock {
} }
} }
impl PartialComponent for LatestBlock { impl PartialComponent<CurrentTab> for LatestBlock {}
fn set_active(&mut self, _current_tab: CurrentTab) {}
}
impl Component for LatestBlock { impl Component for LatestBlock {
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
@ -52,7 +49,7 @@ impl Component for LatestBlock {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, place, _] = super::explorer_block_info_layout(area); let [_, place, _] = super::layouts::explorer_block_info_layout(area);
let text = self.number.to_string(); let text = self.number.to_string();
let (border_style, border_type) = self.palette.create_border_style(false); let (border_style, border_type) = self.palette.create_border_style(false);

View File

@ -0,0 +1,50 @@
use ratatui::layout::{Constraint, Flex, Layout, Rect};
pub fn explorer_layout(area: Rect) -> [Rect; 3] {
Layout::vertical([
Constraint::Percentage(30),
Constraint::Percentage(40),
Constraint::Percentage(30),
]).areas(area)
}
pub fn explorer_header_layout(area: Rect) -> [Rect; 2] {
let [header, _, _] = explorer_layout(area);
Layout::horizontal([
Constraint::Percentage(50),
Constraint::Percentage(50),
]).areas(header)
}
pub fn explorer_info_layout(area: Rect) -> [Rect; 2] {
let [info, _] = explorer_header_layout(area);
Layout::vertical([
Constraint::Percentage(100),
Constraint::Percentage(100),
]).areas(info)
}
pub fn explorer_block_info_layout(area: Rect) -> [Rect; 3] {
let [blocks, _] = explorer_info_layout(area);
Layout::horizontal([
Constraint::Percentage(33),
Constraint::Percentage(33),
Constraint::Percentage(33),
]).flex(Flex::SpaceBetween).areas(blocks)
}
pub fn explorer_era_info_layout(area: Rect) -> [Rect; 2] {
let [_, blocks] = explorer_info_layout(area);
Layout::horizontal([
Constraint::Percentage(50),
Constraint::Percentage(50),
]).flex(Flex::SpaceBetween).areas(blocks)
}
pub fn explorer_scrollbars_layout(area: Rect) -> [Rect; 2] {
let [_, place, _] = explorer_layout(area);
Layout::horizontal([
Constraint::Percentage(50),
Constraint::Percentage(50),
]).areas(place)
}

View File

@ -6,38 +6,19 @@ use ratatui::{
Frame Frame
}; };
use super::{Component, CurrentTab, PartialComponent}; use super::{Component, CurrentTab};
use crate::{ use crate::{
action::Action, components::generic::PartialComponent,
config::Config, action::Action, config::Config, palette::StylePalette
palette::StylePalette,
}; };
#[derive(Default)]
pub struct LogExplorer { pub struct LogExplorer {
is_active: bool,
maybe_log: Option<String>, maybe_log: Option<String>,
palette: StylePalette, palette: StylePalette,
} }
impl Default for LogExplorer { impl PartialComponent<CurrentTab> for LogExplorer {}
fn default() -> Self {
Self::new()
}
}
impl LogExplorer {
pub fn new() -> Self {
Self {
is_active: false,
maybe_log: None,
palette: StylePalette::default(),
}
}
}
impl PartialComponent for LogExplorer {
fn set_active(&mut self, _current_tab: CurrentTab) {}
}
impl Component for LogExplorer { impl Component for LogExplorer {
fn register_config_handler(&mut self, config: Config) -> Result<()> { fn register_config_handler(&mut self, config: Config) -> Result<()> {
@ -58,9 +39,9 @@ impl Component for LogExplorer {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, _, place] = super::explorer_layout(area); let [_, _, place] = super::layouts::explorer_layout(area);
let (border_style, border_type) = self.palette.create_border_style(self.is_active); let (border_style, border_type) = self.palette.create_border_style(false);
let line = match &self.maybe_log { let line = match &self.maybe_log {
Some(log) => Line::from(hex::encode(log)), Some(log) => Line::from(hex::encode(log)),

View File

@ -1,14 +1,15 @@
use color_eyre::Result; use color_eyre::Result;
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{ use ratatui::{layout::Rect, Frame};
layout::{Constraint, Flex, Layout, Rect},
Frame,
};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use super::Component; use super::{
generic::{Activatable, PartialComponent},
Component,
};
use crate::{action::Action, app::Mode, config::Config}; use crate::{action::Action, app::Mode, config::Config};
pub mod layouts;
mod latest_block; mod latest_block;
mod finalized_block; mod finalized_block;
mod block_ticker; mod block_ticker;
@ -29,21 +30,17 @@ use block_explorer::BlockExplorer;
use extrinsic_explorer::ExtrinsicExplorer; use extrinsic_explorer::ExtrinsicExplorer;
use log_explorer::LogExplorer; use log_explorer::LogExplorer;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum CurrentTab { pub enum CurrentTab {
Nothing, Nothing,
Blocks, Blocks,
Extrinsics, Extrinsics,
} }
pub trait PartialComponent: Component {
fn set_active(&mut self, current_tab: CurrentTab);
}
pub struct Explorer { pub struct Explorer {
is_active: bool, is_active: bool,
current_tab: CurrentTab, current_tab: CurrentTab,
components: Vec<Box<dyn PartialComponent>> components: Vec<Box<dyn PartialComponent<CurrentTab>>>
} }
impl Default for Explorer { impl Default for Explorer {
@ -82,10 +79,15 @@ impl Explorer {
} }
} }
impl PartialComponent for Explorer { impl Activatable for Explorer {
fn set_active(&mut self, _current_tab: CurrentTab) {} fn is_active(&self) -> bool { self.is_active }
fn is_inactive(&self) -> bool { !self.is_active }
fn set_inactive(&mut self) { self.is_active = false; }
fn set_active(&mut self) { self.is_active = true; }
} }
impl PartialComponent<CurrentTab> for Explorer {}
impl Component for Explorer { impl Component for Explorer {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> { fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
@ -104,31 +106,35 @@ impl Component for Explorer {
} }
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> { fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
if !self.is_active { return Ok(None); } if self.is_inactive() { return Ok(None); }
match key.code { match key.code {
KeyCode::Esc => { KeyCode::Esc => {
self.is_active = false; self.set_inactive();
self.current_tab = CurrentTab::Nothing; self.current_tab = CurrentTab::Nothing;
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone()); component.set_active_tab(self.current_tab);
} }
return Ok(Some(Action::SetActiveScreen(Mode::Menu))); return Ok(Some(Action::SetActiveScreen(Mode::Menu)));
}, },
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => { KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right => {
self.move_right(); self.move_right();
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone()); component.set_active_tab(self.current_tab);
} }
}, },
KeyCode::Char('h') | KeyCode::Left => { KeyCode::Char('h') | KeyCode::Left => {
self.move_left(); self.move_left();
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone()); component.set_active_tab(self.current_tab);
} }
}, },
_ => { _ => {
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.handle_key_event(key)?; let maybe_action = component.handle_key_event(key);
if let Ok(Some(_)) = maybe_action {
return maybe_action;
}
} }
}, },
} }
@ -137,10 +143,10 @@ impl Component for Explorer {
fn update(&mut self, action: Action) -> Result<Option<Action>> { fn update(&mut self, action: Action) -> Result<Option<Action>> {
if let Action::SetActiveScreen(Mode::Explorer) = action { if let Action::SetActiveScreen(Mode::Explorer) = action {
self.is_active = true; self.set_active();
self.current_tab = CurrentTab::Blocks; self.current_tab = CurrentTab::Blocks;
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.set_active(self.current_tab.clone()); component.set_active_tab(self.current_tab);
} }
} }
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
@ -150,59 +156,10 @@ impl Component for Explorer {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let screen = super::screen_layout(area); let screen = super::layouts::screen_layout(area);
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.draw(frame, screen)?; component.draw(frame, screen)?;
} }
Ok(()) Ok(())
} }
} }
pub fn explorer_layout(area: Rect) -> [Rect; 3] {
Layout::vertical([
Constraint::Percentage(30),
Constraint::Percentage(40),
Constraint::Percentage(30),
]).areas(area)
}
pub fn explorer_header_layout(area: Rect) -> [Rect; 2] {
let [header, _, _] = explorer_layout(area);
Layout::horizontal([
Constraint::Percentage(50),
Constraint::Percentage(50),
]).areas(header)
}
pub fn explorer_info_layout(area: Rect) -> [Rect; 2] {
let [info, _] = explorer_header_layout(area);
Layout::vertical([
Constraint::Percentage(100),
Constraint::Percentage(100),
]).areas(info)
}
pub fn explorer_block_info_layout(area: Rect) -> [Rect; 3] {
let [blocks, _] = explorer_info_layout(area);
Layout::horizontal([
Constraint::Percentage(33),
Constraint::Percentage(33),
Constraint::Percentage(33),
]).flex(Flex::SpaceBetween).areas(blocks)
}
pub fn explorer_era_info_layout(area: Rect) -> [Rect; 2] {
let [_, blocks] = explorer_info_layout(area);
Layout::horizontal([
Constraint::Percentage(50),
Constraint::Percentage(50),
]).flex(Flex::SpaceBetween).areas(blocks)
}
pub fn explorer_scrollbars_layout(area: Rect) -> [Rect; 2] {
let [_, place, _] = explorer_layout(area);
Layout::horizontal([
Constraint::Percentage(50),
Constraint::Percentage(50),
]).areas(place)
}

View File

@ -75,7 +75,7 @@ impl Component for FpsCounter {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, place] = super::header_layout(area); let [_, place] = super::layouts::header_layout(area);
let message = format!( let message = format!(
" {:.2} ticks/sec | {:.2} FPS", " {:.2} ticks/sec | {:.2} FPS",

View File

@ -1,7 +1,9 @@
mod activatable; mod activatable;
mod helpable; mod helpable;
mod scrollable; mod scrollable;
mod partial;
pub use activatable::Activatable; pub use activatable::Activatable;
pub use helpable::Helpable; pub use helpable::Helpable;
pub use scrollable::Scrollable; pub use scrollable::Scrollable;
pub use partial::PartialComponent;

View File

@ -0,0 +1,8 @@
use crate::components::Component;
pub trait PartialComponent<TabType>: Component
where
TabType: std::fmt::Debug + Clone + Copy + PartialEq,
{
fn set_active_tab(&mut self, _current_tab: TabType) {}
}

View File

@ -1,6 +1,7 @@
use std::{ use std::{
cmp::{PartialEq, PartialOrd}, ops::{Add, Sub} cmp::{PartialEq, PartialOrd}, ops::{Add, Sub}
}; };
use crossterm::event::KeyCode;
use color_eyre::Result; use color_eyre::Result;
use crate::action::Action; use crate::action::Action;
@ -13,12 +14,15 @@ pub trait Scrollable {
+ From<u8> + From<u8>
+ Copy; + Copy;
fn apply_next_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>>;
fn apply_prev_row(&mut self, new_index: Self::IndexType) -> Result<Option<Action>>;
fn selected_index(&self) -> Option<Self::IndexType>; fn selected_index(&self) -> Option<Self::IndexType>;
fn items_length(&self) -> Self::IndexType; fn items_length(&self) -> Self::IndexType;
fn apply_next_row(&mut self, _new_index: Self::IndexType) -> Result<Option<Action>> { Ok(None) }
fn apply_prev_row(&mut self, _new_index: Self::IndexType) -> Result<Option<Action>> { Ok(None) }
fn apply_first_row(&mut self) -> Result<Option<Action>> { Ok(None) }
fn apply_last_row(&mut self) -> Result<Option<Action>> { Ok(None) }
fn next_row(&mut self) -> Result<Option<Action>> { fn next_row(&mut self) -> Result<Option<Action>> {
let length = self.items_length(); let length = self.items_length();
if length == 0.into() { if length == 0.into() {
@ -55,4 +59,28 @@ pub trait Scrollable {
}; };
self.apply_prev_row(new_index) self.apply_prev_row(new_index)
} }
fn first_row(&mut self) -> Result<Option<Action>> {
if self.items_length() == 0.into() {
return Ok(None);
}
self.apply_first_row()
}
fn last_row(&mut self) -> Result<Option<Action>> {
if self.items_length() == 0.into() {
return Ok(None);
}
self.apply_last_row()
}
fn handle_scrollable_key_codes(&mut self, key_code: KeyCode) -> Result<Option<Action>> {
match key_code {
KeyCode::Char('k') | KeyCode::Up => self.prev_row(),
KeyCode::Char('j') | KeyCode::Down => self.next_row(),
KeyCode::Char('g') => self.first_row(),
KeyCode::Char('G') => self.last_row(),
_ => Ok(None),
}
}
} }

View File

@ -84,7 +84,7 @@ impl Component for Health {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [place, _] = super::header_layout(area); let [place, _] = super::layouts::header_layout(area);
let message = format!("{:^12} | tx.pool: {:^3} | peers: {:^3} | {:^9} | validators {:^4} | nominators {:^4} |", let message = format!("{:^12} | tx.pool: {:^3} | peers: {:^3} | {:^9} | validators {:^4} | nominators {:^4} |",
self.name_as_string(), self.name_as_string(),

37
src/components/layouts.rs Normal file
View File

@ -0,0 +1,37 @@
use ratatui::layout::{Constraint, Layout, Rect};
pub fn global_layout(area: Rect) -> [Rect; 2] {
Layout::vertical([
Constraint::Length(1),
Constraint::Fill(1),
]).areas(area)
}
pub fn header_layout(area: Rect) -> [Rect; 2] {
let [header, _] = global_layout(area);
Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(27),
]).areas(header)
}
pub fn main_layout(area: Rect) -> [Rect; 2] {
let [_, main] = global_layout(area);
Layout::horizontal([
Constraint::Max(30),
Constraint::Fill(1),
]).areas(main)
}
pub fn menu_layout(area: Rect) -> [Rect; 2] {
let [menu, _] = main_layout(area);
Layout::vertical([
Constraint::Min(0),
Constraint::Length(5),
]).areas(menu)
}
pub fn screen_layout(area: Rect) -> Rect {
let [_, screen] = main_layout(area);
screen
}

View File

@ -118,7 +118,7 @@ impl Component for Menu {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [menu, _] = super::menu_layout(area); let [menu, _] = super::layouts::menu_layout(area);
let (color, border_type) = self.palette.create_border_style(self.is_active()); let (color, border_type) = self.palette.create_border_style(self.is_active());
let block = Block::bordered() let block = Block::bordered()

View File

@ -1,7 +1,7 @@
use color_eyre::Result; use color_eyre::Result;
use crossterm::event::{KeyEvent, MouseEvent}; use crossterm::event::{KeyEvent, MouseEvent};
use ratatui::{ use ratatui::{
layout::{Constraint, Layout, Rect, Size}, layout::{Rect, Size},
Frame, Frame,
}; };
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
@ -9,6 +9,7 @@ use std::sync::mpsc::Sender;
use crate::{palette, action::Action, config::Config, tui::Event}; use crate::{palette, action::Action, config::Config, tui::Event};
pub mod layouts;
pub mod fps; pub mod fps;
pub mod health; pub mod health;
pub mod menu; pub mod menu;
@ -67,39 +68,3 @@ pub trait Component {
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()>; fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()>;
} }
pub fn global_layout(area: Rect) -> [Rect; 2] {
Layout::vertical([
Constraint::Length(1),
Constraint::Fill(1),
]).areas(area)
}
pub fn header_layout(area: Rect) -> [Rect; 2] {
let [header, _] = global_layout(area);
Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(27),
]).areas(header)
}
pub fn main_layout(area: Rect) -> [Rect; 2] {
let [_, main] = global_layout(area);
Layout::horizontal([
Constraint::Max(30),
Constraint::Fill(1),
]).areas(main)
}
pub fn menu_layout(area: Rect) -> [Rect; 2] {
let [menu, _] = main_layout(area);
Layout::vertical([
Constraint::Min(0),
Constraint::Length(5),
]).areas(menu)
}
pub fn screen_layout(area: Rect) -> Rect {
let [_, screen] = main_layout(area);
screen
}

View File

@ -277,7 +277,7 @@ impl Component for Validator {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let screen = super::screen_layout(area); let screen = super::layouts::screen_layout(area);
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.draw(frame, screen)?; component.draw(frame, screen)?;
} }

View File

@ -52,7 +52,7 @@ impl Component for Version {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let [_, version] = super::menu_layout(area); let [_, version] = super::layouts::menu_layout(area);
let text_style = self.palette.create_basic_style(false); let text_style = self.palette.create_basic_style(false);
let (border_style, border_type) = self.palette.create_border_style(false); let (border_style, border_type) = self.palette.create_border_style(false);

View File

@ -239,7 +239,7 @@ impl Component for Wallet {
} }
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
let screen = super::screen_layout(area); let screen = super::layouts::screen_layout(area);
for component in self.components.iter_mut() { for component in self.components.iter_mut() {
component.draw(frame, screen)?; component.draw(frame, screen)?;
} }