got working history tab
This commit is contained in:
parent
b3ab5a5865
commit
20700e8a38
9 changed files with 215 additions and 225 deletions
67
src/app.rs
67
src/app.rs
|
@ -1,69 +1,18 @@
|
|||
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crossterm::event::{Event, self, KeyCode};
|
||||
use tokio;
|
||||
use ratatui::widgets::ListState;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
|
||||
use log::warn;
|
||||
|
||||
use crate::db::DB;
|
||||
use crate::db::data_cache::DataCache;
|
||||
use crate::uis::history::HistoryState;
|
||||
use crate::uis::new_transaction::NewTransactionTabState;
|
||||
use crate::uis::navigation_frame::NavigationState;
|
||||
use crate::db::transaction::TransactionRecord;
|
||||
|
||||
pub type AppResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
pub struct StatefulList<T> {
|
||||
pub state: ListState,
|
||||
pub items: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> StatefulList<T> {
|
||||
pub fn with_items(items: Vec<T>) -> StatefulList<T> {
|
||||
StatefulList {
|
||||
state: ListState::default(),
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i >= self.items.len() - 1 {
|
||||
0
|
||||
} else {
|
||||
i + 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
let i = match self.state.selected() {
|
||||
Some(i) => {
|
||||
if i == 0 {
|
||||
self.items.len() - 1
|
||||
} else {
|
||||
i - 1
|
||||
}
|
||||
}
|
||||
None => 0,
|
||||
};
|
||||
self.state.select(Some(i));
|
||||
}
|
||||
|
||||
pub fn unselect(&mut self) {
|
||||
self.state.select(None);
|
||||
}
|
||||
}
|
||||
|
||||
pub enum InputMode {
|
||||
Insert,
|
||||
Normal,
|
||||
|
@ -77,17 +26,17 @@ pub struct App<'a> {
|
|||
pub input_mode: InputMode,
|
||||
|
||||
pub db: Arc<tokio::sync::Mutex<DB>>,
|
||||
pub records: Arc<Mutex<Vec<TransactionRecord>>>,
|
||||
pub data_cache: DataCache,
|
||||
}
|
||||
|
||||
impl<'a> App<'a> {
|
||||
pub fn new(db: DB, records: Arc<Mutex<Vec<TransactionRecord>>>, r: Receiver<bool>) -> App<'a> {
|
||||
pub fn new(db: DB, data_cache: DataCache) -> App<'a> {
|
||||
App {
|
||||
running: true,
|
||||
states: States::new(r),
|
||||
states: States::new(),
|
||||
input_mode: InputMode::Normal,
|
||||
db: Arc::new(tokio::sync::Mutex::new(db)),
|
||||
records,
|
||||
data_cache,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,20 +109,16 @@ pub struct States<'a> {
|
|||
pub transactions: NewTransactionTabState<'a>,
|
||||
pub history: HistoryState,
|
||||
|
||||
pub new_data: Receiver<bool>,
|
||||
|
||||
pub active_frame: ActiveFrame,
|
||||
}
|
||||
|
||||
impl<'a> States<'a> {
|
||||
pub fn new(r: Receiver<bool>) -> States<'a> {
|
||||
pub fn new() -> States<'a> {
|
||||
States {
|
||||
nav_state: NavigationState::new(),
|
||||
transactions: NewTransactionTabState::new(),
|
||||
history: HistoryState::new(),
|
||||
|
||||
new_data: r,
|
||||
|
||||
active_frame: ActiveFrame::Navigation,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +1,46 @@
|
|||
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crossbeam_channel::Sender;
|
||||
|
||||
use futures::TryStreamExt;
|
||||
|
||||
use sqlx::Error;
|
||||
use sqlx::PgPool;
|
||||
use sqlx::Row;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use sqlx::postgres::types::PgMoney;
|
||||
use time::Date;
|
||||
|
||||
use crate::db::transaction::TransactionRecord;
|
||||
use super::data_cache::DataCache;
|
||||
use super::tables::Transaction;
|
||||
|
||||
pub struct DB {
|
||||
conn_pool: PgPool,
|
||||
|
||||
new_data_notify: Sender<bool>,
|
||||
records: Arc<Mutex<Vec<TransactionRecord>>>,
|
||||
data_cache: DataCache,
|
||||
}
|
||||
|
||||
|
||||
impl DB {
|
||||
pub async fn new(records: Arc<Mutex<Vec<TransactionRecord>>>, s: Sender<bool>) -> Result<DB, sqlx::Error> {
|
||||
pub async fn new(data_cache: DataCache) -> Result<DB, sqlx::Error> {
|
||||
let connection_pool = PgPoolOptions::new()
|
||||
.max_connections(3)
|
||||
.connect("postgres://rcntuser:Devel@pmentPa$$w0rd@10.0.0.183/Borealis").await?;
|
||||
Ok(DB {
|
||||
conn_pool: connection_pool,
|
||||
new_data_notify: s,
|
||||
records,
|
||||
data_cache,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_all_records(&mut self) -> Result<(), Error> {
|
||||
let mut rows = sqlx::query("SELECT trns_id, trns_amount, trns_description, trns_account, trns_bucket, trns_date FROM rcnt.transactions")
|
||||
let mut rows = sqlx::query_as::<_, Transaction>("SELECT trns_id, trns_amount, trns_description, trns_account, trns_bucket, trns_date FROM rcnt.transactions ORDER BY trns_id DESC")
|
||||
.fetch(&self.conn_pool);
|
||||
|
||||
while let Some(row) = rows.try_next().await? {
|
||||
let id: i32 = row.try_get("trns_id")?;
|
||||
let amount: PgMoney = row.try_get("trns_amount")?;
|
||||
let date: Date = row.try_get("trns_date")?;
|
||||
self.records.lock().unwrap().clear();
|
||||
let mut temp_transactions: Vec<Transaction> = Vec::new();
|
||||
|
||||
self.records.lock().unwrap().push(
|
||||
TransactionRecord {
|
||||
id: id.into(),
|
||||
amount: amount.to_decimal(2).to_string(),
|
||||
date: date.to_string()
|
||||
}
|
||||
);
|
||||
while let Some(row) = rows.try_next().await? {
|
||||
temp_transactions.push(row);
|
||||
}
|
||||
|
||||
self.new_data_notify.try_send(true).unwrap();
|
||||
|
||||
{
|
||||
let mut transactions = self.data_cache.transactions.lock().unwrap();
|
||||
transactions.clear();
|
||||
transactions.append(&mut temp_transactions);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
|
||||
|
|
32
src/db/data_cache.rs
Normal file
32
src/db/data_cache.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::tables;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DataCache {
|
||||
|
||||
pub accounts: Arc<Mutex<Vec<tables::Account>>>,
|
||||
|
||||
pub buckets: Arc<Mutex<Vec<tables::Buckets>>>,
|
||||
|
||||
pub transaction_catagories: Arc<Mutex<Vec<tables::TransactionCatagories>>>,
|
||||
|
||||
pub transaction_breakdowns: Arc<Mutex<Vec<tables::TransactionBreakdown>>>,
|
||||
|
||||
pub transactions: Arc<Mutex<Vec<tables::Transaction>>>,
|
||||
|
||||
}
|
||||
|
||||
|
||||
impl DataCache {
|
||||
pub fn new() -> DataCache {
|
||||
DataCache {
|
||||
accounts: Arc::new(Mutex::new(Vec::new())),
|
||||
buckets: Arc::new(Mutex::new(Vec::new())),
|
||||
transaction_catagories: Arc::new(Mutex::new(Vec::new())),
|
||||
transaction_breakdowns: Arc::new(Mutex::new(Vec::new())),
|
||||
transactions: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,6 @@
|
|||
|
||||
pub mod transaction;
|
||||
pub use self::transaction::TransactionRecord;
|
||||
|
||||
pub mod connection;
|
||||
pub use self::connection::DB;
|
||||
|
||||
pub mod tables;
|
||||
pub mod data_cache;
|
||||
|
|
65
src/db/tables.rs
Normal file
65
src/db/tables.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use sqlx::{FromRow, postgres::types::PgMoney};
|
||||
use time::Date;
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct Account {
|
||||
pub acnt_id: i32,
|
||||
pub acnt_dsply_name: String,
|
||||
pub acnt_description: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct Buckets {
|
||||
pub bkt_id: i32,
|
||||
pub bkt_dsply_code: String,
|
||||
pub bkt_dsply_name: String,
|
||||
pub bkt_description: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct TransactionCatagories {
|
||||
pub trns_ctgry_id: i32,
|
||||
pub trns_ctgry_dsply_code: String,
|
||||
pub trns_ctgry_dsply_name: String,
|
||||
pub trns_ctgry_description: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct TransactionBreakdown {
|
||||
pub trns_brkdwn_id: i32,
|
||||
pub trns_brkdwn_amount: PgMoney,
|
||||
pub trns_brkdwn_parent_transaction: i32,
|
||||
pub trns_brkdwn_catagory: i32,
|
||||
pub trns_brkdwn_bucket: i32,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct Transaction{
|
||||
pub trns_id: i32,
|
||||
pub trns_amount: PgMoney,
|
||||
pub trns_description: String,
|
||||
pub trns_bucket: i32,
|
||||
pub trns_date: Date
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
|
||||
pub fn get_header() -> String {
|
||||
return format!(" {:<7} | {:<9} | {:<10}", "Id", "Amount", "Date")
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
return format!(" T{:0>6} | {:>9} | {:>10}", self.trns_id, self.trns_amount.to_decimal(2).to_string(), self.trns_date.to_string())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Cow<str> {
|
||||
return self.to_string().into()
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
use sqlx::FromRow;
|
||||
|
||||
|
||||
#[derive(Clone, FromRow)]
|
||||
pub struct Account {
|
||||
pub acnt_id: i32,
|
||||
pub acnt_dsply_name: String,
|
||||
pub acnt_description: String,
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
|
||||
// cargo add crust_decimal
|
||||
#[derive(Clone)]
|
||||
pub struct TransactionRecord {
|
||||
pub id: i64,
|
||||
// pub amount: Decimal,
|
||||
pub amount: String,
|
||||
//pub record_date: Date,
|
||||
pub date: String,
|
||||
}
|
||||
|
||||
impl TransactionRecord {
|
||||
pub fn new(id: i64, amount: String, record_date: String) -> TransactionRecord {
|
||||
TransactionRecord {
|
||||
id,
|
||||
amount,
|
||||
date: record_date
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_header() -> String {
|
||||
return format!(" {:<7} | {:<9} | {:<10}", "Id", "Amount", "Date")
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
return format!(" T{:0>6} | {:>9} | {:>10}", self.id, self.amount, self.date)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Cow<str> {
|
||||
return self.to_string().into()
|
||||
}
|
||||
}
|
17
src/main.rs
17
src/main.rs
|
@ -1,10 +1,6 @@
|
|||
use std::io;
|
||||
use std::fs::File;
|
||||
use std::time::Duration;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crossbeam_channel::bounded;
|
||||
|
||||
use simplelog::*;
|
||||
|
||||
|
@ -13,10 +9,10 @@ use ratatui::{
|
|||
Terminal,
|
||||
};
|
||||
|
||||
use recount::db::DB;
|
||||
use recount::db::data_cache::DataCache;
|
||||
use recount::app::{App, AppResult};
|
||||
use recount::tui::Tui;
|
||||
use recount::db::DB;
|
||||
use recount::db::tables;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> AppResult<()> {
|
||||
|
@ -24,13 +20,12 @@ async fn main() -> AppResult<()> {
|
|||
let log_file = "testing_log.txt".to_string();
|
||||
init_logger(log_file);
|
||||
|
||||
let records = Arc::new(Mutex::new(Vec::new()));
|
||||
let (s, r) = bounded::<bool>(2);
|
||||
let db = DB::new(Arc::clone(&records), s).await?;
|
||||
|
||||
let data_cache = DataCache::new();
|
||||
let db = DB::new(data_cache.clone()).await?;
|
||||
|
||||
// Create an application.
|
||||
let mut app = App::new(db, records, r);
|
||||
let mut app = App::new(db, data_cache);
|
||||
app.refresh();
|
||||
|
||||
// Initialize the terminal user interface.
|
||||
let backend = CrosstermBackend::new(io::stderr());
|
||||
|
|
|
@ -1,123 +1,135 @@
|
|||
use crossterm::event::{KeyEvent, KeyEventKind, KeyCode};
|
||||
use ratatui::{backend::Backend, Frame, layout::{Rect, Layout, Direction, Constraint}, widgets::{Block, Borders, List, ListItem, Paragraph}, text::{Text, Line, Span}, style::{Style, Color, Modifier}};
|
||||
use crate::{app::{App, StatefulList}, db::TransactionRecord};
|
||||
use ratatui::{backend::Backend, Frame, layout::{Rect, Layout, Direction, Constraint}, widgets::{Block, Borders, List, ListItem, Paragraph}, text::{Text, Line, Span}, style::{Style, Color}};
|
||||
use crate::{app::App, db::tables::Transaction};
|
||||
|
||||
|
||||
pub struct HistoryState {
|
||||
pub transacts_list: StatefulList<TransactionRecord>
|
||||
pub selected_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl HistoryState {
|
||||
|
||||
pub fn new() -> HistoryState {
|
||||
HistoryState {
|
||||
transacts_list: StatefulList::with_items(vec![
|
||||
TransactionRecord::new(1, "$10.00".to_string(), "05-01-2020".to_string()),
|
||||
TransactionRecord::new(2, "$10.00".to_string(), "05-01-2020".to_string()),
|
||||
TransactionRecord::new(3, "$10.00".to_string(), "05-01-2020".to_string()),
|
||||
TransactionRecord::new(4, "$10.00".to_string(), "05-01-2020".to_string()),
|
||||
TransactionRecord::new(5, "$10.00".to_string(), "05-01-2020".to_string()),
|
||||
])
|
||||
selected_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_event(event: KeyEvent, app: &mut App) {
|
||||
|
||||
if app.states.history.selected_index.is_none() {
|
||||
app.states.history.selected_index = Some(0);
|
||||
}
|
||||
|
||||
let max_index = app.data_cache.transactions.lock().unwrap().len();
|
||||
|
||||
if event.kind == KeyEventKind::Press {
|
||||
match event.code {
|
||||
KeyCode::Tab => app.states.transactions.next_tab(),
|
||||
KeyCode::Up => app.states.history.transacts_list.previous(),
|
||||
KeyCode::Down => app.states.history.transacts_list.next(),
|
||||
KeyCode::Up | KeyCode::Tab => app.states.history.next(max_index),
|
||||
KeyCode::Down => app.states.history.previous(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_transactions(app: &mut App) {
|
||||
app.states.history.transacts_list.items.clear();
|
||||
for i in app.records.lock().unwrap().iter() {
|
||||
app.states.history.transacts_list.items.push(i.clone());
|
||||
pub fn next(&mut self, max_index: usize) {
|
||||
|
||||
if self.selected_index.is_some() {
|
||||
let cur_index = self.selected_index.unwrap();
|
||||
if cur_index + 1 < max_index {
|
||||
self.selected_index = Some(cur_index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn previous(&mut self) {
|
||||
|
||||
if self.selected_index.is_some() {
|
||||
let cur_index = self.selected_index.unwrap();
|
||||
if cur_index > 0 {
|
||||
self.selected_index = Some(cur_index - 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_history_tab<B: Backend> (f: &mut Frame<B>, body_rect: Rect, app: &mut App) {
|
||||
|
||||
let do_update = match app.states.new_data.try_recv() {
|
||||
Ok(val) => val,
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
if do_update {
|
||||
HistoryState::update_transactions(app);
|
||||
}
|
||||
|
||||
|
||||
let split_body = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||
.split(body_rect);
|
||||
|
||||
let mut lines: Vec<Line> = vec![];
|
||||
if app.states.history.transacts_list.state.selected().is_some(){
|
||||
let selected_item: &TransactionRecord = &app.states.history.transacts_list.items[app.states.history.transacts_list.state.selected().unwrap()];
|
||||
{
|
||||
let transacts_list = app.data_cache.transactions.lock().unwrap();
|
||||
if app.states.history.selected_index.is_some() {
|
||||
let selected_item: &Transaction = &transacts_list[app.states.history.selected_index.unwrap()];
|
||||
|
||||
let ident_style: Style = Style::default().fg(Color::Yellow);
|
||||
let value_style: Style = Style::default().fg(Color::LightBlue);
|
||||
let ident_style: Style = Style::default().fg(Color::Yellow);
|
||||
let value_style: Style = Style::default().fg(Color::LightBlue);
|
||||
|
||||
lines.push(
|
||||
Line::from(vec![
|
||||
Span::styled("Transaction Id: ", ident_style),
|
||||
Span::styled(format!("T{:0>6}", selected_item.id), value_style),
|
||||
])
|
||||
);
|
||||
lines.push(
|
||||
Line::from(vec![
|
||||
Span::styled("Amount: ", ident_style),
|
||||
Span::styled(format!("{}", selected_item.amount), value_style)
|
||||
])
|
||||
);
|
||||
lines.push(
|
||||
Line::from(vec![
|
||||
Span::styled("Transaction Date: ", ident_style),
|
||||
Span::styled(format!("{}", selected_item.date), value_style)
|
||||
])
|
||||
);
|
||||
}
|
||||
lines.push(
|
||||
Line::from(vec![
|
||||
Span::styled("Transaction Id: ", ident_style),
|
||||
Span::styled(format!("T{:0>6}", selected_item.trns_id), value_style),
|
||||
])
|
||||
);
|
||||
lines.push(
|
||||
Line::from(vec![
|
||||
Span::styled("Amount: ", ident_style),
|
||||
Span::styled(format!("{}", selected_item.trns_amount.to_decimal(2).to_string()), value_style)
|
||||
])
|
||||
);
|
||||
lines.push(
|
||||
Line::from(vec![
|
||||
Span::styled("Transaction Date: ", ident_style),
|
||||
Span::styled(format!("{}", selected_item.trns_date.to_string()), value_style)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
let paragraph = Paragraph::new(lines.clone())
|
||||
.block(Block::default().borders(Borders::ALL).title("Details").title_alignment(ratatui::layout::Alignment::Left));
|
||||
let paragraph = Paragraph::new(lines.clone())
|
||||
.block(Block::default().borders(Borders::ALL).title("Details").title_alignment(ratatui::layout::Alignment::Left));
|
||||
|
||||
f.render_widget(paragraph, split_body[1]);
|
||||
f.render_widget(paragraph, split_body[1]);
|
||||
|
||||
|
||||
let list_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Min(3)])
|
||||
.split(split_body[0]);
|
||||
let list_chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Length(1), Constraint::Min(3)])
|
||||
.split(split_body[0]);
|
||||
|
||||
let list_header = Paragraph::new(
|
||||
Text::styled(TransactionRecord::get_header(), Style::default().fg(Color::Black).bg(Color::Cyan))
|
||||
).block(Block::default().borders(Borders::NONE));
|
||||
let list_header = Paragraph::new(
|
||||
Text::styled(Transaction::get_header(), Style::default().fg(Color::Black).bg(Color::Cyan))
|
||||
).block(Block::default().borders(Borders::NONE));
|
||||
|
||||
f.render_widget(list_header, list_chunks[0]);
|
||||
f.render_widget(list_header, list_chunks[0]);
|
||||
|
||||
let mut items: Vec<ListItem> = Vec::new();
|
||||
|
||||
for i in 0..transacts_list.len() {
|
||||
let style = match app.states.history.selected_index {
|
||||
Some(val) => {
|
||||
if val == i {
|
||||
Style::default().fg(Color::Black).bg(Color::Yellow)
|
||||
} else {
|
||||
Style::default().fg(Color::Black).bg(Color::White)
|
||||
}
|
||||
},
|
||||
None => Style::default().fg(Color::Black).bg(Color::White),
|
||||
};
|
||||
|
||||
items.push(ListItem::new(
|
||||
Text::from(transacts_list.get(i).unwrap().as_str())
|
||||
).style(style));
|
||||
}
|
||||
|
||||
// Iterate through all elements in the `items` app and append some debug text to it.
|
||||
let items: Vec<ListItem> = app
|
||||
.states.history.transacts_list
|
||||
.items
|
||||
.iter()
|
||||
.map(|i| {
|
||||
ListItem::new(Text::from(i.as_str())).style(Style::default().fg(Color::Black).bg(Color::White))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let history_items = List::new(items)
|
||||
.block(Block::default().borders(Borders::NONE))
|
||||
.highlight_style(
|
||||
Style::default()
|
||||
.bg(Color::Gray)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
);
|
||||
.block(Block::default().borders(Borders::NONE));
|
||||
|
||||
f.render_stateful_widget(history_items, list_chunks[1], &mut app.states.history.transacts_list.state)
|
||||
f.render_widget(history_items, list_chunks[1]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue