277 lines
9.5 KiB
Rust
277 lines
9.5 KiB
Rust
use color_eyre::Result;
|
|
use crossterm::event::KeyEvent;
|
|
use ratatui::layout::{Constraint, Margin};
|
|
use ratatui::widgets::{Padding, Scrollbar, ScrollbarOrientation};
|
|
use ratatui::{
|
|
text::Text,
|
|
layout::{Alignment, Rect},
|
|
widgets::{Block, ScrollbarState, Cell, Row, Table, TableState},
|
|
Frame
|
|
};
|
|
use subxt::utils::H256;
|
|
|
|
use super::{Component, CurrentTab};
|
|
use crate::{
|
|
components::generic::{Activatable, Scrollable, PartialComponent},
|
|
action::Action, config::Config, palette::StylePalette,
|
|
};
|
|
|
|
#[derive(Debug, Default)]
|
|
struct BlockInfo {
|
|
block_number: u32,
|
|
finalized: bool,
|
|
}
|
|
|
|
pub struct BlockExplorer {
|
|
is_active: bool,
|
|
blocks: std::collections::VecDeque<BlockInfo>,
|
|
block_headers: std::collections::HashMap<u32, H256>,
|
|
block_authors: std::collections::HashMap<H256, String>,
|
|
scroll_state: ScrollbarState,
|
|
table_state: TableState,
|
|
palette: StylePalette,
|
|
}
|
|
|
|
impl Default for BlockExplorer {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
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 {
|
|
const MAX_BLOCKS: usize = 50;
|
|
|
|
pub fn new() -> Self {
|
|
Self {
|
|
is_active: false,
|
|
blocks: Default::default(),
|
|
block_authors: Default::default(),
|
|
block_headers: Default::default(),
|
|
scroll_state: ScrollbarState::new(0),
|
|
table_state: TableState::new(),
|
|
palette: StylePalette::default(),
|
|
}
|
|
}
|
|
|
|
fn update_latest_block_info(
|
|
&mut self,
|
|
hash: H256,
|
|
block_number: u32,
|
|
) -> Result<Option<Action>> {
|
|
let front_block_number = self.blocks
|
|
.front()
|
|
.map(|block| block.block_number)
|
|
.unwrap_or_default();
|
|
|
|
if front_block_number < block_number {
|
|
self.blocks.push_front(BlockInfo {
|
|
block_number,
|
|
finalized: false,
|
|
});
|
|
|
|
self.block_headers.insert(block_number, hash);
|
|
if self.items_length() > Self::MAX_BLOCKS {
|
|
if let Some(block) = self.blocks.pop_back() {
|
|
if let Some(hash) = self.block_headers.remove(&block.block_number) {
|
|
self.block_authors.remove(&hash);
|
|
}
|
|
}
|
|
}
|
|
|
|
self.scroll_state = self.scroll_state.content_length(self.items_length());
|
|
return match self.table_state.selected() {
|
|
Some(_) => self.next_row(),
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
fn update_finalized_block_info(
|
|
&mut self,
|
|
header: H256,
|
|
block_number: u32,
|
|
) -> Result<Option<Action>> {
|
|
for idx in 0..self.items_length() {
|
|
if self.blocks[idx].finalized { break; }
|
|
else if self.blocks[idx].block_number > block_number { continue; }
|
|
else {
|
|
self.block_headers.insert(block_number, header);
|
|
self.blocks[idx].finalized = true;
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
fn send_used_explorer_block(&mut self, index: usize) -> Result<Option<Action>> {
|
|
let maybe_block_number = self.blocks.get(index).map(|info| info.block_number);
|
|
Ok(Some(Action::UsedExplorerBlock(maybe_block_number)))
|
|
}
|
|
}
|
|
|
|
impl PartialComponent<CurrentTab> for BlockExplorer {
|
|
fn set_active_tab(&mut self, current_tab: CurrentTab) {
|
|
match current_tab {
|
|
CurrentTab::Blocks => self.set_active(),
|
|
CurrentTab::Extrinsics => self.set_inactive(),
|
|
CurrentTab::Nothing => {
|
|
self.set_inactive();
|
|
self.table_state.select(None);
|
|
self.scroll_state = self.scroll_state.position(0);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Component for BlockExplorer {
|
|
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
|
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
|
|
self.palette.with_normal_style(style.get("normal_style").copied());
|
|
self.palette.with_hover_style(style.get("hover_style").copied());
|
|
self.palette.with_normal_border_style(style.get("normal_border_style").copied());
|
|
self.palette.with_hover_border_style(style.get("hover_border_style").copied());
|
|
self.palette.with_normal_title_style(style.get("normal_title_style").copied());
|
|
self.palette.with_hover_title_style(style.get("hover_title_style").copied());
|
|
self.palette.with_highlight_style(style.get("highlight_style").copied());
|
|
self.palette.with_scrollbar_style(style.get("scrollbar_style").copied());
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
|
match action {
|
|
Action::BestBlockInformation(header, block_number) => self.update_latest_block_info(header, block_number),
|
|
Action::FinalizedBlockInformation(header, block_number) => self.update_finalized_block_info(header, block_number),
|
|
Action::SetBlockAuthor(header, author) => {
|
|
let _ = self.block_authors.insert(header, author);
|
|
Ok(None)
|
|
},
|
|
_ => Ok(None),
|
|
}
|
|
}
|
|
|
|
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
|
match self.is_active() {
|
|
true => self.handle_scrollable_key_codes(key.code),
|
|
false => Ok(None),
|
|
}
|
|
}
|
|
|
|
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
|
let [place, _] = super::layouts::explorer_scrollbars_layout(area);
|
|
|
|
let (border_style, border_type) = self.palette.create_border_style(self.is_active());
|
|
let default_hash = H256::repeat_byte(69u8);
|
|
let default_author = "...".to_string();
|
|
let rows = self.blocks
|
|
.iter()
|
|
.map(|info| {
|
|
let header = self.block_headers
|
|
.get(&info.block_number)
|
|
.unwrap_or(&default_hash);
|
|
let author = self.block_authors
|
|
.get(&header)
|
|
.unwrap_or(&default_author);
|
|
|
|
if info.finalized {
|
|
Row::new(vec![
|
|
Cell::from(Text::from(info.block_number.to_string()).alignment(Alignment::Left)),
|
|
Cell::from(Text::from(header.to_string()).alignment(Alignment::Center)),
|
|
Cell::from(Text::from(author.clone()).alignment(Alignment::Right)),
|
|
]).style(self.palette.create_highlight_style())
|
|
} else {
|
|
Row::new(vec![
|
|
Cell::from(Text::from(info.block_number.to_string()).alignment(Alignment::Left)),
|
|
Cell::from(Text::from(header.to_string()).alignment(Alignment::Center)),
|
|
Cell::from(Text::from(author.clone()).alignment(Alignment::Right)),
|
|
])
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let max_block_number_length = self.blocks
|
|
.front()
|
|
.map(|block| block.block_number)
|
|
.unwrap_or_default()
|
|
.checked_ilog10()
|
|
.unwrap_or(0) as u16 + 1;
|
|
|
|
let table = Table::new(
|
|
rows,
|
|
[
|
|
Constraint::Max(max_block_number_length + 2),
|
|
Constraint::Min(15),
|
|
Constraint::Min(0),
|
|
],
|
|
)
|
|
.style(self.palette.create_basic_style(false))
|
|
.highlight_style(self.palette.create_basic_style(true))
|
|
.column_spacing(1)
|
|
.block(Block::bordered()
|
|
.border_style(border_style)
|
|
.border_type(border_type)
|
|
.padding(Padding::right(2))
|
|
.title_alignment(Alignment::Right)
|
|
.title_style(self.palette.create_title_style(false))
|
|
.title("Blocks"));
|
|
|
|
let scrollbar = Scrollbar::default()
|
|
.orientation(ScrollbarOrientation::VerticalRight)
|
|
.begin_symbol(None)
|
|
.end_symbol(None)
|
|
.style(self.palette.create_scrollbar_style());
|
|
|
|
frame.render_stateful_widget(table, place, &mut self.table_state);
|
|
frame.render_stateful_widget(
|
|
scrollbar,
|
|
place.inner(Margin { vertical: 1, horizontal: 1 }),
|
|
&mut self.scroll_state,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
}
|