fixes for active screen and wallet screen draft added
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
This commit is contained in:
parent
405061265b
commit
b3cebfa0a4
@ -21,13 +21,11 @@ human-panic = "2.0.2"
|
||||
json5 = "0.4.1"
|
||||
lazy_static = "1.5.0"
|
||||
libc = "0.2.159"
|
||||
primitive-types = "0.13.1"
|
||||
ratatui = { version = "0.28.1", features = ["serde", "macros"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
signal-hook = "0.3.17"
|
||||
sp-core = "34.0.0"
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
subxt = { version = "0.38.0", features = ["jsonrpsee"] }
|
||||
subxt = { version = "0.38.0", features = ["substrate-compat"] }
|
||||
tokio = { version = "1.40.0", features = ["full"] }
|
||||
tokio-util = "0.7.12"
|
||||
tracing = "0.1.37"
|
||||
|
@ -7,7 +7,7 @@
|
||||
"hover_border_style": "blue",
|
||||
"normal_title_style": "blue",
|
||||
"hover_title_style": "",
|
||||
"tagged_style": "yellow italic",
|
||||
"highlight_style": "yellow italic",
|
||||
},
|
||||
"Explorer": {
|
||||
"normal_style": "",
|
||||
@ -16,17 +16,33 @@
|
||||
"hover_border_style": "blue",
|
||||
"normal_title_style": "blue",
|
||||
"hover_title_style": "",
|
||||
"tagged_style": "yellow bold",
|
||||
"highlight_style": "yellow bold",
|
||||
"scrollbar_style": "white on blue",
|
||||
},
|
||||
"Wallet": {
|
||||
"normal_style": "",
|
||||
"hover_style": "bold yellow italic on blue",
|
||||
"normal_border_style": "blue",
|
||||
"hover_border_style": "blue",
|
||||
"normal_title_style": "blue",
|
||||
"hover_title_style": "",
|
||||
"highlight_style": "yellow bold",
|
||||
}
|
||||
},
|
||||
"keybindings": {
|
||||
"Menu": {
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend",
|
||||
},
|
||||
"Explorer": {
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend",
|
||||
},
|
||||
"ExplorerActive": {
|
||||
"Wallet": {
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
@ -38,11 +54,5 @@
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend",
|
||||
},
|
||||
"EmptyActive": {
|
||||
"<q>": "Quit",
|
||||
"<Ctrl-d>": "Quit",
|
||||
"<Ctrl-c>": "Quit",
|
||||
"<Ctrl-z>": "Suspend",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::Display;
|
||||
use primitive_types::H256;
|
||||
|
||||
use subxt::utils::H256;
|
||||
use subxt::config::substrate::DigestItem;
|
||||
|
||||
use crate::{
|
||||
CasperAccountId,
|
||||
types::{EraInfo, CasperExtrinsicDetails},
|
||||
};
|
||||
|
||||
use subxt::utils::AccountId32;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
|
||||
pub enum Action {
|
||||
Tick,
|
||||
@ -22,6 +23,10 @@ pub enum Action {
|
||||
Help,
|
||||
|
||||
SetMode(crate::app::Mode),
|
||||
SetActiveScreen(crate::app::Mode),
|
||||
UsedExplorerBlock(Option<u32>),
|
||||
UsedExplorerLog(Option<String>),
|
||||
UsedAccount(AccountId32),
|
||||
|
||||
NewBestBlock(u32),
|
||||
NewBestHash(H256),
|
||||
@ -31,7 +36,7 @@ pub enum Action {
|
||||
ExtrinsicsLength(u32, usize),
|
||||
|
||||
GetBlockAuthor(H256, Vec<DigestItem>),
|
||||
SetBlockAuthor(H256, Option<CasperAccountId>),
|
||||
SetBlockAuthor(H256, String),
|
||||
|
||||
GetNodeName,
|
||||
GetSystemHealth,
|
||||
@ -52,10 +57,16 @@ pub enum Action {
|
||||
SetChainName(Option<String>),
|
||||
SetChainVersion(Option<String>),
|
||||
|
||||
BestBlockInformation(H256, u32, Vec<CasperExtrinsicDetails>),
|
||||
FinalizedBlockInformation(H256, u32, Vec<CasperExtrinsicDetails>),
|
||||
BestBlockInformation(H256, u32),
|
||||
FinalizedBlockInformation(H256, u32),
|
||||
ExtrinsicsForBlock(u32, Vec<CasperExtrinsicDetails>),
|
||||
SetActiveEra(EraInfo),
|
||||
SetEpochProgress(u64, u64),
|
||||
SetValidatorsForExplorer(Vec<CasperAccountId>), // TODO: change to BlockAuthor
|
||||
SetPendingExtrinsicsLength(usize), // TODO: rename in oreder to match tx.pool
|
||||
SetPendingExtrinsicsLength(usize),
|
||||
|
||||
GetTotalIssuance,
|
||||
GetExistentialDeposit,
|
||||
|
||||
SetExistentialDeposit(u128),
|
||||
SetTotalIssuance(u128),
|
||||
}
|
||||
|
20
src/app.rs
20
src/app.rs
@ -11,14 +11,17 @@ use crate::{
|
||||
config::Config,
|
||||
tui::{Event, Tui},
|
||||
components::{
|
||||
menu::Menu, version::Version, explorer::Explorer, empty::Empty,
|
||||
health::Health, fps::FpsCounter, Component},
|
||||
menu::Menu, version::Version, explorer::Explorer, wallet::Wallet,
|
||||
empty::Empty, health::Health, fps::FpsCounter, Component,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum Mode {
|
||||
Menu,
|
||||
Explorer,
|
||||
Wallet,
|
||||
WalletActive,
|
||||
ExplorerActive,
|
||||
Empty,
|
||||
EmptyActive,
|
||||
@ -67,6 +70,7 @@ impl App {
|
||||
Box::new(Health::default()),
|
||||
Box::new(Version::default()),
|
||||
Box::new(Explorer::default()),
|
||||
Box::new(Wallet::default()),
|
||||
Box::new(Empty::default()),
|
||||
],
|
||||
should_quite: false,
|
||||
@ -163,6 +167,7 @@ impl App {
|
||||
self.network_tx.send(Action::GetGenesisHash)?;
|
||||
self.network_tx.send(Action::GetChainName)?;
|
||||
self.network_tx.send(Action::GetChainVersion)?;
|
||||
self.network_tx.send(Action::GetExistentialDeposit)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -226,7 +231,7 @@ impl App {
|
||||
}
|
||||
|
||||
match self.mode {
|
||||
Mode::Explorer | Mode::ExplorerActive => {
|
||||
Mode::Explorer => {
|
||||
if let Some(component) = self.components.get_mut(4) {
|
||||
if let Err(err) = component.draw(frame, frame.area()) {
|
||||
let _ = self
|
||||
@ -235,6 +240,15 @@ impl App {
|
||||
}
|
||||
}
|
||||
},
|
||||
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)));
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
if let Some(component) = self.components.last_mut() {
|
||||
if let Err(err) = component.draw(frame, frame.area()) {
|
||||
|
@ -20,7 +20,8 @@ impl Config for CasperConfig {
|
||||
type AssetId = u32;
|
||||
}
|
||||
|
||||
|
||||
/// A 32-byte cryptographic identifier. This is a simplified version of
|
||||
/// `sp_core::crypto::AccountId32`.
|
||||
pub type CasperAccountId = subxt::utils::AccountId32;
|
||||
pub type CasperBlock = Block<CasperConfig, OnlineClient<CasperConfig>>;
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
use color_eyre::Result;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use crossterm::event::{KeyEvent, KeyCode};
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
text::Line,
|
||||
@ -14,6 +16,7 @@ use crate::{
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Empty {
|
||||
is_active: bool,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
}
|
||||
|
||||
impl Empty {
|
||||
@ -84,27 +87,33 @@ impl Empty {
|
||||
]
|
||||
}
|
||||
|
||||
fn set_active(&mut self) -> Result<()> {
|
||||
self.is_active = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unset_active(&mut self) -> Result<()> {
|
||||
fn move_out(&mut self) -> Result<Option<Action>> {
|
||||
self.is_active = false;
|
||||
Ok(())
|
||||
Ok(Some(Action::SetActiveScreen(Mode::Menu)))
|
||||
}
|
||||
}
|
||||
|
||||
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>> {
|
||||
match action {
|
||||
Action::SetMode(Mode::EmptyActive) if !self.is_active => self.set_active()?,
|
||||
Action::SetMode(_) if self.is_active => self.unset_active()?,
|
||||
Action::SetActiveScreen(Mode::Empty) => self.is_active = true,
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Esc if self.is_active => self.move_out(),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let screen = super::screen_layout(area);
|
||||
|
||||
|
296
src/components/explorer/block_explorer.rs
Normal file
296
src/components/explorer/block_explorer.rs
Normal file
@ -0,0 +1,296 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, 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 tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct BlockInfo {
|
||||
block_number: u32,
|
||||
finalized: bool,
|
||||
}
|
||||
|
||||
pub struct BlockExplorer {
|
||||
is_active: bool,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
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 BlockExplorer {
|
||||
const MAX_BLOCKS: usize = 50;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
blocks: Default::default(),
|
||||
action_tx: None,
|
||||
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,
|
||||
) {
|
||||
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.blocks.len() > 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.blocks.len());
|
||||
if self.table_state.selected().is_some() {
|
||||
self.next_row();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_finalized_block_info(
|
||||
&mut self,
|
||||
header: H256,
|
||||
block_number: u32,
|
||||
) {
|
||||
for idx in 0..self.blocks.len() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_used_explorer_block(&mut self, index: usize) {
|
||||
if let Some(action_tx) = &self.action_tx {
|
||||
let maybe_block_number = self.blocks.get(index).map(|info| info.block_number);
|
||||
let _ = action_tx.send(Action::UsedExplorerBlock(maybe_block_number));
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.blocks.len() > 0 {
|
||||
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 {
|
||||
CurrentTab::Blocks => self.is_active = true,
|
||||
CurrentTab::Extrinsics => self.is_active = false,
|
||||
CurrentTab::Nothing => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
self.send_used_explorer_block(usize::MAX);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<()> {
|
||||
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)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Char('k') | KeyCode::Up if self.is_active => self.previous_row(),
|
||||
KeyCode::Char('j') | KeyCode::Down if self.is_active => self.next_row(),
|
||||
KeyCode::Char('K') if self.is_active => self.first_row(),
|
||||
KeyCode::Char('J') if self.is_active => self.last_row(),
|
||||
_ => {},
|
||||
};
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [place, _] = super::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(())
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ use ratatui::{
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::Component;
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
config::Config,
|
||||
widgets::{BigText, PixelSize},
|
||||
@ -46,6 +46,10 @@ impl BlockTicker {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for BlockTicker {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for BlockTicker {
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
@ -102,7 +106,7 @@ impl Component for BlockTicker {
|
||||
let big_text = BigText::builder()
|
||||
.centered()
|
||||
.pixel_size(PixelSize::Quadrant)
|
||||
.style(self.palette.create_text_style(false))
|
||||
.style(self.palette.create_basic_style(false))
|
||||
.lines(vec![
|
||||
text.into(),
|
||||
])
|
||||
|
@ -6,7 +6,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::Component;
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
config::Config,
|
||||
action::Action,
|
||||
@ -34,6 +34,10 @@ impl CurrentEpoch {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for CurrentEpoch {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for CurrentEpoch {
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
@ -97,7 +101,7 @@ impl Component for CurrentEpoch {
|
||||
let big_text = BigText::builder()
|
||||
.centered()
|
||||
.pixel_size(PixelSize::Quadrant)
|
||||
.style(self.palette.create_text_style(false))
|
||||
.style(self.palette.create_basic_style(false))
|
||||
.lines(vec![
|
||||
text.into(),
|
||||
])
|
||||
|
@ -6,7 +6,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::Component;
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
config::Config,
|
||||
action::Action,
|
||||
@ -35,6 +35,10 @@ impl CurrentEra {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for CurrentEra {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for CurrentEra {
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
@ -111,7 +115,7 @@ impl Component for CurrentEra {
|
||||
let big_text = BigText::builder()
|
||||
.centered()
|
||||
.pixel_size(PixelSize::Quadrant)
|
||||
.style(self.palette.create_text_style(false))
|
||||
.style(self.palette.create_basic_style(false))
|
||||
.lines(vec![
|
||||
text.into(),
|
||||
])
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use primitive_types::H256;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
prelude::*,
|
||||
@ -9,7 +8,8 @@ use ratatui::{
|
||||
widgets::{Block, BorderType, Paragraph},
|
||||
Frame
|
||||
};
|
||||
use sp_core::crypto::{AccountId32, Ss58Codec, Ss58AddressFormat};
|
||||
use subxt::ext::sp_core::crypto::{Ss58Codec, Ss58AddressFormat};
|
||||
use subxt::utils::H256;
|
||||
use codec::Decode;
|
||||
|
||||
use super::Component;
|
||||
@ -126,9 +126,10 @@ impl ExplorerBlocks {
|
||||
.authors
|
||||
.get(&hash)
|
||||
.map_or(String::from("..."), |author| {
|
||||
let extended_author = AccountId32::decode(&mut author.as_ref())
|
||||
let extended_author = CasperAccountId::decode(&mut author.as_ref())
|
||||
.expect("author should be valid AccountId32; qed");
|
||||
extended_author.to_ss58check_with_version(Ss58AddressFormat::custom(1996))
|
||||
let account_id = subxt::ext::sp_core::crypto::AccountId32::from(extended_author.0);
|
||||
account_id.to_ss58check_with_version(Ss58AddressFormat::custom(1996))
|
||||
});
|
||||
|
||||
if free_space < Self::LENGTH_OF_BLOCK_HASH + Self::LENGTH_OF_ADDRESS {
|
||||
@ -177,7 +178,7 @@ impl ExplorerBlocks {
|
||||
|
||||
let normal_style = self.palette.create_text_style(false);
|
||||
let active_style = self.palette.create_text_style(true);
|
||||
let finalized_style = self.palette.create_tagged_style();
|
||||
let finalized_style = self.palette.create_highlight_style();
|
||||
|
||||
for (idx, current_block_info) in self.blocks.iter().skip(start_index).enumerate() {
|
||||
if idx == total_length { break; }
|
||||
@ -459,7 +460,7 @@ impl Component for ExplorerBlocks {
|
||||
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_tagged_style(style.get("tagged_style").copied());
|
||||
self.palette.with_highlight_style(style.get("highlight_style").copied());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -499,7 +500,6 @@ impl Component for ExplorerBlocks {
|
||||
|
||||
let (border_style_block, border_type_block) = self.palette.create_border_style(self.is_active && self.used_paragraph_index == 0);
|
||||
let (border_style_extrinsics, border_type_extrinsics) = self.palette.create_border_style(self.is_active && self.used_paragraph_index == 1);
|
||||
// TODO: never used, revisit
|
||||
let (border_style_event, border_type_event) = self.palette.create_border_style(self.is_active && self.used_paragraph_index == 2);
|
||||
|
||||
frame.render_widget(self.prepare_blocks_paragraph(blocks_place, border_style_block, border_type_block), blocks_place);
|
||||
|
280
src/components/explorer/extrinsic_explorer.rs
Normal file
280
src/components/explorer/extrinsic_explorer.rs
Normal file
@ -0,0 +1,280 @@
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::usize;
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, 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 tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::{Component, CurrentTab, PartialComponent};
|
||||
use crate::{
|
||||
types::CasperExtrinsicDetails,
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct ExtrinsicExplorer {
|
||||
is_active: bool,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
extrinsics: HashMap<u32, Vec<CasperExtrinsicDetails>>,
|
||||
current_extrinsics: Option<Vec<CasperExtrinsicDetails>>,
|
||||
block_numbers: VecDeque<u32>,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
palette: StylePalette,
|
||||
}
|
||||
|
||||
impl Default for ExtrinsicExplorer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtrinsicExplorer {
|
||||
const MAX_BLOCKS: usize = 50;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
action_tx: None,
|
||||
current_extrinsics: None,
|
||||
extrinsics: Default::default(),
|
||||
block_numbers: Default::default(),
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_extrinsics_for_header(
|
||||
&mut self,
|
||||
block_number: u32,
|
||||
extrinsics: Vec<CasperExtrinsicDetails>,
|
||||
) {
|
||||
self.extrinsics.insert(block_number, extrinsics);
|
||||
self.block_numbers.push_front(block_number);
|
||||
|
||||
if self.block_numbers.len() > Self::MAX_BLOCKS {
|
||||
if let Some(remove_block_number) = self.block_numbers.pop_back() {
|
||||
let _ = self.extrinsics.remove(&remove_block_number);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_used_explorer_log(&mut self, index: usize) {
|
||||
if let Some(action_tx) = &self.action_tx {
|
||||
let maybe_log = self.current_extrinsics
|
||||
.as_ref()
|
||||
.map(|ext| {
|
||||
ext.get(index).map(|ext| {
|
||||
hex::encode(&ext.field_bytes.clone())
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
let _ = action_tx.send(Action::UsedExplorerLog(maybe_log.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
match &self.current_extrinsics {
|
||||
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 {
|
||||
self.is_active = true;
|
||||
} else {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
self.send_used_explorer_log(usize::MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<()> {
|
||||
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::UsedExplorerBlock(maybe_block_number) => {
|
||||
let block_number = 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) =>
|
||||
self.update_extrinsics_for_header(block_number, extrinsics),
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Char('k') | KeyCode::Up if self.is_active => self.previous_row(),
|
||||
KeyCode::Char('j') | KeyCode::Down if self.is_active => self.next_row(),
|
||||
KeyCode::Char('K') if self.is_active => self.first_row(),
|
||||
KeyCode::Char('J') if self.is_active => self.last_row(),
|
||||
_ => {},
|
||||
};
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, place] = super::explorer_scrollbars_layout(area);
|
||||
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
|
||||
let mut longest_pallet_name_length = 0;
|
||||
let mut longest_variant_name_length = 0;
|
||||
|
||||
let rows = match &self.current_extrinsics {
|
||||
Some(exts) => exts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, ext)| {
|
||||
longest_pallet_name_length = longest_pallet_name_length.max(ext.pallet_name.len());
|
||||
longest_variant_name_length = longest_variant_name_length.max(ext.variant_name.len());
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from(idx.to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(ext.pallet_name.clone()).alignment(Alignment::Right)),
|
||||
Cell::from(Text::from(ext.variant_name.clone()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(ext.hash.to_string()).alignment(Alignment::Right)),
|
||||
])
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
let table = Table::new(
|
||||
rows,
|
||||
[
|
||||
Constraint::Max(2),
|
||||
Constraint::Min(longest_pallet_name_length as u16 + 2),
|
||||
Constraint::Min(longest_variant_name_length as u16 + 2),
|
||||
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("Extrinsics"));
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::Component;
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{palette::StylePalette, config::Config, action::Action};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -70,6 +70,10 @@ impl ExtrinsicsChart {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for ExtrinsicsChart {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for ExtrinsicsChart {
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
|
@ -5,7 +5,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::Component;
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
config::Config,
|
||||
action::Action,
|
||||
@ -26,6 +26,10 @@ impl FinalizedBlock {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for FinalizedBlock {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for FinalizedBlock {
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
@ -73,7 +77,7 @@ impl Component for FinalizedBlock {
|
||||
let big_text = BigText::builder()
|
||||
.centered()
|
||||
.pixel_size(PixelSize::Quadrant)
|
||||
.style(self.palette.create_text_style(false))
|
||||
.style(self.palette.create_basic_style(false))
|
||||
.lines(vec![
|
||||
text.into(),
|
||||
])
|
||||
|
@ -5,7 +5,7 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::Component;
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
config::Config,
|
||||
action::Action,
|
||||
@ -26,6 +26,10 @@ impl LatestBlock {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for LatestBlock {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for LatestBlock {
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(style) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
@ -73,7 +77,7 @@ impl Component for LatestBlock {
|
||||
let big_text = BigText::builder()
|
||||
.centered()
|
||||
.pixel_size(PixelSize::Quadrant)
|
||||
.style(self.palette.create_text_style(false))
|
||||
.style(self.palette.create_basic_style(false))
|
||||
.lines(vec![
|
||||
text.into(),
|
||||
])
|
||||
|
83
src/components/explorer/log_explorer.rs
Normal file
83
src/components/explorer/log_explorer.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
text::Line,
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{Component, CurrentTab, PartialComponent};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct LogExplorer {
|
||||
is_active: bool,
|
||||
maybe_log: Option<String>,
|
||||
palette: StylePalette,
|
||||
}
|
||||
|
||||
impl Default 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 {
|
||||
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_normal_border_style(style.get("normal_border_style").copied());
|
||||
self.palette.with_normal_title_style(style.get("normal_title_style").copied());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::UsedExplorerLog(maybe_log) => self.maybe_log = maybe_log,
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, _, place] = super::explorer_layout(area);
|
||||
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
|
||||
let line = match &self.maybe_log {
|
||||
Some(log) => Line::from(hex::encode(log)),
|
||||
None => Line::from(""),
|
||||
};
|
||||
|
||||
let paragraph = Paragraph::new(line)
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("Logs"))
|
||||
.alignment(Alignment::Center)
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
frame.render_widget(paragraph, place);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
Frame,
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::Component;
|
||||
use crate::{config::Config, action::Action};
|
||||
use crate::{action::Action, app::Mode, config::Config};
|
||||
|
||||
mod latest_block;
|
||||
mod finalized_block;
|
||||
@ -14,7 +15,9 @@ mod block_ticker;
|
||||
mod current_era;
|
||||
mod current_epoch;
|
||||
mod extrinsics_chart;
|
||||
mod explorer_blocks;
|
||||
mod block_explorer;
|
||||
mod extrinsic_explorer;
|
||||
mod log_explorer;
|
||||
|
||||
use latest_block::LatestBlock;
|
||||
use finalized_block::FinalizedBlock;
|
||||
@ -22,15 +25,32 @@ use block_ticker::BlockTicker;
|
||||
use current_era::CurrentEra;
|
||||
use current_epoch::CurrentEpoch;
|
||||
use extrinsics_chart::ExtrinsicsChart;
|
||||
use explorer_blocks::ExplorerBlocks;
|
||||
use block_explorer::BlockExplorer;
|
||||
use extrinsic_explorer::ExtrinsicExplorer;
|
||||
use log_explorer::LogExplorer;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CurrentTab {
|
||||
Nothing,
|
||||
Blocks,
|
||||
Extrinsics,
|
||||
}
|
||||
|
||||
pub trait PartialComponent: Component {
|
||||
fn set_active(&mut self, current_tab: CurrentTab);
|
||||
}
|
||||
|
||||
pub struct Explorer {
|
||||
components: Vec<Box<dyn Component>>
|
||||
is_active: bool,
|
||||
current_tab: CurrentTab,
|
||||
components: Vec<Box<dyn PartialComponent>>
|
||||
}
|
||||
|
||||
impl Default for Explorer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
current_tab: CurrentTab::Nothing,
|
||||
components: vec![
|
||||
Box::new(BlockTicker::default()),
|
||||
Box::new(LatestBlock::default()),
|
||||
@ -38,13 +58,42 @@ impl Default for Explorer {
|
||||
Box::new(CurrentEra::default()),
|
||||
Box::new(CurrentEpoch::default()),
|
||||
Box::new(ExtrinsicsChart::default()),
|
||||
Box::new(ExplorerBlocks::default()),
|
||||
Box::new(BlockExplorer::default()),
|
||||
Box::new(ExtrinsicExplorer::default()),
|
||||
Box::new(LogExplorer::default()),
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Explorer {
|
||||
fn move_left(&mut self) {
|
||||
if let CurrentTab::Extrinsics = self.current_tab {
|
||||
self.current_tab = CurrentTab::Blocks;
|
||||
}
|
||||
}
|
||||
|
||||
fn move_right(&mut self) {
|
||||
match self.current_tab {
|
||||
CurrentTab::Nothing => self.current_tab = CurrentTab::Blocks,
|
||||
CurrentTab::Blocks => self.current_tab = CurrentTab::Extrinsics,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Explorer {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for Explorer {
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||
for component in self.components.iter_mut() {
|
||||
component.register_action_handler(tx.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(_) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
for component in self.components.iter_mut() {
|
||||
@ -55,13 +104,46 @@ impl Component for Explorer {
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
for component in self.components.iter_mut() {
|
||||
component.handle_key_event(key)?;
|
||||
if self.is_active {
|
||||
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::Enter | 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());
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
for component in self.components.iter_mut() {
|
||||
component.handle_key_event(key)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
if let Action::SetActiveScreen(Mode::Explorer) = action {
|
||||
self.is_active = true;
|
||||
self.current_tab = CurrentTab::Blocks;
|
||||
for component in self.components.iter_mut() {
|
||||
component.set_active(self.current_tab.clone());
|
||||
}
|
||||
}
|
||||
for component in self.components.iter_mut() {
|
||||
component.update(action.clone())?;
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ impl FpsCounter {
|
||||
}
|
||||
}
|
||||
|
||||
fn app_tick(&mut self) -> Result<()> {
|
||||
fn app_tick(&mut self) {
|
||||
self.tick_count += 1;
|
||||
let now = Instant::now();
|
||||
let elapsed = (now - self.last_tick_update).as_secs_f64();
|
||||
@ -50,10 +50,9 @@ impl FpsCounter {
|
||||
self.last_tick_update = now;
|
||||
self.tick_count = 0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_tick(&mut self) -> Result<()> {
|
||||
fn render_tick(&mut self) {
|
||||
self.frame_count += 1;
|
||||
let now = Instant::now();
|
||||
let elapsed = (now - self.last_frame_update).as_secs_f64();
|
||||
@ -62,15 +61,14 @@ impl FpsCounter {
|
||||
self.last_frame_update = now;
|
||||
self.frame_count = 0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for FpsCounter {
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::Tick => self.app_tick()?,
|
||||
Action::Render => self.render_tick()?,
|
||||
Action::Tick => self.app_tick(),
|
||||
Action::Render => self.render_tick(),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
|
@ -39,28 +39,6 @@ impl Health {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_sync_state(
|
||||
&mut self,
|
||||
peers: Option<usize>,
|
||||
is_syncing: bool,
|
||||
should_have_peers: bool,
|
||||
) -> Result<()> {
|
||||
self.peers = peers;
|
||||
self.is_syncing = is_syncing;
|
||||
self.should_have_peers = should_have_peers;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_tx_pool_length(&mut self, tx_pool_length: usize) -> Result<()> {
|
||||
self.tx_pool_length = tx_pool_length;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_node_name(&mut self, name: Option<String>) -> Result<()> {
|
||||
self.name = name;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn is_syncing_as_string(&self) -> String {
|
||||
if self.is_syncing {
|
||||
format!("syncing {}", VerticalBlocks::default().to_string())
|
||||
@ -90,10 +68,12 @@ impl Component for Health {
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::SetSystemHealth(peers, is_syncing, should_have_peers) => {
|
||||
self.set_sync_state(peers, is_syncing, should_have_peers)?
|
||||
self.peers = peers;
|
||||
self.is_syncing = is_syncing;
|
||||
self.should_have_peers = should_have_peers;
|
||||
},
|
||||
Action::SetNodeName(name) => self.set_node_name(name)?,
|
||||
Action::SetPendingExtrinsicsLength(length) => self.set_tx_pool_length(length)?,
|
||||
Action::SetNodeName(name) => self.name = name,
|
||||
Action::SetPendingExtrinsicsLength(length) => self.tx_pool_length = length,
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
|
@ -9,8 +9,8 @@ use crate::{config::Config, action::Action, app::Mode};
|
||||
|
||||
pub struct Menu {
|
||||
command_tx: Option<UnboundedSender<Action>>,
|
||||
list_state: ListState,
|
||||
items: Vec<String>,
|
||||
current_item_index: usize,
|
||||
is_active: bool,
|
||||
palette: StylePalette,
|
||||
}
|
||||
@ -23,8 +23,9 @@ impl Default for Menu {
|
||||
|
||||
impl Menu {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
let mut new_list = Self {
|
||||
command_tx: None,
|
||||
list_state: ListState::default(),
|
||||
items: vec![
|
||||
String::from("Explorer"),
|
||||
String::from("Wallet"),
|
||||
@ -33,66 +34,62 @@ impl Menu {
|
||||
String::from("Governance"),
|
||||
String::from("Operations"),
|
||||
],
|
||||
current_item_index: Default::default(),
|
||||
is_active: true,
|
||||
palette: StylePalette::default(),
|
||||
is_active: true,
|
||||
|
||||
};
|
||||
new_list.list_state.select(Some(0));
|
||||
new_list
|
||||
}
|
||||
|
||||
fn next_row(&mut self) -> Result<Option<Action>> {
|
||||
let i = match self.list_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.list_state.select(Some(i));
|
||||
match i {
|
||||
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
||||
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
||||
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
||||
}
|
||||
}
|
||||
|
||||
fn move_current_up(&mut self) -> Result<()> {
|
||||
self.current_item_index = self
|
||||
.current_item_index
|
||||
.saturating_sub(1);
|
||||
|
||||
if let Some(command_tx) = &self.command_tx {
|
||||
match self.current_item_index {
|
||||
0 => command_tx.send(Action::SetMode(Mode::Explorer))?,
|
||||
_ => command_tx.send(Action::SetMode(Mode::Empty))?,
|
||||
}
|
||||
fn previous_row(&mut self) -> Result<Option<Action>> {
|
||||
let i = match self.list_state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
},
|
||||
None => 0
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn move_current_down(&mut self) -> Result<()> {
|
||||
let new_current = self.current_item_index + 1;
|
||||
if new_current < self.items.len() {
|
||||
self.current_item_index = new_current;
|
||||
self.list_state.select(Some(i));
|
||||
match i {
|
||||
0 => Ok(Some(Action::SetMode(Mode::Explorer))),
|
||||
1 => Ok(Some(Action::SetMode(Mode::Wallet))),
|
||||
_ => Ok(Some(Action::SetMode(Mode::Empty))),
|
||||
}
|
||||
|
||||
if let Some(command_tx) = &self.command_tx {
|
||||
match self.current_item_index {
|
||||
0 => command_tx.send(Action::SetMode(Mode::Explorer))?,
|
||||
_ => command_tx.send(Action::SetMode(Mode::Empty))?,
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_active(&mut self) -> Result<()> {
|
||||
self.is_active = true;
|
||||
if let Some(command_tx) = &self.command_tx {
|
||||
match self.current_item_index {
|
||||
0 => command_tx.send(Action::SetMode(Mode::Explorer))?,
|
||||
_ => command_tx.send(Action::SetMode(Mode::Empty))?,
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unset_active(&mut self) -> Result<()> {
|
||||
self.is_active = false;
|
||||
if let Some(command_tx) = &self.command_tx {
|
||||
match self.current_item_index {
|
||||
0 => command_tx.send(Action::SetMode(Mode::ExplorerActive))?,
|
||||
_ => command_tx.send(Action::SetMode(Mode::EmptyActive))?,
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Menu {
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::SetActiveScreen(Mode::Menu) => self.is_active = true,
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||
self.command_tx = Some(tx);
|
||||
Ok(())
|
||||
@ -104,46 +101,48 @@ impl Component for Menu {
|
||||
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());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k') if self.is_active => self.move_current_up()?,
|
||||
KeyCode::Down | KeyCode::Char('j') if self.is_active => self.move_current_down()?,
|
||||
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right if self.is_active => self.unset_active()?,
|
||||
KeyCode::Esc if !self.is_active => self.set_active()?,
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
KeyCode::Up | KeyCode::Char('k') if self.is_active => self.previous_row(),
|
||||
KeyCode::Down | KeyCode::Char('j') if self.is_active => self.next_row(),
|
||||
KeyCode::Enter | KeyCode::Char('l') | KeyCode::Right if self.is_active => {
|
||||
self.is_active = false;
|
||||
match self.list_state.selected() {
|
||||
Some(0) => Ok(Some(Action::SetActiveScreen(Mode::Explorer))),
|
||||
Some(1) => Ok(Some(Action::SetActiveScreen(Mode::Wallet))),
|
||||
_ => Ok(Some(Action::SetActiveScreen(Mode::Empty))),
|
||||
}
|
||||
},
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [menu, _] = super::menu_layout(area);
|
||||
let width = menu.as_size().width as usize;
|
||||
|
||||
let mut text = vec![];
|
||||
for (position, item) in self.items.iter().enumerate() {
|
||||
let active = position == self.current_item_index;
|
||||
let line = Line::raw(format!("{:^width$}", item))
|
||||
.style(self.palette.create_text_style(active))
|
||||
.centered();
|
||||
text.push(line);
|
||||
}
|
||||
|
||||
let (color, border_type) = self.palette.create_border_style(self.is_active);
|
||||
let block = Block::bordered()
|
||||
.border_style(color)
|
||||
.border_type(border_type);
|
||||
|
||||
let paragraph = Paragraph::new(text)
|
||||
let list = List::default()
|
||||
.items(self.items
|
||||
.iter()
|
||||
.map(|item| ListItem::new(
|
||||
Line::raw(item.as_str()).alignment(Alignment::Center)
|
||||
))
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
.block(block)
|
||||
.alignment(Alignment::Center);
|
||||
.style(self.palette.create_basic_style(false))
|
||||
.highlight_style(self.palette.create_highlight_style());
|
||||
|
||||
frame.render_widget(paragraph, menu);
|
||||
frame.render_stateful_widget(list, menu, &mut self.list_state);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ pub mod health;
|
||||
pub mod menu;
|
||||
pub mod version;
|
||||
pub mod explorer;
|
||||
pub mod wallet;
|
||||
pub mod empty;
|
||||
|
||||
pub trait Component {
|
||||
|
@ -5,11 +5,14 @@ use ratatui::{
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
Frame,
|
||||
};
|
||||
use primitive_types::H256;
|
||||
use subxt::utils::H256;
|
||||
|
||||
use super::Component;
|
||||
use crate::{
|
||||
action::Action, palette::StylePalette, widgets::OghamCenter
|
||||
config::Config,
|
||||
action::Action,
|
||||
palette::StylePalette,
|
||||
widgets::OghamCenter,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@ -21,21 +24,6 @@ pub struct Version {
|
||||
}
|
||||
|
||||
impl Version {
|
||||
fn set_chain_name(&mut self, chain_name: Option<String>) -> Result<()> {
|
||||
self.chain_name = chain_name;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_node_version(&mut self, node_version: Option<String>) -> Result<()> {
|
||||
self.node_version = node_version;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_genesis_hash(&mut self, genesis_hash: Option<H256>) -> Result<()> {
|
||||
self.genesis_hash = genesis_hash;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prepared_genesis_hash(&self) -> String {
|
||||
match self.genesis_hash {
|
||||
Some(genesis_hash) => genesis_hash.to_string(),
|
||||
@ -45,20 +33,28 @@ impl Version {
|
||||
}
|
||||
|
||||
impl Component for Version {
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(style) = config.styles.get(&crate::app::Mode::Menu) {
|
||||
self.palette.with_normal_style(style.get("normal_style").copied());
|
||||
self.palette.with_normal_border_style(style.get("normal_border_style").copied());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::SetChainName(maybe_name) => self.set_chain_name(maybe_name)?,
|
||||
Action::SetChainVersion(version) => self.set_node_version(version)?,
|
||||
Action::SetGenesisHash(maybe_genesis) => self.set_genesis_hash(maybe_genesis)?,
|
||||
Action::SetChainName(maybe_name) => self.chain_name = maybe_name,
|
||||
Action::SetChainVersion(version) => self.node_version = version,
|
||||
Action::SetGenesisHash(maybe_genesis) => self.genesis_hash = maybe_genesis,
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, version] = super::menu_layout(area);
|
||||
|
||||
let text_style = self.palette.create_text_style(false);
|
||||
let text_style = self.palette.create_basic_style(false);
|
||||
let (border_style, border_type) = self.palette.create_border_style(false);
|
||||
let text = vec![
|
||||
Line::styled(self.chain_name.clone().unwrap_or(OghamCenter::default().to_string()), text_style),
|
||||
|
278
src/components/wallet/accounts.rs
Normal file
278
src/components/wallet/accounts.rs
Normal file
@ -0,0 +1,278 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Write, BufRead, BufReader};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Constraint, Margin};
|
||||
use ratatui::{
|
||||
text::Text,
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{
|
||||
Block, Cell, Row, Table, TableState, Scrollbar, Padding,
|
||||
ScrollbarOrientation, ScrollbarState,
|
||||
},
|
||||
Frame
|
||||
};
|
||||
use subxt::{
|
||||
tx::PairSigner,
|
||||
ext::sp_core::{
|
||||
Pair as PairT,
|
||||
sr25519::Pair,
|
||||
crypto::{Ss58Codec, Ss58AddressFormat, AccountId32},
|
||||
},
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use super::{PartialComponent, Component, CurrentTab};
|
||||
use crate::casper::CasperConfig;
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct Accounts {
|
||||
is_active: bool,
|
||||
action_tx: Option<UnboundedSender<Action>>,
|
||||
palette: StylePalette,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
wallet_keys: Vec<(String, String, PairSigner<CasperConfig, Pair>)>,
|
||||
}
|
||||
|
||||
impl Default for Accounts {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Accounts {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
action_tx: None,
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
wallet_keys: Vec::new(),
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_or_create(&mut self, file_path: &PathBuf) -> Result<()> {
|
||||
assert!(self.wallet_keys.len() == 0, "wallet_keys already exists");
|
||||
match File::open(file_path) {
|
||||
Ok(file) => {
|
||||
let reader = BufReader::new(file);
|
||||
for line in reader.lines() {
|
||||
let line = line?.replace("\n", "");
|
||||
let line_split_at = line.find(":").unwrap_or(line.len());
|
||||
let (wallet_name, wallet_key) = line.split_at(line_split_at);
|
||||
let wallet_key = &wallet_key[3..];
|
||||
|
||||
let seed: [u8; 32] = hex::decode(wallet_key)
|
||||
.expect("stored seed is valid hex string; qed")
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("stored seed is valid length; qed");
|
||||
|
||||
let pair = Pair::from_seed(&seed);
|
||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
let address = AccountId32::from(seed.clone())
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
|
||||
self.wallet_keys.push((wallet_name.to_string(), address, pair_signer));
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
let (pair, seed) = match std::fs::read_to_string("/etc/ghost/wallet-key") {
|
||||
Ok(content) => {
|
||||
let content = content.replace("\n", "");
|
||||
let content = &content[2..];
|
||||
let seed: [u8; 32] = hex::decode(content)
|
||||
.expect("stored seed is valid hex string; qed")
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("stored seed is valid length; qed");
|
||||
|
||||
let pair = Pair::from_seed(&seed);
|
||||
(pair, seed)
|
||||
}
|
||||
Err(_) => {
|
||||
let (pair, seed) = Pair::generate();
|
||||
(pair, seed)
|
||||
}
|
||||
};
|
||||
|
||||
let secret_seed = hex::encode(seed);
|
||||
let pair_signer = PairSigner::<CasperConfig, Pair>::new(pair);
|
||||
|
||||
let mut new_file = File::create(file_path)?;
|
||||
writeln!(new_file, "ghostie:0x{}", &secret_seed)?;
|
||||
|
||||
let address = AccountId32::from(seed.clone())
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
|
||||
self.wallet_keys.push(("ghostie".to_string(), address, pair_signer));
|
||||
}
|
||||
};
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.content_length(self.wallet_keys.len());
|
||||
self.send_wallet_change(0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_wallet_change(&mut self, index: usize) {
|
||||
if let Some(action_tx) = &self.action_tx {
|
||||
let (_, _, pair) = &self.wallet_keys[index];
|
||||
let _ = action_tx.send(Action::UsedAccount(pair.account_id().clone()));
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.wallet_keys.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.wallet_keys.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.wallet_keys.len() > 0 {
|
||||
let last = self.wallet_keys.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(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);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Accounts {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::Accounts => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Accounts {
|
||||
// TODO network_tx is needed here NOT action_tx
|
||||
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<()> {
|
||||
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());
|
||||
}
|
||||
|
||||
let mut wallet_keys_file = config.config.data_dir;
|
||||
wallet_keys_file.push("wallet-keys");
|
||||
self.read_or_create(&wallet_keys_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k') if self.is_active => self.previous_row(),
|
||||
KeyCode::Down | KeyCode::Char('j') if self.is_active => self.next_row(),
|
||||
KeyCode::Char('K') if self.is_active => self.first_row(),
|
||||
KeyCode::Char('J') if self.is_active => self.last_row(),
|
||||
// TODO: swap on alt+j or G/gg to bottom and up
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, place, _] = super::account_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
|
||||
let table = Table::new(
|
||||
self.wallet_keys
|
||||
.iter()
|
||||
.map(|info| Row::new(vec![
|
||||
Cell::from(Text::from(info.0.clone()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(info.1.clone()).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("31 CSPR".to_string()).alignment(Alignment::Right)),
|
||||
])),
|
||||
[
|
||||
Constraint::Min(0),
|
||||
Constraint::Min(15),
|
||||
Constraint::Min(10),
|
||||
],
|
||||
)
|
||||
.highlight_style(self.palette.create_highlight_style())
|
||||
.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("My Accounts"));
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
82
src/components/wallet/add.rs
Normal file
82
src/components/wallet/add.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crossterm::event::{KeyEvent, KeyCode};
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
widgets::{Block, Clear},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AddAddress {
|
||||
is_shown: bool,
|
||||
palette: StylePalette
|
||||
}
|
||||
|
||||
impl Default for AddAddress {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAddress {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_shown: false,
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for AddAddress {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for AddAddress {
|
||||
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());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Char('a') => self.is_shown = true,
|
||||
KeyCode::Char(' ') => self.is_shown = false,
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
if self.is_shown {
|
||||
let block = Block::bordered().title("Transfer");
|
||||
let v = Layout::vertical([Constraint::Min(55)]).flex(Flex::Center);
|
||||
let h = Layout::horizontal([Constraint::Min(10)]).flex(Flex::Center);
|
||||
let [area] = v.areas(area);
|
||||
let [area] = h.areas(area);
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_widget(block, area);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
273
src/components/wallet/address_book.rs
Normal file
273
src/components/wallet/address_book.rs
Normal file
@ -0,0 +1,273 @@
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
use std::io::{Write, BufRead, BufReader};
|
||||
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::layout::{Constraint, Margin};
|
||||
use ratatui::text::Text;
|
||||
use ratatui::widgets::{Cell, Padding, Scrollbar, ScrollbarOrientation};
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{Block, ScrollbarState, Row, Table, TableState},
|
||||
Frame
|
||||
};
|
||||
use subxt::ext::sp_core::crypto::{Ss58Codec, Ss58AddressFormat, AccountId32};
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
pub struct AddressBook {
|
||||
is_active: bool,
|
||||
address_book: Vec<(String, String, AccountId32)>,
|
||||
scroll_state: ScrollbarState,
|
||||
table_state: TableState,
|
||||
palette: StylePalette,
|
||||
}
|
||||
|
||||
impl Default for AddressBook {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AddressBook {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
address_book: Vec::new(),
|
||||
scroll_state: ScrollbarState::new(0),
|
||||
table_state: TableState::new(),
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_or_create(&mut self, file_path: &PathBuf) -> Result<()> {
|
||||
assert!(self.address_book.len() == 0, "address_book already exists");
|
||||
match File::open(file_path) {
|
||||
Ok(file) => {
|
||||
let reader = BufReader::new(file);
|
||||
for line in reader.lines() {
|
||||
let line = line?.replace("\n", "");
|
||||
let line_split_at = line.find(":").unwrap_or(line.len());
|
||||
let (name, seed) = line.split_at(line_split_at);
|
||||
let seed = &seed[3..];
|
||||
|
||||
let seed: [u8; 32] = hex::decode(seed)
|
||||
.expect("stored seed is valid hex string; qed")
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("stored seed is valid length; qed");
|
||||
|
||||
let account_id = AccountId32::from(seed);
|
||||
let address = AccountId32::from(seed.clone())
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
self.address_book.push((name.to_string(), address, account_id));
|
||||
}
|
||||
},
|
||||
Err(_) => {
|
||||
let chad_boyz = vec![
|
||||
("Pierre", "328d3b7c3046ef7700937d99fb2e98ce2591682c2b5dcf3f562e4da157650237"),
|
||||
("Ghost_7", "3666e4e19f87bb8680495f31864ce1f1c69d4178002cc01911aef2cc7313f203"),
|
||||
("Neptune1", "ac871e8bab00dd56ba3a1c0bd289357203dcaf10010b0b04ad7472870cd22a3c"),
|
||||
("Neptune2", "425ccd7bda4f5c76788ba23bc0381d7a2e496179c93301208c57501c80a4232a"),
|
||||
("Doctor K", "927a98dcf8f721103005f168476c24b91d7d10d580f457006a908e10e62c7729"),
|
||||
("Starman", "ac9e227e30a63ce6eeb55cfbb1fb832aa7e1d3fad2bcb3f663de4a91d744fd50"),
|
||||
("Kitsune1", "46c78fcacffd80abc9cca4917ef8369a37e21a1691ca11e7a3b53f80be745313"),
|
||||
("Scientio", "fa5e5a295ec74c3dda81118d9240db1552b28f831838465ae0712e97e78a6728"),
|
||||
("Kitsune2", "4078ddb1ba1388f768fe6aa40ba9124a72692ecbcc83dc088fa86c735e4dc128"),
|
||||
("Proxmio", "5e1456904c40192cd3a18183df7dffea90d97739830a902cabb702ecdae4f649"),
|
||||
];
|
||||
|
||||
let mut new_file = File::create(file_path)?;
|
||||
chad_boyz
|
||||
.iter()
|
||||
.for_each(|chad_info| {
|
||||
writeln!(new_file, "{}:0x{}", chad_info.0, chad_info.1)
|
||||
.expect("should write to address book; qed");
|
||||
let chad_account_id: [u8; 32] = hex::decode(&chad_info.1[..])
|
||||
.expect("stored seed is valid hex string; qed")
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("stored seed is valid length; qed");
|
||||
let chad_account_id = AccountId32::from(chad_account_id);
|
||||
let address = AccountId32::from(chad_account_id.clone())
|
||||
.to_ss58check_with_version(Ss58AddressFormat::custom(1996));
|
||||
self.address_book.push((chad_info.0.to_string(), address, chad_account_id));
|
||||
});
|
||||
}
|
||||
};
|
||||
self.scroll_state = self.scroll_state.content_length(self.address_book.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn swap_up(&mut self) {
|
||||
if let Some(src_index) = self.table_state.selected() {
|
||||
let dst_index = src_index.saturating_sub(1);
|
||||
if src_index > dst_index {
|
||||
self.address_book.swap(src_index, dst_index);
|
||||
self.previous_row();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn swap_down(&mut self) {
|
||||
if let Some(src_index) = self.table_state.selected() {
|
||||
let dst_index = src_index + 1;
|
||||
if dst_index < self.address_book.len() {
|
||||
self.address_book.swap(src_index, dst_index);
|
||||
self.next_row();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_row(&mut self) {
|
||||
if let Some(index) = self.table_state.selected() {
|
||||
let _ = self.address_book.remove(index);
|
||||
self.previous_row();
|
||||
}
|
||||
}
|
||||
|
||||
fn first_row(&mut self) {
|
||||
if self.address_book.len() > 0 {
|
||||
self.table_state.select(Some(0));
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_row(&mut self) {
|
||||
let i = match self.table_state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.address_book.len() - 1 {
|
||||
i
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
},
|
||||
None => 0,
|
||||
};
|
||||
self.table_state.select(Some(i));
|
||||
self.scroll_state = self.scroll_state.position(i);
|
||||
}
|
||||
|
||||
fn last_row(&mut self) {
|
||||
if self.address_book.len() > 0 {
|
||||
let last = self.address_book.len() - 1;
|
||||
self.table_state.select(Some(last));
|
||||
self.scroll_state = self.scroll_state.position(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);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for AddressBook {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::AddressBook => self.is_active = true,
|
||||
_ => {
|
||||
self.is_active = false;
|
||||
self.table_state.select(None);
|
||||
self.scroll_state = self.scroll_state.position(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for AddressBook {
|
||||
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());
|
||||
}
|
||||
|
||||
let mut address_book_file = config.config.data_dir;
|
||||
address_book_file.push("address-book");
|
||||
self.read_or_create(&address_book_file)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Up | KeyCode::Char('k') if self.is_active => self.previous_row(),
|
||||
KeyCode::Down | KeyCode::Char('j') if self.is_active => self.next_row(),
|
||||
// TODO: swap on alt+j or G/gg to bottom and up
|
||||
KeyCode::Char('g') if self.is_active => self.first_row(),
|
||||
KeyCode::Char('G') if self.is_active => self.last_row(),
|
||||
KeyCode::Char('K') if self.is_active => self.swap_up(),
|
||||
KeyCode::Char('J') if self.is_active => self.swap_down(),
|
||||
KeyCode::Char('d') if self.is_active => self.delete_row(),
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, place] = super::bars_layout(area);
|
||||
let (border_style, border_type) = self.palette.create_border_style(self.is_active);
|
||||
|
||||
let table = Table::new(
|
||||
self.address_book
|
||||
.iter()
|
||||
.map(|info| Row::new(vec![
|
||||
Cell::from(Text::from(info.0.clone()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(info.1.clone()).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("31 CSPR".to_string()).alignment(Alignment::Right)),
|
||||
])),
|
||||
[
|
||||
Constraint::Min(0),
|
||||
Constraint::Min(13),
|
||||
Constraint::Min(10),
|
||||
],
|
||||
)
|
||||
.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("Address Book"));
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
137
src/components/wallet/balance.rs
Normal file
137
src/components/wallet/balance.rs
Normal file
@ -0,0 +1,137 @@
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
text::Text,
|
||||
layout::{Alignment, Constraint, Rect},
|
||||
widgets::{Block, Cell, Row, Table},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Balance {
|
||||
is_active: bool,
|
||||
total_balance: Option<u128>,
|
||||
transferable_balance: Option<u128>,
|
||||
locked_balance: Option<u128>,
|
||||
bonded_balance: Option<u128>,
|
||||
palette: StylePalette
|
||||
}
|
||||
|
||||
impl Default for Balance {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Balance {
|
||||
const DECIMALS_FOR_BALANCE: usize = 5;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
total_balance: None,
|
||||
transferable_balance: None,
|
||||
locked_balance: None,
|
||||
bonded_balance: None,
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_u128(&self, value: u128, after: usize) -> String {
|
||||
let value = value as f64 / 10f64.powi(18);
|
||||
format!("{:.after$}", value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Balance {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::Accounts => self.is_active = true,
|
||||
_ => self.is_active = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Balance {
|
||||
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());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, _, place] = super::account_layout(area);
|
||||
let (border_style, border_type) = self.palette
|
||||
.create_border_style(self.is_active);
|
||||
|
||||
let table = Table::new(
|
||||
[
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("account: ".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(
|
||||
self.total_balance.unwrap_or_default(),
|
||||
Self::DECIMALS_FOR_BALANCE,
|
||||
)).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("CSPR".to_string()).alignment(Alignment::Right))
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("free: ".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(
|
||||
self.transferable_balance.unwrap_or_default(),
|
||||
Self::DECIMALS_FOR_BALANCE,
|
||||
)).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("CSPR".to_string()).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("locked: ".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(
|
||||
self.locked_balance.unwrap_or_default(),
|
||||
Self::DECIMALS_FOR_BALANCE,
|
||||
)).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("CSPR".to_string()).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("bonded: ".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(
|
||||
self.bonded_balance.unwrap_or_default(),
|
||||
Self::DECIMALS_FOR_BALANCE,
|
||||
)).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("CSPR".to_string()).alignment(Alignment::Right)),
|
||||
]),
|
||||
],
|
||||
[
|
||||
Constraint::Max(10),
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(5),
|
||||
]
|
||||
)
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("Balance"));
|
||||
|
||||
frame.render_widget(table, place);
|
||||
Ok(())
|
||||
}
|
||||
}
|
75
src/components/wallet/event_logs.rs
Normal file
75
src/components/wallet/event_logs.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
widgets::{Block, Paragraph, Wrap},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EventLogs {
|
||||
palette: StylePalette
|
||||
}
|
||||
|
||||
impl Default for EventLogs {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLogs {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for EventLogs {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for EventLogs {
|
||||
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());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [_, place] = super::wallet_layout(area);
|
||||
|
||||
let (border_style, border_type) = self.palette.create_border_style(false);
|
||||
let paragraph = Paragraph::new("latest logs")
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("Logs"))
|
||||
.alignment(Alignment::Center)
|
||||
.wrap(Wrap { trim: true });
|
||||
|
||||
frame.render_widget(paragraph, place);
|
||||
Ok(())
|
||||
}
|
||||
}
|
178
src/components/wallet/mod.rs
Normal file
178
src/components/wallet/mod.rs
Normal file
@ -0,0 +1,178 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Layout, Rect},
|
||||
Frame,
|
||||
};
|
||||
|
||||
mod balance;
|
||||
mod transfer;
|
||||
mod address_book;
|
||||
mod add;
|
||||
mod event_logs;
|
||||
mod accounts;
|
||||
mod overview;
|
||||
|
||||
use balance::Balance;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use transfer::Transfer;
|
||||
use address_book::AddressBook;
|
||||
use add::AddAddress;
|
||||
use event_logs::EventLogs;
|
||||
use accounts::Accounts;
|
||||
use overview::Overview;
|
||||
|
||||
use super::Component;
|
||||
use crate::{action::Action, app::Mode, config::Config};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CurrentTab {
|
||||
Nothing,
|
||||
Accounts,
|
||||
AddressBook,
|
||||
}
|
||||
|
||||
pub trait PartialComponent: Component {
|
||||
fn set_active(&mut self, current_tab: CurrentTab);
|
||||
}
|
||||
|
||||
pub struct Wallet {
|
||||
is_active: bool,
|
||||
current_tab: CurrentTab,
|
||||
components: Vec<Box<dyn PartialComponent>>,
|
||||
}
|
||||
|
||||
impl Default for Wallet {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
current_tab: CurrentTab::Accounts,
|
||||
components: vec![
|
||||
Box::new(Overview::default()),
|
||||
Box::new(Accounts::default()),
|
||||
Box::new(Balance::default()),
|
||||
Box::new(AddressBook::default()),
|
||||
Box::new(EventLogs::default()),
|
||||
Box::new(Transfer::default()),
|
||||
Box::new(AddAddress::default()),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Wallet {
|
||||
fn move_left(&mut self) {
|
||||
if let CurrentTab::AddressBook = self.current_tab {
|
||||
self.current_tab = CurrentTab::Accounts;
|
||||
}
|
||||
}
|
||||
|
||||
fn move_right(&mut self) {
|
||||
match self.current_tab {
|
||||
CurrentTab::Nothing => self.current_tab = CurrentTab::Accounts,
|
||||
CurrentTab::Accounts => self.current_tab = CurrentTab::AddressBook,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Wallet {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for Wallet {
|
||||
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
|
||||
for component in self.components.iter_mut() {
|
||||
component.register_action_handler(tx.clone())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_config_handler(&mut self, config: Config) -> Result<()> {
|
||||
if let Some(_) = config.styles.get(&crate::app::Mode::Explorer) {
|
||||
for component in self.components.iter_mut() {
|
||||
component.register_config_handler(config.clone())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
if self.is_active {
|
||||
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::Enter | 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());
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
for component in self.components.iter_mut() {
|
||||
component.handle_key_event(key)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
if let Action::SetActiveScreen(Mode::Wallet) = action {
|
||||
self.is_active = true;
|
||||
self.current_tab = CurrentTab::Accounts;
|
||||
for component in self.components.iter_mut() {
|
||||
component.set_active(self.current_tab.clone());
|
||||
}
|
||||
}
|
||||
for component in self.components.iter_mut() {
|
||||
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 wallet_layout(area: Rect) -> [Rect; 2] {
|
||||
Layout::vertical([
|
||||
Constraint::Percentage(75),
|
||||
Constraint::Percentage(25),
|
||||
]).areas(area)
|
||||
}
|
||||
|
||||
pub fn bars_layout(area: Rect) -> [Rect; 2] {
|
||||
let [place, _] = wallet_layout(area);
|
||||
Layout::horizontal([
|
||||
Constraint::Percentage(50),
|
||||
Constraint::Percentage(50),
|
||||
]).areas(place)
|
||||
}
|
||||
|
||||
pub fn account_layout(area: Rect) -> [Rect; 3] {
|
||||
let [place, _] = bars_layout(area);
|
||||
Layout::vertical([
|
||||
Constraint::Max(4),
|
||||
Constraint::Min(0),
|
||||
Constraint::Max(6),
|
||||
]).areas(place)
|
||||
}
|
119
src/components/wallet/overview.rs
Normal file
119
src/components/wallet/overview.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use color_eyre::Result;
|
||||
use ratatui::{
|
||||
text::Text,
|
||||
layout::{Alignment, Constraint, Rect},
|
||||
widgets::{Block, Cell, Row, Table},
|
||||
Frame
|
||||
};
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Overview {
|
||||
is_active: bool,
|
||||
existential_balance: Option<u128>,
|
||||
total_issuance: Option<u128>,
|
||||
palette: StylePalette
|
||||
}
|
||||
|
||||
impl Default for Overview {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Overview {
|
||||
const DECIMALS_FOR_BALANCE: usize = 5;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_active: false,
|
||||
existential_balance: None,
|
||||
total_issuance: None,
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_u128(&self, value: u128, after: usize) -> String {
|
||||
let value = value as f64 / 10f64.powi(18);
|
||||
format!("{:.after$}", value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Overview {
|
||||
fn set_active(&mut self, current_tab: CurrentTab) {
|
||||
match current_tab {
|
||||
CurrentTab::Accounts => self.is_active = true,
|
||||
_ => self.is_active = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Overview {
|
||||
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());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
Action::SetExistentialDeposit(ed) => self.existential_balance = Some(ed),
|
||||
Action::SetTotalIssuance(issuance) => self.total_issuance = Some(issuance),
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
let [place, _, _] = super::account_layout(area);
|
||||
let (border_style, border_type) = self.palette
|
||||
.create_border_style(self.is_active);
|
||||
|
||||
let table = Table::new(
|
||||
[
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("total supply: ".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(
|
||||
self.total_issuance.unwrap_or_default(),
|
||||
Self::DECIMALS_FOR_BALANCE,
|
||||
)).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("CSPR".to_string()).alignment(Alignment::Right)),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from(Text::from("min deposit: ".to_string()).alignment(Alignment::Left)),
|
||||
Cell::from(Text::from(self.prepare_u128(
|
||||
self.existential_balance.unwrap_or_default(),
|
||||
Self::DECIMALS_FOR_BALANCE,
|
||||
)).alignment(Alignment::Center)),
|
||||
Cell::from(Text::from("CSPR".to_string()).alignment(Alignment::Right)),
|
||||
]),
|
||||
],
|
||||
[
|
||||
Constraint::Max(15),
|
||||
Constraint::Min(0),
|
||||
Constraint::Length(5),
|
||||
]
|
||||
)
|
||||
.block(Block::bordered()
|
||||
.border_style(border_style)
|
||||
.border_type(border_type)
|
||||
.title_alignment(Alignment::Right)
|
||||
.title_style(self.palette.create_title_style(false))
|
||||
.title("Overview"));
|
||||
|
||||
frame.render_widget(table, place);
|
||||
Ok(())
|
||||
}
|
||||
}
|
83
src/components/wallet/transfer.rs
Normal file
83
src/components/wallet/transfer.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use color_eyre::Result;
|
||||
use crossterm::event::{KeyEvent, KeyCode};
|
||||
use ratatui::{
|
||||
layout::{Constraint, Flex, Layout, Rect},
|
||||
widgets::{Block, Clear},
|
||||
Frame,
|
||||
prelude::Stylize,
|
||||
};
|
||||
|
||||
use super::{Component, PartialComponent, CurrentTab};
|
||||
use crate::{
|
||||
action::Action,
|
||||
config::Config,
|
||||
palette::StylePalette,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Transfer {
|
||||
palette: StylePalette,
|
||||
is_shown: bool,
|
||||
}
|
||||
|
||||
impl Default for Transfer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Transfer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
is_shown: false,
|
||||
palette: StylePalette::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialComponent for Transfer {
|
||||
fn set_active(&mut self, _current_tab: CurrentTab) {}
|
||||
}
|
||||
|
||||
impl Component for Transfer {
|
||||
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());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(&mut self, key: KeyEvent) -> Result<Option<Action>> {
|
||||
match key.code {
|
||||
KeyCode::Char('t') => self.is_shown = true,
|
||||
KeyCode::Char(' ') => self.is_shown = false,
|
||||
_ => {},
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn update(&mut self, action: Action) -> Result<Option<Action>> {
|
||||
match action {
|
||||
_ => {}
|
||||
};
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> {
|
||||
if self.is_shown {
|
||||
let block = Block::bordered().on_red().title("Transfer");
|
||||
let v = Layout::vertical([Constraint::Max(10)]).flex(Flex::Center);
|
||||
let h = Layout::horizontal([Constraint::Max(55)]).flex(Flex::Center);
|
||||
let [area] = v.areas(area);
|
||||
let [area] = h.areas(area);
|
||||
frame.render_widget(Clear, area);
|
||||
frame.render_widget(block, area);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -17,13 +17,13 @@ const CONFIG: &str = include_str!("../config/config.json5");
|
||||
pub struct AppConfig {
|
||||
#[serde(default)]
|
||||
pub data_dir: PathBuf,
|
||||
#[allow(unused)]
|
||||
#[serde(default)]
|
||||
pub config_dir: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
pub struct Config {
|
||||
#[allow(unused)]
|
||||
#[serde(default, flatten)]
|
||||
pub config: AppConfig,
|
||||
#[serde(default)]
|
||||
|
@ -65,6 +65,10 @@ impl Network {
|
||||
Action::GetActiveEra => predefinded_calls::get_active_era(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetEpochProgress => predefinded_calls::get_epoch_progress(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetPendingExtrinsics => predefinded_calls::get_pending_extrinsics(&self.action_tx, &self.rpc_client).await,
|
||||
|
||||
Action::GetExistentialDeposit => predefinded_calls::get_existential_deposit(&self.action_tx, &self.online_client_api).await,
|
||||
Action::GetTotalIssuance => predefinded_calls::get_total_issuance(&self.action_tx, &self.online_client_api).await,
|
||||
//Action::UsedAccount(account_id) => predefinded_calls::get_balance(&self.action_tx, &self.online_client_api, account_id).await,
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use primitive_types::H256;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use color_eyre::Result;
|
||||
use subxt::{
|
||||
ext::sp_core::crypto::{AccountId32, Ss58Codec, Ss58AddressFormat},
|
||||
utils::H256,
|
||||
backend::rpc::RpcClient,
|
||||
client::OnlineClient,
|
||||
config::substrate::DigestItem,
|
||||
@ -10,9 +11,12 @@ use subxt::{
|
||||
|
||||
use crate::{
|
||||
action::Action,
|
||||
casper_network::{
|
||||
self,
|
||||
runtime_types::sp_consensus_slots,
|
||||
},
|
||||
types::EraInfo,
|
||||
casper_network::{self, runtime_types::sp_consensus_slots},
|
||||
CasperConfig,
|
||||
CasperAccountId, CasperConfig
|
||||
};
|
||||
|
||||
pub async fn get_block_author(
|
||||
@ -39,7 +43,17 @@ pub async fn get_block_author(
|
||||
_ => None,
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetBlockAuthor(*at_hash, maybe_author.cloned()))?;
|
||||
let validator = match maybe_author {
|
||||
Some(author) => {
|
||||
let extended_author = CasperAccountId::decode(&mut author.as_ref())
|
||||
.expect("author should be valid AccountId32; qed");
|
||||
let account_id = AccountId32::from(extended_author.0);
|
||||
account_id.to_ss58check_with_version(Ss58AddressFormat::custom(1996))
|
||||
},
|
||||
None => "...".to_string(),
|
||||
};
|
||||
|
||||
action_tx.send(Action::SetBlockAuthor(*at_hash, validator))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -108,3 +122,44 @@ pub async fn get_pending_extrinsics(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_total_issuance(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let storage_key = casper_network::storage().balances().total_issuance();
|
||||
let total_issuance = api.storage()
|
||||
.at_latest()
|
||||
.await?
|
||||
.fetch(&storage_key)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
action_tx.send(Action::SetTotalIssuance(total_issuance))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_existential_deposit(
|
||||
action_tx: &UnboundedSender<Action>,
|
||||
api: &OnlineClient<CasperConfig>,
|
||||
) -> Result<()> {
|
||||
let constant_query = casper_network::constants().balances().existential_deposit();
|
||||
let existential_deposit = api.constants().at(&constant_query)?;
|
||||
action_tx.send(Action::SetExistentialDeposit(existential_deposit))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
//pub async fn get_balance(
|
||||
// action_tx: &UnboundedSender<Action>,
|
||||
// api: &OnlineClient<CasperConfig>,
|
||||
// account_id: subxt::utils::AccountId32,
|
||||
//) -> Result<()> {
|
||||
// let storage_key = casper_network::storage().system().account(&account_id);
|
||||
// let balance = api.storage()
|
||||
// .at_latest()
|
||||
// .await?
|
||||
// .fetch(&storage_key)
|
||||
// .await?;
|
||||
//
|
||||
// action_tx.send(Action::SetTotalIssuance(total_issuance))?;
|
||||
// Ok(())
|
||||
//}
|
||||
|
@ -46,8 +46,8 @@ impl FinalizedSubscription {
|
||||
));
|
||||
}
|
||||
|
||||
self.action_tx.send(Action::FinalizedBlockInformation(
|
||||
block_hash, block_number, extrinsic_details))?;
|
||||
self.action_tx.send(Action::FinalizedBlockInformation(block_hash, block_number))?;
|
||||
self.action_tx.send(Action::ExtrinsicsForBlock(block_number, extrinsic_details))?;
|
||||
self.action_tx.send(Action::NewFinalizedHash(block_hash))?;
|
||||
self.action_tx.send(Action::NewFinalizedBlock(block_number))?;
|
||||
|
||||
@ -103,8 +103,8 @@ impl BestSubscription {
|
||||
));
|
||||
}
|
||||
|
||||
self.action_tx.send(Action::BestBlockInformation(
|
||||
block_hash, block_number, extrinsic_details))?;
|
||||
self.action_tx.send(Action::BestBlockInformation(block_hash, block_number))?;
|
||||
self.action_tx.send(Action::ExtrinsicsForBlock(block_number, extrinsic_details))?;
|
||||
self.action_tx.send(Action::NewBestHash(block_hash))?;
|
||||
self.action_tx.send(Action::BestBlockUpdated(block_number))?;
|
||||
self.action_tx.send(Action::NewBestBlock(block_number))?;
|
||||
@ -113,6 +113,7 @@ impl BestSubscription {
|
||||
self.network_tx.send(Action::GetBlockAuthor(block_hash, block.header().digest.logs.clone()))?;
|
||||
self.network_tx.send(Action::GetActiveEra)?;
|
||||
self.network_tx.send(Action::GetEpochProgress)?;
|
||||
self.network_tx.send(Action::GetTotalIssuance)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -12,7 +12,8 @@ pub struct StylePalette {
|
||||
normal_title_style: Option<Style>,
|
||||
hover_title_style: Option<Style>,
|
||||
|
||||
tagged_style: Option<Style>,
|
||||
highlight_style: Option<Style>,
|
||||
scrollbar_style: Option<Style>,
|
||||
|
||||
normal_border_type: BorderType,
|
||||
hover_border_type: BorderType,
|
||||
@ -33,7 +34,8 @@ impl StylePalette {
|
||||
hover_border_style: None,
|
||||
normal_title_style: None,
|
||||
hover_title_style: None,
|
||||
tagged_style: None,
|
||||
highlight_style: None,
|
||||
scrollbar_style: None,
|
||||
|
||||
normal_border_type: BorderType::Plain,
|
||||
hover_border_type: BorderType::Double,
|
||||
@ -64,15 +66,23 @@ impl StylePalette {
|
||||
self.hover_title_style = hover_title_style;
|
||||
}
|
||||
|
||||
pub fn with_tagged_style(&mut self, tagged_style: Option<Style>) {
|
||||
self.tagged_style = tagged_style;
|
||||
pub fn with_highlight_style(&mut self, highlight_style: Option<Style>) {
|
||||
self.highlight_style = highlight_style;
|
||||
}
|
||||
|
||||
pub fn create_tagged_style(&self) -> Style {
|
||||
self.tagged_style.unwrap_or_default()
|
||||
pub fn with_scrollbar_style(&mut self, scrollbar_style: Option<Style>) {
|
||||
self.scrollbar_style = scrollbar_style;
|
||||
}
|
||||
|
||||
pub fn create_text_style(&mut self, active: bool) -> Style {
|
||||
pub fn create_scrollbar_style(&mut self) -> Style {
|
||||
self.scrollbar_style.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn create_highlight_style(&self) -> Style {
|
||||
self.highlight_style.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn create_basic_style(&mut self, active: bool) -> Style {
|
||||
if active {
|
||||
self.hover_style.unwrap_or_default()
|
||||
} else {
|
||||
|
67
src/types/local.rs
Normal file
67
src/types/local.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs::File;
|
||||
use std::io::{Write, BufRead, BufReader};
|
||||
|
||||
use color_eyre::Result;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LocalStorage<ValueType> {
|
||||
file_path: PathBuf,
|
||||
values: Vec<(String, ValueType)>,
|
||||
}
|
||||
|
||||
impl<ValueType: std::fmt::Display> LocalStorage<ValueType> {
|
||||
pub fn new(file_path: PathBuf) -> Self {
|
||||
Self { file_path, values: Vec::new() }
|
||||
}
|
||||
|
||||
fn read_or_create<F1, F2>(
|
||||
&mut self,
|
||||
make_values: F1,
|
||||
fallback_on_error: F2,
|
||||
) -> Result<()>
|
||||
where
|
||||
F1: Fn(&[u8; 32]) -> ValueType,
|
||||
F2: Fn(&PathBuf) -> Vec<(String, ValueType)>,
|
||||
{
|
||||
assert!(self.values.len() == 0, "values already imported from local storage");
|
||||
match File::open(&self.file_path) {
|
||||
Ok(file) => {
|
||||
let reader = BufReader::new(file);
|
||||
|
||||
for line in reader.lines() {
|
||||
let line = line?;
|
||||
|
||||
let line_split_at = line
|
||||
.find(":")
|
||||
.unwrap_or(line.len() - 1);
|
||||
|
||||
let (name, value) = line.split_at(line_split_at);
|
||||
let value = &value[3..value.len() - 1];
|
||||
|
||||
let value: [u8; 32] = hex::decode(value)
|
||||
.expect("stored seed is valid hex string; qed")
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.expect("stored seed is valid length; qed");
|
||||
|
||||
self.values.push((name.to_string(), make_values(&value)));
|
||||
}
|
||||
},
|
||||
Err(_) => self.values = fallback_on_error(&self.file_path),
|
||||
};
|
||||
self.write_to_file()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_to_file(&self) -> Result<()> {
|
||||
let mut file = File::create(&self.file_path)?;
|
||||
self.values
|
||||
.iter()
|
||||
.map(|value| {
|
||||
writeln!(file, "{}:{}", value.0, value.1)
|
||||
.expect("should write in new file; qed");
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user