1+ use crossterm:: event:: { KeyCode , KeyEvent } ;
12use ratatui:: {
23 buffer:: Buffer ,
3- layout:: { Constraint , Margin , Rect } ,
4+ layout:: { Constraint , Layout , Margin , Rect } ,
45 style:: { Color , Modifier , Style , Stylize } ,
56 text:: Text ,
67 widgets:: {
7- Block , Cell , HighlightSpacing , Row , Scrollbar , ScrollbarState , StatefulWidget , Table ,
8- TableState ,
8+ Block , Cell , HighlightSpacing , Paragraph , Row , Scrollbar , ScrollbarState , StatefulWidget ,
9+ Table , TableState , Widget ,
910 } ,
1011} ;
12+ use regex:: Regex ;
1113use utxorpc:: spec:: cardano;
1214
1315use crate :: explorer:: { App , ChainBlock } ;
1416
1517#[ derive( Default ) ]
1618pub struct TransactionsTabState {
17- pub scroll_state : ScrollbarState ,
18- pub table_state : TableState ,
19+ scroll_state : ScrollbarState ,
20+ table_state : TableState ,
21+ input : String ,
22+ input_mode : InputMode ,
23+ character_index : usize ,
24+ }
25+ impl TransactionsTabState {
26+ fn byte_index ( & self ) -> usize {
27+ self . input
28+ . char_indices ( )
29+ . map ( |( i, _) | i)
30+ . nth ( self . character_index )
31+ . unwrap_or ( self . input . len ( ) )
32+ }
33+
34+ pub fn enter_char ( & mut self , new_char : char ) {
35+ let index = self . byte_index ( ) ;
36+ self . input . insert ( index, new_char) ;
37+ self . move_cursor_right ( ) ;
38+ }
39+
40+ pub fn move_cursor_left ( & mut self ) {
41+ let cursor_moved_left = self . character_index . saturating_sub ( 1 ) ;
42+ self . character_index = self . clamp_cursor ( cursor_moved_left) ;
43+ }
44+
45+ pub fn move_cursor_right ( & mut self ) {
46+ let cursor_moved_right = self . character_index . saturating_add ( 1 ) ;
47+ self . character_index = self . clamp_cursor ( cursor_moved_right) ;
48+ }
49+
50+ fn clamp_cursor ( & self , new_cursor_pos : usize ) -> usize {
51+ new_cursor_pos. clamp ( 0 , self . input . chars ( ) . count ( ) )
52+ }
53+
54+ pub fn delete_char ( & mut self ) {
55+ let is_not_cursor_leftmost = self . character_index != 0 ;
56+ if is_not_cursor_leftmost {
57+ let current_index = self . character_index ;
58+ let from_left_to_current_index = current_index - 1 ;
59+
60+ let before_char_to_delete = self . input . chars ( ) . take ( from_left_to_current_index) ;
61+ let after_char_to_delete = self . input . chars ( ) . skip ( current_index) ;
62+
63+ self . input = before_char_to_delete. chain ( after_char_to_delete) . collect ( ) ;
64+ self . move_cursor_left ( ) ;
65+ }
66+ }
67+
68+ pub fn next_row ( & mut self ) {
69+ let i = match self . table_state . selected ( ) {
70+ Some ( i) => i + 1 ,
71+ None => 0 ,
72+ } ;
73+
74+ self . table_state . select ( Some ( i) ) ;
75+ self . scroll_state = self . scroll_state . position ( i * 3 ) ;
76+ }
77+
78+ pub fn previous_row ( & mut self ) {
79+ let i = match self . table_state . selected ( ) {
80+ Some ( i) => {
81+ if i == 0 {
82+ 0
83+ } else {
84+ i - 1
85+ }
86+ }
87+ None => 0 ,
88+ } ;
89+ self . table_state . select ( Some ( i) ) ;
90+ self . scroll_state = self . scroll_state . position ( i * 3 ) ;
91+ }
92+
93+ pub fn handle_key ( & mut self , key : & KeyEvent ) {
94+ match self . input_mode {
95+ InputMode :: Normal => match key. code {
96+ KeyCode :: Char ( 'j' ) | KeyCode :: Down => {
97+ self . next_row ( ) ;
98+ }
99+ KeyCode :: Char ( 'k' ) | KeyCode :: Up => {
100+ self . previous_row ( ) ;
101+ }
102+ KeyCode :: Char ( 'e' ) => self . input_mode = InputMode :: Editing ,
103+ _ => { }
104+ } ,
105+ InputMode :: Editing => match key. code {
106+ KeyCode :: Char ( to_insert) => self . enter_char ( to_insert) ,
107+ KeyCode :: Left => self . move_cursor_left ( ) ,
108+ KeyCode :: Right => self . move_cursor_right ( ) ,
109+ KeyCode :: Esc => self . input_mode = InputMode :: Normal ,
110+ KeyCode :: Backspace => self . delete_char ( ) ,
111+ _ => { }
112+ } ,
113+ }
114+ }
19115}
20116
21117#[ derive( Clone ) ]
22118pub struct TransactionsTab {
23119 pub blocks : Vec < ChainBlock > ,
24120}
25121
122+ #[ derive( Clone , Default , PartialEq , Eq ) ]
123+ pub enum InputMode {
124+ #[ default]
125+ Normal ,
126+ Editing ,
127+ }
128+
26129impl From < & App > for TransactionsTab {
27130 fn from ( value : & App ) -> Self {
28131 Self {
@@ -60,25 +163,58 @@ impl TxView {
60163
61164impl StatefulWidget for TransactionsTab {
62165 type State = TransactionsTabState ;
166+
63167 fn render ( self , area : Rect , buf : & mut Buffer , state : & mut Self :: State )
64168 where
65169 Self : Sized ,
66170 {
171+ let [ search_area, txs_area] =
172+ Layout :: vertical ( [ Constraint :: Length ( 3 ) , Constraint :: Fill ( 1 ) ] ) . areas ( area) ;
173+
174+ let input = Paragraph :: new ( state. input . as_str ( ) )
175+ . style ( match state. input_mode {
176+ InputMode :: Normal => Style :: default ( ) ,
177+ InputMode :: Editing => Style :: default ( ) . fg ( Color :: White ) ,
178+ } )
179+ . block (
180+ Block :: bordered ( )
181+ . title ( " Search " )
182+ . border_style ( match state. input_mode {
183+ InputMode :: Normal => Style :: new ( ) . dark_gray ( ) ,
184+ InputMode :: Editing => Style :: new ( ) . white ( ) ,
185+ } ) ,
186+ ) ;
187+
188+ input. render ( search_area, buf) ;
189+
67190 let header = [ "Hash" , "Slot" , "Certs" , "Assets" , "Total Coin" , "Datum" ]
68191 . into_iter ( )
69192 . map ( Cell :: from)
70193 . collect :: < Row > ( )
71194 . style ( Style :: default ( ) . fg ( Color :: Green ) . bold ( ) )
72195 . height ( 1 ) ;
196+ let mut txs: Vec < TxView > = self
197+ . blocks
198+ . iter ( )
199+ . flat_map ( |chain_block| {
200+ if let Some ( body) = & chain_block. body {
201+ return TxView :: new ( chain_block. slot , body) ;
202+ }
203+ Default :: default ( )
204+ } )
205+ . collect ( ) ;
73206
74- let txs = self . blocks . iter ( ) . flat_map ( |chain_block| {
75- if let Some ( body) = & chain_block. body {
76- return TxView :: new ( chain_block. slot , body) ;
77- }
78- Default :: default ( )
79- } ) ;
207+ if !state. input . is_empty ( ) {
208+ let input_regex = Regex :: new ( & state. input ) . unwrap ( ) ;
209+ txs = txs
210+ . into_iter ( )
211+ . filter ( |tx| {
212+ input_regex. is_match ( & tx. hash ) || input_regex. is_match ( & tx. slot . to_string ( ) )
213+ } )
214+ . collect ( ) ;
215+ }
80216
81- let rows = txs. enumerate ( ) . map ( |( i, tx) | {
217+ let rows = txs. iter ( ) . enumerate ( ) . map ( |( i, tx) | {
82218 let color = match i % 2 {
83219 0 => Color :: Black ,
84220 _ => Color :: Reset ,
@@ -94,7 +230,6 @@ impl StatefulWidget for TransactionsTab {
94230 . style ( Style :: new ( ) . fg ( Color :: White ) . bg ( color) )
95231 . height ( 3 )
96232 } ) ;
97-
98233 let bar = " █ " ;
99234 let table = Table :: new (
100235 rows,
@@ -113,11 +248,10 @@ impl StatefulWidget for TransactionsTab {
113248 . highlight_spacing ( HighlightSpacing :: Always )
114249 . block ( Block :: bordered ( ) ) ;
115250
116- StatefulWidget :: render ( table, area, buf, & mut state. table_state ) ;
117-
251+ StatefulWidget :: render ( table, txs_area, buf, & mut state. table_state ) ;
118252 StatefulWidget :: render (
119253 Scrollbar :: new ( ratatui:: widgets:: ScrollbarOrientation :: VerticalRight ) ,
120- area . inner ( Margin {
254+ txs_area . inner ( Margin {
121255 vertical : 1 ,
122256 horizontal : 1 ,
123257 } ) ,
0 commit comments